mirror of
https://github.com/aljazceru/CTFd.git
synced 2026-02-23 15:14:49 +01:00
1318 pagination users teams submissions (#1513)
* Paginate only the `/api/v1/users`, `/api/v1/teams`, and `/api/v1/submissions` endpoints. * Add a `PaginatedAPIListSuccessResponse` class with a customized `apidoc` method to hack in the pagination scheme * Works on #1318
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
from typing import Any, List, Optional
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
@@ -55,6 +55,51 @@ class APIListSuccessResponse(APIDetailedSuccessResponse):
|
||||
return schema
|
||||
|
||||
|
||||
class PaginatedAPIListSuccessResponse(APIListSuccessResponse):
|
||||
meta: Dict[str, Any]
|
||||
|
||||
@classmethod
|
||||
def apidoc(cls):
|
||||
"""
|
||||
Helper to inline references from the generated schema
|
||||
"""
|
||||
schema = cls.schema()
|
||||
|
||||
schema["properties"]["meta"] = {
|
||||
"title": "Meta",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"pagination": {
|
||||
"title": "Pagination",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"page": {"title": "Page", "type": "integer"},
|
||||
"next": {"title": "Next", "type": "integer"},
|
||||
"prev": {"title": "Prev", "type": "integer"},
|
||||
"pages": {"title": "Pages", "type": "integer"},
|
||||
"per_page": {"title": "Per Page", "type": "integer"},
|
||||
"total": {"title": "Total", "type": "integer"},
|
||||
},
|
||||
"required": ["page", "next", "prev", "pages", "per_page", "total"],
|
||||
}
|
||||
},
|
||||
"required": ["pagination"],
|
||||
}
|
||||
|
||||
try:
|
||||
key = schema["properties"]["data"]["items"]["$ref"]
|
||||
ref = key.split("/").pop()
|
||||
definition = schema["definitions"][ref]
|
||||
schema["properties"]["data"]["items"] = definition
|
||||
del schema["definitions"][ref]
|
||||
if bool(schema["definitions"]) is False:
|
||||
del schema["definitions"]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
return schema
|
||||
|
||||
|
||||
class APISimpleErrorResponse(BaseModel):
|
||||
success: bool = False
|
||||
errors: Optional[List[str]]
|
||||
|
||||
@@ -5,7 +5,10 @@ from flask_restx import Namespace, Resource
|
||||
|
||||
from CTFd.api.v1.helpers.request import validate_args
|
||||
from CTFd.api.v1.helpers.schemas import sqlalchemy_to_pydantic
|
||||
from CTFd.api.v1.schemas import APIDetailedSuccessResponse, APIListSuccessResponse
|
||||
from CTFd.api.v1.schemas import (
|
||||
APIDetailedSuccessResponse,
|
||||
PaginatedAPIListSuccessResponse,
|
||||
)
|
||||
from CTFd.cache import clear_standings
|
||||
from CTFd.models import Submissions, db
|
||||
from CTFd.schemas.submissions import SubmissionSchema
|
||||
@@ -23,7 +26,7 @@ class SubmissionDetailedSuccessResponse(APIDetailedSuccessResponse):
|
||||
data: SubmissionModel
|
||||
|
||||
|
||||
class SubmissionListSuccessResponse(APIListSuccessResponse):
|
||||
class SubmissionListSuccessResponse(PaginatedAPIListSuccessResponse):
|
||||
data: List[SubmissionModel]
|
||||
|
||||
|
||||
@@ -52,17 +55,38 @@ class SubmissionsList(Resource):
|
||||
def get(self):
|
||||
args = request.args.to_dict()
|
||||
schema = SubmissionSchema(many=True)
|
||||
pagination_args = {
|
||||
"per_page": int(args.pop("per_page", 50)),
|
||||
"page": int(args.pop("page", 1)),
|
||||
}
|
||||
if args:
|
||||
submissions = Submissions.query.filter_by(**args).all()
|
||||
submissions = Submissions.query.filter_by(**args).paginate(
|
||||
**pagination_args, max_per_page=100
|
||||
)
|
||||
else:
|
||||
submissions = Submissions.query.all()
|
||||
submissions = Submissions.query.paginate(
|
||||
**pagination_args, max_per_page=100
|
||||
)
|
||||
|
||||
response = schema.dump(submissions)
|
||||
response = schema.dump(submissions.items)
|
||||
|
||||
if response.errors:
|
||||
return {"success": False, "errors": response.errors}, 400
|
||||
|
||||
return {"success": True, "data": response.data}
|
||||
return {
|
||||
"meta": {
|
||||
"pagination": {
|
||||
"page": submissions.page,
|
||||
"next": submissions.next_num,
|
||||
"prev": submissions.prev_num,
|
||||
"pages": submissions.pages,
|
||||
"per_page": submissions.per_page,
|
||||
"total": submissions.total,
|
||||
}
|
||||
},
|
||||
"success": True,
|
||||
"data": response.data,
|
||||
}
|
||||
|
||||
@admins_only
|
||||
@submissions_namespace.doc(
|
||||
|
||||
@@ -5,7 +5,10 @@ from flask import abort, request, session
|
||||
from flask_restx import Namespace, Resource
|
||||
|
||||
from CTFd.api.v1.helpers.schemas import sqlalchemy_to_pydantic
|
||||
from CTFd.api.v1.schemas import APIDetailedSuccessResponse, APIListSuccessResponse
|
||||
from CTFd.api.v1.schemas import (
|
||||
APIDetailedSuccessResponse,
|
||||
PaginatedAPIListSuccessResponse,
|
||||
)
|
||||
from CTFd.cache import clear_standings, clear_team_session, clear_user_session
|
||||
from CTFd.models import Awards, Submissions, Teams, Unlocks, Users, db
|
||||
from CTFd.schemas.awards import AwardSchema
|
||||
@@ -28,7 +31,7 @@ class TeamDetailedSuccessResponse(APIDetailedSuccessResponse):
|
||||
data: TeamModel
|
||||
|
||||
|
||||
class TeamListSuccessResponse(APIListSuccessResponse):
|
||||
class TeamListSuccessResponse(PaginatedAPIListSuccessResponse):
|
||||
data: List[TeamModel]
|
||||
|
||||
|
||||
@@ -56,19 +59,34 @@ class TeamList(Resource):
|
||||
)
|
||||
def get(self):
|
||||
if is_admin() and request.args.get("view") == "admin":
|
||||
teams = Teams.query.filter_by()
|
||||
teams = Teams.query.filter_by().paginate(per_page=50, max_per_page=100)
|
||||
else:
|
||||
teams = Teams.query.filter_by(hidden=False, banned=False)
|
||||
teams = Teams.query.filter_by(hidden=False, banned=False).paginate(
|
||||
per_page=50, max_per_page=100
|
||||
)
|
||||
|
||||
user_type = get_current_user_type(fallback="user")
|
||||
view = copy.deepcopy(TeamSchema.views.get(user_type))
|
||||
view.remove("members")
|
||||
response = TeamSchema(view=view, many=True).dump(teams)
|
||||
response = TeamSchema(view=view, many=True).dump(teams.items)
|
||||
|
||||
if response.errors:
|
||||
return {"success": False, "errors": response.errors}, 400
|
||||
|
||||
return {"success": True, "data": response.data}
|
||||
return {
|
||||
"meta": {
|
||||
"pagination": {
|
||||
"page": teams.page,
|
||||
"next": teams.next_num,
|
||||
"prev": teams.prev_num,
|
||||
"pages": teams.pages,
|
||||
"per_page": teams.per_page,
|
||||
"total": teams.total,
|
||||
}
|
||||
},
|
||||
"success": True,
|
||||
"data": response.data,
|
||||
}
|
||||
|
||||
@admins_only
|
||||
@teams_namespace.doc(
|
||||
|
||||
@@ -4,7 +4,10 @@ from flask import abort, request
|
||||
from flask_restx import Namespace, Resource
|
||||
|
||||
from CTFd.api.v1.helpers.schemas import sqlalchemy_to_pydantic
|
||||
from CTFd.api.v1.schemas import APIDetailedSuccessResponse, APIListSuccessResponse
|
||||
from CTFd.api.v1.schemas import (
|
||||
APIDetailedSuccessResponse,
|
||||
PaginatedAPIListSuccessResponse,
|
||||
)
|
||||
from CTFd.cache import clear_standings, clear_user_session
|
||||
from CTFd.models import (
|
||||
Awards,
|
||||
@@ -40,7 +43,7 @@ class UserDetailedSuccessResponse(APIDetailedSuccessResponse):
|
||||
data: UserModel
|
||||
|
||||
|
||||
class UserListSuccessResponse(APIListSuccessResponse):
|
||||
class UserListSuccessResponse(PaginatedAPIListSuccessResponse):
|
||||
data: List[UserModel]
|
||||
|
||||
|
||||
@@ -68,16 +71,31 @@ class UserList(Resource):
|
||||
)
|
||||
def get(self):
|
||||
if is_admin() and request.args.get("view") == "admin":
|
||||
users = Users.query.filter_by()
|
||||
users = Users.query.filter_by().paginate(per_page=50, max_per_page=100)
|
||||
else:
|
||||
users = Users.query.filter_by(banned=False, hidden=False)
|
||||
users = Users.query.filter_by(banned=False, hidden=False).paginate(
|
||||
per_page=50, max_per_page=100
|
||||
)
|
||||
|
||||
response = UserSchema(view="user", many=True).dump(users)
|
||||
response = UserSchema(view="user", many=True).dump(users.items)
|
||||
|
||||
if response.errors:
|
||||
return {"success": False, "errors": response.errors}, 400
|
||||
|
||||
return {"success": True, "data": response.data}
|
||||
return {
|
||||
"meta": {
|
||||
"pagination": {
|
||||
"page": users.page,
|
||||
"next": users.next_num,
|
||||
"prev": users.prev_num,
|
||||
"pages": users.pages,
|
||||
"per_page": users.per_page,
|
||||
"total": users.total,
|
||||
}
|
||||
},
|
||||
"success": True,
|
||||
"data": response.data,
|
||||
}
|
||||
|
||||
@users_namespace.doc()
|
||||
@admins_only
|
||||
|
||||
Reference in New Issue
Block a user