From 33331109040187531ee11f4aa99f993b0419ef03 Mon Sep 17 00:00:00 2001 From: robban64 <carl@schonfelder.se> Date: Sat, 17 Apr 2021 16:49:43 +0200 Subject: [PATCH] fix: slide order --- server/app/__init__.py | 2 +- server/app/apis/__init__.py | 4 +- server/app/apis/codes.py | 24 +--- server/app/apis/competitions.py | 6 +- server/app/apis/components.py | 24 ++-- server/app/apis/misc.py | 2 +- server/app/apis/slides.py | 30 ++--- server/app/apis/teams.py | 2 +- server/app/core/__init__.py | 2 +- server/app/core/codes.py | 4 +- server/app/core/dto.py | 4 +- server/app/database/__init__.py | 55 ++++++++ server/app/database/base.py | 37 ------ server/app/database/controller/__init__.py | 15 +-- server/app/database/controller/add.py | 113 ++++++++--------- server/app/database/controller/delete.py | 22 +++- server/app/database/controller/edit.py | 15 --- server/app/database/controller/get.py | 86 ++++--------- server/app/database/controller/utils.py | 23 ++++ server/app/database/models.py | 27 +--- server/populate.py | 15 +-- server/tests/test_app.py | 140 ++++----------------- server/tests/test_helpers.py | 49 ++------ 23 files changed, 270 insertions(+), 431 deletions(-) delete mode 100644 server/app/database/base.py create mode 100644 server/app/database/controller/utils.py diff --git a/server/app/__init__.py b/server/app/__init__.py index 8aa7a08a..a2529395 100644 --- a/server/app/__init__.py +++ b/server/app/__init__.py @@ -1,5 +1,5 @@ from flask import Flask, redirect, request -from flask_uploads import IMAGES, UploadSet, configure_uploads +from flask_uploads import configure_uploads 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 50281141..b48b8b33 100644 --- a/server/app/apis/__init__.py +++ b/server/app/apis/__init__.py @@ -23,7 +23,7 @@ def admin_required(): def text_response(message, code=codes.OK): - return {"message": message}, codes.OK + return {"message": message}, code def list_response(items, total=None, code=codes.OK): @@ -63,4 +63,4 @@ 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(code_ns, path="/api/competitions/<CID>/codes") flask_api.add_namespace(question_ns, path="/api/competitions/<CID>") -flask_api.add_namespace(component_ns, path="/api/competitions/<CID>/slides/<SID>/components") +flask_api.add_namespace(component_ns, path="/api/competitions/<CID>/slides/<SOrder>/components") diff --git a/server/app/apis/codes.py b/server/app/apis/codes.py index 70cf9341..af6aee84 100644 --- a/server/app/apis/codes.py +++ b/server/app/apis/codes.py @@ -1,10 +1,9 @@ import app.database.controller as dbc from app.apis import admin_required, item_response, list_response from app.core import http_codes as codes -from app.core.codes import generate_code from app.core.dto import CodeDTO from app.core.parsers import code_parser -from app.database.models import Competition +from app.database.models import Code, Competition from flask_jwt_extended import jwt_required from flask_restx import Resource @@ -21,24 +20,13 @@ class CodesList(Resource): items = dbc.get.code_list(CID) return list_response(list_schema.dump(items), len(items)), codes.OK - @jwt_required - def post(self, CID): - args = code_parser.parse_args(strict=True) - item = dbc.add.code(**args) - return item_response(schema.dump(item)), codes.OK - @api.route("/<code_id>") @api.param("CID, code_id") -class Competitions(Resource): +class CodesById(Resource): @jwt_required def put(self, CID, code_id): - item_code = dbc.get.code(code_id) - item_code = dbc.edit.generate_new_code(item_code) - return item_response(schema.dump(item_code)), codes.OK - - # @jwt_required - # def delete(self, CID, code_id): - # item_code = dbc.get.code(code_id) - # dbc.delete.code(item_code) - # return {}, http_codes.NOT_FOUND + item = dbc.get.one(Code, code_id) + item.code = dbc.utils.generate_unique_code() + dbc.utils.commit_and_refresh(item) + return item_response(schema.dump(item)), codes.OK diff --git a/server/app/apis/competitions.py b/server/app/apis/competitions.py index be376206..26b6f363 100644 --- a/server/app/apis/competitions.py +++ b/server/app/apis/competitions.py @@ -30,20 +30,20 @@ class CompetitionsList(Resource): class Competitions(Resource): @jwt_required def get(self, CID): - item = dbc.get.competition(CID) + item = dbc.get.one(Competition, CID) return item_response(schema.dump(item)) @jwt_required def put(self, CID): args = competition_parser.parse_args(strict=True) - item = dbc.get.competition(CID) + item = dbc.get.one(Competition, CID) item = dbc.edit.competition(item, **args) return item_response(schema.dump(item)) @jwt_required def delete(self, CID): - item = dbc.get.competition(CID) + item = dbc.get.one(Competition, CID) dbc.delete.competition(item) return "deleted" diff --git a/server/app/apis/components.py b/server/app/apis/components.py index da211895..f88e1e2d 100644 --- a/server/app/apis/components.py +++ b/server/app/apis/components.py @@ -3,7 +3,7 @@ import app.database.controller as dbc from app.apis import admin_required, item_response, list_response from app.core.dto import ComponentDTO from app.core.parsers import component_create_parser, component_parser -from app.database.models import Competition +from app.database.models import Competition, Component from flask.globals import request from flask_jwt_extended import jwt_required from flask_restx import Resource @@ -14,37 +14,37 @@ list_schema = ComponentDTO.list_schema @api.route("/<component_id>") -@api.param("CID, SID, component_id") +@api.param("CID, SOrder, component_id") class ComponentByID(Resource): @jwt_required - def get(self, CID, SID, component_id): - item = dbc.get.component(component_id) + def get(self, CID, SOrder, component_id): + item = dbc.get.one(Component, component_id) return item_response(schema.dump(item)) @jwt_required - def put(self, CID, SID, component_id): + def put(self, CID, SOrder, component_id): args = component_parser.parse_args() item = dbc.edit.component(**args) return item_response(schema.dump(item)) @jwt_required - def delete(self, CID, SID, component_id): - item = dbc.get.component(component_id) + def delete(self, CID, SOrder, component_id): + item = dbc.get.one(Component, component_id) dbc.delete.component(item) return {}, codes.NO_CONTENT @api.route("/") -@api.param("CID, SID") +@api.param("CID, SOrder") class ComponentList(Resource): @jwt_required - def get(self, CID, SID): - items = dbc.get.component_list(SID) + def get(self, CID, SOrder): + items = dbc.get.component_list(SOrder) return list_response(list_schema.dump(items)) @jwt_required - def post(self, CID, SID): + def post(self, CID, SOrder): args = component_create_parser.parse_args() - item_slide = dbc.get.slide(CID, SID) + item_slide = dbc.get.slide(CID, SOrder) item = dbc.add.component(item_slide=item_slide, **args) return item_response(schema.dump(item)) diff --git a/server/app/apis/misc.py b/server/app/apis/misc.py index b40f97a8..a78c2f8c 100644 --- a/server/app/apis/misc.py +++ b/server/app/apis/misc.py @@ -63,7 +63,7 @@ class Cities(Resource): item = dbc.get.one(City, ID) args = name_parser.parse_args(strict=True) item.name = args["name"] - dbc.commit_and_refresh(item) + dbc.utils.commit_and_refresh(item) items = dbc.get.all(City) return list_response(city_schema.dump(items)) diff --git a/server/app/apis/slides.py b/server/app/apis/slides.py index 972e1bd6..9aeb793a 100644 --- a/server/app/apis/slides.py +++ b/server/app/apis/slides.py @@ -22,49 +22,49 @@ class SlidesList(Resource): @jwt_required def post(self, CID): - item_comp = dbc.get.competition(CID) + item_comp = dbc.get.one(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) + dbc.utils.refresh(item_comp) return list_response(list_schema.dump(item_comp.slides)) -@api.route("/<SID>") -@api.param("CID,SID") +@api.route("/<SOrder>") +@api.param("CID,SOrder") class Slides(Resource): @jwt_required - def get(self, CID, SID): - item_slide = dbc.get.slide(CID, SID) + def get(self, CID, SOrder): + item_slide = dbc.get.slide(CID, SOrder) return item_response(schema.dump(item_slide)) @jwt_required - def put(self, CID, SID): + def put(self, CID, SOrder): 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.get.slide(CID, SOrder) item_slide = dbc.edit.slide(item_slide, title, timer) return item_response(schema.dump(item_slide)) @jwt_required - def delete(self, CID, SID): - item_slide = dbc.get.slide(CID, SID) + def delete(self, CID, SOrder): + item_slide = dbc.get.slide(CID, SOrder) dbc.delete.slide(item_slide) return {}, codes.NO_CONTENT -@api.route("/<SID>/order") -@api.param("CID,SID") +@api.route("/<SOrder>/order") +@api.param("CID,SOrder") class SlidesOrder(Resource): @jwt_required - def put(self, CID, SID): + def put(self, CID, SOrder): args = slide_parser.parse_args(strict=True) order = args.get("order") - item_slide = dbc.get.slide(CID, SID) + item_slide = dbc.get.slide(CID, SOrder) if order == item_slide.order: return item_response(schema.dump(item_slide)) @@ -77,7 +77,7 @@ class SlidesOrder(Resource): order = order_count - 1 # get slide at the requested order - item_slide_order = dbc.get.slide_by_order(CID, order) + item_slide_order = dbc.get.slide(CID, order) # switch place between them item_slide = dbc.edit.switch_order(item_slide, item_slide_order) diff --git a/server/app/apis/teams.py b/server/app/apis/teams.py index 9600e2a4..6729ccf8 100644 --- a/server/app/apis/teams.py +++ b/server/app/apis/teams.py @@ -23,7 +23,7 @@ class TeamsList(Resource): @jwt_required def post(self, CID): args = team_parser.parse_args(strict=True) - item_comp = dbc.get.competition(CID) + item_comp = dbc.get.one(Competition,CID) item_team = dbc.add.team(args["name"], item_comp) return item_response(schema.dump(item_team)) diff --git a/server/app/core/__init__.py b/server/app/core/__init__.py index 09c321ef..acfca554 100644 --- a/server/app/core/__init__.py +++ b/server/app/core/__init__.py @@ -1,4 +1,4 @@ -from app.database.base import Base, ExtendedQuery +from app.database import Base, ExtendedQuery from flask_bcrypt import Bcrypt from flask_jwt_extended.jwt_manager import JWTManager from flask_marshmallow import Marshmallow diff --git a/server/app/core/codes.py b/server/app/core/codes.py index c13fa84e..47150767 100644 --- a/server/app/core/codes.py +++ b/server/app/core/codes.py @@ -3,11 +3,11 @@ import re import string CODE_LENGTH = 6 -ALLOWED_CHARS = string.ascii_uppercase + string.digits +ALLOWED_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" CODE_RE = re.compile(f"^[{ALLOWED_CHARS}]{{{CODE_LENGTH}}}$") -def generate_code(): +def generate_code_string(): return "".join(random.choices(ALLOWED_CHARS, k=CODE_LENGTH)) diff --git a/server/app/core/dto.py b/server/app/core/dto.py index 4533fc0c..6541ef75 100644 --- a/server/app/core/dto.py +++ b/server/app/core/dto.py @@ -1,9 +1,7 @@ import app.core.rich_schemas as rich_schemas import app.core.schemas as schemas -import marshmallow as ma -from flask_restx import Namespace, fields +from flask_restx import Namespace from flask_uploads import IMAGES, UploadSet -from sqlalchemy.sql.expression import true class ComponentDTO: diff --git a/server/app/database/__init__.py b/server/app/database/__init__.py index e69de29b..ead77d9c 100644 --- a/server/app/database/__init__.py +++ b/server/app/database/__init__.py @@ -0,0 +1,55 @@ +import json + +from flask_restx import abort +from flask_sqlalchemy import BaseQuery +from flask_sqlalchemy.model import Model +from sqlalchemy import Column, DateTime, Text +from sqlalchemy.sql import func +from sqlalchemy.types import TypeDecorator + + +class Base(Model): + __abstract__ = True + _created = Column(DateTime(timezone=True), server_default=func.now()) + _updated = Column(DateTime(timezone=True), onupdate=func.now()) + + +class ExtendedQuery(BaseQuery): + def first_extended(self, required=True, error_message=None, error_code=404): + 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 + + +class Dictionary(TypeDecorator): + + impl = Text(1024) + + def process_bind_param(self, value, dialect): + if value is not None: + value = json.dumps(value).replace("'", '"') + + return value + + def process_result_value(self, value, dialect): + if value is not None: + value = json.loads(value) + return value diff --git a/server/app/database/base.py b/server/app/database/base.py deleted file mode 100644 index 7a16a752..00000000 --- a/server/app/database/base.py +++ /dev/null @@ -1,37 +0,0 @@ -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/database/controller/__init__.py b/server/app/database/controller/__init__.py index d31ded56..2865b523 100644 --- a/server/app/database/controller/__init__.py +++ b/server/app/database/controller/__init__.py @@ -1,16 +1,3 @@ # import add, get from app.core import db -from app.database.controller import add, delete, edit, get, search - - -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() +from app.database.controller import add, delete, edit, get, search, utils diff --git a/server/app/database/controller/add.py b/server/app/database/controller/add.py index 07b49fde..755849bb 100644 --- a/server/app/database/controller/add.py +++ b/server/app/database/controller/add.py @@ -1,6 +1,6 @@ import app.core.http_codes as codes from app.core import db -from app.core.codes import generate_code +from app.database.controller import utils from app.database.models import ( Blacklist, City, @@ -21,92 +21,93 @@ from app.database.models import ( from flask_restx import abort -def db_add(func): - def wrapper(*args, **kwargs): - item = func(*args, **kwargs) - db.session.add(item) - db.session.commit() - db.session.refresh(item) +def db_add(item): + db.session.add(item) + db.session.commit() + db.session.refresh(item) - if not item: - abort(codes.BAD_REQUEST, f"Object could not be created") + if not item: + abort(codes.BAD_REQUEST, f"Object could not be created") - return item + return item - return wrapper +def blacklist(jti): + return db_add(Blacklist(jti)) -@db_add -def component(type_id, item_slide, data, x=0, y=0, w=0, h=0): - return Component(item_slide.id, type_id, data, x, y, w, h) +def mediaType(name): + return db_add(MediaType(name)) -@db_add -def blacklist(jti): - return Blacklist(jti) +def questionType(name): + return db_add(QuestionType(name)) -@db_add -def image(filename, user_id): - return Media(filename, 1, user_id) +def componentType(name): + return db_add(ComponentType(name)) -@db_add -def slide(item_competition): - order = Slide.query.filter(Slide.competition_id == item_competition.id).count() # first element has index 0 - return Slide(order, item_competition.id) +def viewType(name): + return db_add(ViewType(name)) -@db_add -def user(email, password, role_id, city_id, name=None): - return User(email, password, role_id, city_id, name) +def role(name): + return db_add(Role(name)) -@db_add -def question(name, total_score, type_id, item_slide): - return Question(name, total_score, type_id, item_slide.id) +def city(name): + return db_add(City(name)) -@db_add -def competition(name, year, city_id): - return Competition(name, year, city_id) + +def component(type_id, item_slide, data, x=0, y=0, w=0, h=0): + return db_add(Component(item_slide.id, type_id, data, x, y, w, h)) -@db_add -def team(name, item_competition): - return Team(name, item_competition.id) +def image(filename, user_id): + return db_add(Media(filename, 1, user_id)) + + +def user(email, password, role_id, city_id, name=None): + return db_add(User(email, password, role_id, city_id, name)) + + +def question(name, total_score, type_id, item_slide): + return db_add(Question(name, total_score, type_id, item_slide.id)) -@db_add def code(pointer, view_type_id): - return Code(pointer, view_type_id) + code_string = utils.generate_unique_code() + return db_add(Code(code_string, pointer, view_type_id)) -@db_add -def mediaType(name): - return MediaType(name) +def team(name, item_competition): + item = db_add(Team(name, item_competition.id)) + # Add code for the team + code(item.id, 1) -@db_add -def questionType(name): - return QuestionType(name) + return item -@db_add -def componentType(name): - return ComponentType(name) +def slide(item_competition): + order = Slide.query.filter(Slide.competition_id == item_competition.id).count() # first element has index 0 + return db_add(Slide(order, item_competition.id)) -@db_add -def viewType(name): - return ViewType(name) +def competition(name, year, city_id): + item_competition = db_add(Competition(name, year, city_id)) + # Add one slide for the competition + slide(item_competition) -@db_add -def role(name): - return Role(name) + # Add code for Judge view + code(item_competition.id, 2) + # Add code for Audience view + code(item_competition.id, 3) -@db_add -def city(name): - return City(name) + # Add two teams + + utils.refresh(item_competition) + return item_competition diff --git a/server/app/database/controller/delete.py b/server/app/database/controller/delete.py index 2eb040c9..33bea217 100644 --- a/server/app/database/controller/delete.py +++ b/server/app/database/controller/delete.py @@ -12,18 +12,26 @@ def component(item_component): default(item_component) -def slide(item_slide): +def _slide(item_slide): for item_question in item_slide.questions: question(item_question) - deleted_slide_competition_id = item_slide.competition_id - deleted_slide_order = item_slide.order + for item_component in item_slide.components: + default(item_component) + default(item_slide) + +def slide(item_slide): + competition_id = item_slide.competition_id + slide_order = item_slide.order + + _slide(item_slide) + # Update slide order for all slides after the deleted slide - slides_in_same_competition = dbc.get.slide_list(deleted_slide_competition_id) + slides_in_same_competition = dbc.get.slide_list(competition_id) for other_slide in slides_in_same_competition: - if other_slide.order > deleted_slide_order: + if other_slide.order > slide_order: other_slide.order -= 1 db.session.commit() @@ -53,7 +61,9 @@ def question_answers(item_question_answers): def competition(item_competition): for item_slide in item_competition.slides: - slide(item_slide) + _slide(item_slide) for item_team in item_competition.teams: team(item_team) + + # TODO codes default(item_competition) diff --git a/server/app/database/controller/edit.py b/server/app/database/controller/edit.py index 7b3ef334..3afcb4d4 100644 --- a/server/app/database/controller/edit.py +++ b/server/app/database/controller/edit.py @@ -1,6 +1,4 @@ from app.core import db -from app.core.codes import generate_code -from app.database.models import Code def switch_order(item1, item2): @@ -111,16 +109,3 @@ def question(item_question, name=None, total_score=None, type_id=None, slide_id= db.session.refresh(item_question) return item_question - - -def generate_new_code(item_code): - - code = generate_code() - while db.session.query(Code).filter(Code.code == code).count(): - code = generate_code() - - item_code.code = code - db.session.commit() - db.session.refresh(item_code) - - return item_code diff --git a/server/app/database/controller/get.py b/server/app/database/controller/get.py index 4e9a6aa5..269640b3 100644 --- a/server/app/database/controller/get.py +++ b/server/app/database/controller/get.py @@ -1,21 +1,8 @@ from app.core import db -from app.database.models import ( - City, - Code, - Competition, - Component, - ComponentType, - MediaType, - Question, - QuestionType, - Role, - Slide, - Team, - User, - ViewType, -) - -team_view_id = ViewType.query.filter(ViewType.name == "Team").one().id +from app.database.models import (City, Code, Competition, Component, + ComponentType, MediaType, Question, + QuestionType, Role, Slide, Team, User, + ViewType) def all(db_type): @@ -29,34 +16,10 @@ def one(db_type, id, required=True, error_msg=None): def user_exists(email): return User.query.filter(User.email == email).count() > 0 - -def component(ID, required=True, error_msg=None): - return Component.query.filter(Component.id == ID).first_extended(required, error_msg) - - -def competition(CID, required=True, error_msg=None): - return Competition.query.filter(Competition.id == CID).first_extended(required, error_msg) - - -def code(code_id, required=True, error_msg=None): - return Code.query.filter(Code.id == code_id).first_extended(required, error_msg) - - def code_by_code(code): return Code.query.filter(Code.code == code.upper()).first() -def code_list(competition_id): - return ( - Code.query.join(Team, (Code.view_type_id == team_view_id) & (Team.id == Code.pointer), isouter=True) - .filter( - ((Code.view_type_id != team_view_id) & (Code.pointer == competition_id)) - | ((Code.view_type_id == team_view_id) & (competition_id == Team.competition_id)) - ) - .all() - ) - - def user(UID, required=True, error_msg=None): return User.query.filter(User.id == UID).first_extended(required, error_msg) @@ -65,30 +28,38 @@ 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 slide(CID, SOrder, required=True, error_msg=None): + filters = (Slide.competition_id == CID) & (Slide.order == SOrder) + return Slide.query.filter(filters).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, SID, QID, required=True, error_msg=None): - return ( - Question.query.join(Slide, (Slide.competition_id == CID) & (Slide.id == SID) & (Slide.id == Question.slide_id)) - .filter(Question.id == QID) - .first_extended(required, error_msg) +def question(CID, SOrder, QID, required=True, error_msg=None): + join_filters = (Slide.competition_id == CID) & (Slide.order == SOrder) & (Slide.id == Question.slide_id) + return Question.query.join(Slide, join_filters).filter(Question.id == QID).first_extended(required, error_msg) + + + +def code_list(competition_id): + team_view_id = 1 + join_filters = (Code.view_type_id == team_view_id) & (Team.id == Code.pointer) + filters = ((Code.view_type_id != team_view_id) & (Code.pointer == competition_id))( + (Code.view_type_id == team_view_id) & (competition_id == Team.competition_id) ) + return Code.query.join(Team, join_filters, isouter=True).filter(filters).all() def question_list(CID): - return Question.query.join(Slide, (Slide.competition_id == CID) & (Slide.id == Question.slide_id)).all() + join_filters = (Slide.competition_id == CID) & (Slide.id == Question.slide_id) + return Question.query.join(Slide, join_filters).all() + + +def component_list(CID, SOrder): + join_filters = (Slide.competition_id == CID) & (Slide.order == SOrder) & (Component.slide_id == Slide.id) + return Component.query.join(Slide, join_filters).all() def team_list(CID): @@ -99,10 +70,5 @@ def slide_list(CID): return Slide.query.filter(Slide.competition_id == CID).all() -def component_list(SID): - # TODO: Maybe take CID as argument and make sure that SID is in that competition? - return Component.query.filter(Component.slide_id == SID).all() - - def slide_count(CID): return Slide.query.filter(Slide.competition_id == CID).count() diff --git a/server/app/database/controller/utils.py b/server/app/database/controller/utils.py new file mode 100644 index 00000000..61be34ca --- /dev/null +++ b/server/app/database/controller/utils.py @@ -0,0 +1,23 @@ +from app.core import db +from app.core.codes import generate_code_string +from app.database.models import Code + + +def generate_unique_code(): + code = generate_code_string() + while db.session.query(Code).filter(Code.code == code).count(): + code = generate_code_string() + return code + + +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/database/models.py b/server/app/database/models.py index 9479d92c..cfedd923 100644 --- a/server/app/database/models.py +++ b/server/app/database/models.py @@ -1,11 +1,6 @@ -import json - from app.core import bcrypt, db -from app.core.codes import generate_code from sqlalchemy.ext.hybrid import hybrid_method, hybrid_property -from sqlalchemy.orm import backref -from sqlalchemy.types import TypeDecorator - +from app.database import Dictionary STRING_SIZE = 254 @@ -187,20 +182,7 @@ class QuestionAnswer(db.Model): self.team_id = team_id -class Dictionary(TypeDecorator): - - impl = db.Text(1024) - - def process_bind_param(self, value, dialect): - if value is not None: - value = json.dumps(value).replace("'", '"') - return value - - def process_result_value(self, value, dialect): - if value is not None: - value = json.loads(value) - return value class Component(db.Model): @@ -231,12 +213,7 @@ class Code(db.Model): view_type_id = db.Column(db.Integer, db.ForeignKey("view_type.id"), nullable=False) - def __init__(self, pointer, view_type_id): - - code = generate_code() - while db.session.query(Code).filter(Code.code == code).count(): - code = generate_code() - + def __init__(self, code, pointer, view_type_id): self.code = code self.pointer = pointer self.view_type_id = view_type_id diff --git a/server/populate.py b/server/populate.py index 45eb6005..bebcaa8b 100644 --- a/server/populate.py +++ b/server/populate.py @@ -1,8 +1,6 @@ -from sqlalchemy.sql.expression import true - import app.database.controller as dbc from app import create_app, db -from app.database.models import City, Competition, MediaType, QuestionType, Role +from app.database.models import City, Competition, QuestionType, Role def _add_items(): @@ -49,14 +47,11 @@ def _add_items(): dbc.add.competition(f"Test{i+1}", 1971, city_id) item_comps = Competition.query.all() - # Add - for item_comp in item_comps: - for i in range(3): - # Add slide to competition - item_slide = dbc.add.slide(item_comp) - # Add question to competition - dbc.add.question(f"Q{i+1}", i + 1, text_id, item_slide) + for item_comp in item_comps: + for item_slide in item_comp.slides: + for i in range(3): + dbc.add.question(f"Q{i+1}", i + 1, text_id, item_slide) # Add teams to competition for team_name in teams: diff --git a/server/tests/test_app.py b/server/tests/test_app.py index 8086774e..948b9ae4 100644 --- a/server/tests/test_app.py +++ b/server/tests/test_app.py @@ -72,9 +72,6 @@ def test_competition_api(client): assert body["name"] == "c1" competition_id = body["id"] - # Save number of slides - num_slides = len(Slide.query.all()) - # Get competition response, body = get(client, f"/api/competitions/{competition_id}", headers=headers) assert response.status_code == codes.OK @@ -85,11 +82,9 @@ def test_competition_api(client): response, body = get(client, f"/api/competitions/{competition_id}/slides", headers=headers) assert response.status_code == codes.OK - assert len(body["items"]) == 2 + assert len(body["items"]) == 3 - response, body = put( - client, f"/api/competitions/{competition_id}/slides/{num_slides}/order", {"order": 1}, headers=headers - ) + response, body = put(client, f"/api/competitions/{competition_id}/slides/{2}/order", {"order": 1}, headers=headers) assert response.status_code == codes.OK response, body = post(client, f"/api/competitions/{competition_id}/teams", {"name": "t1"}, headers=headers) @@ -235,7 +230,7 @@ def test_slide_api(client): CID = 1 response, body = get(client, f"/api/competitions/{CID}/slides", headers=headers) assert response.status_code == codes.OK - assert body["count"] == 0 + assert body["count"] == 1 # Get slides CID = 2 @@ -247,12 +242,14 @@ def test_slide_api(client): response, body = post(client, f"/api/competitions/{CID}/slides", headers=headers) assert response.status_code == codes.OK assert body["count"] == 4 + # Add another slide + response, body = post(client, f"/api/competitions/{CID}/slides", headers=headers) # Get slide - SID = 1 - response, item_slide = get(client, f"/api/competitions/{CID}/slides/{SID}", headers=headers) + slide_order = 1 + response, item_slide = get(client, f"/api/competitions/{CID}/slides/{slide_order}", headers=headers) assert response.status_code == codes.OK - assert item_slide["id"] == SID + assert item_slide["order"] == slide_order # Edit slide order = 6 @@ -265,7 +262,7 @@ def test_slide_api(client): assert item_slide["timer"] != timer response, item_slide = put( client, - f"/api/competitions/{CID}/slides/{SID}", + f"/api/competitions/{CID}/slides/{slide_order}", # TODO: Implement so these commented lines can be edited # {"order": order, "title": title, "body": body, "timer": timer}, {"title": title, "timer": timer}, @@ -278,29 +275,26 @@ def test_slide_api(client): assert item_slide["timer"] == timer # Delete slide - response, _ = delete(client, f"/api/competitions/{CID}/slides/{SID}", headers=headers) + response, _ = delete(client, f"/api/competitions/{CID}/slides/{slide_order}", headers=headers) assert response.status_code == codes.NO_CONTENT # Checks that there are fewer slides response, body = get(client, f"/api/competitions/{CID}/slides", headers=headers) assert response.status_code == codes.OK - assert body["count"] == 3 + assert body["count"] == 4 - # Tries to delete slide again - response, _ = delete(client, f"/api/competitions/{CID}/slides/{SID}", headers=headers) - assert response.status_code == codes.NOT_FOUND + # Tries to delete slide again, should work since the order is now changed + response, _ = delete(client, f"/api/competitions/{CID}/slides/{slide_order}", headers=headers) + assert response.status_code == codes.NO_CONTENT # Changes the order to the same order - SID = body["items"][0]["id"] - order = body["items"][0]["order"] - response, _ = put(client, f"/api/competitions/{CID}/slides/{SID}/order", {"order": order}, headers=headers) + slide_order = body["items"][0]["order"] + response, _ = put( + client, f"/api/competitions/{CID}/slides/{slide_order}/order", {"order": slide_order}, headers=headers + ) assert response.status_code == codes.OK # Changes the order - change_order_test(client, CID, SID, order + 1, headers) - - # Changes order to 0 - SID = 7 - change_order_test(client, CID, SID, -1, headers) + change_order_test(client, CID, slide_order, slide_order + 1, headers) def test_question_api(client): @@ -313,7 +307,7 @@ def test_question_api(client): # Get questions from empty competition CID = 1 # TODO: Fix api-calls so that the ones not using CID don't require one - SID = 1 + slide_order = 1 response, body = get(client, f"/api/competitions/{CID}/questions", headers=headers) assert response.status_code == codes.OK assert body["count"] == 0 @@ -323,111 +317,30 @@ 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 - # name = "Q2" - # # total_score = 2 - # type_id = 5 - # slide_id = 5 - # response, body = get( - # client, - # f"/api/competitions/{CID}/questions/", - # headers=headers, - # ) - # # print(f"357: {body['items']}") - # assert response.status_code == codes.OK - # assert body["count"] == 1 - # item_question = body["items"][0] - # # print(f"338: {item_question}") - # 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 - # Add question name = "Nytt namn" - # total_score = 2 type_id = 2 - SID = 5 + slide_order = 1 response, item_question = post( client, - f"/api/competitions/{CID}/slides/{SID}/questions", + f"/api/competitions/{CID}/slides/{slide_order}/questions", {"name": name, "type_id": type_id}, headers=headers, ) - num_questions += 1 + num_questions = 4 assert response.status_code == codes.OK assert item_question["name"] == name - # # assert item_question["total_score"] == total_score assert item_question["type"]["id"] == type_id - assert item_question["slide_id"] == SID - # Checks number of questions - response, body = get(client, f"/api/competitions/{CID}/questions", headers=headers) - assert response.status_code == codes.OK - assert body["count"] == num_questions - - # Try to get question in another competition - QID = 1 - response, item_question = get(client, f"/api/competitions/{CID}/slides/{SID}/questions/{QID}", headers=headers) - assert response.status_code == codes.NOT_FOUND - # Get question - QID = 4 - SID = 4 - response, item_question = get(client, f"/api/competitions/{CID}/slides/{SID}/questions/{QID}", headers=headers) - assert response.status_code == codes.OK - assert item_question["id"] == QID - - # Try to edit question in another competition - name = "Nyare namn" - # total_score = 2 - type_id = 3 - SID = 1 - QID = 1 - response, _ = put( - client, - f"/api/competitions/{CID}/slides/{SID}/questions/{QID}", - {"name": name, "type_id": type_id}, - headers=headers, - ) - assert response.status_code == codes.NOT_FOUND - # Checks number of questions - response, body = get(client, f"/api/competitions/{CID}/questions", headers=headers) - assert response.status_code == codes.OK - assert body["count"] == num_questions - - # Edit question - name = "Nyare namn" - # total_score = 2 - type_id = 3 - SID = 4 - NEW_SID = 5 - QID = 4 - assert item_question["name"] != name - # assert item_question["total_score"] != total_score - assert item_question["type"]["id"] != type_id - assert item_question["slide_id"] != NEW_SID - response, item_question = put( - client, - f"/api/competitions/{CID}/slides/{SID}/questions/{QID}", - # {"name": name, "total_score": total_score, "type_id": type_id, "slide_id": slide_id}, - {"name": name, "type_id": type_id, "slide_id": NEW_SID}, - headers=headers, - ) - assert response.status_code == codes.OK - assert item_question["name"] == name - # # assert item_question["total_score"] == total_score - assert item_question["type"]["id"] == type_id - assert item_question["slide_id"] == NEW_SID # Checks number of questions response, body = get(client, f"/api/competitions/{CID}/questions", headers=headers) assert response.status_code == codes.OK assert body["count"] == num_questions - + """ # Delete question - response, _ = delete(client, f"/api/competitions/{CID}/slides/{NEW_SID}/questions/{QID}", headers=headers) + response, _ = delete(client, f"/api/competitions/{CID}/slides/{slide_order}/questions/{QID}", headers=headers) num_questions -= 1 assert response.status_code == codes.NO_CONTENT @@ -437,5 +350,6 @@ def test_question_api(client): assert body["count"] == num_questions # Tries to delete question again - response, _ = delete(client, f"/api/competitions/{CID}/slides/{NEW_SID}/questions/{QID}", headers=headers) + response, _ = delete(client, f"/api/competitions/{CID}/slides/{NEW_slide_order}/questions/{QID}", headers=headers) assert response.status_code == codes.NOT_FOUND + """ diff --git a/server/tests/test_helpers.py b/server/tests/test_helpers.py index 6b6bf7a0..fbd77d9e 100644 --- a/server/tests/test_helpers.py +++ b/server/tests/test_helpers.py @@ -39,23 +39,25 @@ def add_default_values(): dbc.add.user("test@test.se", "password", item_admin.id, item_city.id, "Olle Olsson") # Add competitions - dbc.add.competition("Tom tävling", 2012, item_city.id) + item_competition = dbc.add.competition("Tom tävling", 2012, item_city.id) for j in range(2): item_comp = dbc.add.competition(f"Tävling {j}", 2012, item_city.id) + # Add two more slides to competition + dbc.add.slide(item_comp) + dbc.add.slide(item_comp) # Add slides - for i in range(len(question_types)): - # Add slide to competition - item_slide = dbc.add.slide(item_comp) - + i = 1 + for item_slide in item_comp.slides: # Populate slide with data item_slide.title = f"Title {i}" item_slide.body = f"Body {i}" item_slide.timer = 100 + i # item_slide.settings = "{}" - + dbc.utils.commit_and_refresh(item_slide) # Add question to competition - dbc.add.question(name=f"Q{i+1}", total_score=i + 1, type_id=i + 1, item_slide=item_slide) + dbc.add.question(name=f"Q{i}", total_score=i, type_id=1, item_slide=item_slide) + i += 1 def get_body(response): @@ -124,37 +126,12 @@ def assert_object_values(obj, values): # Changes order of slides -def change_order_test(client, cid, sid, order, h): - sid_at_order = -1 - actual_order = 0 if order < 0 else order # used to find the slide_id - response, body = get(client, f"/api/competitions/{cid}/slides", headers=h) - assert response.status_code == codes.OK - - # Finds the slide_id of the slide that will be swapped with - for item_slide in body["items"]: - if item_slide["order"] == actual_order: - assert item_slide["id"] != sid - sid_at_order = item_slide["id"] - assert sid_at_order != -1 - - # Gets old versions of slides - response, item_slide_10 = get(client, f"/api/competitions/{cid}/slides/{sid}", headers=h) +def change_order_test(client, cid, order, new_order, h): + response, new_order_body = get(client, f"/api/competitions/{cid}/slides/{new_order}", headers=h) assert response.status_code == codes.OK - response, item_slide_20 = get(client, f"/api/competitions/{cid}/slides/{sid_at_order}", headers=h) + response, order_body = get(client, f"/api/competitions/{cid}/slides/{order}", headers=h) assert response.status_code == codes.OK # Changes order - response, _ = put( - client, f"/api/competitions/{cid}/slides/{sid}/order", {"order": order}, headers=h - ) # uses order to be able to test negative order + response, _ = put(client, f"/api/competitions/{cid}/slides/{order}/order", {"order": new_order}, headers=h) assert response.status_code == codes.OK - - # Gets new versions of slides - response, item_slide_11 = get(client, f"/api/competitions/{cid}/slides/{sid}", headers=h) - assert response.status_code == codes.OK - response, item_slide_21 = get(client, f"/api/competitions/{cid}/slides/{sid_at_order}", headers=h) - assert response.status_code == codes.OK - - # Checks that the order was indeed swapped - assert item_slide_10["order"] == item_slide_21["order"] - assert item_slide_11["order"] == item_slide_20["order"] -- GitLab