diff --git a/server/app/__init__.py b/server/app/__init__.py index 5365f636732398fcc1aced7badb39057e1eb8d89..d1bbd3af80ccef9d5692f55d3a48ce2e34d920f6 100644 --- a/server/app/__init__.py +++ b/server/app/__init__.py @@ -1,7 +1,7 @@ from flask import Flask, redirect, request import app.core.models as models -from app.core import bcrypt, db, jwt +from app.core import bcrypt, db, jwt, ma def create_app(config_name="configmodule.DevelopmentConfig"): @@ -13,6 +13,7 @@ def create_app(config_name="configmodule.DevelopmentConfig"): bcrypt.init_app(app) jwt.init_app(app) db.init_app(app) + ma.init_app(app) from app.apis import flask_api diff --git a/server/app/apis/__init__.py b/server/app/apis/__init__.py index c9ff2f51143eb9b085f3b779dbc3fdca86ee5982..493a1a947d372e175785663896d4573a1f78714c 100644 --- a/server/app/apis/__init__.py +++ b/server/app/apis/__init__.py @@ -1,7 +1,9 @@ from functools import wraps +import app.core.http_codes as codes from flask_jwt_extended import verify_jwt_in_request from flask_jwt_extended.utils import get_jwt_claims +from flask_restx.errors import abort def admin_required(): @@ -13,27 +15,29 @@ def admin_required(): if claims["role"] == "Admin": return fn(*args, **kwargs) else: - return {"message:": "Admins only"}, 403 + return {"message:": "Admins only"}, codes.FORBIDDEN return decorator return wrapper -def text_response(text, code=200): - return {"message": text}, code +def text_response(message, code=200): + return {"message": message}, 200 -def query_response(db_items, code=200): - if type(db_items) is not list: - db_items = [db_items] - return {"result": [i.get_dict() for i in db_items]}, code +def list_response(items, total=None, code=200): + if type(items) is not list: + abort(codes.INTERNAL_SERVER_ERROR) + if not total: + total = len(items) + return {"items": items, "count": len(items), "total_count": total}, code -def object_response(items, code=200): - if type(items) is not list: - items = [items] - return {"result": items}, code +def item_response(item, code=200): + if isinstance(item, list): + abort(codes.INTERNAL_SERVER_ERROR) + return item, code from flask_restx import Api diff --git a/server/app/apis/auth.py b/server/app/apis/auth.py index 8fe25b6b7e52b0b4f28a4aa0b88e5ac9d9c830b3..920d2d65ea482e70a51c4547419656f97f157a31 100644 --- a/server/app/apis/auth.py +++ b/server/app/apis/auth.py @@ -1,16 +1,22 @@ import app.core.controller as dbc import app.core.http_codes as codes -from app.apis import admin_required +from app.apis import admin_required, item_response, text_response from app.core.dto import AuthDTO from app.core.models import User from app.core.parsers import create_user_parser, login_parser -from flask_jwt_extended import (create_access_token, create_refresh_token, - get_jwt_identity, get_raw_jwt, - jwt_refresh_token_required, jwt_required) +from flask_jwt_extended import ( + create_access_token, + create_refresh_token, + get_jwt_identity, + get_raw_jwt, + jwt_refresh_token_required, + jwt_required, +) from flask_restx import Namespace, Resource, cors api = AuthDTO.api -user_model = AuthDTO.model +schema = AuthDTO.schema +list_schema = AuthDTO.list_schema def get_user_claims(item_user): @@ -20,7 +26,6 @@ def get_user_claims(item_user): @api.route("/signup") class AuthSignup(Resource): @jwt_required - @cors.crossdomain(origin="*") def post(self): args = create_user_parser.parse_args(strict=True) email = args.get("email") @@ -35,26 +40,24 @@ class AuthSignup(Resource): if not item_user: api.abort(codes.BAD_REQUEST, "User could not be created") - return {"id": item_user.id} + return item_response(schema.dump(item_user)) @api.route("/delete/<ID>") @api.param("ID") class AuthDelete(Resource): @jwt_required - @cors.crossdomain(origin="*") def delete(self, ID): item_user = User.query.filter(User.id == ID).first() dbc.delete.default(item_user) if ID == get_jwt_identity(): jti = get_raw_jwt()["jti"] dbc.add.blacklist(jti) - return "deleted" + return text_response(f"User {ID} deleted") @api.route("/login") class AuthLogin(Resource): - @cors.crossdomain(origin="*") def post(self): args = login_parser.parse_args(strict=True) email = args.get("email") @@ -74,18 +77,16 @@ class AuthLogin(Resource): @api.route("/logout") class AuthLogout(Resource): @jwt_required - @cors.crossdomain(origin="*") def post(self): jti = get_raw_jwt()["jti"] dbc.add.blacklist(jti) - return "logout" + return text_response("User logout") @api.route("/refresh") class AuthRefresh(Resource): @jwt_required @jwt_refresh_token_required - @cors.crossdomain(origin="*") def post(self): old_jti = get_raw_jwt()["jti"] diff --git a/server/app/apis/competitions.py b/server/app/apis/competitions.py index ac7384dfdea97a738af22aa91f89aa0eea56904d..b2c0799928e577805f3d5797982d850a8ace3be9 100644 --- a/server/app/apis/competitions.py +++ b/server/app/apis/competitions.py @@ -1,15 +1,14 @@ import app.core.controller as dbc -import app.core.http_codes as codes -from app.apis import admin_required +from app.apis import admin_required, item_response, list_response from app.core.dto import CompetitionDTO -from app.core.models import Competition, Slide, Team +from app.core.models import Competition from app.core.parsers import competition_parser, competition_search_parser -from flask_jwt_extended import get_jwt_identity, jwt_required -from flask_restx import Resource, reqparse +from flask_jwt_extended import jwt_required +from flask_restx import Resource api = CompetitionDTO.api -competition_model = CompetitionDTO.model -competition_list_model = CompetitionDTO.user_list_model +schema = CompetitionDTO.schema +list_schema = CompetitionDTO.list_schema def get_comp(CID): @@ -19,7 +18,6 @@ def get_comp(CID): @api.route("/") class CompetitionsList(Resource): @jwt_required - @api.marshal_with(competition_model) def post(self): args = competition_parser.parse_args(strict=True) @@ -29,25 +27,24 @@ class CompetitionsList(Resource): style_id = args.get("style_id") # Add competition - item_competition = dbc.add.default(Competition(name, year, style_id, city_id)) + item = dbc.add.default(Competition(name, year, style_id, city_id)) # Add default slide - item_slide = dbc.add.slide(item_competition.id) + item_slide = dbc.add.slide(item) - return item_competition + dbc.refresh(item) + return item_response(schema.dump(item)) @api.route("/<ID>") @api.param("ID") class Competitions(Resource): @jwt_required - @api.marshal_with(competition_model) def get(self, ID): item = get_comp(ID) - return item + return item_response(schema.dump(item)) @jwt_required - @api.marshal_with(competition_model) def put(self, ID): args = competition_parser.parse_args(strict=True) @@ -56,7 +53,8 @@ class Competitions(Resource): year = args.get("year") city_id = args.get("city_id") style_id = args.get("style_id") - return dbc.edit.competition(item, name, year, city_id, style_id) + item = dbc.edit.competition(item, name, year, city_id, style_id) + return item_response(schema.dump(item)) @jwt_required def delete(self, ID): @@ -68,7 +66,6 @@ class Competitions(Resource): @api.route("/search") class CompetitionSearch(Resource): @jwt_required - @api.marshal_with(competition_list_model) def get(self): args = competition_search_parser.parse_args(strict=True) name = args.get("name") @@ -77,6 +74,8 @@ class CompetitionSearch(Resource): style_id = args.get("style_id") page = args.get("page", 0) page_size = args.get("page_size", 15) - result, total = dbc.get.search_competitions(name, year, city_id, style_id, page, page_size) + order = args.get("order", 1) + order_by = args.get("order_by") - return {"competitions": result, "count": len(result), "total": total} + items, total = dbc.get.search_competitions(name, year, city_id, style_id, page, page_size, order, order_by) + return list_response(list_schema.dump(items), total) diff --git a/server/app/apis/misc.py b/server/app/apis/misc.py index d44c26ee9cebd836bba90ea65bdae72917124c9d..13055f211e480a2f16b42d74d9517514e04c67f7 100644 --- a/server/app/apis/misc.py +++ b/server/app/apis/misc.py @@ -1,4 +1,5 @@ import app.core.controller as dbc +from app.apis import admin_required, item_response, list_response from app.core.dto import MiscDTO from app.core.models import City, MediaType, QuestionType, Role from flask_jwt_extended import jwt_required @@ -6,10 +7,10 @@ from flask_restx import Resource, reqparse api = MiscDTO.api -question_type_model = MiscDTO.question_type_model -media_type_model = MiscDTO.media_type_model -role_model = MiscDTO.role_model -city_model = MiscDTO.city_model +question_type_schema = MiscDTO.question_type_schema +media_type_schema = MiscDTO.media_type_schema +role_schema = MiscDTO.role_schema +city_schema = MiscDTO.city_schema name_parser = reqparse.RequestParser() @@ -19,57 +20,57 @@ name_parser.add_argument("name", type=str, required=True, location="json") @api.route("/media_types") class MediaTypeList(Resource): @jwt_required - @api.marshal_with(media_type_model) def get(self): - return MediaType.query.all() + items = MediaType.query.all() + return list_response(media_type_schema.dump(items)) @api.route("/question_types") class QuestionTypeList(Resource): @jwt_required - @api.marshal_with(question_type_model) def get(self): - return QuestionType.query.all() + items = QuestionType.query.all() + return list_response(question_type_schema.dump(items)) @api.route("/roles") class RoleList(Resource): @jwt_required - @api.marshal_with(role_model) def get(self): - return Role.query.all() + items = Role.query.all() + return list_response(role_schema.dump(items)) @api.route("/cities") class CitiesList(Resource): @jwt_required - @api.marshal_with(city_model) def get(self): - return City.query.all() + items = City.query.all() + return list_response(city_schema.dump(items)) @jwt_required - @api.marshal_with(role_model) def post(self): args = name_parser.parse_args(strict=True) dbc.add.default(City(args["name"])) - return City.query.all() + items = City.query.all() + return list_response(city_schema.dump(items)) @api.route("/cities/<ID>") @api.param("ID") class Cities(Resource): @jwt_required - @api.marshal_with(city_model) def put(self, ID): item = City.query.filter(City.id == ID).first() args = name_parser.parse_args(strict=True) item.name = args["name"] - dbc.edit.default(item) - return City.query.all() + dbc.commit_and_refresh(item) + items = City.query.all() + return list_response(city_schema.dump(items)) @jwt_required - @api.marshal_with(city_model) def delete(self, ID): item = City.query.filter(City.id == ID).first() dbc.delete.default(item) - return City.query.all() + items = City.query.all() + return list_response(city_schema.dump(items)) diff --git a/server/app/apis/slides.py b/server/app/apis/slides.py index 6399ebc77c999563522db32a14db55bf126bf63b..6a2b1714a101cde574f188d3876243e9a5d97f86 100644 --- a/server/app/apis/slides.py +++ b/server/app/apis/slides.py @@ -1,6 +1,7 @@ import app.core.controller as dbc import app.core.http_codes as codes -from app.apis import admin_required +from app.apis import admin_required, item_response, list_response +from app.core import schemas from app.core.dto import SlideDTO from app.core.models import Competition, Slide from app.core.parsers import slide_parser @@ -8,7 +9,8 @@ from flask_jwt_extended import get_jwt_identity, jwt_required from flask_restx import Namespace, Resource, reqparse api = SlideDTO.api -model = SlideDTO.model +schema = SlideDTO.schema +list_schema = SlideDTO.list_schema def get_comp(CID): @@ -19,38 +21,36 @@ def get_comp(CID): @api.param("CID") class SlidesList(Resource): @jwt_required - @api.marshal_with(model) def get(self, CID): item_comp = get_comp(CID) - return item_comp.slides + return list_response(list_schema.dump(item_comp.slides)) @jwt_required - @api.marshal_with(model) def post(self, CID): - dbc.add.slide(CID) item_comp = get_comp(CID) - return item_comp.slides + dbc.add.slide(item_comp) + dbc.refresh(item_comp) + return list_response(list_schema.dump(item_comp.slides)) @api.route("/<SID>") @api.param("CID,SID") class Slides(Resource): @jwt_required - @api.marshal_with(model) def get(self, CID, SID): item_slide = dbc.get.slide(CID, SID) - return item_slide + return item_response(schema.dump(item_slide)) @jwt_required - @api.marshal_with(model) def put(self, CID, SID): args = slide_parser.parse_args(strict=True) title = args.get("title") timer = args.get("timer") item_slide = dbc.get.slide(CID, SID) + item_slide = dbc.edit.slide(item_slide, title, timer) - return dbc.edit.slide(item_slide, title, timer) + return item_response(schema.dump(item_slide)) @jwt_required def delete(self, CID, SID): @@ -63,7 +63,6 @@ class Slides(Resource): @api.param("CID,SID") class SlidesOrder(Resource): @jwt_required - @api.marshal_with(model) def put(self, CID, SID): args = slide_parser.parse_args(strict=True) order = args.get("order") @@ -86,4 +85,4 @@ class SlidesOrder(Resource): # switch place between them item_slide = dbc.edit.switch_order(item_slide, item_slide_order) - return item_slide + return item_response(schema.dump(item_slide)) diff --git a/server/app/apis/teams.py b/server/app/apis/teams.py index 02c730bb64200a26fa160210b6b42a526b334070..3f183bf5ea0965c3282b9a0407c9f95476486b7d 100644 --- a/server/app/apis/teams.py +++ b/server/app/apis/teams.py @@ -1,13 +1,14 @@ import app.core.controller as dbc import app.core.http_codes as codes -from app.apis import admin_required +from app.apis import admin_required, item_response, list_response from app.core.dto import TeamDTO from app.core.models import Competition, Team from flask_jwt_extended import get_jwt_identity, jwt_required from flask_restx import Namespace, Resource, reqparse api = TeamDTO.api -model = TeamDTO.model +schema = TeamDTO.schema +list_schema = TeamDTO.list_schema def get_comp(CID): @@ -18,13 +19,11 @@ def get_comp(CID): @api.param("CID") class TeamsList(Resource): @jwt_required - @api.marshal_with(model) def get(self, CID): item_comp = get_comp(CID) - return item_comp.teams + return list_response(list_schema.dump(item_comp.teams)) @jwt_required - @api.marshal_with(model) def post(self, CID): parser = reqparse.RequestParser() parser.add_argument("name", type=str, location="json") @@ -32,17 +31,16 @@ class TeamsList(Resource): dbc.add.default(Team(args["name"], CID)) item_comp = get_comp(CID) - return item_comp.teams + return list_response(list_schema.dump(item_comp.teams)) @api.route("/<TID>") @api.param("CID,TID") class Teams(Resource): @jwt_required - @api.marshal_with(model) def get(self, CID, TID): - item_team = dbc.get.team(CID, TID) - return item_team + item = dbc.get.team(CID, TID) + return item_response(schema.dump(item)) @jwt_required def delete(self, CID, TID): diff --git a/server/app/apis/users.py b/server/app/apis/users.py index 43e304c11a5c86bee2e9482a30d22a9c163e9861..4aaf372b831a1c58c78cc6f78592ae5513c4d213 100644 --- a/server/app/apis/users.py +++ b/server/app/apis/users.py @@ -1,6 +1,6 @@ import app.core.controller as dbc import app.core.http_codes as codes -from app.apis import admin_required +from app.apis import admin_required, item_response, list_response from app.core.dto import UserDTO from app.core.models import User from app.core.parsers import user_parser, user_search_parser @@ -8,8 +8,8 @@ from flask_jwt_extended import get_jwt_identity, jwt_required from flask_restx import Namespace, Resource api = UserDTO.api -user_model = UserDTO.model -user_list_model = UserDTO.user_list_model +schema = UserDTO.schema +list_schema = UserDTO.list_schema def edit_user(item_user, args): @@ -28,38 +28,37 @@ def edit_user(item_user, args): @api.route("/") class UsersList(Resource): @jwt_required - @api.marshal_list_with(user_model) def get(self): - return User.query.filter(User.id == get_jwt_identity()).first() + item = User.query.filter(User.id == get_jwt_identity()).first() + return item_response(schema.dump(item)) @jwt_required - @api.marshal_with(user_model) def put(self): args = user_parser.parse_args(strict=True) - item_user = User.query.filter(User.id == get_jwt_identity()).first() - return edit_user(item_user, args) + item = User.query.filter(User.id == get_jwt_identity()).first() + item = edit_user(item, args) + return item_response(schema.dump(item)) @api.route("/<ID>") @api.param("ID") class Users(Resource): @jwt_required - @api.marshal_with(user_model) def get(self, ID): - return User.query.filter(User.id == ID).first() + item = User.query.filter(User.id == ID).first() + return item_response(schema.dump(item)) @jwt_required - @api.marshal_with(user_model) def put(self, ID): args = user_parser.parse_args(strict=True) - item_user = User.query.filter(User.id == ID).first() - return edit_user(item_user, args) + item = User.query.filter(User.id == ID).first() + item = edit_user(item, args) + return item_response(schema.dump(item)) @api.route("/search") class UserSearch(Resource): @jwt_required - @api.marshal_list_with(user_list_model) def get(self): args = user_search_parser.parse_args(strict=True) name = args.get("name") @@ -68,6 +67,8 @@ class UserSearch(Resource): city_id = args.get("city_id") page = args.get("page", 0) page_size = args.get("page_size", 15) + order = args.get("order", 1) + order_by = args.get("order_by") - result, total = dbc.get.search_user(email, name, city_id, role_id, page, page_size) - return {"users": result, "count": len(result), "total": total} + items, total = dbc.get.search_user(email, name, city_id, role_id, page, page_size, order, order_by) + return list_response(list_schema.dump(items), total) diff --git a/server/app/core/__init__.py b/server/app/core/__init__.py index 1f6cad4846d15460bd6c6e113297c4aa319dbb63..e9d132cb332637eb5f064c37427d228c6e57d87f 100644 --- a/server/app/core/__init__.py +++ b/server/app/core/__init__.py @@ -1,6 +1,7 @@ import sqlalchemy as sa from flask_bcrypt import Bcrypt from flask_jwt_extended.jwt_manager import JWTManager +from flask_marshmallow import Marshmallow from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy.model import Model from sqlalchemy.sql import func @@ -15,3 +16,4 @@ class Base(Model): db = SQLAlchemy(model_class=Base) bcrypt = Bcrypt() jwt = JWTManager() +ma = Marshmallow() diff --git a/server/app/core/controller/__init__.py b/server/app/core/controller/__init__.py index ec746b748474a23bcfeff64ecda5161b2c2425b6..57f9b429697d46cab59aa8e8028f27990edf5b5e 100644 --- a/server/app/core/controller/__init__.py +++ b/server/app/core/controller/__init__.py @@ -1,3 +1,16 @@ # import add, get from app.core import db from app.core.controller import add, delete, edit, get + + +def commit_and_refresh(item): + db.session.commit() + db.session.refresh(item) + + +def refresh(item): + db.session.refresh(item) + + +def commit(item): + db.session.commit() diff --git a/server/app/core/controller/add.py b/server/app/core/controller/add.py index 958cf766d06cb2b30cab7c0c8fa9fd347df501c9..14c5eecf04940b2daf1c6e563b09f617e68b3cf1 100644 --- a/server/app/core/controller/add.py +++ b/server/app/core/controller/add.py @@ -14,9 +14,9 @@ def blacklist(jti): db.session.commit() -def slide(competition_id): - order = Slide.query.filter(Slide.competition_id == competition_id).count() - item = Slide(order, competition_id) +def slide(item_competition): + order = Slide.query.filter(Slide.competition_id == item_competition.id).count() + item = Slide(order, item_competition.id) db.session.add(item) db.session.commit() db.session.refresh(item) diff --git a/server/app/core/controller/edit.py b/server/app/core/controller/edit.py index 77c726a7daf1d5cd8280e2a3919e17af233f96b0..9373ebc794f0427982010e27b4f8e9931816f8c9 100644 --- a/server/app/core/controller/edit.py +++ b/server/app/core/controller/edit.py @@ -1,12 +1,6 @@ from app.core import db -def default(item): - db.session.commit() - db.session.refresh(item) - return item - - def switch_order(item1, item2): old_order = item1.order new_order = item2.order diff --git a/server/app/core/controller/get.py b/server/app/core/controller/get.py index ef86ef6bd23222a9157ecdc8d6481f91d6a6e4c7..d5b5978a2d4bb56a4f4d3054fcba0a1f8c4ee1c6 100644 --- a/server/app/core/controller/get.py +++ b/server/app/core/controller/get.py @@ -13,7 +13,19 @@ def team(CID, TID): return Team.query.filter((Team.competition_id == CID) & (Team.id == TID)).first() -def search_user(email=None, name=None, city_id=None, role_id=None, page=0, page_size=15): +def _search(query, order_column, page=0, page_size=15, order=1): + if order == 1: + query = query.order_by(order_column) + else: + query = query.order_by(order_column.desc()) + + total = query.count() + query = query.limit(page_size).offset(page * page_size) + items = query.all() + return items, total + + +def search_user(email=None, name=None, city_id=None, role_id=None, page=0, page_size=15, order=1, order_by=None): query = User.query if name: query = query.filter(User.name.like(f"%{name}%")) @@ -24,14 +36,16 @@ def search_user(email=None, name=None, city_id=None, role_id=None, page=0, page_ if role_id: query = query.filter(User.role_id == role_id) - total = query.count() - query = query.limit(page_size).offset(page * page_size) - result = query.all() + order_column = User.id # Default order_by + if order_by: + order_column = getattr(User.__table__.c, order_by) - return result, total + return _search(query, order_column, page, page_size, order) -def search_competitions(name=None, year=None, city_id=None, style_id=None, page=0, page_size=15): +def search_competitions( + name=None, year=None, city_id=None, style_id=None, page=0, page_size=15, order=1, order_by=None +): query = Competition.query if name: query = query.filter(Competition.name.like(f"%{name}%")) @@ -42,8 +56,8 @@ def search_competitions(name=None, year=None, city_id=None, style_id=None, page= if style_id: query = query.filter(Competition.style_id == style_id) - total = query.count() - query = query.limit(page_size).offset(page * page_size) - result = query.all() + order_column = Competition.year # Default order_by + if order_by: + order_column = getattr(Competition.columns, order_by) - return result, total + return _search(query, order_column, page, page_size, order) diff --git a/server/app/core/dto.py b/server/app/core/dto.py index 6c47877c1de2202348d3ed6c91dcae50aeeaf701..3378a17bcad43e410331fadc31812982fd95563f 100644 --- a/server/app/core/dto.py +++ b/server/app/core/dto.py @@ -1,69 +1,42 @@ +import app.core.rich_schemas as rich_schemas +import app.core.schemas as schemas +import marshmallow as ma from flask_restx import Namespace, fields class AuthDTO: api = Namespace("auth") - model = api.model("Auth", {"id": fields.Integer()}) + schema = rich_schemas.UserSchemaRich(many=False) + list_schema = rich_schemas.UserSchemaRich(many=True) class UserDTO: api = Namespace("users") - model = api.model( - "User", - { - "id": fields.Integer(), - "name": fields.String(), - "email": fields.String(), - "role_id": fields.Integer(), - "city_id": fields.Integer(), - }, - ) - user_list_model = api.model( - "UserList", - {"users": fields.List(fields.Nested(model)), "count": fields.Integer(), "total": fields.Integer()}, - ) + schema = rich_schemas.UserSchemaRich(many=False) + list_schema = rich_schemas.UserSchemaRich(many=True) class CompetitionDTO: api = Namespace("competitions") - model = api.model( - "Competition", - { - "id": fields.Integer(), - "name": fields.String(), - "year": fields.Integer(), - "style_id": fields.Integer(), - "city_id": fields.Integer(), - }, - ) - user_list_model = api.model( - "CompetitionList", - {"competitions": fields.List(fields.Nested(model)), "count": fields.Integer(), "total": fields.Integer()}, - ) + schema = rich_schemas.CompetitionSchemaRich(many=False) + list_schema = rich_schemas.CompetitionSchemaRich(many=True) class SlideDTO: api = Namespace("slides") - model = api.model( - "Slide", - { - "id": fields.Integer(), - "order": fields.Integer(), - "title": fields.String(), - "timer": fields.Integer(), - "competition_id": fields.Integer(), - }, - ) + schema = schemas.SlideSchema(many=False) + list_schema = schemas.SlideSchema(many=True) class TeamDTO: api = Namespace("teams") - model = api.model("Team", {"id": fields.Integer(), "name": fields.String(), "competition_id": fields.Integer()}) + schema = schemas.TeamSchema(many=False) + list_schema = schemas.TeamSchema(many=True) class MiscDTO: api = Namespace("misc") - role_model = api.model("Role", {"id": fields.Integer(), "name": fields.String()}) - question_type_model = api.model("QuestionType", {"id": fields.Integer(), "name": fields.String()}) - media_type_model = api.model("MediaType", {"id": fields.Integer(), "name": fields.String()}) - city_model = api.model("City", {"id": fields.Integer(), "name": fields.String()}) + role_schema = schemas.RoleSchema(many=True) + question_type_schema = schemas.QuestionTypeSchema(many=True) + media_type_schema = schemas.MediaTypeSchema(many=True) + city_schema = schemas.CitySchema(many=True) diff --git a/server/app/core/parsers.py b/server/app/core/parsers.py index 3fc63f1a14680920767c7f0a3d1bcfe7eeec2142..6f287cb271a5984c3c5ba5f0e8b49a38ff7196c5 100644 --- a/server/app/core/parsers.py +++ b/server/app/core/parsers.py @@ -2,8 +2,10 @@ from flask_restx import inputs, reqparse ###SEARCH#### search_parser = reqparse.RequestParser() -search_parser.add_argument("page", type=int, default=0) -search_parser.add_argument("page_size", type=int, default=15) +search_parser.add_argument("page", type=int, default=0, location="args") +search_parser.add_argument("page_size", type=int, default=15, location="args") +search_parser.add_argument("order", type=int, default=1, location="args") +search_parser.add_argument("order_by", type=str, location="args") ###LOGIN#### login_parser = reqparse.RequestParser() diff --git a/server/app/core/rich_schemas.py b/server/app/core/rich_schemas.py new file mode 100644 index 0000000000000000000000000000000000000000..56aec85e5f8f93eaf663d8a374738b0fee478fd8 --- /dev/null +++ b/server/app/core/rich_schemas.py @@ -0,0 +1,39 @@ +import app.core.models as models +import app.core.schemas as schemas +from app.core import ma +from marshmallow import fields as fields2 +from marshmallow_sqlalchemy import fields + + +class RichSchema(ma.SQLAlchemySchema): + class Meta: + strict = True + load_instance = True + include_relationships = True + + +class UserSchemaRich(RichSchema): + class Meta(RichSchema.Meta): + model = models.User + + id = ma.auto_field() + name = ma.auto_field() + email = ma.auto_field() + role = fields.Nested(schemas.RoleSchema, many=False) + city = fields.Nested(schemas.CitySchema, many=False) + + +class CompetitionSchemaRich(RichSchema): + class Meta(RichSchema.Meta): + model = models.Competition + + id = ma.auto_field() + name = ma.auto_field() + year = ma.auto_field() + slides = fields.Nested(schemas.SlideSchema, many=True) + style = fields.Nested(schemas.RoleSchema, many=False) + city = fields.Nested(schemas.CitySchema, many=False) + + +class UserListSchema(ma.Schema): + users = fields2.Nested(UserSchemaRich, many=False) diff --git a/server/app/core/schemas.py b/server/app/core/schemas.py new file mode 100644 index 0000000000000000000000000000000000000000..9962479fe3bcd241bcf77a5ed8b389b4e55ec19e --- /dev/null +++ b/server/app/core/schemas.py @@ -0,0 +1,82 @@ +import app.core.models as models +from app.core import ma +from marshmallow_sqlalchemy import fields + + +class BaseSchema(ma.SQLAlchemySchema): + class Meta: + strict = True + load_instance = False + include_relationships = False + + +class QuestionTypeSchema(BaseSchema): + class Meta(BaseSchema.Meta): + model = models.QuestionType + + id = ma.auto_field() + name = ma.auto_field() + + +class MediaTypeSchema(BaseSchema): + class Meta(BaseSchema.Meta): + model = models.MediaType + + id = ma.auto_field() + name = ma.auto_field() + + +class RoleSchema(BaseSchema): + class Meta(BaseSchema.Meta): + model = models.Role + + id = ma.auto_field() + name = ma.auto_field() + + +class CitySchema(BaseSchema): + class Meta(BaseSchema.Meta): + model = models.City + + id = ma.auto_field() + name = ma.auto_field() + + +class MediaSchema(BaseSchema): + class Meta(BaseSchema.Meta): + model = models.Media + + id = ma.auto_field() + filename = ma.auto_field() + type_id = ma.auto_field() + upload_by_id = ma.auto_field() + + +class StyleSchema(BaseSchema): + class Meta(BaseSchema.Meta): + model = models.Style + + id = ma.auto_field() + name = ma.auto_field() + css = ma.auto_field() + bg_image = fields.Nested(MediaSchema, many=False) + + +class SlideSchema(BaseSchema): + class Meta(BaseSchema.Meta): + model = models.Slide + + id = ma.auto_field() + order = ma.auto_field() + title = ma.auto_field() + timer = ma.auto_field() + competition_id = ma.auto_field() + + +class TeamSchema(BaseSchema): + class Meta(BaseSchema.Meta): + model = models.Team + + id = ma.auto_field() + name = ma.auto_field() + competition_id = ma.auto_field() diff --git a/server/requirements.txt b/server/requirements.txt index 463ef4fe07f14b03b7771a25842170c8903f73f7..172f152510c3ccd6245f19e5cb53ac702a6b4370 100644 Binary files a/server/requirements.txt and b/server/requirements.txt differ diff --git a/server/tests/test_app.py b/server/tests/test_app.py index ce897883c3dcf15d793ccc9d7a6d6bda0ac03498..383cb574b5b3ad56def838bb169cdf761bd6bf59 100644 --- a/server/tests/test_app.py +++ b/server/tests/test_app.py @@ -2,6 +2,38 @@ from tests import app, client, db from tests.test_helpers import add_default_values, delete, get, post, put +def test_misc(client): + add_default_values() + + # Login in with default user + response, body = post(client, "/api/auth/login", {"email": "test@test.se", "password": "password"}) + assert response.status_code == 200 + headers = {"Authorization": "Bearer " + body["access_token"]} + + ## Get misc + response, body = get(client, "/api/misc/roles", headers=headers) + assert body["count"] >= 2 + + response, body = get(client, "/api/misc/cities", headers=headers) + assert body["count"] >= 1 + assert body["items"][0]["name"] == "Linköping" + + response, body = get(client, "/api/misc/media_types", headers=headers) + assert body["count"] >= 2 + + response, body = get(client, "/api/misc/question_types", headers=headers) + assert body["count"] >= 3 + + ## Cities + response, body = post(client, "/api/misc/cities", {"name": "Göteborg"}, headers=headers) + assert body["count"] >= 2 + assert body["items"][1]["name"] == "Göteborg" + + response, body = put(client, "/api/misc/cities/2", {"name": "Gbg"}, headers=headers) + assert body["count"] >= 2 + assert body["items"][1]["name"] == "Gbg" + + def test_competition(client): add_default_values() @@ -26,7 +58,7 @@ def test_competition(client): response, body = get(client, "/api/competitions/1/slides", headers=headers) assert response.status_code == 200 - assert len(body) == 2 + assert len(body["items"]) == 2 response, body = put(client, "/api/competitions/1/slides/1/order", {"order": 1}, headers=headers) assert response.status_code == 200 @@ -36,8 +68,8 @@ def test_competition(client): response, body = get(client, "/api/competitions/1/teams", headers=headers) assert response.status_code == 200 - assert len(body) == 1 - assert body[0]["name"] == "t1" + assert len(body["items"]) == 1 + assert body["items"][0]["name"] == "t1" response, body = delete(client, "/api/competitions/1", {}, headers=headers) assert response.status_code == 200 @@ -57,6 +89,7 @@ def test_app(client): assert response.status_code == 200 assert body["id"] == 2 assert "password" not in body + assert "_password" not in body # Try loggin with wrong PASSWORD response, body = post(client, "/api/auth/login", {"email": "test1@test.se", "password": "abc1234"})