From 0e07d10f7c4f7a8fb666ad87bfd8893826d4d956 Mon Sep 17 00:00:00 2001
From: Josef Olsson <josol381@student.liu.se>
Date: Tue, 20 Apr 2021 22:52:32 +0200
Subject: [PATCH 1/5] Add functionality to copy a slide

---
 server/app/database/controller/copy.py | 75 ++++++++++++++++++++++++++
 1 file changed, 75 insertions(+)
 create mode 100644 server/app/database/controller/copy.py

diff --git a/server/app/database/controller/copy.py b/server/app/database/controller/copy.py
new file mode 100644
index 00000000..ef6f0ecf
--- /dev/null
+++ b/server/app/database/controller/copy.py
@@ -0,0 +1,75 @@
+"""
+This file contains functionality to copy and duplicate data to the database.
+"""
+
+from app.database.controller import add, utils
+from app.database.models import Question, Slide
+from flask_sqlalchemy import utils
+
+
+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.
+    """
+
+    order = Slide.query.filter(
+        Slide.competition_id == item_slide_old.item_competition.id
+    ).count()  # first element has index 0
+    item_slide_new = add.db_add(Slide(order, item_slide_old.item_competition.id))
+
+    # 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_slide_new, item_component)
+
+    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
-- 
GitLab


From dcfe6b290d6396ec519e6b485faec2b048baa289 Mon Sep 17 00:00:00 2001
From: Josef Olsson <josol381@student.liu.se>
Date: Wed, 21 Apr 2021 00:16:09 +0200
Subject: [PATCH 2/5] Test copying a slide and all sub items

---
 server/app/database/controller/__init__.py |  2 +-
 server/app/database/controller/copy.py     |  7 +--
 server/tests/test_db.py                    | 69 +++++++++++++++++++++-
 server/tests/test_helpers.py               | 16 ++---
 4 files changed, 81 insertions(+), 13 deletions(-)

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/copy.py b/server/app/database/controller/copy.py
index ef6f0ecf..de4f8c1e 100644
--- a/server/app/database/controller/copy.py
+++ b/server/app/database/controller/copy.py
@@ -4,7 +4,6 @@ This file contains functionality to copy and duplicate data to the database.
 
 from app.database.controller import add, utils
 from app.database.models import Question, Slide
-from flask_sqlalchemy import utils
 
 
 def _question(item_question_old, slide_id):
@@ -53,9 +52,9 @@ def slide(item_slide_old):
     """
 
     order = Slide.query.filter(
-        Slide.competition_id == item_slide_old.item_competition.id
+        Slide.competition_id == item_slide_old.competition_id
     ).count()  # first element has index 0
-    item_slide_new = add.db_add(Slide(order, item_slide_old.item_competition.id))
+    item_slide_new = add.db_add(Slide(order, item_slide_old.competition_id))
 
     # Copy all fields
     item_slide_new.title = item_slide_old.title
@@ -66,7 +65,7 @@ def slide(item_slide_old):
     # TODO: Add background image
 
     for item_component in item_slide_old.components:
-        _component(item_slide_new, item_component)
+        _component(item_component, item_slide_new)
 
     for item_question in item_slide_old.questions:
         _question(item_question, item_slide_new.id)
diff --git a/server/tests/test_db.py b/server/tests/test_db.py
index 3bb998ae..3d68bc3e 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,73 @@ 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 = list_item_competitions[0]
+
+    # Fetches the first slide in that competition
+    num_slides = 3
+    item_slides, total = dbc.search.slide(competition_id=item_competition.id)
+    assert total == 3
+    item_slide_original = item_slides[0]
+
+    # Copies the slide and double checks the results
+    item_slide_copy = dbc.copy.slide(item_slide_original)
+    num_slides += 1
+    assert item_slide_copy.order == num_slides - 1  # 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
+    assert item_slide_copy.competition_id == item_slide_original.competition_id
+
+    # 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_competition.id)
+    assert total == num_slides
+    assert item_slide_copy == item_slides[num_slides - 1]
+
+    # Inserts several copies of the same slide
+    num_copies = 10
+    for i in range(num_copies):
+        # The order must be correct
+        item_slide_original = dbc.copy.slide(item_slide_original)
+        assert item_slide_original.order == i + num_slides
+
+    # Checks that the last slide is correct
+    item_slides, total = dbc.search.slide(competition_id=item_competition.id)
+    assert total == num_copies + num_slides
+    assert item_slide_original == item_slides[num_copies + num_slides - 1]
+
+
 """
 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


From 89ea34eba303e0663760ef62c625cb04803414e9 Mon Sep 17 00:00:00 2001
From: Josef Olsson <josol381@student.liu.se>
Date: Wed, 21 Apr 2021 09:37:25 +0200
Subject: [PATCH 3/5] Copy and test competition

---
 server/app/database/controller/add.py  | 24 ++++++++++--
 server/app/database/controller/copy.py | 46 ++++++++++++++++++++---
 server/tests/test_db.py                | 51 +++++++++++++++-----------
 3 files changed, 89 insertions(+), 32 deletions(-)

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
index de4f8c1e..dabd0d78 100644
--- a/server/app/database/controller/copy.py
+++ b/server/app/database/controller/copy.py
@@ -2,8 +2,8 @@
 This file contains functionality to copy and duplicate data to the database.
 """
 
