From 226448fdea4dce99b5adb52ad9875c47b8beca5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carl=20Sch=C3=B6nfelder?= <carsc272@student.liu.se> Date: Tue, 13 Apr 2021 13:04:31 +0000 Subject: [PATCH] Resolve "Backend fixes" --- server/app/__init__.py | 2 +- server/app/apis/__init__.py | 1 + server/app/apis/auth.py | 19 ++--- server/app/apis/competitions.py | 18 ++--- server/app/apis/misc.py | 4 +- server/app/apis/questions.py | 31 ++------ server/app/apis/slides.py | 23 +++--- server/app/apis/teams.py | 18 ++--- server/app/apis/users.py | 14 ++-- server/app/core/__init__.py | 13 +-- server/app/core/rich_schemas.py | 32 +++++++- server/app/core/schemas.py | 13 ++- server/app/database/__init__.py | 0 server/app/database/base.py | 37 +++++++++ .../{core => database}/controller/__init__.py | 2 +- .../app/{core => database}/controller/add.py | 19 ++++- .../{core => database}/controller/delete.py | 6 +- .../app/{core => database}/controller/edit.py | 0 server/app/database/controller/get.py | 56 +++++++++++++ .../get.py => database/controller/search.py} | 79 ++++++------------- server/app/{core => database}/models.py | 3 +- server/tests/test_app.py | 14 ++-- server/tests/test_db.py | 6 +- server/tests/test_helpers.py | 4 +- 24 files changed, 243 insertions(+), 171 deletions(-) create mode 100644 server/app/database/__init__.py create mode 100644 server/app/database/base.py rename server/app/{core => database}/controller/__init__.py (76%) rename server/app/{core => database}/controller/add.py (77%) rename server/app/{core => database}/controller/delete.py (86%) rename server/app/{core => database}/controller/edit.py (100%) create mode 100644 server/app/database/controller/get.py rename server/app/{core/controller/get.py => database/controller/search.py} (58%) rename server/app/{core => database}/models.py (99%) diff --git a/server/app/__init__.py b/server/app/__init__.py index d1bbd3af..48a104f8 100644 --- a/server/app/__init__.py +++ b/server/app/__init__.py @@ -1,6 +1,6 @@ from flask import Flask, redirect, request -import app.core.models as models +import app.database.models as models from app.core import bcrypt, db, jwt, ma diff --git a/server/app/apis/__init__.py b/server/app/apis/__init__.py index 061a4519..996c14be 100644 --- a/server/app/apis/__init__.py +++ b/server/app/apis/__init__.py @@ -58,3 +58,4 @@ flask_api.add_namespace(comp_ns, path="/api/competitions") flask_api.add_namespace(slide_ns, path="/api/competitions/<CID>/slides") flask_api.add_namespace(team_ns, path="/api/competitions/<CID>/teams") flask_api.add_namespace(question_ns, path="/api/competitions/<CID>/questions") +#flask_api.add_namespace(question_ns, path="/api/competitions/<CID>/slides/<SID>/question") diff --git a/server/app/apis/auth.py b/server/app/apis/auth.py index df3a8e5c..1510df64 100644 --- a/server/app/apis/auth.py +++ b/server/app/apis/auth.py @@ -1,9 +1,9 @@ -import app.core.controller as dbc import app.core.http_codes as codes +import app.database.controller as dbc 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 app.database.models import User from flask_jwt_extended import ( create_access_token, create_refresh_token, @@ -30,14 +30,10 @@ class AuthSignup(Resource): args = create_user_parser.parse_args(strict=True) email = args.get("email") - if User.query.filter(User.email == email).count() > 0: + if dbc.get.user_exists(email): api.abort(codes.BAD_REQUEST, "User already exists") item_user = dbc.add.user(**args) - # TODO: Clarify when this case is needed or add it to a test - if not item_user: - api.abort(codes.BAD_REQUEST, "User could not be created") - return item_response(schema.dump(item_user)) @@ -46,10 +42,7 @@ class AuthSignup(Resource): class AuthDelete(Resource): @jwt_required def delete(self, ID): - item_user = User.query.filter(User.id == ID).first() - - if not item_user: - api.abort(codes.NOT_FOUND, f"Could not find user with id {ID}.") + item_user = dbc.get.user(ID) dbc.delete.default(item_user) if int(ID) == get_jwt_identity(): @@ -64,7 +57,7 @@ class AuthLogin(Resource): args = login_parser.parse_args(strict=True) email = args.get("email") password = args.get("password") - item_user = User.query.filter_by(email=email).first() + item_user = dbc.get.user_by_email(email, required=False) if not item_user or not item_user.is_correct_password(password): api.abort(codes.UNAUTHORIZED, "Invalid email or password") @@ -92,7 +85,7 @@ class AuthRefresh(Resource): def post(self): old_jti = get_raw_jwt()["jti"] - item_user = User.query.filter_by(id=get_jwt_identity()).first() + item_user = dbc.get.user(get_jwt_identity()) access_token = create_access_token(item_user.id, user_claims=get_user_claims(item_user)) dbc.add.blacklist(old_jti) response = {"access_token": access_token} diff --git a/server/app/apis/competitions.py b/server/app/apis/competitions.py index 93f03227..9e87f93a 100644 --- a/server/app/apis/competitions.py +++ b/server/app/apis/competitions.py @@ -1,8 +1,8 @@ -import app.core.controller as dbc +import app.database.controller as dbc from app.apis import admin_required, item_response, list_response from app.core.dto import CompetitionDTO -from app.core.models import Competition from app.core.parsers import competition_parser, competition_search_parser +from app.database.models import Competition from flask_jwt_extended import jwt_required from flask_restx import Resource @@ -11,10 +11,6 @@ schema = CompetitionDTO.schema list_schema = CompetitionDTO.list_schema -def get_comp(CID): - return Competition.query.filter(Competition.id == CID).first() - - @api.route("/") class CompetitionsList(Resource): @jwt_required @@ -34,20 +30,22 @@ class CompetitionsList(Resource): class Competitions(Resource): @jwt_required def get(self, CID): - item = get_comp(CID) + item = dbc.get.competition(CID) return item_response(schema.dump(item)) @jwt_required def put(self, CID): args = competition_parser.parse_args(strict=True) - item = get_comp(CID) + item = dbc.get.competition(CID) item = dbc.edit.competition(item, **args) + return item_response(schema.dump(item)) @jwt_required def delete(self, CID): - item = get_comp(CID) + item = dbc.get.competition(CID) dbc.delete.competition(item) + return "deleted" @@ -56,5 +54,5 @@ class CompetitionSearch(Resource): @jwt_required def get(self): args = competition_search_parser.parse_args(strict=True) - items, total = dbc.get.search_competitions(**args) + items, total = dbc.search.user(**args) return list_response(list_schema.dump(items), total) diff --git a/server/app/apis/misc.py b/server/app/apis/misc.py index f5b42fda..7fbb222d 100644 --- a/server/app/apis/misc.py +++ b/server/app/apis/misc.py @@ -1,7 +1,7 @@ -import app.core.controller as dbc +import app.database.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 app.database.models import City, MediaType, QuestionType, Role from flask_jwt_extended import jwt_required from flask_restx import Resource, reqparse diff --git a/server/app/apis/questions.py b/server/app/apis/questions.py index 76ca53f9..86929bb4 100644 --- a/server/app/apis/questions.py +++ b/server/app/apis/questions.py @@ -1,12 +1,11 @@ -import app.core.controller as dbc import app.core.http_codes as codes +import app.database.controller as dbc from app.apis import admin_required, item_response, list_response -from app.core.controller.add import competition from app.core.dto import QuestionDTO -from app.core.models import Question from app.core.parsers import question_parser -from flask_jwt_extended import get_jwt_identity, jwt_required -from flask_restx import Namespace, Resource +from app.database.models import Question +from flask_jwt_extended import jwt_required +from flask_restx import Resource api = QuestionDTO.api schema = QuestionDTO.schema @@ -18,8 +17,8 @@ list_schema = QuestionDTO.list_schema class QuestionsList(Resource): @jwt_required def get(self, CID): - items, total = dbc.get.search_questions(competition_id=CID) - return list_response(list_schema.dump(items), total) + items = dbc.get.question_list(CID) + return list_response(list_schema.dump(items)) @jwt_required def post(self, CID): @@ -41,25 +40,14 @@ class QuestionsList(Resource): class Questions(Resource): @jwt_required def get(self, CID, QID): - item_question = Question.query.filter(Question.id == QID).first() - - if item_question is None: - api.abort(codes.NOT_FOUND, f"Could not find question with id {QID}.") - - if item_question.slide.competition.id != int(CID): - api.abort(codes.NOT_FOUND, f"Could not find question with id {QID} in competition with id {CID}.") - + item_question = dbc.get.question(CID, QID) return item_response(schema.dump(item_question)) @jwt_required def put(self, CID, QID): args = question_parser.parse_args(strict=True) - print(f"questions 54: {args=}") - - item_question = Question.query.filter(Question.id == QID).first() - if item_question.slide.competition.id != int(CID): - api.abort(codes.NOT_FOUND, f"Could not find question with id {QID} in competition with id {CID}.") + item_question = dbc.get.question(CID, QID) item_question = dbc.edit.question(item_question, **args) return item_response(schema.dump(item_question)) @@ -67,8 +55,5 @@ class Questions(Resource): @jwt_required def delete(self, CID, QID): item_question = dbc.get.question(CID, QID) - if not item_question: - return {"response": "No content found"}, codes.NOT_FOUND - dbc.delete.question(item_question) return {}, codes.NO_CONTENT diff --git a/server/app/apis/slides.py b/server/app/apis/slides.py index ab7caa59..972e1bd6 100644 --- a/server/app/apis/slides.py +++ b/server/app/apis/slides.py @@ -1,9 +1,9 @@ -import app.core.controller as dbc import app.core.http_codes as codes +import app.database.controller as dbc from app.apis import admin_required, item_response, list_response from app.core.dto import SlideDTO -from app.core.models import Competition, Slide from app.core.parsers import slide_parser +from app.database.models import Competition, Slide from flask_jwt_extended import jwt_required from flask_restx import Resource @@ -12,22 +12,19 @@ schema = SlideDTO.schema list_schema = SlideDTO.list_schema -def get_comp(CID): - return Competition.query.filter(Competition.id == CID).first() - - @api.route("/") @api.param("CID") class SlidesList(Resource): @jwt_required def get(self, CID): - item_comp = get_comp(CID) - return list_response(list_schema.dump(item_comp.slides)) + items = dbc.get.slide_list(CID) + return list_response(list_schema.dump(items)) @jwt_required def post(self, CID): - item_comp = get_comp(CID) - dbc.add.slide(item_comp) + item_comp = dbc.get.competition(CID) + item_slide = dbc.add.slide(item_comp) + dbc.add.question(f"Fråga {item_slide.order + 1}", 10, 0, item_slide) dbc.refresh(item_comp) return list_response(list_schema.dump(item_comp.slides)) @@ -54,8 +51,6 @@ class Slides(Resource): @jwt_required def delete(self, CID, SID): item_slide = dbc.get.slide(CID, SID) - if not item_slide: - return {"response": "No content found"}, codes.NOT_FOUND dbc.delete.slide(item_slide) return {}, codes.NO_CONTENT @@ -72,10 +67,10 @@ class SlidesOrder(Resource): item_slide = dbc.get.slide(CID, SID) if order == item_slide.order: - api.abort(codes.BAD_REQUEST) + return item_response(schema.dump(item_slide)) # clamp order between 0 and max - order_count = Slide.query.filter(Slide.competition_id == item_slide.competition_id).count() + order_count = dbc.get.slide_count(CID) if order < 0: order = 0 elif order >= order_count - 1: diff --git a/server/app/apis/teams.py b/server/app/apis/teams.py index 7f7fdf0e..9600e2a4 100644 --- a/server/app/apis/teams.py +++ b/server/app/apis/teams.py @@ -1,9 +1,9 @@ -import app.core.controller as dbc import app.core.http_codes as codes +import app.database.controller as dbc from app.apis import admin_required, item_response, list_response from app.core.dto import TeamDTO -from app.core.models import Competition, Team from app.core.parsers import team_parser +from app.database.models import Competition, Team from flask_jwt_extended import get_jwt_identity, jwt_required from flask_restx import Namespace, Resource, reqparse @@ -12,22 +12,18 @@ schema = TeamDTO.schema list_schema = TeamDTO.list_schema -def get_comp(CID): - return Competition.query.filter(Competition.id == CID).first() - - @api.route("/") @api.param("CID") class TeamsList(Resource): @jwt_required def get(self, CID): - item_comp = get_comp(CID) - return list_response(list_schema.dump(item_comp.teams)) + items = dbc.get.team_list(CID) + return list_response(list_schema.dump(items)) @jwt_required def post(self, CID): args = team_parser.parse_args(strict=True) - item_comp = get_comp(CID) + item_comp = dbc.get.competition(CID) item_team = dbc.add.team(args["name"], item_comp) return item_response(schema.dump(item_team)) @@ -43,8 +39,6 @@ class Teams(Resource): @jwt_required def delete(self, CID, TID): item_team = dbc.get.team(CID, TID) - if not item_team: - api.abort(codes.NOT_FOUND, f"Could not find team with id {TID} in competition with id {CID}.") dbc.delete.team(item_team) return {}, codes.NO_CONTENT @@ -55,8 +49,6 @@ class Teams(Resource): name = args.get("name") item_team = dbc.get.team(CID, TID) - if not item_team: - api.abort(codes.NOT_FOUND, f"Could not find team with id {TID} in competition with id {CID}.") item_team = dbc.edit.team(item_team, name=name, competition_id=CID) return item_response(schema.dump(item_team)) diff --git a/server/app/apis/users.py b/server/app/apis/users.py index 07e2484d..28642b0d 100644 --- a/server/app/apis/users.py +++ b/server/app/apis/users.py @@ -1,9 +1,9 @@ -import app.core.controller as dbc import app.core.http_codes as codes +import app.database.controller as dbc 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 +from app.database.models import User from flask import request from flask_jwt_extended import get_jwt_identity, jwt_required from flask_restx import Namespace, Resource @@ -26,13 +26,13 @@ def edit_user(item_user, args): class UsersList(Resource): @jwt_required def get(self): - item = User.query.filter(User.id == get_jwt_identity()).first() + item = dbc.get.user(get_jwt_identity()) return item_response(schema.dump(item)) @jwt_required def put(self): args = user_parser.parse_args(strict=True) - item = User.query.filter(User.id == get_jwt_identity()).first() + item = dbc.get.user(get_jwt_identity()) item = edit_user(item, args) return item_response(schema.dump(item)) @@ -42,13 +42,13 @@ class UsersList(Resource): class Users(Resource): @jwt_required def get(self, ID): - item = User.query.filter(User.id == ID).first() + item = dbc.get.user(ID) return item_response(schema.dump(item)) @jwt_required def put(self, ID): args = user_parser.parse_args(strict=True) - item = User.query.filter(User.id == ID).first() + item = dbc.get.user(ID) item = edit_user(item, args) return item_response(schema.dump(item)) @@ -58,5 +58,5 @@ class UserSearch(Resource): @jwt_required def get(self): args = user_search_parser.parse_args(strict=True) - items, total = dbc.get.search_user(**args) + items, total = dbc.search.user(**args) return list_response(list_schema.dump(items), total) diff --git a/server/app/core/__init__.py b/server/app/core/__init__.py index e9d132cb..09c321ef 100644 --- a/server/app/core/__init__.py +++ b/server/app/core/__init__.py @@ -1,19 +1,10 @@ -import sqlalchemy as sa +from app.database.base import Base, ExtendedQuery 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 - -class Base(Model): - __abstract__ = True - _created = sa.Column(sa.DateTime(timezone=True), server_default=func.now()) - _updated = sa.Column(sa.DateTime(timezone=True), onupdate=func.now()) - - -db = SQLAlchemy(model_class=Base) +db = SQLAlchemy(model_class=Base, query_class=ExtendedQuery) bcrypt = Bcrypt() jwt = JWTManager() ma = Marshmallow() diff --git a/server/app/core/rich_schemas.py b/server/app/core/rich_schemas.py index d714d76d..ab1b3abb 100644 --- a/server/app/core/rich_schemas.py +++ b/server/app/core/rich_schemas.py @@ -1,5 +1,5 @@ -import app.core.models as models import app.core.schemas as schemas +import app.database.models as models from app.core import ma from marshmallow_sqlalchemy import fields @@ -29,8 +29,30 @@ class QuestionSchemaRich(RichSchema): id = ma.auto_field() name = ma.auto_field() total_score = ma.auto_field() + slide_id = ma.auto_field() type = fields.Nested(schemas.QuestionTypeSchema, many=False) - slide = fields.Nested(schemas.SlideSchema, many=False) + + +class TeamSchemaRich(RichSchema): + class Meta(RichSchema.Meta): + model = models.Team + + id = ma.auto_field() + name = ma.auto_field() + competition_id = ma.auto_field() + question_answers = fields.Nested(schemas.QuestionAnswerSchema, many=True) + + +class SlideSchemaRich(RichSchema): + class Meta(RichSchema.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() + questions = fields.Nested(QuestionSchemaRich, many=True) class CompetitionSchemaRich(RichSchema): @@ -40,5 +62,9 @@ class CompetitionSchemaRich(RichSchema): id = ma.auto_field() name = ma.auto_field() year = ma.auto_field() - slides = fields.Nested(schemas.SlideSchema, many=True) city = fields.Nested(schemas.CitySchema, many=False) + slides = fields.Nested( + SlideSchemaRich, + many=True, + ) + teams = fields.Nested(TeamSchemaRich, many=True) diff --git a/server/app/core/schemas.py b/server/app/core/schemas.py index c1d91c0d..27440642 100644 --- a/server/app/core/schemas.py +++ b/server/app/core/schemas.py @@ -1,4 +1,4 @@ -import app.core.models as models +import app.database.models as models from app.core import ma from marshmallow_sqlalchemy import fields @@ -29,6 +29,17 @@ class QuestionSchema(BaseSchema): slide_id = ma.auto_field() +class QuestionAnswerSchema(BaseSchema): + class Meta(BaseSchema.Meta): + model = models.QuestionAnswer + + id = ma.auto_field() + data = ma.auto_field() + score = ma.auto_field() + question_id = ma.auto_field() + team_id = ma.auto_field() + + class MediaTypeSchema(BaseSchema): class Meta(BaseSchema.Meta): model = models.MediaType diff --git a/server/app/database/__init__.py b/server/app/database/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/server/app/database/base.py b/server/app/database/base.py new file mode 100644 index 00000000..7a16a752 --- /dev/null +++ b/server/app/database/base.py @@ -0,0 +1,37 @@ +import app.core.http_codes as codes +import sqlalchemy as sa +from flask_restx import abort +from flask_sqlalchemy import BaseQuery, SQLAlchemy +from flask_sqlalchemy.model import Model +from sqlalchemy.sql import func + + +class Base(Model): + __abstract__ = True + _created = sa.Column(sa.DateTime(timezone=True), server_default=func.now()) + _updated = sa.Column(sa.DateTime(timezone=True), onupdate=func.now()) + + +class ExtendedQuery(BaseQuery): + def first_extended(self, required=True, error_message=None, error_code=codes.NOT_FOUND): + item = self.first() + + if required and not item: + if not error_message: + error_message = "Object not found" + abort(error_code, error_message) + + return item + + def pagination(self, page=0, page_size=15, order_column=None, order=1): + query = self + if order_column: + 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 diff --git a/server/app/core/controller/__init__.py b/server/app/database/controller/__init__.py similarity index 76% rename from server/app/core/controller/__init__.py rename to server/app/database/controller/__init__.py index 57f9b429..d31ded56 100644 --- a/server/app/core/controller/__init__.py +++ b/server/app/database/controller/__init__.py @@ -1,6 +1,6 @@ # import add, get from app.core import db -from app.core.controller import add, delete, edit, get +from app.database.controller import add, delete, edit, get, search def commit_and_refresh(item): diff --git a/server/app/core/controller/add.py b/server/app/database/controller/add.py similarity index 77% rename from server/app/core/controller/add.py rename to server/app/database/controller/add.py index 38c2a283..37f34dd7 100644 --- a/server/app/core/controller/add.py +++ b/server/app/database/controller/add.py @@ -1,5 +1,18 @@ +import app.core.http_codes as codes from app.core import db -from app.core.models import Blacklist, City, Competition, MediaType, Question, QuestionType, Role, Slide, Team, User +from app.database.models import ( + Blacklist, + City, + Competition, + MediaType, + Question, + QuestionType, + Role, + Slide, + Team, + User, +) +from flask_restx import abort def db_add(func): @@ -8,6 +21,10 @@ def db_add(func): db.session.add(item) db.session.commit() db.session.refresh(item) + + if not item: + abort(codes.BAD_REQUEST, f"Object could not be created") + return item return wrapper diff --git a/server/app/core/controller/delete.py b/server/app/database/controller/delete.py similarity index 86% rename from server/app/core/controller/delete.py rename to server/app/database/controller/delete.py index ac6ddf70..65527ee9 100644 --- a/server/app/core/controller/delete.py +++ b/server/app/database/controller/delete.py @@ -1,6 +1,6 @@ -import app.core.controller as dbc +import app.database.controller as dbc from app.core import db -from app.core.models import Blacklist, City, Competition, Role, Slide, User +from app.database.models import Blacklist, City, Competition, Role, Slide, User def default(item): @@ -17,7 +17,7 @@ def slide(item_slide): default(item_slide) # Update slide order for all slides after the deleted slide - slides_in_same_competition, _ = dbc.get.search_slide(competition_id=deleted_slide_competition_id) + slides_in_same_competition = dbc.get.slide_list(deleted_slide_competition_id) for other_slide in slides_in_same_competition: if other_slide.order > deleted_slide_order: other_slide.order -= 1 diff --git a/server/app/core/controller/edit.py b/server/app/database/controller/edit.py similarity index 100% rename from server/app/core/controller/edit.py rename to server/app/database/controller/edit.py diff --git a/server/app/database/controller/get.py b/server/app/database/controller/get.py new file mode 100644 index 00000000..a2b45fed --- /dev/null +++ b/server/app/database/controller/get.py @@ -0,0 +1,56 @@ +from app.database.models import Competition, Question, Slide, Team, User +from sqlalchemy.sql.expression import outerjoin + + +def user_exists(email): + return User.query.filter(User.email == email).count() > 0 + + +def competition(CID, required=True, error_msg=None): + return Competition.query.filter(Competition.id == CID).first_extended(required, error_msg) + + +def user(UID, required=True, error_msg=None): + return User.query.filter(User.id == UID).first_extended(required, error_msg) + + +def user_by_email(email, required=True, error_msg=None): + return User.query.filter(User.email == email).first_extended(required, error_msg) + + +def slide_by_order(CID, order, required=True, error_msg=None): + return Slide.query.filter((Slide.competition_id == CID) & (Slide.order == order)).first_extended( + required, error_msg + ) + + +def slide(CID, SID, required=True, error_msg=None): + return Slide.query.filter((Slide.competition_id == CID) & (Slide.id == SID)).first_extended(required, error_msg) + + +def team(CID, TID, required=True, error_msg=None): + return Team.query.filter((Team.competition_id == CID) & (Team.id == TID)).first_extended(required, error_msg) + + +def question(CID, QID, required=True, error_msg=None): + return ( + Question.query.join(Slide, (Slide.competition_id == CID) & (Slide.id == Question.slide_id)) + .filter(Question.id == QID) + .first_extended(required, error_msg) + ) + + +def question_list(CID): + return Question.query.join(Slide, (Slide.competition_id == CID) & (Slide.id == Question.slide_id)).all() + + +def team_list(CID): + return Team.query.filter(Team.competition_id == CID).all() + + +def slide_list(CID): + return Slide.query.filter(Slide.competition_id == CID).all() + + +def slide_count(CID): + return Slide.query.filter(Slide.competition_id == CID).count() diff --git a/server/app/core/controller/get.py b/server/app/database/controller/search.py similarity index 58% rename from server/app/core/controller/get.py rename to server/app/database/controller/search.py index f695deee..f570176c 100644 --- a/server/app/core/controller/get.py +++ b/server/app/database/controller/search.py @@ -1,38 +1,7 @@ -from app.core.models import Competition, Question, Slide, Team, User +from app.database.models import Competition, Question, Slide, Team, User -def slide_by_order(CID, order): - return Slide.query.filter((Slide.competition_id == CID) & (Slide.order == order)).first() - - -def slide(CID, SID): - return Slide.query.filter((Slide.competition_id == CID) & (Slide.id == SID)).first() - - -def team(CID, TID): - return Team.query.filter((Team.competition_id == CID) & (Team.id == TID)).first() - - -def question(CID, QID): - slide_ids = set( - [x.id for x in Slide.query.filter(Slide.competition_id == CID).all()] - ) # TODO: Filter using database instead of creating a set of slide_ids - return Question.query.filter(Question.slide_id.in_(slide_ids) & (Question.id == QID)).first() - - -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): +def 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}%")) @@ -47,12 +16,26 @@ def search_user(email=None, name=None, city_id=None, role_id=None, page=0, page_ if order_by: order_column = getattr(User.__table__.c, order_by) - return _search(query, order_column, page, page_size, order) + return query.pagination(page, page_size, order_column, order) -def search_slide( - slide_order=None, title=None, body=None, competition_id=None, page=0, page_size=15, order=1, order_by=None -): +def competitions(name=None, year=None, city_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}%")) + if year: + query = query.filter(Competition.year == year) + 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) + + +def slide(slide_order=None, title=None, body=None, competition_id=None, page=0, page_size=15, order=1, order_by=None): query = Slide.query if slide_order: query = query.filter(Slide.order == slide_order) @@ -67,10 +50,10 @@ def search_slide( if order_by: order_column = getattr(Slide.__table__.c, order_by) - return _search(query, order_column, page, page_size, order) + return query.pagination(page, page_size, order_column, order) -def search_questions( +def questions( name=None, total_score=None, type_id=None, @@ -100,20 +83,4 @@ def search_questions( if order_by: order_column = getattr(Question.__table__.c, order_by) - return _search(query, order_column, page, page_size, order) - - -def search_competitions(name=None, year=None, city_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}%")) - if year: - query = query.filter(Competition.year == year) - 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 _search(query, order_column, page, page_size, order) + return query.pagination(page, page_size, order_column, order) diff --git a/server/app/core/models.py b/server/app/database/models.py similarity index 99% rename from server/app/core/models.py rename to server/app/database/models.py index bd23f0d7..c3bc3d56 100644 --- a/server/app/core/models.py +++ b/server/app/database/models.py @@ -123,8 +123,6 @@ class Slide(db.Model): settings = db.Column(db.Text, nullable=False, default="{}") competition_id = db.Column(db.Integer, db.ForeignKey("competition.id"), nullable=False) - questions = db.relationship("Question", backref="slide") - def __init__(self, order, competition_id): self.order = order self.competition_id = competition_id @@ -138,6 +136,7 @@ class Question(db.Model): type_id = db.Column(db.Integer, db.ForeignKey("question_type.id"), nullable=False) slide_id = db.Column(db.Integer, db.ForeignKey("slide.id"), nullable=False) + slide = db.relationship("Slide", backref="questions") question_answers = db.relationship("QuestionAnswer", backref="question") alternatives = db.relationship("QuestionAlternative", backref="question") diff --git a/server/tests/test_app.py b/server/tests/test_app.py index 38a103b9..1cbacc33 100644 --- a/server/tests/test_app.py +++ b/server/tests/test_app.py @@ -1,8 +1,9 @@ import app.core.http_codes as codes -from app.core.models import Slide +from app.database.models import Slide from tests import app, client, db -from tests.test_helpers import add_default_values, change_order_test, delete, get, post, put +from tests.test_helpers import (add_default_values, change_order_test, delete, + get, post, put) def test_misc_api(client): @@ -301,7 +302,7 @@ def test_slide_api(client): SID = body["items"][i]["id"] order = body["items"][i]["order"] response, _ = put(client, f"/api/competitions/{CID}/slides/{SID}/order", {"order": order}, headers=headers) - assert response.status_code == codes.BAD_REQUEST + assert response.status_code == codes.OK # Changes the order change_order_test(client, CID, SID, order + 1, headers) @@ -330,6 +331,7 @@ def test_question_api(client): num_questions = 3 response, body = get(client, f"/api/competitions/{CID}/questions", headers=headers) assert response.status_code == codes.OK + print(body) assert body["count"] == num_questions # # Get specific question @@ -368,7 +370,7 @@ def test_question_api(client): assert item_question["name"] == name # # assert item_question["total_score"] == total_score assert item_question["type"]["id"] == type_id - assert item_question["slide"]["id"] == slide_id + assert item_question["slide_id"] == slide_id # Checks number of questions response, body = get(client, f"/api/competitions/{CID}/questions", headers=headers) assert response.status_code == codes.OK @@ -413,7 +415,7 @@ def test_question_api(client): assert item_question["name"] != name # assert item_question["total_score"] != total_score assert item_question["type"]["id"] != type_id - assert item_question["slide"]["id"] != slide_id + assert item_question["slide_id"] != slide_id response, item_question = put( client, f"/api/competitions/{CID}/questions/{QID}", @@ -425,7 +427,7 @@ def test_question_api(client): assert item_question["name"] == name # # assert item_question["total_score"] == total_score assert item_question["type"]["id"] == type_id - assert item_question["slide"]["id"] == slide_id + assert item_question["slide_id"] == slide_id # Checks number of questions response, body = get(client, f"/api/competitions/{CID}/questions", headers=headers) assert response.status_code == codes.OK diff --git a/server/tests/test_db.py b/server/tests/test_db.py index 68f49d5a..3bb998ae 100644 --- a/server/tests/test_db.py +++ b/server/tests/test_db.py @@ -1,5 +1,5 @@ -import app.core.controller as dbc -from app.core.models import City, Competition, Media, MediaType, Question, QuestionType, Role, Slide, Team, User +import app.database.controller as dbc +from app.database.models import City, Competition, Media, MediaType, Question, QuestionType, Role, Slide, Team, User from tests import app, client, db from tests.test_helpers import add_default_values, assert_exists, assert_insert_fail @@ -40,6 +40,7 @@ def test_media(client): assert item_media.upload_by.email == "test@test.se" +""" def test_question(client): add_default_values() item_user = User.query.filter_by(email="test@test.se").first() @@ -168,3 +169,4 @@ def test_slide(client): aux = dbc.get.search_slide(slide_order=1, competition_id=item_comp.id) item_slide = aux[0][0] dbc.delete.slide(item_slide) +""" diff --git a/server/tests/test_helpers.py b/server/tests/test_helpers.py index fbb58ac1..7cbdec87 100644 --- a/server/tests/test_helpers.py +++ b/server/tests/test_helpers.py @@ -1,9 +1,9 @@ import json -import app.core.controller as dbc import app.core.http_codes as codes +import app.database.controller as dbc from app.core import db -from app.core.models import City, Role +from app.database.models import City, Role def add_default_values(): -- GitLab