diff --git a/server/app/apis/__init__.py b/server/app/apis/__init__.py index 31d1be3df8d545c7c8e7ac658e545e6dc970dfd8..ac68c91111ef9ffac50d45dee605fd68209aca68 100644 --- a/server/app/apis/__init__.py +++ b/server/app/apis/__init__.py @@ -139,9 +139,11 @@ flask_api = Api() def init_api(): from .auth import blp as auth_blp + from .competitions import blp as competitions_blp from .misc import blp as misc_blp from .users import blp as user_blp flask_api.register_blueprint(user_blp) flask_api.register_blueprint(auth_blp) + flask_api.register_blueprint(competitions_blp) flask_api.register_blueprint(misc_blp) diff --git a/server/app/apis/competitions.py b/server/app/apis/competitions.py index 2d381425c9e5d3f41bba347128aab7b659bf8885..e28c000b345120accb38a8889651347003a227aa 100644 --- a/server/app/apis/competitions.py +++ b/server/app/apis/competitions.py @@ -4,101 +4,103 @@ Default route: /api/competitions """ import app.database.controller as dbc -from app.apis import item_response, list_response, protect_route -from app.core.dto import CompetitionDTO -from app.core.parsers import search_parser, sentinel +from app.apis import http_codes, protect_route +from app.core import ma +from app.core.rich_schemas import CompetitionSchemaRich +from app.core.schemas import BaseSchema, CompetitionSchema +from app.database import models from app.database.models import Competition -from flask_restx import Resource, reqparse +from flask.views import MethodView +from flask_smorest import Blueprint, arguments -api = CompetitionDTO.api -schema = CompetitionDTO.schema -rich_schema = CompetitionDTO.rich_schema -list_schema = CompetitionDTO.list_schema +blp = Blueprint("competitions", "competitions", url_prefix="/api/competitions", description="Operations competitions") -competition_parser_add = reqparse.RequestParser() -competition_parser_add.add_argument("name", type=str, required=True, location="json") -competition_parser_add.add_argument("year", type=int, required=True, location="json") -competition_parser_add.add_argument("city_id", type=int, required=True, location="json") -competition_parser_edit = reqparse.RequestParser() -competition_parser_edit.add_argument("name", type=str, default=sentinel, location="json") -competition_parser_edit.add_argument("year", type=int, default=sentinel, location="json") -competition_parser_edit.add_argument("city_id", type=int, default=sentinel, location="json") -competition_parser_edit.add_argument("background_image_id", default=sentinel, type=int, location="json") +class CompetitionAddArgsSchema(BaseSchema): + class Meta(BaseSchema.Meta): + model = models.Competition -competition_parser_search = search_parser.copy() -competition_parser_search.add_argument("name", type=str, default=sentinel, location="args") -competition_parser_search.add_argument("year", type=int, default=sentinel, location="args") -competition_parser_search.add_argument("city_id", type=int, default=sentinel, location="args") + name = ma.auto_field() + year = ma.auto_field() + city_id = ma.auto_field() -@api.route("") -class CompetitionsList(Resource): - @protect_route(allowed_roles=["*"]) - def post(self): - """ Posts a new competition. """ +class CompetitionEditArgsSchema(BaseSchema): + class Meta(BaseSchema.Meta): + model = models.Competition + + name = ma.auto_field(required=False) + year = ma.auto_field(required=False) + city_id = ma.auto_field(required=False) + background_image_id = ma.auto_field(required=False) + + +class CompetitionSearchArgsSchema(BaseSchema): + class Meta(BaseSchema.Meta): + model = models.Competition - args = competition_parser_add.parse_args(strict=True) + name = ma.auto_field(required=False) + year = ma.auto_field(required=False) + city_id = ma.auto_field(required=False) + background_image_id = ma.auto_field(required=False) - # Add competition - item = dbc.add.competition(**args) - # Add default slide - # dbc.add.slide(item.id) - return item_response(schema.dump(item)) +@blp.route("") +class CompetitionsList(MethodView): + @protect_route(allowed_roles=["*"]) + @blp.arguments(CompetitionAddArgsSchema) + @blp.response(http_codes.OK, CompetitionSchema) + @blp.alt_response(http_codes.CONFLICT, None, description="Competition could not be added") + def post(self, args): + """ Adds a new competition. """ + return dbc.add.competition(**args) -@api.route("/<competition_id>") -@api.param("competition_id") -class Competitions(Resource): +@blp.route("/<competition_id>") +class Competitions(MethodView): @protect_route(allowed_roles=["*"], allowed_views=["*"]) + @blp.response(http_codes.OK, CompetitionSchemaRich) + @blp.alt_response(http_codes.NOT_FOUND, None, description="Competition not found") def get(self, competition_id): """ Gets the specified competition. """ - - item = dbc.get.competition(competition_id) - - return item_response(rich_schema.dump(item)) + return dbc.get.competition(competition_id) @protect_route(allowed_roles=["*"]) - def put(self, competition_id): + @blp.arguments(CompetitionEditArgsSchema) + @blp.response(http_codes.OK, CompetitionSchema) + @blp.alt_response(http_codes.NOT_FOUND, None, description="Competition not found") + @blp.alt_response(http_codes.CONFLICT, None, description="Competition could not be updated") + def put(self, args, competition_id): """ Edits the specified competition with the specified arguments. """ - - args = competition_parser_edit.parse_args(strict=True) - item = dbc.get.one(Competition, competition_id) - item = dbc.edit.default(item, **args) - - return item_response(schema.dump(item)) + return dbc.edit.default(dbc.get.one(Competition, competition_id), **args) @protect_route(allowed_roles=["*"]) + @blp.response(http_codes.NO_CONTENT, None) + @blp.alt_response(http_codes.NOT_FOUND, None, description="Competition not found") + @blp.alt_response(http_codes.CONFLICT, None, description="Competition could not be deleted") def delete(self, competition_id): """ Deletes the specified competition. """ + dbc.delete.competition(dbc.get.one(Competition, competition_id)) + return None - item = dbc.get.one(Competition, competition_id) - dbc.delete.competition(item) - - return "deleted" - -@api.route("/search") -class CompetitionSearch(Resource): +@blp.route("/search") +class CompetitionSearch(MethodView): @protect_route(allowed_roles=["*"]) - def get(self): + @blp.arguments(CompetitionSearchArgsSchema, location="query") + @blp.paginate() + @blp.response(http_codes.OK, CompetitionSchema(many=True)) + def get(self, args, pagination_parameters): """ Finds a specific competition based on the provided arguments. """ - - args = competition_parser_search.parse_args(strict=True) - items, total = dbc.search.competition(**args) - return list_response(list_schema.dump(items), total) + return dbc.search.competition(pagination_parameters, **args) -@api.route("/<competition_id>/copy") -@api.param("competition_id") -class SlidesOrder(Resource): +@blp.route("/<competition_id>/copy") +class SlidesOrder(MethodView): @protect_route(allowed_roles=["*"]) + @blp.response(http_codes.OK, CompetitionSchema) + @blp.alt_response(http_codes.NOT_FOUND, None, description="Competition not found") + @blp.alt_response(http_codes.CONFLICT, None, description="Competition could not be copied") def post(self, competition_id): """ Creates a deep copy of the specified competition. """ - - item_competition = dbc.get.competition(competition_id) - - item_competition_copy = dbc.copy.competition(item_competition) - - return item_response(schema.dump(item_competition_copy)) + return dbc.copy.competition(dbc.get.competition(competition_id)) diff --git a/server/app/apis/users.py b/server/app/apis/users.py index a7b90e44d99f75762614838b2a9192e79c903e5a..4856e46ca74160cd65a91abe31290750804dd527 100644 --- a/server/app/apis/users.py +++ b/server/app/apis/users.py @@ -37,8 +37,8 @@ class UserEditArgsSchema(BaseSchema): class Meta(BaseSchema.Meta): model = models.User - name = ma.auto_field() - email = ma.auto_field() + name = ma.auto_field(required=False) + email = ma.auto_field(required=False) role_id = ma.auto_field(required=False) city_id = ma.auto_field(required=False) diff --git a/server/app/database/__init__.py b/server/app/database/__init__.py index 594894480efea039c5cdf0c544e964ad4d554b3f..b3b99a5ca1eaccffd4c4860504a3f719051c3aaa 100644 --- a/server/app/database/__init__.py +++ b/server/app/database/__init__.py @@ -46,7 +46,7 @@ class ExtendedQuery(BaseQuery): item = self.first() if required and not item: - abort(error_code, error_message or "Object not found") + abort(error_code, message=error_message or "Objektet hittades inte") return item diff --git a/server/app/database/controller/copy.py b/server/app/database/controller/copy.py index d64d8a5bb43b91e3eff9935df74ed6d3a48901fe..f16ce3feb54b300aed46e8575cb1a78d8c82a967 100644 --- a/server/app/database/controller/copy.py +++ b/server/app/database/controller/copy.py @@ -3,7 +3,7 @@ This file contains functionality to copy and duplicate data to the database. """ from app.database.controller import add, get, search, utils -from app.database.models import Question +from app.database.models import Competition, Question from app.database.types import IMAGE_COMPONENT_ID, QUESTION_COMPONENT_ID, TEXT_COMPONENT_ID @@ -115,9 +115,9 @@ def competition(item_competition_old): """ name = "Kopia av " + item_competition_old.name - item_competition, total = search.competition(name=name) - if item_competition: - name = "Kopia av " + item_competition[total - 1].name + + while item_competition := Competition.query.filter(Competition.name == name).first(): + name = "Kopia av " + item_competition.name item_competition_new = add._competition_no_slides( name, diff --git a/server/app/database/controller/get.py b/server/app/database/controller/get.py index e620b0593d7687e132c8e01f07ab9c42601dd19f..536a80a3ba761c23d74d5a105fb23bdd74add0b3 100644 --- a/server/app/database/controller/get.py +++ b/server/app/database/controller/get.py @@ -297,7 +297,7 @@ def component_list(competition_id, slide_id): ### Competitions ### -def competition(competition_id): +def competition(competition_id, required=True): """ Get Competition and all it's sub-entities. """ join_component = joinedload(Competition.slides).subqueryload(Slide.components) @@ -310,5 +310,5 @@ def competition(competition_id): .options(join_alternatives) .options(join_question_alternative_answer) .options(join_question_score) - .first() + .first_api(required, "Tävlingen kunde inte hittas") ) diff --git a/server/app/database/controller/search.py b/server/app/database/controller/search.py index 7582579c3311be6a84b56dc01de1c444981adc5d..2ea1a811a00f44cf8c0e52a38eafb1c585568274 100644 --- a/server/app/database/controller/search.py +++ b/server/app/database/controller/search.py @@ -40,13 +40,10 @@ def user( def competition( + pagination_parameters, name=None, year=None, city_id=None, - page=0, - page_size=15, - order=1, - order_by=None, ): """ Finds and returns a competition from the provided parameters. """ @@ -58,11 +55,9 @@ def competition( if city_id: query = query.filter(Competition.city_id == city_id) - order_column = Competition.year # Default order_by - if order_by: - order_column = getattr(Competition.columns, order_by) - - return query.pagination(page, page_size, order_column, order) + pagination = query.paginate(page=pagination_parameters.page, per_page=pagination_parameters.page_size) + pagination_parameters.item_count = pagination.total + return pagination.items def slide(