From c1bb0cbc120af98ad48d745d2ab1009ccb1286bb Mon Sep 17 00:00:00 2001 From: Josef Olsson <josol381@student.liu.se> Date: Wed, 21 Apr 2021 09:11:41 +0000 Subject: [PATCH] Resolve "Duplicate slide and competition api" --- server/app/apis/competitions.py | 12 +++ server/app/apis/slides.py | 12 +++ server/app/database/controller/__init__.py | 2 +- server/app/database/controller/add.py | 24 ++++- server/app/database/controller/copy.py | 108 +++++++++++++++++++++ server/tests/test_app.py | 15 +++ server/tests/test_db.py | 76 ++++++++++++++- server/tests/test_helpers.py | 16 +-- 8 files changed, 252 insertions(+), 13 deletions(-) create mode 100644 server/app/database/controller/copy.py diff --git a/server/app/apis/competitions.py b/server/app/apis/competitions.py index db2ca68a..a18ed85e 100644 --- a/server/app/apis/competitions.py +++ b/server/app/apis/competitions.py @@ -61,3 +61,15 @@ class CompetitionSearch(Resource): args = competition_search_parser.parse_args(strict=True) items, total = dbc.search.competition(**args) return list_response(list_schema.dump(items), total) + + +@api.route("/<CID>/copy") +@api.param("CID") +class SlidesOrder(Resource): + @check_jwt(editor=True) + def post(self, CID): + item_competition = dbc.get.competition(CID) + + item_competition_copy = dbc.copy.competition(item_competition) + + return item_response(schema.dump(item_competition_copy)) diff --git a/server/app/apis/slides.py b/server/app/apis/slides.py index 02d0d3d6..3c25f901 100644 --- a/server/app/apis/slides.py +++ b/server/app/apis/slides.py @@ -83,3 +83,15 @@ class SlidesOrder(Resource): item_slide = dbc.edit.switch_order(item_slide, item_slide_order) return item_response(schema.dump(item_slide)) + + +@api.route("/<SOrder>/copy") +@api.param("CID,SOrder") +class SlidesOrder(Resource): + @check_jwt(editor=True) + def post(self, CID, SOrder): + item_slide = dbc.get.slide(CID, SOrder) + + item_slide_copy = dbc.copy.slide(item_slide) + + return item_response(schema.dump(item_slide_copy)) diff --git a/server/app/database/controller/__init__.py b/server/app/database/controller/__init__.py index 2865b523..a46a65f1 100644 --- a/server/app/database/controller/__init__.py +++ b/server/app/database/controller/__init__.py @@ -1,3 +1,3 @@ # import add, get from app.core import db -from app.database.controller import add, delete, edit, get, search, utils +from app.database.controller import add, copy, delete, edit, get, search, utils diff --git a/server/app/database/controller/add.py b/server/app/database/controller/add.py index de0135f2..be7a773e 100644 --- a/server/app/database/controller/add.py +++ b/server/app/database/controller/add.py @@ -140,20 +140,36 @@ def slide(item_competition): def competition(name, year, city_id): - """ Adds a competition to the database using the provided arguments. """ + """ + 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 slide(item_competition) + # TODO: Add two teams + + return item_competition + + +def _competition(name, year, city_id, font=None): + """ + Internal function. Adds a competition to the database + using the provided arguments. Also adds codes. + """ + + item_competition = db_add(Competition(name, year, city_id)) + if font: + item_competition.font = font + # Add code for Judge view code(item_competition.id, 2) # Add code for Audience view code(item_competition.id, 3) - # TODO: Add two teams - utils.refresh(item_competition) return item_competition diff --git a/server/app/database/controller/copy.py b/server/app/database/controller/copy.py new file mode 100644 index 00000000..dabd0d78 --- /dev/null +++ b/server/app/database/controller/copy.py @@ -0,0 +1,108 @@ +""" +This file contains functionality to copy and duplicate data to the database. +""" + +from app.database.controller import add, get, search, utils +from app.database.models import Question + + +def _question(item_question_old, slide_id): + """ + Internal function. Makes a copy of the provided question item to the + specified slide. Does not copy team, question answers or alternatives. + """ + + item_question_new = add.db_add( + Question( + item_question_old.name, + item_question_old.total_score, + item_question_old.type_id, + slide_id, + ) + ) + + # TODO: Add question alternatives + # for item_alternatives in item_question_old.alternatives: + # dbc.add.alternatives() + + return item_question_new + + +def _component(item_component, item_slide_new): + """ + Internal function. Makes a copy of the provided + component item to the specified slide. + """ + + add.component( + item_component.type_id, + item_slide_new, + item_component.data, + item_component.x, + item_component.y, + item_component.w, + item_component.h, + ) + + +def slide(item_slide_old): + """ + Deep copies a slide to the same competition. + Does not copy team, question answers or alternatives. + """ + + item_competition = get.competition(item_slide_old.competition_id) + + return slide_to_competition(item_slide_old, item_competition) + + +def slide_to_competition(item_slide_old, item_competition): + """ + Deep copies a slide to the provided competition. + Does not copy team, question answers or alternatives. + """ + + item_slide_new = add.slide(item_competition) + + # Copy all fields + item_slide_new.title = item_slide_old.title + item_slide_new.body = item_slide_old.body + item_slide_new.timer = item_slide_old.timer + item_slide_new.settings = item_slide_old.settings + + # TODO: Add background image + + for item_component in item_slide_old.components: + _component(item_component, item_slide_new) + + for item_question in item_slide_old.questions: + _question(item_question, item_slide_new.id) + + utils.commit_and_refresh(item_slide_new) + return item_slide_new + + +def competition(item_competition_old): + """ + Adds a deep-copy of the provided competition. + Will not copy teams, question answers or alternatives. + """ + + name = "Kopia av " + item_competition_old.name + item_competition, total = search.competition(name=name) + if item_competition: + print(f"{item_competition[total-1].name}, {total=}") + name = "Kopia av " + item_competition[total - 1].name + + item_competition_new = add._competition( + name, + item_competition_old.year, + item_competition_old.city_id, + item_competition_old.font, + ) + # TODO: Add background image + + for item_slide in item_competition_old.slides: + slide_to_competition(item_slide, item_competition_new) + + return item_competition_new diff --git a/server/tests/test_app.py b/server/tests/test_app.py index 467fd059..cc50d4fd 100644 --- a/server/tests/test_app.py +++ b/server/tests/test_app.py @@ -98,6 +98,16 @@ def test_competition_api(client): response, body = delete(client, f"/api/competitions/{competition_id}", headers=headers) assert response.status_code == codes.OK + # Get competition + competition_id = 2 + response, body = get(client, f"/api/competitions/{competition_id}", headers=headers) + assert response.status_code == codes.OK + + # Copies competition + for _ in range(10): + response, _ = post(client, f"/api/competitions/{competition_id}/copy", headers=headers) + assert response.status_code == codes.OK + def test_auth_and_user_api(client): add_default_values() @@ -301,6 +311,11 @@ def test_slide_api(client): # Changes the order change_order_test(client, CID, slide_order, slide_order + 1, headers) + # Copies slide + for _ in range(10): + response, _ = post(client, f"/api/competitions/{CID}/slides/{slide_order}/copy", headers=headers) + assert response.status_code == codes.OK + def test_question_api(client): add_default_values() diff --git a/server/tests/test_db.py b/server/tests/test_db.py index 3bb998ae..7d162065 100644 --- a/server/tests/test_db.py +++ b/server/tests/test_db.py @@ -1,5 +1,5 @@ import app.database.controller as dbc -from app.database.models import City, Competition, Media, MediaType, Question, QuestionType, Role, Slide, Team, User +from app.database.models import City, Media, MediaType, Role, User from tests import app, client, db from tests.test_helpers import add_default_values, assert_exists, assert_insert_fail @@ -40,6 +40,80 @@ def test_media(client): assert item_media.upload_by.email == "test@test.se" +def test_copy(client): + add_default_values() + + # Fetches an empty competition + list_item_competitions, _ = dbc.search.competition(name="Tävling 1") + item_competition_original = list_item_competitions[0] + + # Fetches the first slide in that competition + num_slides = 3 + item_slides, total = dbc.search.slide(competition_id=item_competition_original.id) + assert total == num_slides + item_slide_original = item_slides[0] + + # Inserts several copies of the same slide + num_copies = 10 + for _ in range(num_copies): + item_slide_copy = dbc.copy.slide(item_slide_original) + num_slides += 1 + check_slides_copy(item_slide_original, item_slide_copy, num_slides, num_slides - 1) + assert item_slide_copy.competition_id == item_slide_original.competition_id + + # Copies competition + num_copies = 3 + for _ in range(num_copies): + item_competition_copy = dbc.copy.competition(item_competition_original) + for order, item_slide in enumerate(item_competition_copy.slides): + item_slide_original = item_competition_original.slides[order] + check_slides_copy(item_slide_original, item_slide, num_slides, order) + assert item_slide.competition_id != item_slide_original.competition_id + + +def check_slides_copy(item_slide_original, item_slide_copy, num_slides, order): + """ Checks that two slides are correct copies of each other. Looks big but is quite fast. """ + assert item_slide_copy.order == order # 0 indexing + assert item_slide_copy.title == item_slide_original.title + assert item_slide_copy.body == item_slide_original.body + assert item_slide_copy.timer == item_slide_original.timer + assert item_slide_copy.settings == item_slide_original.settings + + # Checks that all components were correctly copied + assert len(item_slide_copy.components) == len(item_slide_original.components) + for i, c1 in enumerate(item_slide_original.components): + c2 = item_slide_copy.components[i] + assert c1 != c2 + assert c1.x == c2.x + assert c1.y == c2.y + assert c1.w == c2.w + assert c1.h == c2.h + assert c1.data == c2.data + assert c1.slide_id == item_slide_original.id + assert c2.slide_id == item_slide_copy.id + assert c1.type_id == c2.type_id + + # Checks that all questions were correctly copied + assert len(item_slide_copy.questions) == len(item_slide_original.questions) + for i, q1 in enumerate(item_slide_original.questions): + q2 = item_slide_copy.questions[i] + assert q1 != q2 + assert q1.name == q2.name + assert q1.total_score == q2.total_score + assert q1.type_id == q2.type_id + assert q1.slide_id == item_slide_original.id + assert q2.slide_id == item_slide_copy.id + # TODO: Assert alternatives + + # Checks that the copy put the slide in the database + item_slides, total = dbc.search.slide( + competition_id=item_slide_copy.competition_id, + # page_size=num_slides + 1, # Use this total > 15 + ) + assert total == num_slides + assert item_slide_copy == item_slides[order] + + """ def test_question(client): add_default_values() diff --git a/server/tests/test_helpers.py b/server/tests/test_helpers.py index fbd77d9e..7a68655b 100644 --- a/server/tests/test_helpers.py +++ b/server/tests/test_helpers.py @@ -47,17 +47,19 @@ def add_default_values(): dbc.add.slide(item_comp) # Add slides - i = 1 - for item_slide in item_comp.slides: + for i, item_slide in enumerate(item_comp.slides): # Populate slide with data - item_slide.title = f"Title {i}" - item_slide.body = f"Body {i}" - item_slide.timer = 100 + i + item_slide.title = f"Title {i+1}" + item_slide.body = f"Body {i+1}" + item_slide.timer = 100 + i + 1 # item_slide.settings = "{}" dbc.utils.commit_and_refresh(item_slide) + # Add question to competition - dbc.add.question(name=f"Q{i}", total_score=i, type_id=1, item_slide=item_slide) - i += 1 + dbc.add.question(name=f"Q{i+1}", total_score=i + 1, type_id=1, item_slide=item_slide) + + # Add text component + dbc.add.component(1, item_slide, {"text": "Text"}, i, 2 * i, 3 * i, 4 * i) def get_body(response): -- GitLab