From 9e12da9243db0f44d0f40c3306f2379840661e97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20L=C3=B6fgren?= <viclo211@student.liu.se> Date: Wed, 21 Apr 2021 13:38:08 +0200 Subject: [PATCH 01/17] competitions: CID -> competition_id --- server/app/apis/competitions.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/server/app/apis/competitions.py b/server/app/apis/competitions.py index a18ed85e..950fb881 100644 --- a/server/app/apis/competitions.py +++ b/server/app/apis/competitions.py @@ -29,26 +29,26 @@ class CompetitionsList(Resource): return item_response(schema.dump(item)) -@api.route("/<CID>") -@api.param("CID") +@api.route("/<competition_id>") +@api.param("competition_id") class Competitions(Resource): @check_jwt(editor=True) - def get(self, CID): - item = dbc.get.competition(CID) + def get(self, competition_id): + item = dbc.get.competition(competition_id) return item_response(rich_schema.dump(item)) @check_jwt(editor=True) - def put(self, CID): + def put(self, competition_id): args = competition_parser.parse_args(strict=True) - item = dbc.get.one(Competition, CID) + item = dbc.get.one(Competition, competition_id) item = dbc.edit.competition(item, **args) return item_response(schema.dump(item)) @check_jwt(editor=True) - def delete(self, CID): - item = dbc.get.one(Competition, CID) + def delete(self, competition_id): + item = dbc.get.one(Competition, competition_id) dbc.delete.competition(item) return "deleted" @@ -63,12 +63,12 @@ class CompetitionSearch(Resource): return list_response(list_schema.dump(items), total) -@api.route("/<CID>/copy") -@api.param("CID") +@api.route("/<competition_id>/copy") +@api.param("competition_id") class SlidesOrder(Resource): @check_jwt(editor=True) - def post(self, CID): - item_competition = dbc.get.competition(CID) + def post(self, competition_id): + item_competition = dbc.get.competition(competition_id) item_competition_copy = dbc.copy.competition(item_competition) -- GitLab From a3eca98f9dd26318bb22fafab9093c6e2733cd6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20L=C3=B6fgren?= <viclo211@student.liu.se> Date: Wed, 21 Apr 2021 13:39:26 +0200 Subject: [PATCH 02/17] slides: CID -> competition_id --- server/app/apis/__init__.py | 2 +- server/app/apis/slides.py | 40 ++++++++++++++++++------------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/server/app/apis/__init__.py b/server/app/apis/__init__.py index 181a6dca..37d7a019 100644 --- a/server/app/apis/__init__.py +++ b/server/app/apis/__init__.py @@ -74,7 +74,7 @@ flask_api.add_namespace(misc_ns, path="/api/misc") flask_api.add_namespace(user_ns, path="/api/users") flask_api.add_namespace(auth_ns, path="/api/auth") flask_api.add_namespace(comp_ns, path="/api/competitions") -flask_api.add_namespace(slide_ns, path="/api/competitions/<CID>/slides") +flask_api.add_namespace(slide_ns, path="/api/competitions/<competition_id>/slides") flask_api.add_namespace(alternative_ns, path="/api/competitions/<CID>/slides/<SOrder>/questions/<QID>/alternatives") flask_api.add_namespace(answer_ns, path="/api/competitions/<CID>/teams/<TID>/answers") flask_api.add_namespace(team_ns, path="/api/competitions/<CID>/teams") diff --git a/server/app/apis/slides.py b/server/app/apis/slides.py index 3c25f901..2a95e190 100644 --- a/server/app/apis/slides.py +++ b/server/app/apis/slides.py @@ -13,16 +13,16 @@ list_schema = SlideDTO.list_schema @api.route("/") -@api.param("CID") +@api.param("competition_id") class SlidesList(Resource): @check_jwt(editor=True) - def get(self, CID): - items = dbc.get.slide_list(CID) + def get(self, competition_id): + items = dbc.get.slide_list(competition_id) return list_response(list_schema.dump(items)) @check_jwt(editor=True) - def post(self, CID): - item_comp = dbc.get.one(Competition, CID) + def post(self, competition_id): + item_comp = dbc.get.one(Competition, competition_id) item_slide = dbc.add.slide(item_comp) dbc.add.question(f"Fråga {item_slide.order + 1}", 10, 0, item_slide) dbc.utils.refresh(item_comp) @@ -30,54 +30,54 @@ class SlidesList(Resource): @api.route("/<SOrder>") -@api.param("CID,SOrder") +@api.param("competition_id,SOrder") class Slides(Resource): @check_jwt(editor=True) - def get(self, CID, SOrder): - item_slide = dbc.get.slide(CID, SOrder) + def get(self, competition_id, SOrder): + item_slide = dbc.get.slide(competition_id, SOrder) return item_response(schema.dump(item_slide)) @check_jwt(editor=True) - def put(self, CID, SOrder): + def put(self, competition_id, SOrder): args = slide_parser.parse_args(strict=True) title = args.get("title") timer = args.get("timer") - item_slide = dbc.get.slide(CID, SOrder) + item_slide = dbc.get.slide(competition_id, SOrder) item_slide = dbc.edit.slide(item_slide, title, timer) return item_response(schema.dump(item_slide)) @check_jwt(editor=True) - def delete(self, CID, SOrder): - item_slide = dbc.get.slide(CID, SOrder) + def delete(self, competition_id, SOrder): + item_slide = dbc.get.slide(competition_id, SOrder) dbc.delete.slide(item_slide) return {}, codes.NO_CONTENT @api.route("/<SOrder>/order") -@api.param("CID,SOrder") +@api.param("competition_id,SOrder") class SlidesOrder(Resource): @check_jwt(editor=True) - def put(self, CID, SOrder): + def put(self, competition_id, SOrder): args = slide_parser.parse_args(strict=True) order = args.get("order") - item_slide = dbc.get.slide(CID, SOrder) + item_slide = dbc.get.slide(competition_id, SOrder) if order == item_slide.order: return item_response(schema.dump(item_slide)) # clamp order between 0 and max - order_count = dbc.get.slide_count(CID) + order_count = dbc.get.slide_count(competition_id) if order < 0: order = 0 elif order >= order_count - 1: order = order_count - 1 # get slide at the requested order - item_slide_order = dbc.get.slide(CID, order) + item_slide_order = dbc.get.slide(competition_id, order) # switch place between them item_slide = dbc.edit.switch_order(item_slide, item_slide_order) @@ -86,11 +86,11 @@ class SlidesOrder(Resource): @api.route("/<SOrder>/copy") -@api.param("CID,SOrder") +@api.param("competition_id,SOrder") class SlidesOrder(Resource): @check_jwt(editor=True) - def post(self, CID, SOrder): - item_slide = dbc.get.slide(CID, SOrder) + def post(self, competition_id, SOrder): + item_slide = dbc.get.slide(competition_id, SOrder) item_slide_copy = dbc.copy.slide(item_slide) -- GitLab From 45d26406b252cbce6f88a1538aa92272ea65b909 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20L=C3=B6fgren?= <viclo211@student.liu.se> Date: Wed, 21 Apr 2021 13:44:28 +0200 Subject: [PATCH 03/17] Change CID to competition_id --- server/app/apis/__init__.py | 12 ++++++------ server/app/apis/alternatives.py | 12 ++++++------ server/app/apis/answers.py | 10 +++++----- server/app/apis/codes.py | 13 ++++++------- server/app/apis/components.py | 18 +++++++++--------- server/app/apis/questions.py | 26 +++++++++++++------------- server/app/apis/teams.py | 26 +++++++++++++------------- 7 files changed, 58 insertions(+), 59 deletions(-) diff --git a/server/app/apis/__init__.py b/server/app/apis/__init__.py index 37d7a019..04449e4c 100644 --- a/server/app/apis/__init__.py +++ b/server/app/apis/__init__.py @@ -75,9 +75,9 @@ flask_api.add_namespace(user_ns, path="/api/users") flask_api.add_namespace(auth_ns, path="/api/auth") flask_api.add_namespace(comp_ns, path="/api/competitions") flask_api.add_namespace(slide_ns, path="/api/competitions/<competition_id>/slides") -flask_api.add_namespace(alternative_ns, path="/api/competitions/<CID>/slides/<SOrder>/questions/<QID>/alternatives") -flask_api.add_namespace(answer_ns, path="/api/competitions/<CID>/teams/<TID>/answers") -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/<SOrder>/components") +flask_api.add_namespace(alternative_ns, path="/api/competitions/<competition_id>/slides/<SOrder>/questions/<QID>/alternatives") +flask_api.add_namespace(answer_ns, path="/api/competitions/<competition_id>/teams/<TID>/answers") +flask_api.add_namespace(team_ns, path="/api/competitions/<competition_id>/teams") +flask_api.add_namespace(code_ns, path="/api/competitions/<competition_id>/codes") +flask_api.add_namespace(question_ns, path="/api/competitions/<competition_id>") +flask_api.add_namespace(component_ns, path="/api/competitions/<competition_id>/slides/<SOrder>/components") diff --git a/server/app/apis/alternatives.py b/server/app/apis/alternatives.py index 8c247b40..265e1968 100644 --- a/server/app/apis/alternatives.py +++ b/server/app/apis/alternatives.py @@ -16,32 +16,32 @@ list_schema = QuestionAlternativeDTO.list_schema @api.route("/") -@api.param("CID, SOrder, QID") +@api.param("competition_id, SOrder, QID") class QuestionAlternativeList(Resource): @check_jwt(editor=True) - def get(self, CID, SOrder, QID): + def get(self, competition_id, SOrder, QID): items = dbc.get.question_alternatives(QID) return list_response(list_schema.dump(items)) @check_jwt(editor=True) - def post(self, CID, SOrder, QID): + def post(self, competition_id, SOrder, QID): args = question_alternative_parser.parse_args(strict=True) item = dbc.add.question_alternative(**args, question_id=QID) return item_response(schema.dump(item)) @api.route("/<AID>") -@api.param("CID, SOrder, QID, AID") +@api.param("competition_id, SOrder, QID, AID") class QuestionAlternatives(Resource): @check_jwt(editor=True) - def put(self, CID, SOrder, QID, AID): + def put(self, competition_id, SOrder, QID, AID): args = question_alternative_parser.parse_args(strict=True) item = dbc.get.one(QuestionAlternative, AID) item = dbc.edit.question_alternative(item, **args) return item_response(schema.dump(item)) @check_jwt(editor=True) - def delete(self, CID, SOrder, QID, AID): + def delete(self, competition_id, SOrder, QID, AID): item = dbc.get.one(QuestionAlternative, AID) dbc.delete.default(item) return {}, codes.NO_CONTENT diff --git a/server/app/apis/answers.py b/server/app/apis/answers.py index 40e9407f..95ce38dc 100644 --- a/server/app/apis/answers.py +++ b/server/app/apis/answers.py @@ -16,25 +16,25 @@ list_schema = QuestionAnswerDTO.list_schema @api.route("/") -@api.param("CID, TID") +@api.param("competition_id, TID") class QuestionAnswerList(Resource): @check_jwt(editor=True) - def get(self, CID, TID): + def get(self, competition_id, TID): items = dbc.get.question_answers(TID) return list_response(list_schema.dump(items)) @check_jwt(editor=True) - def post(self, CID, TID): + def post(self, competition_id, TID): args = question_answer_parser.parse_args(strict=True) item = dbc.add.question_answer(**args, team_id=TID) return item_response(schema.dump(item)) @api.route("/<AID>") -@api.param("CID, TID, AID") +@api.param("competition_id, TID, AID") class QuestionAnswers(Resource): @check_jwt(editor=True) - def put(self, CID, TID, AID): + def put(self, competition_id, TID, AID): args = question_answer_edit_parser.parse_args(strict=True) item = dbc.get.one(QuestionAnswer, AID) item = dbc.edit.question_answer(item, **args) diff --git a/server/app/apis/codes.py b/server/app/apis/codes.py index 332d5f3e..c761420f 100644 --- a/server/app/apis/codes.py +++ b/server/app/apis/codes.py @@ -1,12 +1,11 @@ import app.database.controller as dbc -from app.apis import item_response, list_response +from app.apis import check_jwt, item_response, list_response from app.core import http_codes as codes from app.core.dto import CodeDTO from app.core.parsers import code_parser from app.database.models import Code, Competition from flask_jwt_extended import jwt_required from flask_restx import Resource -from app.apis import check_jwt api = CodeDTO.api schema = CodeDTO.schema @@ -14,19 +13,19 @@ list_schema = CodeDTO.list_schema @api.route("/") -@api.param("CID") +@api.param("competition_id") class CodesList(Resource): @check_jwt(editor=True) - def get(self, CID): - items = dbc.get.code_list(CID) + def get(self, competition_id): + items = dbc.get.code_list(competition_id) return list_response(list_schema.dump(items), len(items)), codes.OK @api.route("/<code_id>") -@api.param("CID, code_id") +@api.param("competition_id, code_id") class CodesById(Resource): @check_jwt(editor=False) - def put(self, CID, code_id): + def put(self, competition_id, code_id): item = dbc.get.one(Code, code_id) item.code = dbc.utils.generate_unique_code() dbc.utils.commit_and_refresh(item) diff --git a/server/app/apis/components.py b/server/app/apis/components.py index c68ca928..3e50785a 100644 --- a/server/app/apis/components.py +++ b/server/app/apis/components.py @@ -14,38 +14,38 @@ list_schema = ComponentDTO.list_schema @api.route("/<component_id>") -@api.param("CID, SOrder, component_id") +@api.param("competition_id, SOrder, component_id") class ComponentByID(Resource): @check_jwt(editor=True) - def get(self, CID, SOrder, component_id): + def get(self, competition_id, SOrder, component_id): item = dbc.get.one(Component, component_id) return item_response(schema.dump(item)) @check_jwt(editor=True) - def put(self, CID, SOrder, component_id): + def put(self, competition_id, SOrder, component_id): args = component_parser.parse_args() item = dbc.get.one(Component, component_id) item = dbc.edit.component(item, **args) return item_response(schema.dump(item)) @check_jwt(editor=True) - def delete(self, CID, SOrder, component_id): + def delete(self, competition_id, SOrder, component_id): item = dbc.get.one(Component, component_id) dbc.delete.component(item) return {}, codes.NO_CONTENT @api.route("/") -@api.param("CID, SOrder") +@api.param("competition_id, SOrder") class ComponentList(Resource): @check_jwt(editor=True) - def get(self, CID, SOrder): - items = dbc.get.component_list(CID, SOrder) + def get(self, competition_id, SOrder): + items = dbc.get.component_list(competition_id, SOrder) return list_response(list_schema.dump(items)) @check_jwt(editor=True) - def post(self, CID, SOrder): + def post(self, competition_id, SOrder): args = component_create_parser.parse_args() - item_slide = dbc.get.slide(CID, SOrder) + item_slide = dbc.get.slide(competition_id, SOrder) item = dbc.add.component(item_slide=item_slide, **args) return item_response(schema.dump(item)) diff --git a/server/app/apis/questions.py b/server/app/apis/questions.py index 7d16d3c8..31b00728 100644 --- a/server/app/apis/questions.py +++ b/server/app/apis/questions.py @@ -13,47 +13,47 @@ list_schema = QuestionDTO.list_schema @api.route("/questions") -@api.param("CID") +@api.param("competition_id") class QuestionList(Resource): @check_jwt(editor=True) - def get(self, CID): - items = dbc.get.question_list(CID) + def get(self, competition_id): + items = dbc.get.question_list(competition_id) return list_response(list_schema.dump(items)) @api.route("/slides/<SID>/questions") -@api.param("CID, SID") +@api.param("competition_id, SID") class QuestionListForSlide(Resource): @check_jwt(editor=True) - def post(self, SID, CID): + def post(self, SID, competition_id): args = question_parser.parse_args(strict=True) del args["slide_id"] - item_slide = dbc.get.slide(CID, SID) + item_slide = dbc.get.slide(competition_id, SID) item = dbc.add.question(item_slide=item_slide, **args) return item_response(schema.dump(item)) @api.route("/slides/<SID>/questions/<QID>") -@api.param("CID, SID, QID") +@api.param("competition_id, SID, QID") class QuestionById(Resource): @check_jwt(editor=True) - def get(self, CID, SID, QID): - item_question = dbc.get.question(CID, SID, QID) + def get(self, competition_id, SID, QID): + item_question = dbc.get.question(competition_id, SID, QID) return item_response(schema.dump(item_question)) @check_jwt(editor=True) - def put(self, CID, SID, QID): + def put(self, competition_id, SID, QID): args = question_parser.parse_args(strict=True) - item_question = dbc.get.question(CID, SID, QID) + item_question = dbc.get.question(competition_id, SID, QID) item_question = dbc.edit.question(item_question, **args) return item_response(schema.dump(item_question)) @check_jwt(editor=True) - def delete(self, CID, SID, QID): - item_question = dbc.get.question(CID, SID, QID) + def delete(self, competition_id, SID, QID): + item_question = dbc.get.question(competition_id, SID, QID) dbc.delete.question(item_question) return {}, codes.NO_CONTENT diff --git a/server/app/apis/teams.py b/server/app/apis/teams.py index 2bb0a235..9a88ffb8 100644 --- a/server/app/apis/teams.py +++ b/server/app/apis/teams.py @@ -13,45 +13,45 @@ list_schema = TeamDTO.list_schema @api.route("/") -@api.param("CID") +@api.param("competition_id") class TeamsList(Resource): @check_jwt(editor=True) - def get(self, CID): - items = dbc.get.team_list(CID) + def get(self, competition_id): + items = dbc.get.team_list(competition_id) return list_response(list_schema.dump(items)) @check_jwt(editor=True) - def post(self, CID): + def post(self, competition_id): args = team_parser.parse_args(strict=True) - item_comp = dbc.get.one(Competition, CID) + item_comp = dbc.get.one(Competition, competition_id) item_team = dbc.add.team(args["name"], item_comp) return item_response(schema.dump(item_team)) @api.route("/<TID>") -@api.param("CID,TID") +@api.param("competition_id,TID") class Teams(Resource): @jwt_required @check_jwt(editor=True) - def get(self, CID, TID): - item = dbc.get.team(CID, TID) + def get(self, competition_id, TID): + item = dbc.get.team(competition_id, TID) return item_response(schema.dump(item)) @jwt_required @check_jwt(editor=True) - def delete(self, CID, TID): - item_team = dbc.get.team(CID, TID) + def delete(self, competition_id, TID): + item_team = dbc.get.team(competition_id, TID) dbc.delete.team(item_team) return {}, codes.NO_CONTENT @jwt_required @check_jwt(editor=True) - def put(self, CID, TID): + def put(self, competition_id, TID): args = team_parser.parse_args(strict=True) name = args.get("name") - item_team = dbc.get.team(CID, TID) + item_team = dbc.get.team(competition_id, TID) - item_team = dbc.edit.team(item_team, name=name, competition_id=CID) + item_team = dbc.edit.team(item_team, name=name, competition_id=competition_id) return item_response(schema.dump(item_team)) -- GitLab From 0ef0f82fd0cadb090399be82f90815f6096c302f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20L=C3=B6fgren?= <viclo211@student.liu.se> Date: Wed, 21 Apr 2021 13:51:04 +0200 Subject: [PATCH 04/17] Rename SOrder to slide_order --- server/app/apis/__init__.py | 6 ++++-- server/app/apis/alternatives.py | 12 +++++------ server/app/apis/components.py | 18 ++++++++--------- server/app/apis/slides.py | 36 ++++++++++++++++----------------- 4 files changed, 37 insertions(+), 35 deletions(-) diff --git a/server/app/apis/__init__.py b/server/app/apis/__init__.py index 04449e4c..a049f65e 100644 --- a/server/app/apis/__init__.py +++ b/server/app/apis/__init__.py @@ -75,9 +75,11 @@ flask_api.add_namespace(user_ns, path="/api/users") flask_api.add_namespace(auth_ns, path="/api/auth") flask_api.add_namespace(comp_ns, path="/api/competitions") flask_api.add_namespace(slide_ns, path="/api/competitions/<competition_id>/slides") -flask_api.add_namespace(alternative_ns, path="/api/competitions/<competition_id>/slides/<SOrder>/questions/<QID>/alternatives") +flask_api.add_namespace( + alternative_ns, path="/api/competitions/<competition_id>/slides/<slide_order>/questions/<QID>/alternatives" +) flask_api.add_namespace(answer_ns, path="/api/competitions/<competition_id>/teams/<TID>/answers") flask_api.add_namespace(team_ns, path="/api/competitions/<competition_id>/teams") flask_api.add_namespace(code_ns, path="/api/competitions/<competition_id>/codes") flask_api.add_namespace(question_ns, path="/api/competitions/<competition_id>") -flask_api.add_namespace(component_ns, path="/api/competitions/<competition_id>/slides/<SOrder>/components") +flask_api.add_namespace(component_ns, path="/api/competitions/<competition_id>/slides/<slide_order>/components") diff --git a/server/app/apis/alternatives.py b/server/app/apis/alternatives.py index 265e1968..2061b32f 100644 --- a/server/app/apis/alternatives.py +++ b/server/app/apis/alternatives.py @@ -16,32 +16,32 @@ list_schema = QuestionAlternativeDTO.list_schema @api.route("/") -@api.param("competition_id, SOrder, QID") +@api.param("competition_id, slide_order, QID") class QuestionAlternativeList(Resource): @check_jwt(editor=True) - def get(self, competition_id, SOrder, QID): + def get(self, competition_id, slide_order, QID): items = dbc.get.question_alternatives(QID) return list_response(list_schema.dump(items)) @check_jwt(editor=True) - def post(self, competition_id, SOrder, QID): + def post(self, competition_id, slide_order, QID): args = question_alternative_parser.parse_args(strict=True) item = dbc.add.question_alternative(**args, question_id=QID) return item_response(schema.dump(item)) @api.route("/<AID>") -@api.param("competition_id, SOrder, QID, AID") +@api.param("competition_id, slide_order, QID, AID") class QuestionAlternatives(Resource): @check_jwt(editor=True) - def put(self, competition_id, SOrder, QID, AID): + def put(self, competition_id, slide_order, QID, AID): args = question_alternative_parser.parse_args(strict=True) item = dbc.get.one(QuestionAlternative, AID) item = dbc.edit.question_alternative(item, **args) return item_response(schema.dump(item)) @check_jwt(editor=True) - def delete(self, competition_id, SOrder, QID, AID): + def delete(self, competition_id, slide_order, QID, AID): item = dbc.get.one(QuestionAlternative, AID) dbc.delete.default(item) return {}, codes.NO_CONTENT diff --git a/server/app/apis/components.py b/server/app/apis/components.py index 3e50785a..c8620bc3 100644 --- a/server/app/apis/components.py +++ b/server/app/apis/components.py @@ -14,38 +14,38 @@ list_schema = ComponentDTO.list_schema @api.route("/<component_id>") -@api.param("competition_id, SOrder, component_id") +@api.param("competition_id, slide_order, component_id") class ComponentByID(Resource): @check_jwt(editor=True) - def get(self, competition_id, SOrder, component_id): + def get(self, competition_id, slide_order, component_id): item = dbc.get.one(Component, component_id) return item_response(schema.dump(item)) @check_jwt(editor=True) - def put(self, competition_id, SOrder, component_id): + def put(self, competition_id, slide_order, component_id): args = component_parser.parse_args() item = dbc.get.one(Component, component_id) item = dbc.edit.component(item, **args) return item_response(schema.dump(item)) @check_jwt(editor=True) - def delete(self, competition_id, SOrder, component_id): + def delete(self, competition_id, slide_order, component_id): item = dbc.get.one(Component, component_id) dbc.delete.component(item) return {}, codes.NO_CONTENT @api.route("/") -@api.param("competition_id, SOrder") +@api.param("competition_id, slide_order") class ComponentList(Resource): @check_jwt(editor=True) - def get(self, competition_id, SOrder): - items = dbc.get.component_list(competition_id, SOrder) + def get(self, competition_id, slide_order): + items = dbc.get.component_list(competition_id, slide_order) return list_response(list_schema.dump(items)) @check_jwt(editor=True) - def post(self, competition_id, SOrder): + def post(self, competition_id, slide_order): args = component_create_parser.parse_args() - item_slide = dbc.get.slide(competition_id, SOrder) + item_slide = dbc.get.slide(competition_id, slide_order) item = dbc.add.component(item_slide=item_slide, **args) return item_response(schema.dump(item)) diff --git a/server/app/apis/slides.py b/server/app/apis/slides.py index 2a95e190..2724325f 100644 --- a/server/app/apis/slides.py +++ b/server/app/apis/slides.py @@ -29,42 +29,42 @@ class SlidesList(Resource): return list_response(list_schema.dump(item_comp.slides)) -@api.route("/<SOrder>") -@api.param("competition_id,SOrder") +@api.route("/<slide_order>") +@api.param("competition_id,slide_order") class Slides(Resource): @check_jwt(editor=True) - def get(self, competition_id, SOrder): - item_slide = dbc.get.slide(competition_id, SOrder) + def get(self, competition_id, slide_order): + item_slide = dbc.get.slide(competition_id, slide_order) return item_response(schema.dump(item_slide)) @check_jwt(editor=True) - def put(self, competition_id, SOrder): + def put(self, competition_id, slide_order): args = slide_parser.parse_args(strict=True) title = args.get("title") timer = args.get("timer") - item_slide = dbc.get.slide(competition_id, SOrder) + item_slide = dbc.get.slide(competition_id, slide_order) item_slide = dbc.edit.slide(item_slide, title, timer) return item_response(schema.dump(item_slide)) @check_jwt(editor=True) - def delete(self, competition_id, SOrder): - item_slide = dbc.get.slide(competition_id, SOrder) + def delete(self, competition_id, slide_order): + item_slide = dbc.get.slide(competition_id, slide_order) dbc.delete.slide(item_slide) return {}, codes.NO_CONTENT -@api.route("/<SOrder>/order") -@api.param("competition_id,SOrder") -class SlidesOrder(Resource): +@api.route("/<slide_order>/order") +@api.param("competition_id,slide_order") +class Slideslide_order(Resource): @check_jwt(editor=True) - def put(self, competition_id, SOrder): + def put(self, competition_id, slide_order): args = slide_parser.parse_args(strict=True) order = args.get("order") - item_slide = dbc.get.slide(competition_id, SOrder) + item_slide = dbc.get.slide(competition_id, slide_order) if order == item_slide.order: return item_response(schema.dump(item_slide)) @@ -85,12 +85,12 @@ class SlidesOrder(Resource): return item_response(schema.dump(item_slide)) -@api.route("/<SOrder>/copy") -@api.param("competition_id,SOrder") -class SlidesOrder(Resource): +@api.route("/<slide_order>/copy") +@api.param("competition_id,slide_order") +class Slideslide_order(Resource): @check_jwt(editor=True) - def post(self, competition_id, SOrder): - item_slide = dbc.get.slide(competition_id, SOrder) + def post(self, competition_id, slide_order): + item_slide = dbc.get.slide(competition_id, slide_order) item_slide_copy = dbc.copy.slide(item_slide) -- GitLab From ee19fa8dc8a5d7a676733ccab7f48563a219807b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20L=C3=B6fgren?= <viclo211@student.liu.se> Date: Wed, 21 Apr 2021 14:08:14 +0200 Subject: [PATCH 05/17] Change SOrder to slide_order in questions --- server/app/apis/questions.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/server/app/apis/questions.py b/server/app/apis/questions.py index 31b00728..f51543f4 100644 --- a/server/app/apis/questions.py +++ b/server/app/apis/questions.py @@ -21,39 +21,39 @@ class QuestionList(Resource): return list_response(list_schema.dump(items)) -@api.route("/slides/<SID>/questions") -@api.param("competition_id, SID") +@api.route("/slides/<slide_order>/questions") +@api.param("competition_id, slide_order") class QuestionListForSlide(Resource): @check_jwt(editor=True) - def post(self, SID, competition_id): + def post(self, slide_order, competition_id): args = question_parser.parse_args(strict=True) del args["slide_id"] - item_slide = dbc.get.slide(competition_id, SID) + item_slide = dbc.get.slide(competition_id, slide_order) item = dbc.add.question(item_slide=item_slide, **args) return item_response(schema.dump(item)) -@api.route("/slides/<SID>/questions/<QID>") -@api.param("competition_id, SID, QID") +@api.route("/slides/<slide_order>/questions/<QID>") +@api.param("competition_id, slide_order, QID") class QuestionById(Resource): @check_jwt(editor=True) - def get(self, competition_id, SID, QID): - item_question = dbc.get.question(competition_id, SID, QID) + def get(self, competition_id, slide_order, QID): + item_question = dbc.get.question(competition_id, slide_order, QID) return item_response(schema.dump(item_question)) @check_jwt(editor=True) - def put(self, competition_id, SID, QID): + def put(self, competition_id, slide_order, QID): args = question_parser.parse_args(strict=True) - item_question = dbc.get.question(competition_id, SID, QID) + item_question = dbc.get.question(competition_id, slide_order, QID) item_question = dbc.edit.question(item_question, **args) return item_response(schema.dump(item_question)) @check_jwt(editor=True) - def delete(self, competition_id, SID, QID): - item_question = dbc.get.question(competition_id, SID, QID) + def delete(self, competition_id, slide_order, QID): + item_question = dbc.get.question(competition_id, slide_order, QID) dbc.delete.question(item_question) return {}, codes.NO_CONTENT -- GitLab From 0bb550007d542c037d2764dd53aecb34f742d55a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20L=C3=B6fgren?= <viclo211@student.liu.se> Date: Wed, 21 Apr 2021 14:14:31 +0200 Subject: [PATCH 06/17] Rename CID and SOrder in get --- server/app/database/controller/get.py | 46 +++++++++++++++------------ 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/server/app/database/controller/get.py b/server/app/database/controller/get.py index 45cbb6ec..9e1885cd 100644 --- a/server/app/database/controller/get.py +++ b/server/app/database/controller/get.py @@ -60,44 +60,48 @@ def user_by_email(email, required=True, error_msg=None): return User.query.filter(User.email == email).first_extended(required, error_msg) -def slide(CID, SOrder, required=True, error_msg=None): +def slide(competition_id, slide_order, required=True, error_msg=None): """ Gets the slide object associated with the provided id and order. """ - filters = (Slide.competition_id == CID) & (Slide.order == SOrder) + filters = (Slide.competition_id == competition_id) & (Slide.order == slide_order) return Slide.query.filter(filters).first_extended(required, error_msg) -def team(CID, TID, required=True, error_msg=None): +def team(competition_id, TID, required=True, error_msg=None): """ Gets the team object associated with the provided id and competition id. """ - return Team.query.filter((Team.competition_id == CID) & (Team.id == TID)).first_extended(required, error_msg) + return Team.query.filter((Team.competition_id == competition_id) & (Team.id == TID)).first_extended( + required, error_msg + ) -def question(CID, SOrder, QID, required=True, error_msg=None): +def question(competition_id, slide_order, QID, required=True, error_msg=None): """ Gets the question object associated with the provided id, slide order and competition id. """ - join_filters = (Slide.competition_id == CID) & (Slide.order == SOrder) & (Slide.id == Question.slide_id) + join_filters = ( + (Slide.competition_id == competition_id) & (Slide.order == slide_order) & (Slide.id == Question.slide_id) + ) return Question.query.join(Slide, join_filters).filter(Question.id == QID).first_extended(required, error_msg) def question_alternatives(QID): - # join_filters = (Slide.competition_id == CID) & (Slide.order == SOrder) + # join_filters = (Slide.competition_id == competition_id) & (Slide.order == slide_order) return QuestionAlternative.query.filter(QuestionAlternative.question_id == QID).all() def question_answers(TID): - # join_filters = (Slide.competition_id == CID) & (Slide.order == SOrder) + # join_filters = (Slide.competition_id == competition_id) & (Slide.order == slide_order) return QuestionAnswer.query.filter(QuestionAnswer.team_id == TID).all() -def competition(CID): +def competition(competition_id): """ Get Competition and all it's sub-entities """ """ HOT PATH """ os1 = joinedload(Competition.slides).joinedload(Slide.components) os2 = joinedload(Competition.slides).joinedload(Slide.questions).joinedload(Question.alternatives) ot = joinedload(Competition.teams).joinedload(Team.question_answers) - return Competition.query.filter(Competition.id == CID).options(os1).options(os2).options(ot).first() + return Competition.query.filter(Competition.id == competition_id).options(os1).options(os2).options(ot).first() def code_list(competition_id): @@ -111,33 +115,35 @@ def code_list(competition_id): return Code.query.join(Team, join_filters, isouter=True).filter(filters).all() -def question_list(CID): +def question_list(competition_id): """ Gets a list of all question objects associated with a the provided competition. """ - join_filters = (Slide.competition_id == CID) & (Slide.id == Question.slide_id) + join_filters = (Slide.competition_id == competition_id) & (Slide.id == Question.slide_id) return Question.query.join(Slide, join_filters).all() -def component_list(CID, SOrder): +def component_list(competition_id, slide_order): """ Gets a list of all component objects associated with a the provided competition id and slide order. """ - join_filters = (Slide.competition_id == CID) & (Slide.order == SOrder) & (Component.slide_id == Slide.id) + join_filters = ( + (Slide.competition_id == competition_id) & (Slide.order == slide_order) & (Component.slide_id == Slide.id) + ) return Component.query.join(Slide, join_filters).all() -def team_list(CID): +def team_list(competition_id): """ Gets a list of all team objects associated with a the provided competition. """ - return Team.query.filter(Team.competition_id == CID).all() + return Team.query.filter(Team.competition_id == competition_id).all() -def slide_list(CID): +def slide_list(competition_id): """ Gets a list of all slide objects associated with a the provided competition. """ - return Slide.query.filter(Slide.competition_id == CID).all() + return Slide.query.filter(Slide.competition_id == competition_id).all() -def slide_count(CID): +def slide_count(competition_id): """ Gets the number of slides in the provided competition. """ - return Slide.query.filter(Slide.competition_id == CID).count() + return Slide.query.filter(Slide.competition_id == competition_id).count() -- GitLab From 806d6f7f59834dc7dc0fe256bb7af190e7317529 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20L=C3=B6fgren?= <viclo211@student.liu.se> Date: Wed, 21 Apr 2021 16:02:29 +0200 Subject: [PATCH 07/17] Add order to question --- server/app/apis/questions.py | 19 ++++++++----------- server/app/core/parsers.py | 1 - server/app/core/rich_schemas.py | 1 + server/app/core/schemas.py | 1 + server/app/database/controller/add.py | 3 ++- server/app/database/controller/copy.py | 1 + server/app/database/controller/get.py | 8 ++++++-- server/app/database/models.py | 6 ++++-- 8 files changed, 23 insertions(+), 17 deletions(-) diff --git a/server/app/apis/questions.py b/server/app/apis/questions.py index f51543f4..2ae7e915 100644 --- a/server/app/apis/questions.py +++ b/server/app/apis/questions.py @@ -27,33 +27,30 @@ class QuestionListForSlide(Resource): @check_jwt(editor=True) def post(self, slide_order, competition_id): args = question_parser.parse_args(strict=True) - del args["slide_id"] - item_slide = dbc.get.slide(competition_id, slide_order) item = dbc.add.question(item_slide=item_slide, **args) - return item_response(schema.dump(item)) -@api.route("/slides/<slide_order>/questions/<QID>") -@api.param("competition_id, slide_order, QID") +@api.route("/slides/<slide_order>/questions/<question_order>") +@api.param("competition_id, slide_order, question_order") class QuestionById(Resource): @check_jwt(editor=True) - def get(self, competition_id, slide_order, QID): - item_question = dbc.get.question(competition_id, slide_order, QID) + def get(self, competition_id, slide_order, question_order): + item_question = dbc.get.question(competition_id, slide_order, question_order) return item_response(schema.dump(item_question)) @check_jwt(editor=True) - def put(self, competition_id, slide_order, QID): + def put(self, competition_id, slide_order, question_order): args = question_parser.parse_args(strict=True) - item_question = dbc.get.question(competition_id, slide_order, QID) + item_question = dbc.get.question(competition_id, slide_order, question_order) item_question = dbc.edit.question(item_question, **args) return item_response(schema.dump(item_question)) @check_jwt(editor=True) - def delete(self, competition_id, slide_order, QID): - item_question = dbc.get.question(competition_id, slide_order, QID) + def delete(self, competition_id, slide_order, question_order): + item_question = dbc.get.question(competition_id, slide_order, question_order) dbc.delete.question(item_question) return {}, codes.NO_CONTENT diff --git a/server/app/core/parsers.py b/server/app/core/parsers.py index 45c0a6e3..71f8fdee 100644 --- a/server/app/core/parsers.py +++ b/server/app/core/parsers.py @@ -58,7 +58,6 @@ question_parser = reqparse.RequestParser() question_parser.add_argument("name", type=str, default=None, location="json") question_parser.add_argument("total_score", type=int, default=None, location="json") question_parser.add_argument("type_id", type=int, default=None, location="json") -question_parser.add_argument("slide_id", type=int, location="json") ###QUESTION ALTERNATIVES#### diff --git a/server/app/core/rich_schemas.py b/server/app/core/rich_schemas.py index 8852c392..bb1b1391 100644 --- a/server/app/core/rich_schemas.py +++ b/server/app/core/rich_schemas.py @@ -17,6 +17,7 @@ class QuestionSchemaRich(RichSchema): id = ma.auto_field() name = ma.auto_field() + order = ma.auto_field() total_score = ma.auto_field() slide_id = ma.auto_field() type_id = ma.auto_field() diff --git a/server/app/core/schemas.py b/server/app/core/schemas.py index 79bdbc1c..60c36165 100644 --- a/server/app/core/schemas.py +++ b/server/app/core/schemas.py @@ -52,6 +52,7 @@ class QuestionSchema(BaseSchema): id = ma.auto_field() name = ma.auto_field() + order = ma.auto_field() total_score = ma.auto_field() type_id = ma.auto_field() slide_id = ma.auto_field() diff --git a/server/app/database/controller/add.py b/server/app/database/controller/add.py index 929410fa..f378b570 100644 --- a/server/app/database/controller/add.py +++ b/server/app/database/controller/add.py @@ -113,7 +113,8 @@ def question(name, total_score, type_id, item_slide): Adds a question to the specified slide using the provided arguments. """ - return db_add(Question(name, total_score, type_id, item_slide.id)) + order = Question.query.filter(Question.slide_id == item_slide.id).count() # first element has index 0 + return db_add(Question(name, order, total_score, type_id, item_slide.id)) def question_alternative(text, value, question_id): diff --git a/server/app/database/controller/copy.py b/server/app/database/controller/copy.py index a6b408ec..9d41ca24 100644 --- a/server/app/database/controller/copy.py +++ b/server/app/database/controller/copy.py @@ -20,6 +20,7 @@ def _question(item_question_old, slide_id): item_question_new = add.db_add( Question( item_question_old.name, + item_question_old.order, item_question_old.total_score, item_question_old.type_id, slide_id, diff --git a/server/app/database/controller/get.py b/server/app/database/controller/get.py index 9e1885cd..51b92afc 100644 --- a/server/app/database/controller/get.py +++ b/server/app/database/controller/get.py @@ -75,13 +75,17 @@ def team(competition_id, TID, required=True, error_msg=None): ) -def question(competition_id, slide_order, QID, required=True, error_msg=None): +def question(competition_id, slide_order, question_order, required=True, error_msg=None): """ Gets the question object associated with the provided id, slide order and competition id. """ join_filters = ( (Slide.competition_id == competition_id) & (Slide.order == slide_order) & (Slide.id == Question.slide_id) ) - return Question.query.join(Slide, join_filters).filter(Question.id == QID).first_extended(required, error_msg) + return ( + Question.query.join(Slide, join_filters) + .filter(Question.order == question_order) + .first_extended(required, error_msg) + ) def question_alternatives(QID): diff --git a/server/app/database/models.py b/server/app/database/models.py index 2063774f..15efb6d1 100644 --- a/server/app/database/models.py +++ b/server/app/database/models.py @@ -140,9 +140,10 @@ class Slide(db.Model): class Question(db.Model): - __table_args__ = (db.UniqueConstraint("slide_id", "name"),) + __table_args__ = (db.UniqueConstraint("slide_id", "order"),) id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(STRING_SIZE), nullable=False) + order = db.Column(db.Integer, nullable=False) total_score = db.Column(db.Integer, nullable=False, default=1) 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) @@ -150,8 +151,9 @@ class Question(db.Model): question_answers = db.relationship("QuestionAnswer", backref="question") alternatives = db.relationship("QuestionAlternative", backref="question") - def __init__(self, name, total_score, type_id, slide_id): + def __init__(self, name, order, total_score, type_id, slide_id): self.name = name + self.order = order self.total_score = total_score self.type_id = type_id self.slide_id = slide_id -- GitLab From f7f26b1022446a263dea2ad3903f151212c1b90f Mon Sep 17 00:00:00 2001 From: robban64 <carl@schonfelder.se> Date: Thu, 22 Apr 2021 08:21:56 +0200 Subject: [PATCH 08/17] fix: QID to question id in get --- server/app/database/controller/get.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/app/database/controller/get.py b/server/app/database/controller/get.py index 571e1d69..070584ca 100644 --- a/server/app/database/controller/get.py +++ b/server/app/database/controller/get.py @@ -88,7 +88,7 @@ def question(competition_id, slide_order, question_id, required=True, error_msg= def question_alternatives(question_id): # join_filters = (Slide.competition_id == competition_id) & (Slide.order == slide_order) - return QuestionAlternative.query.filter(QuestionAlternative.question_id == QID).all() + return QuestionAlternative.query.filter(QuestionAlternative.question_id == question_id).all() def question_answers(team_id): -- GitLab From 2045b4ea1af43d57f964472a0c81d4096f7e7bec Mon Sep 17 00:00:00 2001 From: robban64 <carl@schonfelder.se> Date: Thu, 22 Apr 2021 09:58:50 +0200 Subject: [PATCH 09/17] fix: alternatives and answers queries and other minor fixes --- server/app/apis/__init__.py | 4 +- server/app/apis/alternatives.py | 29 +++-- server/app/apis/answers.py | 20 ++- server/app/apis/users.py | 12 +- server/app/database/controller/add.py | 21 ++-- server/app/database/controller/delete.py | 12 +- server/app/database/controller/get.py | 150 ++++++++++++++--------- server/app/database/controller/utils.py | 29 +++-- server/populate.py | 2 +- 9 files changed, 166 insertions(+), 113 deletions(-) diff --git a/server/app/apis/__init__.py b/server/app/apis/__init__.py index a049f65e..61d1d063 100644 --- a/server/app/apis/__init__.py +++ b/server/app/apis/__init__.py @@ -76,9 +76,9 @@ flask_api.add_namespace(auth_ns, path="/api/auth") flask_api.add_namespace(comp_ns, path="/api/competitions") flask_api.add_namespace(slide_ns, path="/api/competitions/<competition_id>/slides") flask_api.add_namespace( - alternative_ns, path="/api/competitions/<competition_id>/slides/<slide_order>/questions/<QID>/alternatives" + alternative_ns, path="/api/competitions/<competition_id>/slides/<slide_order>/questions/<question_id>/alternatives" ) -flask_api.add_namespace(answer_ns, path="/api/competitions/<competition_id>/teams/<TID>/answers") +flask_api.add_namespace(answer_ns, path="/api/competitions/<competition_id>/teams/<team_id>/answers") flask_api.add_namespace(team_ns, path="/api/competitions/<competition_id>/teams") flask_api.add_namespace(code_ns, path="/api/competitions/<competition_id>/codes") flask_api.add_namespace(question_ns, path="/api/competitions/<competition_id>") diff --git a/server/app/apis/alternatives.py b/server/app/apis/alternatives.py index 3586242e..9e3b53ef 100644 --- a/server/app/apis/alternatives.py +++ b/server/app/apis/alternatives.py @@ -4,8 +4,6 @@ from app.apis import check_jwt, item_response, list_response from app.core.dto import QuestionAlternativeDTO, QuestionDTO from app.core.parsers import question_alternative_parser from app.core.schemas import QuestionAlternativeSchema -from app.database.controller.add import question_alternative -from app.database.controller.get import question_alternatives from app.database.models import Question, QuestionAlternative from flask_jwt_extended import jwt_required from flask_restx import Resource @@ -16,32 +14,37 @@ list_schema = QuestionAlternativeDTO.list_schema @api.route("/") -@api.param("competition_id, slide_order, QID") +@api.param("competition_id, slide_order, question_id") class QuestionAlternativeList(Resource): @check_jwt(editor=True) - def get(self, competition_id, slide_order, QID): - items = dbc.get.question_alternatives(QID) + def get(self, competition_id, slide_order, question_id): + items = dbc.get.question_alternative_list(competition_id, slide_order, question_id) return list_response(list_schema.dump(items)) @check_jwt(editor=True) - def post(self, competition_id, slide_order, QID): + def post(self, competition_id, slide_order, question_id): args = question_alternative_parser.parse_args(strict=True) - item = dbc.add.question_alternative(**args, question_id=QID) + item = dbc.add.question_alternative(**args, question_id=question_id) return item_response(schema.dump(item)) -@api.route("/<AID>") -@api.param("competition_id, slide_order, QID, AID") +@api.route("/<alternative_id>") +@api.param("competition_id, slide_order, question_id, alternative_id") class QuestionAlternatives(Resource): @check_jwt(editor=True) - def put(self, competition_id, slide_order, QID, AID): + def get(self, competition_id, slide_order, question_id, alternative_id): + items = dbc.get.question_alternative(competition_id, slide_order, question_id, alternative_id) + return item_response(schema.dump(items)) + + @check_jwt(editor=True) + def put(self, competition_id, slide_order, question_id, alternative_id): args = question_alternative_parser.parse_args(strict=True) - item = dbc.get.one(QuestionAlternative, AID) + item = dbc.get.question_alternative(competition_id, slide_order, question_id, alternative_id) item = dbc.edit.default(item, **args) return item_response(schema.dump(item)) @check_jwt(editor=True) - def delete(self, competition_id, slide_order, QID, AID): - item = dbc.get.one(QuestionAlternative, AID) + def delete(self, competition_id, slide_order, question_id, alternative_id): + item = dbc.get.question_alternative(competition_id, slide_order, question_id, alternative_id) dbc.delete.default(item) return {}, codes.NO_CONTENT diff --git a/server/app/apis/answers.py b/server/app/apis/answers.py index fb70dbb1..d636f15a 100644 --- a/server/app/apis/answers.py +++ b/server/app/apis/answers.py @@ -4,8 +4,6 @@ from app.apis import check_jwt, item_response, list_response from app.core.dto import QuestionAnswerDTO from app.core.parsers import question_answer_edit_parser, question_answer_parser from app.core.schemas import QuestionAlternativeSchema -from app.database.controller.add import question_alternative -from app.database.controller.get import question_alternatives from app.database.models import Question, QuestionAlternative, QuestionAnswer from flask_jwt_extended import jwt_required from flask_restx import Resource @@ -16,26 +14,26 @@ list_schema = QuestionAnswerDTO.list_schema @api.route("/") -@api.param("competition_id, TID") +@api.param("competition_id, team_id") class QuestionAnswerList(Resource): @check_jwt(editor=True) - def get(self, competition_id, TID): - items = dbc.get.question_answers(TID) + def get(self, competition_id, team_id): + items = dbc.get.question_answer_list(competition_id, team_id) return list_response(list_schema.dump(items)) @check_jwt(editor=True) - def post(self, competition_id, TID): + def post(self, competition_id, team_id): args = question_answer_parser.parse_args(strict=True) - item = dbc.add.question_answer(**args, team_id=TID) + item = dbc.add.question_answer(**args, team_id=team_id) return item_response(schema.dump(item)) -@api.route("/<AID>") -@api.param("competition_id, TID, AID") +@api.route("/<answer_id>") +@api.param("competition_id, team_id, answer_id") class QuestionAnswers(Resource): @check_jwt(editor=True) - def put(self, competition_id, TID, AID): + def put(self, competition_id, team_id, answer_id): args = question_answer_edit_parser.parse_args(strict=True) - item = dbc.get.one(QuestionAnswer, AID) + item = dbc.get.question_answer(competition_id, team_id, answer_id) item = dbc.edit.default(item, **args) return item_response(schema.dump(item)) diff --git a/server/app/apis/users.py b/server/app/apis/users.py index 38b24d6a..767f01cb 100644 --- a/server/app/apis/users.py +++ b/server/app/apis/users.py @@ -15,19 +15,15 @@ list_schema = UserDTO.list_schema def edit_user(item_user, args): email = args.get("email") + name = args.get("name") + if email: - if User.query.filter(User.email == args["email"]).count() > 0: + if dbc.get.user_exists(email): api.abort(codes.BAD_REQUEST, "Email is already in use") - name = args.get("name") if name: args["name"] = args["name"].title() - """ - try: - args["name"] = args.get("name").title() - except Exception: - pass -""" + return dbc.edit.default(item_user, **args) diff --git a/server/app/database/controller/add.py b/server/app/database/controller/add.py index be6449af..265347e3 100644 --- a/server/app/database/controller/add.py +++ b/server/app/database/controller/add.py @@ -25,6 +25,7 @@ from app.database.models import ( ViewType, ) from flask_restx import abort +from sqlalchemy import exc def db_add(item): @@ -32,13 +33,18 @@ def db_add(item): Internal function. Adds item to the database and handles comitting and refreshing. """ - - db.session.add(item) - db.session.commit() - db.session.refresh(item) - - if not item: - abort(codes.BAD_REQUEST, f"Object could not be created") + try: + db.session.add(item) + db.session.commit() + db.session.refresh(item) + except (exc.SQLAlchemyError, exc.DBAPIError): + db.session.rollback() + # SQL errors such as item already exists + abort(codes.BAD_REQUEST, f"Item of type {type(item)} could not be created") + except: + db.session.rollback() + # Catching other errors + abort(codes.INTERNAL_SERVER_ERROR, f"Something went wrong when creating {type(item)}") return item @@ -113,7 +119,6 @@ def question(name, total_score, type_id, item_slide): Adds a question to the specified slide using the provided arguments. """ - order = Question.query.filter(Question.slide_id == item_slide.id).count() # first element has index 0 return db_add(Question(name, total_score, type_id, item_slide.id)) diff --git a/server/app/database/controller/delete.py b/server/app/database/controller/delete.py index 806f3672..f3dc2dd5 100644 --- a/server/app/database/controller/delete.py +++ b/server/app/database/controller/delete.py @@ -2,16 +2,22 @@ This file contains functionality to delete data to the database. """ +import app.core.http_codes as codes import app.database.controller as dbc from app.core import db from app.database.models import Blacklist, City, Competition, Role, Slide, User +from flask_restx import abort +from sqlalchemy import exc def default(item): """ Deletes item and commits. """ - - db.session.delete(item) - db.session.commit() + try: + db.session.delete(item) + db.session.commit() + except: + db.session.rollback() + abort(codes.INTERNAL_SERVER_ERROR, f"Item of type {type(item)} could not be deleted") def component(item_component): diff --git a/server/app/database/controller/get.py b/server/app/database/controller/get.py index 070584ca..d44894af 100644 --- a/server/app/database/controller/get.py +++ b/server/app/database/controller/get.py @@ -5,23 +5,17 @@ This file contains functionality to get data from the database. from app.core import db from app.core import http_codes as codes from app.database.models import ( - City, Code, Competition, Component, - ComponentType, - MediaType, Question, QuestionAlternative, QuestionAnswer, - QuestionType, - Role, Slide, Team, User, - ViewType, ) -from sqlalchemy.orm import contains_eager, joinedload, subqueryload +from sqlalchemy.orm import joinedload, subqueryload def all(db_type): @@ -36,18 +30,31 @@ def one(db_type, id, required=True, error_msg=None): return db_type.query.filter(db_type.id == id).first_extended(required, error_msg) -def user_exists(email): - """ Checks if an user has that email. """ - - return User.query.filter(User.email == email).count() > 0 - - +### Codes ### def code_by_code(code, required=True, error_msg=None): """ Gets the code object associated with the provided code. """ return Code.query.filter(Code.code == code.upper()).first_extended(required, error_msg, codes.UNAUTHORIZED) +def code_list(competition_id): + """ Gets a list of all code objects associated with a the provided competition. """ + + 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() + + +### Users ### +def user_exists(email): + """ Checks if an user has that email. """ + + return User.query.filter(User.email == email).count() > 0 + + def user(user_id, required=True, error_msg=None): """ Gets the user object associated with the provided id. """ @@ -60,6 +67,7 @@ def user_by_email(email, required=True, error_msg=None): return User.query.filter(User.email == email).first_extended(required, error_msg) +### Slides ### def slide(competition_id, slide_order, required=True, error_msg=None): """ Gets the slide object associated with the provided id and order. """ @@ -67,6 +75,19 @@ def slide(competition_id, slide_order, required=True, error_msg=None): return Slide.query.filter(filters).first_extended(required, error_msg) +def slide_list(competition_id): + """ Gets a list of all slide objects associated with a the provided competition. """ + + return Slide.query.filter(Slide.competition_id == competition_id).all() + + +def slide_count(competition_id): + """ Gets the number of slides in the provided competition. """ + + return Slide.query.filter(Slide.competition_id == competition_id).count() + + +### Teams ### def team(competition_id, team_id, required=True, error_msg=None): """ Gets the team object associated with the provided id and competition id. """ @@ -75,55 +96,86 @@ def team(competition_id, team_id, required=True, error_msg=None): ) +def team_list(competition_id): + """ Gets a list of all team objects associated with a the provided competition. """ + + return Team.query.filter(Team.competition_id == competition_id).all() + + +### Questions ### def question(competition_id, slide_order, question_id, required=True, error_msg=None): """ Gets the question object associated with the provided id, slide order and competition id. """ - join_filters = ( - (Slide.competition_id == competition_id) & (Slide.order == slide_order) & (Slide.id == Question.slide_id) - ) + # Join all slides for one competition + join_filters = (Slide.competition_id == competition_id) & (Slide.order == slide_order) return ( Question.query.join(Slide, join_filters).filter(Question.id == question_id).first_extended(required, error_msg) ) -def question_alternatives(question_id): - # join_filters = (Slide.competition_id == competition_id) & (Slide.order == slide_order) - return QuestionAlternative.query.filter(QuestionAlternative.question_id == question_id).all() +def question_list(competition_id): + """ Gets a list of all question objects associated with a the provided competition. """ + join_filters = (Slide.competition_id == competition_id) & (Slide.id == Question.slide_id) + return Question.query.join(Slide, join_filters).all() -def question_answers(team_id): - # join_filters = (Slide.competition_id == competition_id) & (Slide.order == slide_order) - return QuestionAnswer.query.filter(QuestionAnswer.team_id == team_id).all() +### Question Alternative ### +def question_alternative(competition_id, slide_order, question_id, alternative_id, required=True, error_msg=None): + """ Get question alternative for a given question based on its competition and slide and ID. """ -def competition(competition_id): - """ Get Competition and all it's sub-entities """ - """ HOT PATH """ + join_slide = (Slide.competition_id == competition_id) & (Slide.order == slide_order) + join_question = (Question.id == question_id) & (Question.id == QuestionAlternative.question_id) + filters = QuestionAlternative.id == alternative_id + return ( + QuestionAlternative.query.join(Slide, join_slide) + .join(Question, join_question) + .filter(filters) + .first_extended(required, error_msg) + ) - os1 = joinedload(Competition.slides).joinedload(Slide.components) - os2 = joinedload(Competition.slides).joinedload(Slide.questions).joinedload(Question.alternatives) - ot = joinedload(Competition.teams).joinedload(Team.question_answers) - return Competition.query.filter(Competition.id == competition_id).options(os1).options(os2).options(ot).first() +def question_alternative_list(competition_id, slide_order, question_id): + """ Get all question alternatives for a given question based on its competition and slide. """ + join_slide = (Slide.competition_id == competition_id) & (Slide.order == slide_order) + join_question = (Question.id == question_id) & (Question.id == QuestionAlternative.question_id) + filters = QuestionAlternative.question_id == question_id + return QuestionAlternative.query.join(Slide, join_slide).join(Question, join_question).all() -def code_list(competition_id): - """ Gets a list of all code objects associated with a the provided competition. """ - 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) +### Question Answers ### +def question_answer(competition_id, team_id, answer_id): + """ Get question answer for a given team based on its competition and ID. """ + + join_filters = ( + (Team.competition_id == competition_id) + & (QuestionAnswer.team_id == Team.id) + & (QuestionAnswer.team_id == team_id) ) - return Code.query.join(Team, join_filters, isouter=True).filter(filters).all() + return QuestionAnswer.query.join(Team, join_filters).filter(QuestionAnswer.id == answer_id).first_extended() -def question_list(competition_id): - """ Gets a list of all question objects associated with a the provided competition. """ +def question_answer_list(competition_id, team_id): + """ Get question answer for a given team based on its competition. """ - join_filters = (Slide.competition_id == competition_id) & (Slide.id == Question.slide_id) - return Question.query.join(Slide, join_filters).all() + join_filters = ( + (Team.competition_id == competition_id) + & (QuestionAnswer.team_id == Team.id) + & (QuestionAnswer.team_id == team_id) + ) + return QuestionAnswer.query.join(Team, join_filters).all() + + +### Competitions ### +def competition(competition_id): + """ Get Competition and all it's sub-entities """ + os1 = joinedload(Competition.slides).joinedload(Slide.components) + os2 = joinedload(Competition.slides).joinedload(Slide.questions).joinedload(Question.alternatives) + ot = joinedload(Competition.teams).joinedload(Team.question_answers) + return Competition.query.filter(Competition.id == competition_id).options(os1).options(os2).options(ot).first() +### Components ### def component_list(competition_id, slide_order): """ Gets a list of all component objects associated with a the provided competition id and slide order. """ @@ -131,21 +183,3 @@ def component_list(competition_id, slide_order): (Slide.competition_id == competition_id) & (Slide.order == slide_order) & (Component.slide_id == Slide.id) ) return Component.query.join(Slide, join_filters).all() - - -def team_list(competition_id): - """ Gets a list of all team objects associated with a the provided competition. """ - - return Team.query.filter(Team.competition_id == competition_id).all() - - -def slide_list(competition_id): - """ Gets a list of all slide objects associated with a the provided competition. """ - - return Slide.query.filter(Slide.competition_id == competition_id).all() - - -def slide_count(competition_id): - """ Gets the number of slides in the provided competition. """ - - return Slide.query.filter(Slide.competition_id == competition_id).count() diff --git a/server/app/database/controller/utils.py b/server/app/database/controller/utils.py index 4b49e46e..6b11384a 100644 --- a/server/app/database/controller/utils.py +++ b/server/app/database/controller/utils.py @@ -2,9 +2,12 @@ This file contains some miscellaneous functionality. """ +import app.core.http_codes as codes from app.core import db from app.core.codes import generate_code_string from app.database.models import Code +from flask_restx import abort +from sqlalchemy import exc def generate_unique_code(): @@ -16,19 +19,27 @@ def generate_unique_code(): return code -def commit_and_refresh(item): - """ Commits and refreshes the provided item. """ - - db.session.commit() - db.session.refresh(item) - - def refresh(item): """ Refreshes the provided item. """ + try: + db.session.refresh(item) + except Exception as e: + abort(codes.INTERNAL_SERVER_ERROR, f"Refresh failed!\n{str(e)}") - db.session.refresh(item) + return item def commit(): """ Commits. """ - db.session.commit() + try: + db.session.commit() + except Exception as e: + db.session.rollback() + abort(codes.INTERNAL_SERVER_ERROR, f"Commit failed!\n{str(e)}") + + +def commit_and_refresh(item): + """ Commits and refreshes the provided item. """ + + commit() + return refresh(item) diff --git a/server/populate.py b/server/populate.py index 609193ba..c9813b40 100644 --- a/server/populate.py +++ b/server/populate.py @@ -47,7 +47,7 @@ def _add_items(): # Add competitions for i in range(len(question_types_items)): item_comp = dbc.add.competition(f"Tävling {i}", 2000 + i, city_id) - dbc.edit.slide(item_comp.slides[0], timer=5, title="test-slide-title") + dbc.edit.default(item_comp.slides[0], timer=5, title="test-slide-title") # Add two more slides to competition dbc.add.slide(item_comp) -- GitLab From bb82f65066b4eefde3aff4363e9a63ddc2a229e9 Mon Sep 17 00:00:00 2001 From: robban64 <carl@schonfelder.se> Date: Thu, 22 Apr 2021 11:27:43 +0200 Subject: [PATCH 10/17] fix: queries are now using proper joins --- .vscode/settings.json | 93 +++++++-------- server/app/apis/__init__.py | 4 +- server/app/apis/alternatives.py | 22 ++-- server/app/apis/answers.py | 5 + server/app/apis/auth.py | 2 +- server/app/apis/components.py | 24 ++-- server/app/apis/questions.py | 31 ++--- server/app/apis/slides.py | 40 +++---- server/app/apis/teams.py | 3 - server/app/database/controller/add.py | 2 +- server/app/database/controller/get.py | 159 ++++++++++++++++---------- server/tests/test_app.py | 42 ++++--- server/tests/test_helpers.py | 10 +- 13 files changed, 242 insertions(+), 195 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 99228c86..db2b68f4 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,51 +1,44 @@ { - //editor - "editor.formatOnSave": true, - "editor.formatOnPaste": false, - "editor.tabCompletion": "on", - "editor.codeActionsOnSave": { - "source.fixAll.eslint": true, - "source.organizeImports": true - }, - //python - "python.venvPath": "${workspaceFolder}\\server", - "python.analysis.extraPaths": [ - "server" - ], - "python.terminal.activateEnvironment": true, - "python.formatting.provider": "black", - "python.formatting.blackPath": "server\\env\\Scripts\\black.exe", - "python.formatting.blackArgs": [ - "--line-length", - "119" - ], - //eslint - "eslint.workingDirectories": [ - "./client" - ], - "eslint.options": { - "configFile": "./.eslintrc" - }, - "prettier.configPath": "./client/.prettierrc", - //git - "git.ignoreLimitWarning": true, - //language specific - "[typescript]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, - "[typescriptreact]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, - "files.exclude": { - "**/__pycache__": true, - "**/.pytest_cache": true, - "**/env/Include": true, - "**/env/Lib": true - }, - "files.watcherExclude": { - "**/env/Lib/**": true - }, - "search.exclude": { - "**/env": true - }, -} \ No newline at end of file + //editor + "editor.formatOnSave": true, + "editor.formatOnPaste": false, + "editor.tabCompletion": "on", + "editor.codeActionsOnSave": { + "source.fixAll.eslint": true, + "source.organizeImports": false + }, + //python + "python.venvPath": "${workspaceFolder}\\server", + "python.analysis.extraPaths": ["server"], + "python.terminal.activateEnvironment": true, + "python.formatting.provider": "black", + "python.formatting.blackPath": "server\\env\\Scripts\\black.exe", + "python.formatting.blackArgs": ["--line-length", "119"], + //eslint + "eslint.workingDirectories": ["./client"], + "eslint.options": { + "configFile": "./.eslintrc" + }, + "prettier.configPath": "./client/.prettierrc", + //git + "git.ignoreLimitWarning": true, + //language specific + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[typescriptreact]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "files.exclude": { + "**/__pycache__": true, + "**/.pytest_cache": true, + "**/env/Include": true, + "**/env/Lib": true + }, + "files.watcherExclude": { + "**/env/Lib/**": true + }, + "search.exclude": { + "**/env": true + } +} diff --git a/server/app/apis/__init__.py b/server/app/apis/__init__.py index 61d1d063..5eab3f82 100644 --- a/server/app/apis/__init__.py +++ b/server/app/apis/__init__.py @@ -76,10 +76,10 @@ flask_api.add_namespace(auth_ns, path="/api/auth") flask_api.add_namespace(comp_ns, path="/api/competitions") flask_api.add_namespace(slide_ns, path="/api/competitions/<competition_id>/slides") flask_api.add_namespace( - alternative_ns, path="/api/competitions/<competition_id>/slides/<slide_order>/questions/<question_id>/alternatives" + alternative_ns, path="/api/competitions/<competition_id>/slides/<slide_id>/questions/<question_id>/alternatives" ) flask_api.add_namespace(answer_ns, path="/api/competitions/<competition_id>/teams/<team_id>/answers") flask_api.add_namespace(team_ns, path="/api/competitions/<competition_id>/teams") flask_api.add_namespace(code_ns, path="/api/competitions/<competition_id>/codes") flask_api.add_namespace(question_ns, path="/api/competitions/<competition_id>") -flask_api.add_namespace(component_ns, path="/api/competitions/<competition_id>/slides/<slide_order>/components") +flask_api.add_namespace(component_ns, path="/api/competitions/<competition_id>/slides/<slide_id>/components") diff --git a/server/app/apis/alternatives.py b/server/app/apis/alternatives.py index 9e3b53ef..d56a2f8b 100644 --- a/server/app/apis/alternatives.py +++ b/server/app/apis/alternatives.py @@ -14,37 +14,37 @@ list_schema = QuestionAlternativeDTO.list_schema @api.route("/") -@api.param("competition_id, slide_order, question_id") +@api.param("competition_id, slide_id, question_id") class QuestionAlternativeList(Resource): @check_jwt(editor=True) - def get(self, competition_id, slide_order, question_id): - items = dbc.get.question_alternative_list(competition_id, slide_order, question_id) + def get(self, competition_id, slide_id, question_id): + items = dbc.get.question_alternative_list(competition_id, slide_id, question_id) return list_response(list_schema.dump(items)) @check_jwt(editor=True) - def post(self, competition_id, slide_order, question_id): + def post(self, competition_id, slide_id, question_id): args = question_alternative_parser.parse_args(strict=True) item = dbc.add.question_alternative(**args, question_id=question_id) return item_response(schema.dump(item)) @api.route("/<alternative_id>") -@api.param("competition_id, slide_order, question_id, alternative_id") +@api.param("competition_id, slide_id, question_id, alternative_id") class QuestionAlternatives(Resource): @check_jwt(editor=True) - def get(self, competition_id, slide_order, question_id, alternative_id): - items = dbc.get.question_alternative(competition_id, slide_order, question_id, alternative_id) + def get(self, competition_id, slide_id, question_id, alternative_id): + items = dbc.get.question_alternative(competition_id, slide_id, question_id, alternative_id) return item_response(schema.dump(items)) @check_jwt(editor=True) - def put(self, competition_id, slide_order, question_id, alternative_id): + def put(self, competition_id, slide_id, question_id, alternative_id): args = question_alternative_parser.parse_args(strict=True) - item = dbc.get.question_alternative(competition_id, slide_order, question_id, alternative_id) + item = dbc.get.question_alternative(competition_id, slide_id, question_id, alternative_id) item = dbc.edit.default(item, **args) return item_response(schema.dump(item)) @check_jwt(editor=True) - def delete(self, competition_id, slide_order, question_id, alternative_id): - item = dbc.get.question_alternative(competition_id, slide_order, question_id, alternative_id) + def delete(self, competition_id, slide_id, question_id, alternative_id): + item = dbc.get.question_alternative(competition_id, slide_id, question_id, alternative_id) dbc.delete.default(item) return {}, codes.NO_CONTENT diff --git a/server/app/apis/answers.py b/server/app/apis/answers.py index d636f15a..308b1001 100644 --- a/server/app/apis/answers.py +++ b/server/app/apis/answers.py @@ -31,6 +31,11 @@ class QuestionAnswerList(Resource): @api.route("/<answer_id>") @api.param("competition_id, team_id, answer_id") class QuestionAnswers(Resource): + @check_jwt(editor=True) + def get(self, competition_id, team_id, answer_id): + item = dbc.get.question_answer(competition_id, team_id, answer_id) + return item_response(schema.dump(item)) + @check_jwt(editor=True) def put(self, competition_id, team_id, answer_id): args = question_answer_edit_parser.parse_args(strict=True) diff --git a/server/app/apis/auth.py b/server/app/apis/auth.py index 86ac53d5..d249fec5 100644 --- a/server/app/apis/auth.py +++ b/server/app/apis/auth.py @@ -58,7 +58,7 @@ class AuthLogin(Resource): args = login_parser.parse_args(strict=True) email = args.get("email") password = args.get("password") - item_user = dbc.get.user_by_email(email, required=False) + item_user = dbc.get.user_by_email(email) if not item_user or not item_user.is_correct_password(password): api.abort(codes.UNAUTHORIZED, "Invalid email or password") diff --git a/server/app/apis/components.py b/server/app/apis/components.py index 6e514039..c712a4e6 100644 --- a/server/app/apis/components.py +++ b/server/app/apis/components.py @@ -14,38 +14,38 @@ list_schema = ComponentDTO.list_schema @api.route("/<component_id>") -@api.param("competition_id, slide_order, component_id") +@api.param("competition_id, slide_id, component_id") class ComponentByID(Resource): @check_jwt(editor=True) - def get(self, competition_id, slide_order, component_id): - item = dbc.get.one(Component, component_id) + def get(self, competition_id, slide_id, component_id): + item = dbc.get.component(competition_id, slide_id, component_id) return item_response(schema.dump(item)) @check_jwt(editor=True) - def put(self, competition_id, slide_order, component_id): + def put(self, competition_id, slide_id, component_id): args = component_parser.parse_args() - item = dbc.get.one(Component, component_id) + item = dbc.get.component(competition_id, slide_id, component_id) item = dbc.edit.default(item, **args) return item_response(schema.dump(item)) @check_jwt(editor=True) - def delete(self, competition_id, slide_order, component_id): - item = dbc.get.one(Component, component_id) + def delete(self, competition_id, slide_id, component_id): + item = dbc.get.component(competition_id, slide_id, component_id) dbc.delete.component(item) return {}, codes.NO_CONTENT @api.route("/") -@api.param("competition_id, slide_order") +@api.param("competition_id, slide_id") class ComponentList(Resource): @check_jwt(editor=True) - def get(self, competition_id, slide_order): - items = dbc.get.component_list(competition_id, slide_order) + def get(self, competition_id, slide_id): + items = dbc.get.component_list(competition_id, slide_id) return list_response(list_schema.dump(items)) @check_jwt(editor=True) - def post(self, competition_id, slide_order): + def post(self, competition_id, slide_id): args = component_create_parser.parse_args() - item_slide = dbc.get.slide(competition_id, slide_order) + item_slide = dbc.get.slide(competition_id, slide_id) item = dbc.add.component(item_slide=item_slide, **args) return item_response(schema.dump(item)) diff --git a/server/app/apis/questions.py b/server/app/apis/questions.py index 74ca7608..9b519e5f 100644 --- a/server/app/apis/questions.py +++ b/server/app/apis/questions.py @@ -17,40 +17,45 @@ list_schema = QuestionDTO.list_schema class QuestionList(Resource): @check_jwt(editor=True) def get(self, competition_id): - items = dbc.get.question_list(competition_id) + items = dbc.get.question_list_for_competition(competition_id) return list_response(list_schema.dump(items)) -@api.route("/slides/<slide_order>/questions") -@api.param("competition_id, slide_order") +@api.route("/slides/<slide_id>/questions") +@api.param("competition_id, slide_id") class QuestionListForSlide(Resource): @check_jwt(editor=True) - def post(self, slide_order, competition_id): + def get(self, competition_id, slide_id): + items = dbc.get.question_list(competition_id, slide_id) + return list_response(list_schema.dump(items)) + + @check_jwt(editor=True) + def post(self, slide_id, competition_id): args = question_parser.parse_args(strict=True) - item_slide = dbc.get.slide(competition_id, slide_order) + item_slide = dbc.get.slide(competition_id, slide_id) item = dbc.add.question(item_slide=item_slide, **args) return item_response(schema.dump(item)) -@api.route("/slides/<slide_order>/questions/<question_id>") -@api.param("competition_id, slide_order, question_id") +@api.route("/slides/<slide_id>/questions/<question_id>") +@api.param("competition_id, slide_id, question_id") class QuestionById(Resource): @check_jwt(editor=True) - def get(self, competition_id, slide_order, question_id): - item_question = dbc.get.question(competition_id, slide_order, question_id) + def get(self, competition_id, slide_id, question_id): + item_question = dbc.get.question(competition_id, slide_id, question_id) return item_response(schema.dump(item_question)) @check_jwt(editor=True) - def put(self, competition_id, slide_order, question_id): + def put(self, competition_id, slide_id, question_id): args = question_parser.parse_args(strict=True) - item_question = dbc.get.question(competition_id, slide_order, question_id) + item_question = dbc.get.question(competition_id, slide_id, question_id) item_question = dbc.edit.default(item_question, **args) return item_response(schema.dump(item_question)) @check_jwt(editor=True) - def delete(self, competition_id, slide_order, question_id): - item_question = dbc.get.question(competition_id, slide_order, question_id) + def delete(self, competition_id, slide_id, question_id): + item_question = dbc.get.question(competition_id, slide_id, question_id) dbc.delete.question(item_question) return {}, codes.NO_CONTENT diff --git a/server/app/apis/slides.py b/server/app/apis/slides.py index 7c94caa2..fffdff0b 100644 --- a/server/app/apis/slides.py +++ b/server/app/apis/slides.py @@ -29,40 +29,40 @@ class SlidesList(Resource): return list_response(list_schema.dump(item_comp.slides)) -@api.route("/<slide_order>") -@api.param("competition_id,slide_order") +@api.route("/<slide_id>") +@api.param("competition_id,slide_id") class Slides(Resource): @check_jwt(editor=True) - def get(self, competition_id, slide_order): - item_slide = dbc.get.slide(competition_id, slide_order) + def get(self, competition_id, slide_id): + item_slide = dbc.get.slide(competition_id, slide_id) return item_response(schema.dump(item_slide)) @check_jwt(editor=True) - def put(self, competition_id, slide_order): + def put(self, competition_id, slide_id): args = slide_parser.parse_args(strict=True) - item_slide = dbc.get.slide(competition_id, slide_order) + item_slide = dbc.get.slide(competition_id, slide_id) item_slide = dbc.edit.default(item_slide, **args) return item_response(schema.dump(item_slide)) @check_jwt(editor=True) - def delete(self, competition_id, slide_order): - item_slide = dbc.get.slide(competition_id, slide_order) + def delete(self, competition_id, slide_id): + item_slide = dbc.get.slide(competition_id, slide_id) dbc.delete.slide(item_slide) return {}, codes.NO_CONTENT -@api.route("/<slide_order>/order") -@api.param("competition_id,slide_order") -class Slideslide_order(Resource): +@api.route("/<slide_id>/order") +@api.param("competition_id,slide_id") +class SlideOrder(Resource): @check_jwt(editor=True) - def put(self, competition_id, slide_order): + def put(self, competition_id, slide_id): args = slide_parser.parse_args(strict=True) order = args.get("order") - item_slide = dbc.get.slide(competition_id, slide_order) + item_slide = dbc.get.slide(competition_id, slide_id) if order == item_slide.order: return item_response(schema.dump(item_slide)) @@ -75,20 +75,20 @@ class Slideslide_order(Resource): order = order_count - 1 # get slide at the requested order - item_slide_order = dbc.get.slide(competition_id, order) + item_slide_id = dbc.get.slide(competition_id, order) # switch place between them - item_slide = dbc.edit.switch_order(item_slide, item_slide_order) + item_slide = dbc.edit.switch_order(item_slide, item_slide_id) return item_response(schema.dump(item_slide)) -@api.route("/<slide_order>/copy") -@api.param("competition_id,slide_order") -class Slideslide_order(Resource): +@api.route("/<slide_id>/copy") +@api.param("competition_id,slide_id") +class SlideCopy(Resource): @check_jwt(editor=True) - def post(self, competition_id, slide_order): - item_slide = dbc.get.slide(competition_id, slide_order) + def post(self, competition_id, slide_id): + item_slide = dbc.get.slide(competition_id, slide_id) item_slide_copy = dbc.copy.slide(item_slide) diff --git a/server/app/apis/teams.py b/server/app/apis/teams.py index 8b66a48c..535de695 100644 --- a/server/app/apis/teams.py +++ b/server/app/apis/teams.py @@ -31,13 +31,11 @@ class TeamsList(Resource): @api.route("/<team_id>") @api.param("competition_id,team_id") class Teams(Resource): - @jwt_required @check_jwt(editor=True) def get(self, competition_id, team_id): item = dbc.get.team(competition_id, team_id) return item_response(schema.dump(item)) - @jwt_required @check_jwt(editor=True) def delete(self, competition_id, team_id): item_team = dbc.get.team(competition_id, team_id) @@ -45,7 +43,6 @@ class Teams(Resource): dbc.delete.team(item_team) return {}, codes.NO_CONTENT - @jwt_required @check_jwt(editor=True) def put(self, competition_id, team_id): args = team_parser.parse_args(strict=True) diff --git a/server/app/database/controller/add.py b/server/app/database/controller/add.py index 265347e3..7b77732b 100644 --- a/server/app/database/controller/add.py +++ b/server/app/database/controller/add.py @@ -40,7 +40,7 @@ def db_add(item): except (exc.SQLAlchemyError, exc.DBAPIError): db.session.rollback() # SQL errors such as item already exists - abort(codes.BAD_REQUEST, f"Item of type {type(item)} could not be created") + abort(codes.INTERNAL_SERVER_ERROR, f"Item of type {type(item)} could not be created") except: db.session.rollback() # Catching other errors diff --git a/server/app/database/controller/get.py b/server/app/database/controller/get.py index d44894af..15e908ef 100644 --- a/server/app/database/controller/get.py +++ b/server/app/database/controller/get.py @@ -24,17 +24,17 @@ def all(db_type): return db_type.query.all() -def one(db_type, id, required=True, error_msg=None): +def one(db_type, id): """ Get lazy db-item in the table that has the same id. """ - return db_type.query.filter(db_type.id == id).first_extended(required, error_msg) + return db_type.query.filter(db_type.id == id).first_extended() ### Codes ### -def code_by_code(code, required=True, error_msg=None): +def code_by_code(code): """ Gets the code object associated with the provided code. """ - return Code.query.filter(Code.code == code.upper()).first_extended(required, error_msg, codes.UNAUTHORIZED) + return Code.query.filter(Code.code == code.upper()).first_extended() def code_list(competition_id): @@ -55,30 +55,32 @@ def user_exists(email): return User.query.filter(User.email == email).count() > 0 -def user(user_id, required=True, error_msg=None): +def user(user_id): """ Gets the user object associated with the provided id. """ - return User.query.filter(User.id == user_id).first_extended(required, error_msg) + return User.query.filter(User.id == user_id).first_extended() -def user_by_email(email, required=True, error_msg=None): +def user_by_email(email): """ Gets the user object associated with the provided email. """ - - return User.query.filter(User.email == email).first_extended(required, error_msg) + return User.query.filter(User.email == email).first_extended(error_code=codes.UNAUTHORIZED) ### Slides ### -def slide(competition_id, slide_order, required=True, error_msg=None): +def slide(competition_id, slide_id): """ Gets the slide object associated with the provided id and order. """ + join_competition = Competition.id == Slide.competition_id + filters = (Competition.id == competition_id) & (Slide.id == slide_id) - filters = (Slide.competition_id == competition_id) & (Slide.order == slide_order) - return Slide.query.filter(filters).first_extended(required, error_msg) + return Slide.query.join(Competition, join_competition).filter(filters).first_extended() def slide_list(competition_id): """ Gets a list of all slide objects associated with a the provided competition. """ + join_competition = Competition.id == Slide.competition_id + filters = Competition.id == competition_id - return Slide.query.filter(Slide.competition_id == competition_id).all() + return Slide.query.join(Competition, join_competition).filter(filters).all() def slide_count(competition_id): @@ -88,82 +90,129 @@ def slide_count(competition_id): ### Teams ### -def team(competition_id, team_id, required=True, error_msg=None): +def team(competition_id, team_id): """ Gets the team object associated with the provided id and competition id. """ + join_competition = Competition.id == Team.competition_id + filters = (Competition.id == competition_id) & (Team.id == team_id) - return Team.query.filter((Team.competition_id == competition_id) & (Team.id == team_id)).first_extended( - required, error_msg - ) + return Team.query.join(Competition, join_competition).filter(filters).first_extended() def team_list(competition_id): """ Gets a list of all team objects associated with a the provided competition. """ - return Team.query.filter(Team.competition_id == competition_id).all() + join_competition = Competition.id == Team.competition_id + filters = Competition.id == competition_id + + return Team.query.join(Competition, join_competition).filter(filters).all() ### Questions ### -def question(competition_id, slide_order, question_id, required=True, error_msg=None): +def question(competition_id, slide_id, question_id): """ Gets the question object associated with the provided id, slide order and competition id. """ - # Join all slides for one competition - join_filters = (Slide.competition_id == competition_id) & (Slide.order == slide_order) - return ( - Question.query.join(Slide, join_filters).filter(Question.id == question_id).first_extended(required, error_msg) - ) + join_competition = Competition.id == Slide.competition_id + join_slide = Slide.id == Question.slide_id + filters = (Competition.id == competition_id) & (Slide.id == slide_id) & (Question.id == question_id) + + return Question.query.join(Competition, join_competition).join(Slide, join_slide).filter(filters).first_extended() + + +def question_list(competition_id, slide_id): + """ Gets a list of all question objects associated with a the provided competition and slide. """ + + join_competition = Competition.id == Slide.competition_id + join_slide = Slide.id == Question.slide_id + filters = (Competition.id == competition_id) & (Slide.id == slide_id) + return Question.query.join(Competition, join_competition).join(Slide, join_slide).filter(filters).all() -def question_list(competition_id): + +def question_list_for_competition(competition_id): """ Gets a list of all question objects associated with a the provided competition. """ - join_filters = (Slide.competition_id == competition_id) & (Slide.id == Question.slide_id) - return Question.query.join(Slide, join_filters).all() + join_competition = Competition.id == Slide.competition_id + join_slide = Slide.id == Question.slide_id + filters = Competition.id == competition_id + + return Question.query.join(Competition, join_competition).join(Slide, join_slide).filter(filters).all() ### Question Alternative ### -def question_alternative(competition_id, slide_order, question_id, alternative_id, required=True, error_msg=None): +def question_alternative(competition_id, slide_id, question_id, alternative_id): """ Get question alternative for a given question based on its competition and slide and ID. """ - join_slide = (Slide.competition_id == competition_id) & (Slide.order == slide_order) - join_question = (Question.id == question_id) & (Question.id == QuestionAlternative.question_id) - filters = QuestionAlternative.id == alternative_id + join_competition = Competition.id == Slide.competition_id + join_slide = Slide.id == Question.slide_id + join_question = Question.id == QuestionAlternative.question_id + filters = ( + (Competition.id == competition_id) + & (Slide.id == slide_id) + & (Question.id == question_id) + & (QuestionAlternative.id == alternative_id) + ) + return ( - QuestionAlternative.query.join(Slide, join_slide) + QuestionAlternative.query.join(Competition, join_competition) + .join(Slide, join_slide) .join(Question, join_question) .filter(filters) - .first_extended(required, error_msg) + .first_extended() ) -def question_alternative_list(competition_id, slide_order, question_id): +def question_alternative_list(competition_id, slide_id, question_id): """ Get all question alternatives for a given question based on its competition and slide. """ - join_slide = (Slide.competition_id == competition_id) & (Slide.order == slide_order) - join_question = (Question.id == question_id) & (Question.id == QuestionAlternative.question_id) - filters = QuestionAlternative.question_id == question_id - return QuestionAlternative.query.join(Slide, join_slide).join(Question, join_question).all() + join_competition = Competition.id == Slide.competition_id + join_slide = Slide.id == Question.slide_id + join_question = Question.id == QuestionAlternative.question_id + filters = (Competition.id == competition_id) & (Slide.id == slide_id) & (Question.id == question_id) + + return ( + QuestionAlternative.query.join(Competition, join_competition) + .join(Slide, join_slide) + .join(Question, join_question) + .filter(filters) + .all() + ) ### Question Answers ### def question_answer(competition_id, team_id, answer_id): """ Get question answer for a given team based on its competition and ID. """ - - join_filters = ( - (Team.competition_id == competition_id) - & (QuestionAnswer.team_id == Team.id) - & (QuestionAnswer.team_id == team_id) + join_competition = Competition.id == Team.competition_id + join_team = Team.id == QuestionAnswer.team_id + filters = (Competition.id == competition_id) & (Team.id == team_id) & (QuestionAnswer.id == answer_id) + return ( + QuestionAnswer.query.join(Competition, join_competition).join(Team, join_team).filter(filters).first_extended() ) - return QuestionAnswer.query.join(Team, join_filters).filter(QuestionAnswer.id == answer_id).first_extended() def question_answer_list(competition_id, team_id): """ Get question answer for a given team based on its competition. """ + join_competition = Competition.id == Team.competition_id + join_team = Team.id == QuestionAnswer.team_id + filters = (Competition.id == competition_id) & (Team.id == team_id) + return QuestionAnswer.query.join(Competition, join_competition).join(Team, join_team).filter(filters).all() - join_filters = ( - (Team.competition_id == competition_id) - & (QuestionAnswer.team_id == Team.id) - & (QuestionAnswer.team_id == team_id) - ) - return QuestionAnswer.query.join(Team, join_filters).all() + +### Components ### +def component(competition_id, slide_id, component_id): + """ Gets a list of all component objects associated with a the provided competition id and slide order. """ + + join_competition = Competition.id == Slide.competition_id + join_slide = Slide.id == Component.slide_id + filters = (Competition.id == competition_id) & (Slide.id == slide_id) & (Component.id == component_id) + return Component.query.join(Competition, join_competition).join(Slide, join_slide).filter(filters).first_extended() + + +def component_list(competition_id, slide_id): + """ Gets a list of all component objects associated with a the provided competition id and slide order. """ + + join_competition = Competition.id == Slide.competition_id + join_slide = Slide.id == Component.slide_id + filters = (Competition.id == competition_id) & (Slide.id == slide_id) + return Component.query.join(Competition, join_competition).join(Slide, join_slide).filter(filters).all() ### Competitions ### @@ -173,13 +222,3 @@ def competition(competition_id): os2 = joinedload(Competition.slides).joinedload(Slide.questions).joinedload(Question.alternatives) ot = joinedload(Competition.teams).joinedload(Team.question_answers) return Competition.query.filter(Competition.id == competition_id).options(os1).options(os2).options(ot).first() - - -### Components ### -def component_list(competition_id, slide_order): - """ Gets a list of all component objects associated with a the provided competition id and slide order. """ - - join_filters = ( - (Slide.competition_id == competition_id) & (Slide.order == slide_order) & (Component.slide_id == Slide.id) - ) - return Component.query.join(Slide, join_filters).all() diff --git a/server/tests/test_app.py b/server/tests/test_app.py index cc50d4fd..48e74c5a 100644 --- a/server/tests/test_app.py +++ b/server/tests/test_app.py @@ -84,8 +84,10 @@ def test_competition_api(client): assert response.status_code == codes.OK assert len(body["items"]) == 3 + """ 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) assert response.status_code == codes.OK @@ -257,27 +259,22 @@ 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 - slide_order = 1 - response, item_slide = get(client, f"/api/competitions/{CID}/slides/{slide_order}", headers=headers) + slide_id = 2 + response, item_slide = get(client, f"/api/competitions/{CID}/slides/{slide_id}", headers=headers) assert response.status_code == codes.OK - assert item_slide["order"] == slide_order # Edit slide - order = 6 title = "Ny titel" body = "Ny body" timer = 43 - assert item_slide["order"] != order assert item_slide["title"] != title # assert item_slide["body"] != body assert item_slide["timer"] != timer response, item_slide = put( client, - f"/api/competitions/{CID}/slides/{slide_order}", + f"/api/competitions/{CID}/slides/{slide_id}", # TODO: Implement so these commented lines can be edited # {"order": order, "title": title, "body": body, "timer": timer}, {"title": title, "timer": timer}, @@ -290,31 +287,40 @@ def test_slide_api(client): assert item_slide["timer"] == timer # Delete slide - response, _ = delete(client, f"/api/competitions/{CID}/slides/{slide_order}", headers=headers) + response, _ = delete(client, f"/api/competitions/{CID}/slides/{slide_id}", 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"] == 4 + assert body["count"] == 3 - # 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 + # Tries to delete slide again, which will fail + response, _ = delete(client, f"/api/competitions/{CID}/slides/{slide_id}", headers=headers) + assert response.status_code != codes.OK + # Get all slides + response, body = get(client, f"/api/competitions/{CID}/slides", headers=headers) + assert response.status_code == codes.OK + assert body["count"] == 3 + assert body["items"][0]["id"] == 3 + assert body["items"][0]["order"] == 0 + slide_id = 3 + + """ # Changes the order to the same order - slide_order = body["items"][0]["order"] response, _ = put( - client, f"/api/competitions/{CID}/slides/{slide_order}/order", {"order": slide_order}, headers=headers + client, f"/api/competitions/{CID}/slides/{slide_id}/order", {"order": 0}, headers=headers ) assert response.status_code == codes.OK # Changes the order - change_order_test(client, CID, slide_order, slide_order + 1, headers) + change_order_test(client, CID, slide_id, slide_id + 1, headers) # Copies slide for _ in range(10): - response, _ = post(client, f"/api/competitions/{CID}/slides/{slide_order}/copy", headers=headers) + response, _ = post(client, f"/api/competitions/{CID}/slides/{slide_id}/copy", headers=headers) assert response.status_code == codes.OK + """ def test_question_api(client): @@ -342,7 +348,7 @@ def test_question_api(client): # Add question name = "Nytt namn" type_id = 2 - slide_order = 1 + slide_order = 6 response, item_question = post( client, f"/api/competitions/{CID}/slides/{slide_order}/questions", diff --git a/server/tests/test_helpers.py b/server/tests/test_helpers.py index 7a68655b..190e248c 100644 --- a/server/tests/test_helpers.py +++ b/server/tests/test_helpers.py @@ -128,12 +128,14 @@ def assert_object_values(obj, values): # Changes order of slides -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) +def change_order_test(client, cid, slide_id, new_slide_id, h): + response, new_order_body = get(client, f"/api/competitions/{cid}/slides/{new_slide_id}", headers=h) assert response.status_code == codes.OK - response, order_body = get(client, f"/api/competitions/{cid}/slides/{order}", headers=h) + response, order_body = get(client, f"/api/competitions/{cid}/slides/{slide_id}", headers=h) assert response.status_code == codes.OK + new_order = new_order_body["order"] + # Changes order - response, _ = put(client, f"/api/competitions/{cid}/slides/{order}/order", {"order": new_order}, headers=h) + response, _ = put(client, f"/api/competitions/{cid}/slides/{slide_id}/order", {"order": new_order}, headers=h) assert response.status_code == codes.OK -- GitLab From d1f5e06448ea46a124a11e087668859a6c1f9647 Mon Sep 17 00:00:00 2001 From: robban64 <carl@schonfelder.se> Date: Thu, 22 Apr 2021 11:47:02 +0200 Subject: [PATCH 11/17] fix: dbc.add use ID instead of item as argument --- server/app/apis/competitions.py | 2 +- server/app/apis/components.py | 3 +-- server/app/apis/questions.py | 5 ++--- server/app/apis/slides.py | 8 +++----- server/app/apis/teams.py | 3 +-- server/app/database/controller/add.py | 20 ++++++++++---------- server/app/database/controller/copy.py | 4 ++-- server/populate.py | 10 +++++----- server/tests/test_app.py | 2 ++ server/tests/test_helpers.py | 8 ++++---- 10 files changed, 31 insertions(+), 34 deletions(-) diff --git a/server/app/apis/competitions.py b/server/app/apis/competitions.py index eeea67bb..3c71f8c2 100644 --- a/server/app/apis/competitions.py +++ b/server/app/apis/competitions.py @@ -25,7 +25,7 @@ class CompetitionsList(Resource): item = dbc.add.competition(**args) # Add default slide - dbc.add.slide(item) + dbc.add.slide(item.id) return item_response(schema.dump(item)) diff --git a/server/app/apis/components.py b/server/app/apis/components.py index c712a4e6..c895f023 100644 --- a/server/app/apis/components.py +++ b/server/app/apis/components.py @@ -46,6 +46,5 @@ class ComponentList(Resource): @check_jwt(editor=True) def post(self, competition_id, slide_id): args = component_create_parser.parse_args() - item_slide = dbc.get.slide(competition_id, slide_id) - item = dbc.add.component(item_slide=item_slide, **args) + item = dbc.add.component(slide_id=slide_id, **args) return item_response(schema.dump(item)) diff --git a/server/app/apis/questions.py b/server/app/apis/questions.py index 9b519e5f..5797872a 100644 --- a/server/app/apis/questions.py +++ b/server/app/apis/questions.py @@ -30,10 +30,9 @@ class QuestionListForSlide(Resource): return list_response(list_schema.dump(items)) @check_jwt(editor=True) - def post(self, slide_id, competition_id): + def post(self, competition_id, slide_id): args = question_parser.parse_args(strict=True) - item_slide = dbc.get.slide(competition_id, slide_id) - item = dbc.add.question(item_slide=item_slide, **args) + item = dbc.add.question(slide_id=slide_id, **args) return item_response(schema.dump(item)) diff --git a/server/app/apis/slides.py b/server/app/apis/slides.py index fffdff0b..72562480 100644 --- a/server/app/apis/slides.py +++ b/server/app/apis/slides.py @@ -22,11 +22,9 @@ class SlidesList(Resource): @check_jwt(editor=True) def post(self, competition_id): - item_comp = dbc.get.one(Competition, competition_id) - item_slide = dbc.add.slide(item_comp) - dbc.add.question(f"Fråga {item_slide.order + 1}", 10, 0, item_slide) - dbc.utils.refresh(item_comp) - return list_response(list_schema.dump(item_comp.slides)) + item_slide = dbc.add.slide(competition_id) + dbc.add.question(f"Fråga {item_slide.order + 1}", 10, 0, item_slide.id) + return item_response(schema.dump(item_slide)) @api.route("/<slide_id>") diff --git a/server/app/apis/teams.py b/server/app/apis/teams.py index 535de695..6596244c 100644 --- a/server/app/apis/teams.py +++ b/server/app/apis/teams.py @@ -23,8 +23,7 @@ class TeamsList(Resource): @check_jwt(editor=True) def post(self, competition_id): args = team_parser.parse_args(strict=True) - item_comp = dbc.get.one(Competition, competition_id) - item_team = dbc.add.team(args["name"], item_comp) + item_team = dbc.add.team(args["name"], competition_id) return item_response(schema.dump(item_team)) diff --git a/server/app/database/controller/add.py b/server/app/database/controller/add.py index 7b77732b..73279056 100644 --- a/server/app/database/controller/add.py +++ b/server/app/database/controller/add.py @@ -91,13 +91,13 @@ def city(name): return db_add(City(name)) -def component(type_id, item_slide, data, x=0, y=0, w=0, h=0): +def component(type_id, slide_id, data, x=0, y=0, w=0, h=0): """ Adds a component to the slide at the specified coordinates with the provided size and data . """ - return db_add(Component(item_slide.id, type_id, data, x, y, w, h)) + return db_add(Component(slide_id, type_id, data, x, y, w, h)) def image(filename, user_id): @@ -114,12 +114,12 @@ 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): +def question(name, total_score, type_id, slide_id): """ Adds a question to the specified slide using the provided arguments. """ - return db_add(Question(name, total_score, type_id, item_slide.id)) + return db_add(Question(name, total_score, type_id, slide_id)) def question_alternative(text, value, question_id): @@ -137,10 +137,10 @@ def code(pointer, view_type_id): return db_add(Code(code_string, pointer, view_type_id)) -def team(name, item_competition): +def team(name, competition_id): """ Adds a team with the specified name to the provided competition. """ - item = db_add(Team(name, item_competition.id)) + item = db_add(Team(name, competition_id)) # Add code for the team code(item.id, 1) @@ -148,11 +148,11 @@ def team(name, item_competition): return item -def slide(item_competition): +def slide(competition_id): """ Adds a slide to the provided 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)) + order = Slide.query.filter(Slide.competition_id == competition_id).count() # first element has index 0 + return db_add(Slide(order, competition_id)) def competition(name, year, city_id): @@ -164,7 +164,7 @@ def competition(name, year, city_id): item_competition = _competition(name, year, city_id) # Add one slide for the competition - slide(item_competition) + slide(item_competition.id) # TODO: Add two teams diff --git a/server/app/database/controller/copy.py b/server/app/database/controller/copy.py index a6b408ec..cd6bbd77 100644 --- a/server/app/database/controller/copy.py +++ b/server/app/database/controller/copy.py @@ -40,7 +40,7 @@ def _component(item_component, item_slide_new): add.component( item_component.type_id, - item_slide_new, + item_slide_new.id, item_component.data, item_component.x, item_component.y, @@ -66,7 +66,7 @@ def slide_to_competition(item_slide_old, item_competition): Does not copy team, question answers. """ - item_slide_new = add.slide(item_competition) + item_slide_new = add.slide(item_competition.id) # Copy all fields item_slide_new.title = item_slide_old.title diff --git a/server/populate.py b/server/populate.py index c9813b40..d842671e 100644 --- a/server/populate.py +++ b/server/populate.py @@ -50,8 +50,8 @@ def _add_items(): dbc.edit.default(item_comp.slides[0], timer=5, title="test-slide-title") # Add two more slides to competition - dbc.add.slide(item_comp) - dbc.add.slide(item_comp) + dbc.add.slide(item_comp.id) + dbc.add.slide(item_comp.id) # Add slides for j, item_slide in enumerate(item_comp.slides): @@ -67,7 +67,7 @@ def _add_items(): name=f"Question {j}: {question_types_items[j].name}", total_score=j, type_id=question_types_items[j].id, - item_slide=item_slide, + slide_id=item_slide.id, ) for i in range(3): @@ -80,7 +80,7 @@ def _add_items(): y = random.randrange(1, 500) w = random.randrange(150, 400) h = random.randrange(150, 400) - dbc.add.component(1, item_slide, {"text": f"hej{k}"}, x, y, w, h) + dbc.add.component(1, item_slide.id, {"text": f"hej{k}"}, x, y, w, h) # TODO: Remove comments when slide without questions is fixed # item_slide = dbc.add.slide(item_comp) @@ -92,7 +92,7 @@ def _add_items(): # Add teams for name in teams: - dbc.add.team(f"{name}{i}", item_comp) + dbc.add.team(f"{name}{i}", item_comp.id) if __name__ == "__main__": diff --git a/server/tests/test_app.py b/server/tests/test_app.py index 48e74c5a..b268f3e5 100644 --- a/server/tests/test_app.py +++ b/server/tests/test_app.py @@ -258,6 +258,8 @@ def test_slide_api(client): # Add slide response, body = post(client, f"/api/competitions/{CID}/slides", headers=headers) assert response.status_code == codes.OK + + response, body = get(client, f"/api/competitions/{CID}/slides", headers=headers) assert body["count"] == 4 # Get slide diff --git a/server/tests/test_helpers.py b/server/tests/test_helpers.py index 190e248c..a90574bb 100644 --- a/server/tests/test_helpers.py +++ b/server/tests/test_helpers.py @@ -43,8 +43,8 @@ def add_default_values(): 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) + dbc.add.slide(item_comp.id) + dbc.add.slide(item_comp.id) # Add slides for i, item_slide in enumerate(item_comp.slides): @@ -56,10 +56,10 @@ def add_default_values(): 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=1, item_slide=item_slide) + dbc.add.question(name=f"Q{i+1}", total_score=i + 1, type_id=1, slide_id=item_slide.id) # Add text component - dbc.add.component(1, item_slide, {"text": "Text"}, i, 2 * i, 3 * i, 4 * i) + dbc.add.component(1, item_slide.id, {"text": "Text"}, i, 2 * i, 3 * i, 4 * i) def get_body(response): -- GitLab From 929fbf14027d351b401e66d636061a6a9b6d3481 Mon Sep 17 00:00:00 2001 From: robban64 <carl@schonfelder.se> Date: Thu, 22 Apr 2021 12:44:11 +0200 Subject: [PATCH 12/17] fix: consistency between api and db-controller --- server/app/apis/competitions.py | 2 +- server/app/apis/slides.py | 1 - server/app/database/controller/add.py | 43 ++++++++++++++++++++----- server/app/database/controller/copy.py | 4 +-- server/app/database/controller/utils.py | 20 ++++++++++++ server/configmodule.py | 13 ++++---- server/populate.py | 4 ++- server/tests/test_app.py | 4 +-- server/tests/test_helpers.py | 2 +- 9 files changed, 71 insertions(+), 22 deletions(-) diff --git a/server/app/apis/competitions.py b/server/app/apis/competitions.py index 3c71f8c2..386d4051 100644 --- a/server/app/apis/competitions.py +++ b/server/app/apis/competitions.py @@ -25,7 +25,7 @@ class CompetitionsList(Resource): item = dbc.add.competition(**args) # Add default slide - dbc.add.slide(item.id) + # dbc.add.slide(item.id) return item_response(schema.dump(item)) diff --git a/server/app/apis/slides.py b/server/app/apis/slides.py index 72562480..ef8cf89c 100644 --- a/server/app/apis/slides.py +++ b/server/app/apis/slides.py @@ -23,7 +23,6 @@ class SlidesList(Resource): @check_jwt(editor=True) def post(self, competition_id): item_slide = dbc.add.slide(competition_id) - dbc.add.question(f"Fråga {item_slide.order + 1}", 10, 0, item_slide.id) return item_response(schema.dump(item_slide)) diff --git a/server/app/database/controller/add.py b/server/app/database/controller/add.py index 73279056..1dbcf442 100644 --- a/server/app/database/controller/add.py +++ b/server/app/database/controller/add.py @@ -2,6 +2,7 @@ This file contains functionality to add data to the database. """ +from sqlalchemy.orm.session import sessionmaker import app.core.http_codes as codes from app.core import db from app.database.controller import utils @@ -151,8 +152,30 @@ def team(name, competition_id): def slide(competition_id): """ Adds a slide to the provided competition. """ - order = Slide.query.filter(Slide.competition_id == competition_id).count() # first element has index 0 - return db_add(Slide(order, competition_id)) + # Get the last order from given competition + order = Slide.query.filter(Slide.competition_id == competition_id).count() + + # Add slide + item_slide = db_add(Slide(order, competition_id)) + + # Add default question + question(f"Fråga {item_slide.order + 1}", 10, 0, item_slide.id) + + item_slide = utils.refresh(item_slide) + return item_slide + + +def slide_without_question(competition_id): + """ Adds a slide to the provided competition. """ + + # Get the last order from given competition + order = Slide.query.filter(Slide.competition_id == competition_id).count() + + # Add slide + item_slide = db_add(Slide(order, competition_id)) + + item_slide = utils.refresh(item_slide) + return item_slide def competition(name, year, city_id): @@ -160,18 +183,22 @@ def competition(name, year, city_id): Adds a competition to the database using the provided arguments. Also adds slide and codes. """ + item_competition = db_add(Competition(name, year, city_id)) - item_competition = _competition(name, year, city_id) - - # Add one slide for the competition + # Add default slide slide(item_competition.id) - # TODO: Add two teams + # Add code for Judge view + code(item_competition.id, 2) + + # Add code for Audience view + code(item_competition.id, 3) + item_competition = utils.refresh(item_competition) return item_competition -def _competition(name, year, city_id, font=None): +def _competition_no_slides(name, year, city_id, font=None): """ Internal function. Adds a competition to the database using the provided arguments. Also adds codes. @@ -187,5 +214,5 @@ def _competition(name, year, city_id, font=None): # Add code for Audience view code(item_competition.id, 3) - utils.refresh(item_competition) + item_competition = utils.refresh(item_competition) return item_competition diff --git a/server/app/database/controller/copy.py b/server/app/database/controller/copy.py index cd6bbd77..79a4df0f 100644 --- a/server/app/database/controller/copy.py +++ b/server/app/database/controller/copy.py @@ -66,7 +66,7 @@ def slide_to_competition(item_slide_old, item_competition): Does not copy team, question answers. """ - item_slide_new = add.slide(item_competition.id) + item_slide_new = add.slide_without_question(item_competition.id) # Copy all fields item_slide_new.title = item_slide_old.title @@ -98,7 +98,7 @@ def competition(item_competition_old): print(f"{item_competition[total-1].name}, {total=}") name = "Kopia av " + item_competition[total - 1].name - item_competition_new = add._competition( + item_competition_new = add._competition_no_slides( name, item_competition_old.year, item_competition_old.city_id, diff --git a/server/app/database/controller/utils.py b/server/app/database/controller/utils.py index 6b11384a..c01205a6 100644 --- a/server/app/database/controller/utils.py +++ b/server/app/database/controller/utils.py @@ -10,6 +10,26 @@ from flask_restx import abort from sqlalchemy import exc +def move_slides(item_competition, start_order, end_order): + slides = item_competition.slides + # Move up + if start_order < end_order: + for i in range(start_order + 1, end_order): + slides[i].order -= 1 + + # Move down + elif start_order > end_order: + for i in range(end_order, start_order): + slides[i].order += 1 + + # start = 5, end = 1 + # 1->2, 2->3, 4->5 + # 5 = 1 + + slides[start_order].order = end_order + return commit_and_refresh(item_competition) + + def generate_unique_code(): """ Generates a unique competition code. """ diff --git a/server/configmodule.py b/server/configmodule.py index 78537a0e..e202abd3 100644 --- a/server/configmodule.py +++ b/server/configmodule.py @@ -21,6 +21,7 @@ class Config: class DevelopmentConfig(Config): DEBUG = True + SQLALCHEMY_ECHO = False # HOST = "localhost" # PORT = 5432 # USER = "postgres" @@ -28,7 +29,6 @@ class DevelopmentConfig(Config): # DATABASE = "teknik8" # SQLALCHEMY_DATABASE_URI = "sqlite:///database.db" # SQLALCHEMY_DATABASE_URI = "postgresql://" + USER + ":" + PASSWORD + "@" + HOST + ":" + str(PORT) + "/" + DATABASE - SQLALCHEMY_ECHO = False class TestingConfig(Config): @@ -38,9 +38,10 @@ class TestingConfig(Config): class ProductionConfig(Config): SQLALCHEMY_DATABASE_URI = "sqlite:///database.db" - # HOST = 'postgresql' + # HOST = "localhost" # PORT = 5432 - # USER = 'postgres' - # PASSWORD = 'password' - # DATABASE = 'teknik8' - # SQLALCHEMY_DATABASE_URI = 'postgresql://'+USER+":"+PASSWORD+"@"+HOST+":"+str(PORT)+"/"+DATABASE + # USER = "postgres" + # PASSWORD = "password" + # DATABASE = "teknik8" + # SQLALCHEMY_DATABASE_URI = "sqlite:///database.db" + # SQLALCHEMY_DATABASE_URI = "postgresql://" + USER + ":" + PASSWORD + "@" + HOST + ":" + str(PORT) + "/" + DATABASE diff --git a/server/populate.py b/server/populate.py index d842671e..46e190a9 100644 --- a/server/populate.py +++ b/server/populate.py @@ -63,15 +63,17 @@ def _add_items(): dbc.utils.commit_and_refresh(item_slide) # Add question to competition + """ item_question = dbc.add.question( name=f"Question {j}: {question_types_items[j].name}", total_score=j, type_id=question_types_items[j].id, slide_id=item_slide.id, ) + """ for i in range(3): - dbc.add.question_alternative(f"Alternative {i}", 0, item_question.id) + dbc.add.question_alternative(f"Alternative {i}", 0, item_slide.questions[0].id) # Add text components # TODO: Add images as components diff --git a/server/tests/test_app.py b/server/tests/test_app.py index b268f3e5..62d8655a 100644 --- a/server/tests/test_app.py +++ b/server/tests/test_app.py @@ -82,7 +82,7 @@ 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"]) == 3 + assert len(body["items"]) == 2 """ response, body = put(client, f"/api/competitions/{competition_id}/slides/{2}/order", {"order": 1}, headers=headers) @@ -338,7 +338,7 @@ def test_question_api(client): slide_order = 1 response, body = get(client, f"/api/competitions/{CID}/questions", headers=headers) assert response.status_code == codes.OK - assert body["count"] == 0 + assert body["count"] == 1 # Get questions from another competition that should have some questions CID = 3 diff --git a/server/tests/test_helpers.py b/server/tests/test_helpers.py index a90574bb..cc630626 100644 --- a/server/tests/test_helpers.py +++ b/server/tests/test_helpers.py @@ -56,7 +56,7 @@ def add_default_values(): 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=1, slide_id=item_slide.id) + # dbc.add.question(name=f"Q{i+1}", total_score=i + 1, type_id=1, slide_id=item_slide.id) # Add text component dbc.add.component(1, item_slide.id, {"text": "Text"}, i, 2 * i, 3 * i, 4 * i) -- GitLab From c6ef6749d9e878daee576ed45ae3e1f74f8db79c Mon Sep 17 00:00:00 2001 From: Albin Henriksson <albhe428@student.liu.se> Date: Thu, 22 Apr 2021 13:20:36 +0200 Subject: [PATCH 13/17] Fix client --- .../PresentationEditorPage.tsx | 14 ++++++------- .../components/RndComponent.tsx | 6 ++---- .../components/SlideSettings.tsx | 20 +++++++++---------- .../components/TextComponentEdit.tsx | 5 +++-- 4 files changed, 22 insertions(+), 23 deletions(-) diff --git a/client/src/pages/presentationEditor/PresentationEditorPage.tsx b/client/src/pages/presentationEditor/PresentationEditorPage.tsx index 09ef5181..4087d6c7 100644 --- a/client/src/pages/presentationEditor/PresentationEditorPage.tsx +++ b/client/src/pages/presentationEditor/PresentationEditorPage.tsx @@ -36,7 +36,7 @@ import { const initialState = { mouseX: null, mouseY: null, - slideOrder: null, + slideId: null, } const leftDrawerWidth = 150 @@ -111,15 +111,15 @@ const PresentationEditorPage: React.FC = () => { const [contextState, setContextState] = React.useState<{ mouseX: null | number mouseY: null | number - slideOrder: null | number + slideId: null | number }>(initialState) - const handleRightClick = (event: React.MouseEvent<HTMLDivElement>, slideOrder: number) => { + const handleRightClick = (event: React.MouseEvent<HTMLDivElement>, slideId: number) => { event.preventDefault() setContextState({ mouseX: event.clientX - 2, mouseY: event.clientY - 4, - slideOrder: slideOrder, + slideId: slideId, }) } @@ -128,13 +128,13 @@ const PresentationEditorPage: React.FC = () => { } const handleRemoveSlide = async () => { - await axios.delete(`/competitions/${id}/slides/${contextState.slideOrder}`) + await axios.delete(`/competitions/${id}/slides/${contextState.slideId}`) dispatch(getEditorCompetition(id)) setContextState(initialState) } const handleDuplicateSlide = async () => { - await axios.post(`/competitions/${id}/slides/${contextState.slideOrder}/copy`) + await axios.post(`/competitions/${id}/slides/${contextState.slideId}/copy`) dispatch(getEditorCompetition(id)) setContextState(initialState) } @@ -214,7 +214,7 @@ const PresentationEditorPage: React.FC = () => { key={slide.id} selected={slide.id === activeSlideId} onClick={() => setActiveSlideId(slide.id)} - onContextMenu={(event) => handleRightClick(event, slide.order)} + onContextMenu={(event) => handleRightClick(event, slide.id)} > {renderSlideIcon(slide)} <ListItemText primary={`Sida ${slide.order + 1}`} /> diff --git a/client/src/pages/presentationEditor/components/RndComponent.tsx b/client/src/pages/presentationEditor/components/RndComponent.tsx index b1b586ff..890e5160 100644 --- a/client/src/pages/presentationEditor/components/RndComponent.tsx +++ b/client/src/pages/presentationEditor/components/RndComponent.tsx @@ -20,15 +20,13 @@ const RndComponent = ({ component }: ImageComponentProps) => { const competitionId = useAppSelector((state) => state.editor.competition.id) const slideId = useAppSelector((state) => state.editor.activeSlideId) const handleUpdatePos = (pos: Position) => { - // TODO: change path to /slides/${slideId} - axios.put(`/competitions/${competitionId}/slides/0/components/${component.id}`, { + axios.put(`/competitions/${competitionId}/slides/${slideId}/components/${component.id}`, { x: pos.x, y: pos.y, }) } const handleUpdateSize = (size: Size) => { - // TODO: change path to /slides/${slideId} - axios.put(`/competitions/${competitionId}/slides/0/components/${component.id}`, { + axios.put(`/competitions/${competitionId}/slides/${slideId}/components/${component.id}`, { w: size.w, h: size.h, }) diff --git a/client/src/pages/presentationEditor/components/SlideSettings.tsx b/client/src/pages/presentationEditor/components/SlideSettings.tsx index e6be5c15..7cc78b34 100644 --- a/client/src/pages/presentationEditor/components/SlideSettings.tsx +++ b/client/src/pages/presentationEditor/components/SlideSettings.tsx @@ -133,7 +133,7 @@ const SlideSettings: React.FC = () => { if (selectedSlideType === 0) { // Change slide type from a question type to information await axios - .delete(`/competitions/${id}/slides/${activeSlide.order}/questions/${activeSlide.questions[0].id}`) + .delete(`/competitions/${id}/slides/${activeSlide.id}/questions/${activeSlide.questions[0].id}`) .then(() => { dispatch(getEditorCompetition(id)) }) @@ -141,10 +141,10 @@ const SlideSettings: React.FC = () => { } else { // Change slide type from question type to another question type await axios - .delete(`/competitions/${id}/slides/${activeSlide.order}/questions/${activeSlide.questions[0].id}`) + .delete(`/competitions/${id}/slides/${activeSlide.id}/questions/${activeSlide.questions[0].id}`) .catch(console.log) await axios - .post(`/competitions/${id}/slides/${activeSlide.order}/questions`, { + .post(`/competitions/${id}/slides/${activeSlide.id}/questions`, { name: 'Ny fråga', total_score: 0, type_id: selectedSlideType, @@ -158,7 +158,7 @@ const SlideSettings: React.FC = () => { } else if (selectedSlideType !== 0) { // Change slide type from information to a question type await axios - .post(`/competitions/${id}/slides/${activeSlide.order}/questions`, { + .post(`/competitions/${id}/slides/${activeSlide.id}/questions`, { name: 'Ny fråga', total_score: 0, type_id: selectedSlideType, @@ -208,10 +208,10 @@ const SlideSettings: React.FC = () => { const addAlternative = async () => { if (activeSlide && activeSlide.questions[0]) { await axios - .post( - `/competitions/${id}/slides/${activeSlide?.order}/questions/${activeSlide?.questions[0].id}/alternatives`, - { text: '', value: 0 } - ) + .post(`/competitions/${id}/slides/${activeSlide?.id}/questions/${activeSlide?.questions[0].id}/alternatives`, { + text: '', + value: 0, + }) .then(() => { dispatch(getEditorCompetition(id)) }) @@ -237,7 +237,7 @@ const SlideSettings: React.FC = () => { const handleAddText = async () => { if (activeSlide) { - await axios.post(`/competitions/${id}/slides/${activeSlide?.order}/components`, { + await axios.post(`/competitions/${id}/slides/${activeSlide?.id}/components`, { type_id: 1, data: { text: 'Ny text' }, w: 315, @@ -261,7 +261,7 @@ const SlideSettings: React.FC = () => { setTimer(+event.target.value) if (activeSlide) { await axios - .put(`/competitions/${id}/slides/${activeSlide.order}`, { timer: event.target.value }) + .put(`/competitions/${id}/slides/${activeSlide.id}`, { timer: event.target.value }) .then(() => { dispatch(getEditorCompetition(id)) }) diff --git a/client/src/pages/presentationEditor/components/TextComponentEdit.tsx b/client/src/pages/presentationEditor/components/TextComponentEdit.tsx index 92202993..e0f0d5b4 100644 --- a/client/src/pages/presentationEditor/components/TextComponentEdit.tsx +++ b/client/src/pages/presentationEditor/components/TextComponentEdit.tsx @@ -20,6 +20,7 @@ const TextComponentEdit = ({ component }: ImageComponentProps) => { const competitionId = useAppSelector((state) => state.editor.competition.id) const [content, setContent] = useState('') const [timerHandle, setTimerHandle] = React.useState<number | undefined>(undefined) + const activeSlideId = useAppSelector((state) => state.editor.activeSlideId) const dispatch = useAppDispatch() useEffect(() => { @@ -36,7 +37,7 @@ const TextComponentEdit = ({ component }: ImageComponentProps) => { setTimerHandle( window.setTimeout(async () => { console.log('Content was updated on server. id: ', component.id) - await axios.put(`/competitions/${competitionId}/slides/0/components/${component.id}`, { + await axios.put(`/competitions/${competitionId}/slides/${activeSlideId}/components/${component.id}`, { data: { ...component.data, text: a }, }) dispatch(getEditorCompetition(id)) @@ -45,7 +46,7 @@ const TextComponentEdit = ({ component }: ImageComponentProps) => { } const handleDeleteText = async (componentId: number) => { - await axios.delete(`/competitions/${id}/slides/0/components/${componentId}`) + await axios.delete(`/competitions/${id}/slides/${activeSlideId}/components/${componentId}`) dispatch(getEditorCompetition(id)) } -- GitLab From 9135627f464c2fb412d795d5bc321fa500a17fe2 Mon Sep 17 00:00:00 2001 From: Albin Henriksson <albhe428@student.liu.se> Date: Thu, 22 Apr 2021 14:52:53 +0200 Subject: [PATCH 14/17] Add text field for question name --- .../components/SlideSettings.tsx | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/client/src/pages/presentationEditor/components/SlideSettings.tsx b/client/src/pages/presentationEditor/components/SlideSettings.tsx index e6be5c15..84df217b 100644 --- a/client/src/pages/presentationEditor/components/SlideSettings.tsx +++ b/client/src/pages/presentationEditor/components/SlideSettings.tsx @@ -294,7 +294,12 @@ const SlideSettings: React.FC = () => { <div className={classes.whiteBackground}> <FormControl variant="outlined" className={classes.dropDown}> <InputLabel>Sidtyp</InputLabel> - <Select value={activeSlide?.questions[0]?.type_id || 0} label="Sidtyp" className={classes.panelList}> + <Select + value={activeSlide?.questions[0]?.type_id || 0} + label="Sidtyp" + className={classes.panelList} + fullWidth={true} + > <MenuItem value={0}> <Typography variant="button" onClick={() => openSlideTypeDialog(0)}> Informationssida @@ -341,6 +346,7 @@ const SlideSettings: React.FC = () => { <ListItem> <TextField id="standard-number" + fullWidth={true} variant="outlined" placeholder="Antal sekunder" helperText="Lämna blank för att inte använda timerfunktionen" @@ -350,7 +356,16 @@ const SlideSettings: React.FC = () => { value={timer || ''} /> </ListItem> - + <ListItem> + <TextField + label="Frågetitel" + fullWidth={true} + defaultValue={activeSlide?.questions[0].name} + onChange={updateQuestionName} + variant="outlined" + /> + <Divider /> + </ListItem> <List className={classes.panelList}> <ListItem divider> <ListItemText -- GitLab From ca3be5ee5c68cdc2ae28e677eecdcdbbd7e2c745 Mon Sep 17 00:00:00 2001 From: Albin Henriksson <albhe428@student.liu.se> Date: Thu, 22 Apr 2021 16:06:59 +0200 Subject: [PATCH 15/17] change default value --- .../components/SlideSettings.tsx | 41 ++++++++++++++++++- .../presentationEditor/components/styled.tsx | 1 + 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/client/src/pages/presentationEditor/components/SlideSettings.tsx b/client/src/pages/presentationEditor/components/SlideSettings.tsx index 31b97975..b951cf40 100644 --- a/client/src/pages/presentationEditor/components/SlideSettings.tsx +++ b/client/src/pages/presentationEditor/components/SlideSettings.tsx @@ -268,6 +268,32 @@ const SlideSettings: React.FC = () => { .catch(console.log) } } + + const updateMaxScore = async (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => { + const questionId = activeSlide?.questions[0].id + if (activeSlide) { + await axios + .put(`/competitions/${id}/slides/${activeSlide.id}/questions/${questionId}`, { + total_score: event.target.value, + }) + .then(() => { + dispatch(getEditorCompetition(id)) + }) + .catch(console.log) + } + } + + const updateQuestionName = async (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => { + const questionId = activeSlide?.questions[0].id + if (activeSlide && questionId) { + await axios + .put(`/competitions/${id}/slides/${activeSlide.id}/questions/${questionId}`, { name: event.target.value }) + .then(() => { + dispatch(getEditorCompetition(id)) + }) + .catch(console.log) + } + } const [timer, setTimer] = useState<number | undefined>(0) useEffect(() => { setTimer(activeSlide?.timer) @@ -356,11 +382,24 @@ const SlideSettings: React.FC = () => { value={timer || ''} /> </ListItem> + + <ListItem> + <TextField + id="standard-number" + fullWidth={true} + variant="outlined" + label="Maxpoäng" + type="number" + onChange={updateMaxScore} + defaultValue={(activeSlide?.questions[0] && activeSlide?.questions[0].total_score) || ''} + /> + </ListItem> + <ListItem> <TextField label="Frågetitel" fullWidth={true} - defaultValue={activeSlide?.questions[0].name} + defaultValue={activeSlide?.questions[0] && activeSlide?.questions[0].name} onChange={updateQuestionName} variant="outlined" /> diff --git a/client/src/pages/presentationEditor/components/styled.tsx b/client/src/pages/presentationEditor/components/styled.tsx index 8bee7f0d..061fef10 100644 --- a/client/src/pages/presentationEditor/components/styled.tsx +++ b/client/src/pages/presentationEditor/components/styled.tsx @@ -62,5 +62,6 @@ interface TextComponentContainerProps { export const TextComponentContainer = styled.div<TextComponentContainerProps>` height: 100%; width: 100%; + padding: ${(props) => (props.hover ? 0 : 1)}px; border: solid ${(props) => (props.hover ? 1 : 0)}px; ` -- GitLab From 115b4ab63c9c6638348c710c88f61d564705ef2c Mon Sep 17 00:00:00 2001 From: Emil <Emil> Date: Thu, 22 Apr 2021 16:09:54 +0200 Subject: [PATCH 16/17] started refactoring --- .../components/Alternatives.tsx | 137 +++++++ .../components/SlideSettings.tsx | 362 +++--------------- .../components/SlideType.tsx | 136 +++++++ .../presentationEditor/components/Timer.tsx | 50 +++ .../presentationEditor/components/styled.tsx | 60 ++- 5 files changed, 434 insertions(+), 311 deletions(-) create mode 100644 client/src/pages/presentationEditor/components/Alternatives.tsx create mode 100644 client/src/pages/presentationEditor/components/SlideType.tsx create mode 100644 client/src/pages/presentationEditor/components/Timer.tsx diff --git a/client/src/pages/presentationEditor/components/Alternatives.tsx b/client/src/pages/presentationEditor/components/Alternatives.tsx new file mode 100644 index 00000000..d40882da --- /dev/null +++ b/client/src/pages/presentationEditor/components/Alternatives.tsx @@ -0,0 +1,137 @@ +import { Checkbox, ListItem, ListItemText, withStyles } from '@material-ui/core' +import { CheckboxProps } from '@material-ui/core/Checkbox' +import { green, grey } from '@material-ui/core/colors' +import CloseIcon from '@material-ui/icons/Close' +import axios from 'axios' +import React from 'react' +import { getEditorCompetition } from '../../../actions/editor' +import { useAppDispatch, useAppSelector } from '../../../hooks' +import { QuestionAlternative } from '../../../interfaces/ApiModels' +import { RichSlide } from '../../../interfaces/ApiRichModels' +import { AddButton, Center, Clickable, SettingsList, TextInput, WhiteBackground } from './styled' + +type AlternativeProps = { + activeSlide: RichSlide + competitionId: string +} + +const Alternatives = ({ activeSlide, competitionId }: AlternativeProps) => { + const dispatch = useAppDispatch() + const competition = useAppSelector((state) => state.editor.competition) + const activeSlideId = useAppSelector((state) => state.editor.activeSlideId) + const GreenCheckbox = withStyles({ + root: { + color: grey[900], + '&$checked': { + color: green[600], + }, + }, + checked: {}, + })((props: CheckboxProps) => <Checkbox color="default" {...props} />) + + const numberToBool = (num: number) => { + if (num === 0) return false + else return true + } + + const updateAlternativeValue = async (alternative: QuestionAlternative) => { + if (activeSlide && activeSlide.questions[0]) { + let newValue: number + if (alternative.value === 0) { + newValue = 1 + } else newValue = 0 + console.log('newValue: ' + newValue) + await axios + .put( + `/competitions/${competitionId}/slides/${activeSlide?.id}/questions/${activeSlide?.questions[0].id}/alternatives/${alternative.id}`, + { value: newValue } + ) + .then(() => { + dispatch(getEditorCompetition(competitionId)) + }) + .catch(console.log) + } + } + + const updateAlternativeText = async (alternative_id: number, newText: string) => { + if (activeSlide && activeSlide.questions[0]) { + await axios + .put( + `/competitions/${competitionId}/slides/${activeSlide?.id}/questions/${activeSlide?.questions[0].id}/alternatives/${alternative_id}`, + { text: newText } + ) + .then(() => { + dispatch(getEditorCompetition(competitionId)) + }) + .catch(console.log) + } + } + + const addAlternative = async () => { + if (activeSlide && activeSlide.questions[0]) { + await axios + .post( + `/competitions/${competitionId}/slides/${activeSlide?.order}/questions/${activeSlide?.questions[0].id}/alternatives`, + { text: '', value: 0 } + ) + .then(() => { + dispatch(getEditorCompetition(competitionId)) + }) + .catch(console.log) + } + } + + const handleCloseAnswerClick = async (alternative_id: number) => { + if (activeSlide && activeSlide.questions[0]) { + await axios + .delete( + `/competitions/${competitionId}/slides/${activeSlideId}/questions/${activeSlide?.questions[0].id}/alternatives/${alternative_id}` + ) + .then(() => { + dispatch(getEditorCompetition(competitionId)) + }) + .catch(console.log) + } + } + + return ( + <SettingsList> + <WhiteBackground> + <ListItem divider> + <Center> + <ListItemText + primary="Svarsalternativ" + secondary="(Fyll i rutan höger om textfältet för att markera korrekt svar)" + /> + </Center> + </ListItem> + {activeSlide && + activeSlide.questions[0] && + activeSlide.questions[0].alternatives && + activeSlide.questions[0].alternatives.map((alt) => ( + <div key={alt.id}> + <ListItem divider> + <TextInput + id="outlined-basic" + defaultValue={alt.text} + onChange={(event) => updateAlternativeText(alt.id, event.target.value)} + variant="outlined" + /> + <GreenCheckbox checked={numberToBool(alt.value)} onChange={() => updateAlternativeValue(alt)} /> + <Clickable> + <CloseIcon onClick={() => handleCloseAnswerClick(alt.id)} /> + </Clickable> + </ListItem> + </div> + ))} + <ListItem button onClick={addAlternative}> + <Center> + <AddButton variant="button">Lägg till svarsalternativ</AddButton> + </Center> + </ListItem> + </WhiteBackground> + </SettingsList> + ) +} + +export default Alternatives diff --git a/client/src/pages/presentationEditor/components/SlideSettings.tsx b/client/src/pages/presentationEditor/components/SlideSettings.tsx index e59586bb..54854abd 100644 --- a/client/src/pages/presentationEditor/components/SlideSettings.tsx +++ b/client/src/pages/presentationEditor/components/SlideSettings.tsx @@ -1,32 +1,14 @@ -import { - Button, - Checkbox, - Dialog, - DialogActions, - DialogContent, - DialogContentText, - DialogTitle, - FormControl, - InputLabel, - List, - ListItem, - ListItemText, - MenuItem, - Select, - TextField, - Typography, -} from '@material-ui/core' -import { CheckboxProps } from '@material-ui/core/Checkbox' -import { green, grey } from '@material-ui/core/colors' -import { createStyles, makeStyles, Theme, withStyles } from '@material-ui/core/styles' +import { List, ListItem, ListItemText, Typography } from '@material-ui/core' +import { createStyles, makeStyles, Theme } from '@material-ui/core/styles' import CloseIcon from '@material-ui/icons/Close' -import axios from 'axios' -import React, { useEffect, useState } from 'react' +import React, { useState } from 'react' import { useParams } from 'react-router-dom' -import { getEditorCompetition } from '../../../actions/editor' -import { useAppDispatch, useAppSelector } from '../../../hooks' -import { QuestionAlternative, TextComponent } from '../../../interfaces/ApiModels' -import { HiddenInput } from './styled' +import { useAppSelector } from '../../../hooks' +import { TextComponent } from '../../../interfaces/ApiModels' +import Alternatives from './Alternatives' +import SlideType from './SlideType' +import { Center, HiddenInput, SettingsList, SlidePanel, TextInput } from './styled' +import Timer from './Timer' const useStyles = makeStyles((theme: Theme) => createStyles({ @@ -37,25 +19,6 @@ const useStyles = makeStyles((theme: Theme) => background: 'white', }, }, - textInput: { - margin: theme.spacing(2), - width: '87%', - background: 'white', - }, - textCenter: { - textAlign: 'center', - }, - center: { - display: 'flex', - justifyContent: 'center', - background: 'white', - }, - dropDown: { - margin: theme.spacing(2), - width: '87%', - background: 'white', - padding: 0, - }, clickableIcon: { cursor: 'pointer', background: 'white', @@ -88,26 +51,11 @@ interface CompetitionParams { const SlideSettings: React.FC = () => { const classes = useStyles() const { id }: CompetitionParams = useParams() - const dispatch = useAppDispatch() - const competition = useAppSelector((state) => state.editor.competition) - const activeSlideId = useAppSelector((state) => state.editor.activeSlideId) + const activeSlide = useAppSelector((state) => state.editor.competition.slides.find((slide) => slide && slide.id === state.editor.activeSlideId) ) - const handleCloseAnswerClick = async (alternative_id: number) => { - if (activeSlide && activeSlide.questions[0]) { - await axios - .delete( - `/competitions/${id}/slides/${activeSlideId}/questions/${activeSlide?.questions[0].id}/alternatives/${alternative_id}` - ) - .then(() => { - dispatch(getEditorCompetition(id)) - }) - .catch(console.log) - } - } - const texts = useAppSelector( (state) => state.editor.competition.slides @@ -124,99 +72,6 @@ const SlideSettings: React.FC = () => { } const [pictures, setPictures] = useState(pictureList) - const updateSlideType = async () => { - closeSlideTypeDialog() - if (activeSlide) { - if (activeSlide.questions[0] && activeSlide.questions[0].type_id !== selectedSlideType) { - if (selectedSlideType === 0) { - // Change slide type from a question type to information - await axios - .delete(`/competitions/${id}/slides/${activeSlide.order}/questions/${activeSlide.questions[0].id}`) - .then(() => { - dispatch(getEditorCompetition(id)) - }) - .catch(console.log) - } else { - // Change slide type from question type to another question type - await axios - .delete(`/competitions/${id}/slides/${activeSlide.order}/questions/${activeSlide.questions[0].id}`) - .catch(console.log) - await axios - .post(`/competitions/${id}/slides/${activeSlide.order}/questions`, { - name: 'Ny fråga', - total_score: 0, - type_id: selectedSlideType, - slide_id: activeSlide.id, - }) - .then(() => { - dispatch(getEditorCompetition(id)) - }) - .catch(console.log) - } - } else if (selectedSlideType !== 0) { - // Change slide type from information to a question type - await axios - .post(`/competitions/${id}/slides/${activeSlide.order}/questions`, { - name: 'Ny fråga', - total_score: 0, - type_id: selectedSlideType, - slide_id: activeSlide.id, - }) - .then(() => { - dispatch(getEditorCompetition(id)) - }) - .catch(console.log) - } - } - } - - const updateAlternativeValue = async (alternative: QuestionAlternative) => { - if (activeSlide && activeSlide.questions[0]) { - let newValue: number - if (alternative.value === 0) { - newValue = 1 - } else newValue = 0 - console.log('newValue: ' + newValue) - await axios - .put( - `/competitions/${id}/slides/${activeSlide?.id}/questions/${activeSlide?.questions[0].id}/alternatives/${alternative.id}`, - { value: newValue } - ) - .then(() => { - dispatch(getEditorCompetition(id)) - }) - .catch(console.log) - } - } - - const updateAlternativeText = async (alternative_id: number, newText: string) => { - if (activeSlide && activeSlide.questions[0]) { - await axios - .put( - `/competitions/${id}/slides/${activeSlide?.id}/questions/${activeSlide?.questions[0].id}/alternatives/${alternative_id}`, - { text: newText } - ) - .then(() => { - dispatch(getEditorCompetition(id)) - }) - .catch(console.log) - } - } - - const addAlternative = async () => { - if (activeSlide && activeSlide.questions[0]) { - await axios - .post( - `/competitions/${id}/slides/${activeSlide?.order}/questions/${activeSlide?.questions[0].id}/alternatives`, - { text: '', value: 0 } - ) - .then(() => { - dispatch(getEditorCompetition(id)) - }) - .catch(console.log) - } - } - const handleFileSelected = (e: React.ChangeEvent<HTMLInputElement>): void => { if (e.target.files !== null && e.target.files[0]) { const files = Array.from(e.target.files) @@ -239,167 +94,43 @@ const SlideSettings: React.FC = () => { // setTexts([...texts, { id: 'newText', name: 'New Text' }]) } - const GreenCheckbox = withStyles({ - root: { - color: grey[900], - '&$checked': { - color: green[600], - }, - }, - checked: {}, - })((props: CheckboxProps) => <Checkbox color="default" {...props} />) - - const updateTimer = async (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => { - setTimer(+event.target.value) - if (activeSlide) { - await axios - .put(`/competitions/${id}/slides/${activeSlide.order}`, { timer: event.target.value }) - .then(() => { - dispatch(getEditorCompetition(id)) - }) - .catch(console.log) - } - } - const [timer, setTimer] = useState<number | undefined>(0) - useEffect(() => { - setTimer(activeSlide?.timer) - }, [activeSlide]) - - // For "slide type" dialog - const [selectedSlideType, setSelectedSlideType] = useState(0) - const [slideTypeDialog, setSlideTypeDialog] = useState(false) - const openSlideTypeDialog = (type_id: number) => { - setSelectedSlideType(type_id) - setSlideTypeDialog(true) - } - const closeSlideTypeDialog = () => { - setSlideTypeDialog(false) - } - - const numberToBool = (num: number) => { - if (num === 0) return false - else return true - } - return ( - <div className={classes.textInputContainer}> - <div className={classes.whiteBackground}> - <FormControl variant="outlined" className={classes.dropDown}> - <InputLabel>Sidtyp</InputLabel> - <Select value={activeSlide?.questions[0]?.type_id || 0} label="Sidtyp" className={classes.panelList}> - <MenuItem value={0}> - <Typography variant="button" onClick={() => openSlideTypeDialog(0)}> - Informationssida - </Typography> - </MenuItem> - <MenuItem value={1}> - <Typography variant="button" onClick={() => openSlideTypeDialog(1)}> - Skriftlig fråga - </Typography> - </MenuItem> - <MenuItem value={2}> - <Typography variant="button" onClick={() => openSlideTypeDialog(2)}> - Praktisk fråga - </Typography> - </MenuItem> - <MenuItem value={3}> - <Typography variant="button" onClick={() => openSlideTypeDialog(3)}> - Flervalsfråga - </Typography> - </MenuItem> - </Select> - </FormControl> - </div> - <Dialog open={slideTypeDialog} onClose={closeSlideTypeDialog}> - <DialogTitle className={classes.center} color="secondary"> - Varning! - </DialogTitle> - <DialogContent> - <DialogContentText> - Om du ändrar sidtypen kommer eventuella frågeinställningar gå förlorade. Det inkluderar: frågans namn, poäng - och svarsalternativ.{' '} - </DialogContentText> - </DialogContent> - <DialogActions> - <Button onClick={closeSlideTypeDialog} color="secondary"> - Avbryt - </Button> - <Button onClick={updateSlideType} color="primary"> - Bekräfta - </Button> - </DialogActions> - </Dialog> - - <ListItem> - <TextField - id="standard-number" - variant="outlined" - placeholder="Antal sekunder" - helperText="Lämna blank för att inte använda timerfunktionen" - label="Timer" - type="number" - defaultValue={activeSlide?.timer || 0} - onChange={updateTimer} - value={timer} - /> - </ListItem> - - <List className={classes.panelList}> + <SlidePanel> + <SettingsList> + {activeSlide && <SlideType activeSlide={activeSlide} competitionId={id} />} + {activeSlide && <Timer activeSlide={activeSlide} competitionId={id} />} + </SettingsList> + {activeSlide && <Alternatives activeSlide={activeSlide} competitionId={id} />} + + <SettingsList> <ListItem divider> - <ListItemText - className={classes.textCenter} - primary="Svarsalternativ" - secondary="(Fyll i rutan höger om textfältet för att markera korrekt svar)" - /> - </ListItem> - {activeSlide && - activeSlide.questions[0] && - activeSlide.questions[0].alternatives && - activeSlide.questions[0].alternatives.map((alt) => ( - <div key={alt.id}> - <ListItem divider> - <TextField - className={classes.textInput} - id="outlined-basic" - defaultValue={alt.text} - onChange={(event) => updateAlternativeText(alt.id, event.target.value)} - variant="outlined" - /> - <GreenCheckbox checked={numberToBool(alt.value)} onChange={() => updateAlternativeValue(alt)} /> - <CloseIcon className={classes.clickableIcon} onClick={() => handleCloseAnswerClick(alt.id)} /> - </ListItem> - </div> - ))} - <ListItem className={classes.center} button onClick={addAlternative}> - <Typography className={classes.addButtons} variant="button"> - Lägg till svarsalternativ - </Typography> - </ListItem> - </List> - - <List className={classes.panelList}> - <ListItem divider> - <ListItemText className={classes.textCenter} primary="Text" /> + <Center> + <ListItemText primary="Text" /> + </Center> </ListItem> {texts && texts.map((text) => ( <div key={text.id}> <ListItem divider> - <TextField className={classes.textInput} label={text.data.text} variant="outlined" /> + <TextInput label={text.data.text} variant="outlined" /> <CloseIcon className={classes.clickableIcon} /> </ListItem> </div> ))} - <ListItem className={classes.center} button onClick={handleAddText}> - <Typography className={classes.addButtons} variant="button"> - Lägg till text - </Typography> + <ListItem button onClick={handleAddText}> + <Center> + <Typography className={classes.addButtons} variant="button"> + Lägg till text + </Typography> + </Center> </ListItem> - </List> + </SettingsList> <List className={classes.panelList}> <ListItem divider> - <ListItemText className={classes.textCenter} primary="Bilder" /> + <Center> + <ListItemText primary="Bilder" /> + </Center> </ListItem> {pictures.map((picture) => ( <div key={picture.id}> @@ -409,17 +140,26 @@ const SlideSettings: React.FC = () => { src="https://i1.wp.com/stickoutmedia.se/wp-content/uploads/2021/01/placeholder-3.png?ssl=1" className={classes.importedImage} /> - <ListItemText className={classes.textCenter} primary={picture.name} /> + <Center> + <ListItemText primary={picture.name} /> + </Center> <CloseIcon onClick={() => handleClosePictureClick(picture.id)} /> </ListItem> </div> ))} - <ListItem className={classes.center} button> - <HiddenInput accept="image/*" id="contained-button-file" multiple type="file" onChange={handleFileSelected} /> - - <label className={classes.addImageButton} htmlFor="contained-button-file"> - <Typography variant="button">Lägg till bild</Typography> - </label> + <ListItem button> + <Center> + <HiddenInput + accept="image/*" + id="contained-button-file" + multiple + type="file" + onChange={handleFileSelected} + /> + <label className={classes.addImageButton} htmlFor="contained-button-file"> + <Typography variant="button">Lägg till bild</Typography> + </label> + </Center> </ListItem> </List> @@ -429,9 +169,11 @@ const SlideSettings: React.FC = () => { src="https://i1.wp.com/stickoutmedia.se/wp-content/uploads/2021/01/placeholder-3.png?ssl=1" className={classes.importedImage} /> - <ListItemText className={classes.textCenter}>Välj bakgrundsbild ...</ListItemText> + <Center> + <ListItemText>Välj bakgrundsbild ...</ListItemText> + </Center> </ListItem> - </div> + </SlidePanel> ) } diff --git a/client/src/pages/presentationEditor/components/SlideType.tsx b/client/src/pages/presentationEditor/components/SlideType.tsx new file mode 100644 index 00000000..73004c32 --- /dev/null +++ b/client/src/pages/presentationEditor/components/SlideType.tsx @@ -0,0 +1,136 @@ +import { + Button, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, + InputLabel, + MenuItem, + Select, + Typography, +} from '@material-ui/core' +import axios from 'axios' +import React, { useState } from 'react' +import { getEditorCompetition } from '../../../actions/editor' +import { useAppDispatch } from '../../../hooks' +import { RichSlide } from '../../../interfaces/ApiRichModels' +import { Center, FormControlDropdown, WhiteBackground } from './styled' + +type SlideTypeProps = { + activeSlide: RichSlide + competitionId: string +} + +const SlideType = ({ activeSlide, competitionId }: SlideTypeProps) => { + const dispatch = useAppDispatch() + // For "slide type" dialog + const [selectedSlideType, setSelectedSlideType] = useState(0) + const [slideTypeDialog, setSlideTypeDialog] = useState(false) + const openSlideTypeDialog = (type_id: number) => { + setSelectedSlideType(type_id) + setSlideTypeDialog(true) + } + const closeSlideTypeDialog = () => { + setSlideTypeDialog(false) + } + const updateSlideType = async () => { + closeSlideTypeDialog() + if (activeSlide) { + if (activeSlide.questions[0] && activeSlide.questions[0].type_id !== selectedSlideType) { + if (selectedSlideType === 0) { + // Change slide type from a question type to information + await axios + .delete( + `/competitions/${competitionId}/slides/${activeSlide.order}/questions/${activeSlide.questions[0].id}` + ) + .then(() => { + dispatch(getEditorCompetition(competitionId)) + }) + .catch(console.log) + } else { + // Change slide type from question type to another question type + await axios + .delete( + `/competitions/${competitionId}/slides/${activeSlide.order}/questions/${activeSlide.questions[0].id}` + ) + .catch(console.log) + await axios + .post(`/competitions/${competitionId}/slides/${activeSlide.order}/questions`, { + name: 'Ny fråga', + total_score: 0, + type_id: selectedSlideType, + slide_id: activeSlide.id, + }) + .then(() => { + dispatch(getEditorCompetition(competitionId)) + }) + .catch(console.log) + } + } else if (selectedSlideType !== 0) { + // Change slide type from information to a question type + await axios + .post(`/competitions/${competitionId}/slides/${activeSlide.order}/questions`, { + name: 'Ny fråga', + total_score: 0, + type_id: selectedSlideType, + slide_id: activeSlide.id, + }) + .then(() => { + dispatch(getEditorCompetition(competitionId)) + }) + .catch(console.log) + } + } + } + return ( + <WhiteBackground> + <FormControlDropdown variant="outlined"> + <InputLabel>Sidtyp</InputLabel> + <Select value={activeSlide?.questions[0]?.type_id || 0} label="Sidtyp"> + <MenuItem value={0}> + <Typography variant="button" onClick={() => openSlideTypeDialog(0)}> + Informationssida + </Typography> + </MenuItem> + <MenuItem value={1}> + <Typography variant="button" onClick={() => openSlideTypeDialog(1)}> + Skriftlig fråga + </Typography> + </MenuItem> + <MenuItem value={2}> + <Typography variant="button" onClick={() => openSlideTypeDialog(2)}> + Praktisk fråga + </Typography> + </MenuItem> + <MenuItem value={3}> + <Typography variant="button" onClick={() => openSlideTypeDialog(3)}> + Flervalsfråga + </Typography> + </MenuItem> + </Select> + </FormControlDropdown> + <Dialog open={slideTypeDialog} onClose={closeSlideTypeDialog}> + <Center> + <DialogTitle color="secondary">Varning!</DialogTitle> + </Center> + <DialogContent> + <DialogContentText> + Om du ändrar sidtypen kommer eventuella frågeinställningar gå förlorade. Det inkluderar: frågans namn, poäng + och svarsalternativ.{' '} + </DialogContentText> + </DialogContent> + <DialogActions> + <Button onClick={closeSlideTypeDialog} color="secondary"> + Avbryt + </Button> + <Button onClick={updateSlideType} color="primary"> + Bekräfta + </Button> + </DialogActions> + </Dialog> + </WhiteBackground> + ) +} + +export default SlideType diff --git a/client/src/pages/presentationEditor/components/Timer.tsx b/client/src/pages/presentationEditor/components/Timer.tsx new file mode 100644 index 00000000..e5b622be --- /dev/null +++ b/client/src/pages/presentationEditor/components/Timer.tsx @@ -0,0 +1,50 @@ +import { ListItem, TextField } from '@material-ui/core' +import axios from 'axios' +import React, { useEffect, useState } from 'react' +import { getEditorCompetition } from '../../../actions/editor' +import { useAppDispatch } from '../../../hooks' +import { RichSlide } from '../../../interfaces/ApiRichModels' +import { WhiteBackground } from './styled' + +type TimerProps = { + activeSlide: RichSlide + competitionId: string +} + +const Timer = ({ activeSlide, competitionId }: TimerProps) => { + const dispatch = useAppDispatch() + const updateTimer = async (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => { + setTimer(+event.target.value) + if (activeSlide) { + await axios + .put(`/competitions/${competitionId}/slides/${activeSlide.order}`, { timer: event.target.value }) + .then(() => { + dispatch(getEditorCompetition(competitionId)) + }) + .catch(console.log) + } + } + const [timer, setTimer] = useState<number | undefined>(0) + useEffect(() => { + setTimer(activeSlide?.timer) + }, [activeSlide]) + return ( + <WhiteBackground> + <ListItem> + <TextField + id="standard-number" + variant="outlined" + placeholder="Antal sekunder" + helperText="Lämna blank för att inte använda timerfunktionen" + label="Timer" + type="number" + defaultValue={activeSlide?.timer || 0} + onChange={updateTimer} + value={timer} + /> + </ListItem> + </WhiteBackground> + ) +} + +export default Timer diff --git a/client/src/pages/presentationEditor/components/styled.tsx b/client/src/pages/presentationEditor/components/styled.tsx index 32df0ac9..bcb479f3 100644 --- a/client/src/pages/presentationEditor/components/styled.tsx +++ b/client/src/pages/presentationEditor/components/styled.tsx @@ -1,4 +1,4 @@ -import { Tab } from '@material-ui/core' +import { FormControl, List, ListItem, Tab, TextField, Typography } from '@material-ui/core' import styled from 'styled-components' export const SettingsTab = styled(Tab)` @@ -44,3 +44,61 @@ export const ToolbarPadding = styled.div` height: 0; padding-top: 55px; ` + +export const FormControlDropdown = styled(FormControl)` + ${({ theme }) => ` + margin: ${theme.spacing(2)}px; + width: 87%; + padding: 0; +`} +` + +export const TextInput = styled(TextField)` + ${({ theme }) => ` + margin: ${theme.spacing(2)}px; + width: 87%; +`} +` + +export const NoPadding = styled.div` + padding: 0; + height: 100%; + width: 100%; +` + +export const Center = styled.div` + display: flex; + justify-content: center; + text-align: center; + height: 100%; + width: 100%; +` + +export const SlidePanel = styled.div` + ${({ theme }) => ` + margin: ${theme.spacing(1)}px; + width: 100%; +`} +` + +export const WhiteBackground = styled.div` + background: white; +` + +export const AddButton = styled(Typography)` + padding: 5; +` + +export const Clickable = styled.div` + cursor: pointer; +` + +export const SettingsList = styled(List)` + padding-top: 5px; + padding-bottom: 5px; +` + +export const SettingsListItem = styled(ListItem)` + padding-top: 5px; + padding-bottom: 5px; +` -- GitLab From 5effacbb78cb2fd455fe026748235fb25eda3909 Mon Sep 17 00:00:00 2001 From: Emil <Emil> Date: Fri, 23 Apr 2021 23:21:01 +0200 Subject: [PATCH 17/17] fix: client test, alternative api calls --- .../components/Alternatives.tsx | 8 ++--- .../components/SlideType.tsx | 5 +-- .../presentationEditor/components/styled.tsx | 36 ++++++++++++------- 3 files changed, 31 insertions(+), 18 deletions(-) diff --git a/client/src/pages/presentationEditor/components/Alternatives.tsx b/client/src/pages/presentationEditor/components/Alternatives.tsx index c8b9a131..e699d003 100644 --- a/client/src/pages/presentationEditor/components/Alternatives.tsx +++ b/client/src/pages/presentationEditor/components/Alternatives.tsx @@ -42,7 +42,7 @@ const Alternatives = ({ activeSlide, competitionId }: AlternativeProps) => { } else newValue = 0 await axios .put( - `/competitions/${competitionId}/slides/${activeSlide?.id}/questions/${activeSlide?.questions[0].id}/alternatives/${alternative.id}`, + `/api/competitions/${competitionId}/slides/${activeSlide?.id}/questions/${activeSlide?.questions[0].id}/alternatives/${alternative.id}`, { value: newValue } ) .then(() => { @@ -56,7 +56,7 @@ const Alternatives = ({ activeSlide, competitionId }: AlternativeProps) => { if (activeSlide && activeSlide.questions[0]) { await axios .put( - `/competitions/${competitionId}/slides/${activeSlide?.id}/questions/${activeSlide?.questions[0].id}/alternatives/${alternative_id}`, + `/api/competitions/${competitionId}/slides/${activeSlide?.id}/questions/${activeSlide?.questions[0].id}/alternatives/${alternative_id}`, { text: newText } ) .then(() => { @@ -70,7 +70,7 @@ const Alternatives = ({ activeSlide, competitionId }: AlternativeProps) => { if (activeSlide && activeSlide.questions[0]) { await axios .post( - `/competitions/${competitionId}/slides/${activeSlide?.order}/questions/${activeSlide?.questions[0].id}/alternatives`, + `/api/competitions/${competitionId}/slides/${activeSlide?.id}/questions/${activeSlide?.questions[0].id}/alternatives`, { text: '', value: 0 } ) .then(() => { @@ -84,7 +84,7 @@ const Alternatives = ({ activeSlide, competitionId }: AlternativeProps) => { if (activeSlide && activeSlide.questions[0]) { await axios .delete( - `/competitions/${competitionId}/slides/${activeSlideId}/questions/${activeSlide?.questions[0].id}/alternatives/${alternative_id}` + `/api/competitions/${competitionId}/slides/${activeSlideId}/questions/${activeSlide?.questions[0].id}/alternatives/${alternative_id}` ) .then(() => { dispatch(getEditorCompetition(competitionId)) diff --git a/client/src/pages/presentationEditor/components/SlideType.tsx b/client/src/pages/presentationEditor/components/SlideType.tsx index e5b90420..ba104fe6 100644 --- a/client/src/pages/presentationEditor/components/SlideType.tsx +++ b/client/src/pages/presentationEditor/components/SlideType.tsx @@ -6,6 +6,7 @@ import { DialogContentText, DialogTitle, InputLabel, + ListItem, MenuItem, Select, Typography, @@ -15,7 +16,7 @@ import React, { useState } from 'react' import { getEditorCompetition } from '../../../actions/editor' import { useAppDispatch } from '../../../hooks' import { RichSlide } from '../../../interfaces/ApiRichModels' -import { Center, FormControlDropdown, WhiteBackground } from './styled' +import { Center, FormControlDropdown, SlideTypeInputLabel, WhiteBackground } from './styled' type SlideTypeProps = { activeSlide: RichSlide @@ -86,7 +87,7 @@ const SlideType = ({ activeSlide, competitionId }: SlideTypeProps) => { return ( <WhiteBackground> <FormControlDropdown variant="outlined"> - <InputLabel>Sidtyp</InputLabel> + <SlideTypeInputLabel>Sidtyp</SlideTypeInputLabel> <Select fullWidth={true} value={activeSlide?.questions[0]?.type_id || 0} label="Sidtyp"> <MenuItem value={0}> <Typography variant="button" onClick={() => openSlideTypeDialog(0)}> diff --git a/client/src/pages/presentationEditor/components/styled.tsx b/client/src/pages/presentationEditor/components/styled.tsx index 48735914..a636d9d2 100644 --- a/client/src/pages/presentationEditor/components/styled.tsx +++ b/client/src/pages/presentationEditor/components/styled.tsx @@ -1,4 +1,15 @@ -import { FormControl, List, Tab, TextField, Typography, Button, Card, ListItem } from '@material-ui/core' +import { + FormControl, + List, + Tab, + TextField, + Typography, + Button, + Card, + ListItem, + Select, + InputLabel, +} from '@material-ui/core' import styled from 'styled-components' export const SettingsTab = styled(Tab)` @@ -46,18 +57,21 @@ export const ToolbarPadding = styled.div` ` export const FormControlDropdown = styled(FormControl)` - ${({ theme }) => ` - margin: ${theme.spacing(2)}px; - width: 87%; - padding: 0; -`} + width: 100%; + margin-top: 10px; + padding: 8px; + padding-left: 16px; + padding-right: 16px; +` + +export const SlideTypeInputLabel = styled(InputLabel)` + width: 100%; + padding: 10px; + padding-left: 22px; ` export const TextInput = styled(TextField)` - ${({ theme }) => ` - margin: ${theme.spacing(2)}px; width: 87%; -`} ` export const NoPadding = styled.div` @@ -75,10 +89,8 @@ export const Center = styled.div` ` export const SlidePanel = styled.div` - ${({ theme }) => ` - margin: ${theme.spacing(1)}px; + padding: 10px; width: 100%; -`} ` export const WhiteBackground = styled.div` -- GitLab