-from app.database.controller import add, utils
-from app.database.models import Question, Slide
+from app.database.controller import add, get, search, utils
+from app.database.models import Question
 
 
 def _question(item_question_old, slide_id):
@@ -51,10 +51,18 @@ def slide(item_slide_old):
     Does not copy team, question answers or alternatives.
     """
 
-    order = Slide.query.filter(
-        Slide.competition_id == item_slide_old.competition_id
-    ).count()  # first element has index 0
-    item_slide_new = add.db_add(Slide(order, item_slide_old.competition_id))
+    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
@@ -72,3 +80,29 @@ def slide(item_slide_old):
 
     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_db.py b/server/tests/test_db.py
index 3d68bc3e..7d162065 100644
--- a/server/tests/test_db.py
+++ b/server/tests/test_db.py
@@ -45,23 +45,39 @@ def test_copy(client):
 
     # Fetches an empty competition
     list_item_competitions, _ = dbc.search.competition(name="Tävling 1")
-    item_competition = list_item_competitions[0]
+    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.id)
-    assert total == 3
+    item_slides, total = dbc.search.slide(competition_id=item_competition_original.id)
+    assert total == num_slides
     item_slide_original = item_slides[0]
 
-    # Copies the slide and double checks the results
-    item_slide_copy = dbc.copy.slide(item_slide_original)
-    num_slides += 1
-    assert item_slide_copy.order == num_slides - 1  # 0 indexing
+    # 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
-    assert item_slide_copy.competition_id == item_slide_original.competition_id
 
     # Checks that all components were correctly copied
     assert len(item_slide_copy.components) == len(item_slide_original.components)
@@ -90,21 +106,12 @@ def test_copy(client):
         # TODO: Assert alternatives
 
     # Checks that the copy put the slide in the database
-    item_slides, total = dbc.search.slide(competition_id=item_competition.id)
+    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[num_slides - 1]
-
-    # Inserts several copies of the same slide
-    num_copies = 10
-    for i in range(num_copies):
-        # The order must be correct
-        item_slide_original = dbc.copy.slide(item_slide_original)
-        assert item_slide_original.order == i + num_slides
-
-    # Checks that the last slide is correct
-    item_slides, total = dbc.search.slide(competition_id=item_competition.id)
-    assert total == num_copies + num_slides
-    assert item_slide_original == item_slides[num_copies + num_slides - 1]
+    assert item_slide_copy == item_slides[order]
 
 
 """
-- 
GitLab


From 8e371b1b48f13a94ea593c2a1b729376c5875318 Mon Sep 17 00:00:00 2001
From: Josef Olsson <josol381@student.liu.se>
Date: Wed, 21 Apr 2021 10:00:56 +0200
Subject: [PATCH 4/5] Add copy slide api and test

---
 server/app/apis/slides.py | 12 ++++++++++++
 server/tests/test_app.py  |  5 +++++
 2 files changed, 17 insertions(+)

diff --git a/server/app/apis/slides.py b/server/app/apis/slides.py
index 02d0d3d6..3d9ce6fe 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 put(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/tests/test_app.py b/server/tests/test_app.py
index 467fd059..aa1758f9 100644
--- a/server/tests/test_app.py
+++ b/server/tests/test_app.py
@@ -301,6 +301,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, _ = put(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()
-- 
GitLab


From 0d6070d2c992877acb88904d5b4098d7b85bd7f7 Mon Sep 17 00:00:00 2001
From: Josef Olsson <josol381@student.liu.se>
Date: Wed, 21 Apr 2021 10:13:19 +0200
Subject: [PATCH 5/5] Add copy competition api and test

---
 server/app/apis/competitions.py | 12 ++++++++++++
 server/app/apis/slides.py       |  2 +-
 server/tests/test_app.py        | 12 +++++++++++-
 3 files changed, 24 insertions(+), 2 deletions(-)

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 3d9ce6fe..3c25f901 100644
--- a/server/app/apis/slides.py
+++ b/server/app/apis/slides.py
@@ -89,7 +89,7 @@ class SlidesOrder(Resource):
 @api.param("CID,SOrder")
 class SlidesOrder(Resource):
     @check_jwt(editor=True)
-    def put(self, CID, SOrder):
+    def post(self, CID, SOrder):
         item_slide = dbc.get.slide(CID, SOrder)
 
         item_slide_copy = dbc.copy.slide(item_slide)
diff --git a/server/tests/test_app.py b/server/tests/test_app.py
index aa1758f9..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()
@@ -303,7 +313,7 @@ def test_slide_api(client):
 
     # Copies slide
     for _ in range(10):
-        response, _ = put(client, f"/api/competitions/{CID}/slides/{slide_order}/copy", headers=headers)
+        response, _ = post(client, f"/api/competitions/{CID}/slides/{slide_order}/copy", headers=headers)
         assert response.status_code == codes.OK
 
 
-- 
GitLab