From 1c34ae3c319154b3aa990be053c61353c2b50b33 Mon Sep 17 00:00:00 2001 From: Timo Ludwig Date: Thu, 9 Jun 2022 22:21:32 +0200 Subject: [PATCH] Add database constraints to enforce unique version numbers --- .../0031_unique_version_constraint.py | 44 ++++++++++++ .../cms/models/events/event_translation.py | 7 ++ .../models/pages/imprint_page_translation.py | 7 ++ .../cms/models/pages/page_translation.py | 7 ++ .../cms/models/pois/poi_translation.py | 7 ++ integreat_cms/locale/de/LC_MESSAGES/django.po | 2 +- tests/xliff/xliff_test.py | 69 +++++++++++-------- 7 files changed, 114 insertions(+), 29 deletions(-) create mode 100644 integreat_cms/cms/migrations/0031_unique_version_constraint.py diff --git a/integreat_cms/cms/migrations/0031_unique_version_constraint.py b/integreat_cms/cms/migrations/0031_unique_version_constraint.py new file mode 100644 index 0000000000..c1695bb9e0 --- /dev/null +++ b/integreat_cms/cms/migrations/0031_unique_version_constraint.py @@ -0,0 +1,44 @@ +# Generated by Django 3.2.13 on 2022-06-09 20:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + """ + Ensure version numbers are unique for all content objects in each language. + """ + + dependencies = [ + ("cms", "0030_alter_region_aliases"), + ] + + operations = [ + migrations.AddConstraint( + model_name="eventtranslation", + constraint=models.UniqueConstraint( + fields=("event", "language", "version"), + name="eventtranslation_unique_version", + ), + ), + migrations.AddConstraint( + model_name="imprintpagetranslation", + constraint=models.UniqueConstraint( + fields=("page", "language", "version"), + name="imprintpagetranslation_unique_version", + ), + ), + migrations.AddConstraint( + model_name="pagetranslation", + constraint=models.UniqueConstraint( + fields=("page", "language", "version"), + name="pagetranslation_unique_version", + ), + ), + migrations.AddConstraint( + model_name="poitranslation", + constraint=models.UniqueConstraint( + fields=("poi", "language", "version"), + name="poitranslation_unique_version", + ), + ), + ] diff --git a/integreat_cms/cms/models/events/event_translation.py b/integreat_cms/cms/models/events/event_translation.py index fa533e85f1..7e3ca9895d 100644 --- a/integreat_cms/cms/models/events/event_translation.py +++ b/integreat_cms/cms/models/events/event_translation.py @@ -91,3 +91,10 @@ class Meta: default_permissions = () #: The fields which are used to sort the returned objects of a QuerySet ordering = ["event__pk", "language__pk", "-version"] + #: A list of database constraints for this model + constraints = [ + models.UniqueConstraint( + fields=["event", "language", "version"], + name="%(class)s_unique_version", + ), + ] diff --git a/integreat_cms/cms/models/pages/imprint_page_translation.py b/integreat_cms/cms/models/pages/imprint_page_translation.py index 530f4ea9ba..8fba832bd5 100644 --- a/integreat_cms/cms/models/pages/imprint_page_translation.py +++ b/integreat_cms/cms/models/pages/imprint_page_translation.py @@ -93,3 +93,10 @@ class Meta: default_permissions = () #: The fields which are used to sort the returned objects of a QuerySet ordering = ["page__pk", "language__pk", "-version"] + #: A list of database constraints for this model + constraints = [ + models.UniqueConstraint( + fields=["page", "language", "version"], + name="%(class)s_unique_version", + ), + ] diff --git a/integreat_cms/cms/models/pages/page_translation.py b/integreat_cms/cms/models/pages/page_translation.py index e797c653db..8f567e4f26 100644 --- a/integreat_cms/cms/models/pages/page_translation.py +++ b/integreat_cms/cms/models/pages/page_translation.py @@ -338,3 +338,10 @@ class Meta: default_permissions = () #: The fields which are used to sort the returned objects of a QuerySet ordering = ["page__pk", "language__pk", "-version"] + #: A list of database constraints for this model + constraints = [ + models.UniqueConstraint( + fields=["page", "language", "version"], + name="%(class)s_unique_version", + ), + ] diff --git a/integreat_cms/cms/models/pois/poi_translation.py b/integreat_cms/cms/models/pois/poi_translation.py index aa22a46829..69ca7b9084 100644 --- a/integreat_cms/cms/models/pois/poi_translation.py +++ b/integreat_cms/cms/models/pois/poi_translation.py @@ -93,3 +93,10 @@ class Meta: default_permissions = () #: The fields which are used to sort the returned objects of a QuerySet ordering = ["poi__pk", "language__pk", "-version"] + #: A list of database constraints for this model + constraints = [ + models.UniqueConstraint( + fields=["poi", "language", "version"], + name="%(class)s_unique_version", + ), + ] diff --git a/integreat_cms/locale/de/LC_MESSAGES/django.po b/integreat_cms/locale/de/LC_MESSAGES/django.po index dcfe90a069..13f50a3e87 100644 --- a/integreat_cms/locale/de/LC_MESSAGES/django.po +++ b/integreat_cms/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: 1.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-06-08 20:12+0000\n" +"POT-Creation-Date: 2022-06-09 22:40+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: Integreat \n" "Language-Team: Integreat \n" diff --git a/tests/xliff/xliff_test.py b/tests/xliff/xliff_test.py index 6d5d9b4f92..a0968e12c6 100644 --- a/tests/xliff/xliff_test.py +++ b/tests/xliff/xliff_test.py @@ -138,31 +138,42 @@ def test_xliff_import(login_role_user, settings): import_path = "tests/xliff/files/import" file_1 = "augsburg_de_en_1_2_willkommen.xliff" file_2 = "augsburg_de_en_2_1_willkommen-in-augsburg.xliff" + # Upload once for successful import with open(f"{import_path}/{file_1}", encoding="utf-8") as f1: with open(f"{import_path}/{file_2}", encoding="utf-8") as f2: - response = client.post( + response1 = client.post( upload_xliff, data={"xliff_file": [f1, f2]}, format="multipart" ) - print(response.headers) + print(response1.headers) + # Upload second time to test unchanged import + with open(f"{import_path}/{file_1}", encoding="utf-8") as f1: + with open(f"{import_path}/{file_2}", encoding="utf-8") as f2: + response2 = client.post( + upload_xliff, data={"xliff_file": [f1, f2]}, format="multipart" + ) + print(response2.headers) if role in PRIV_STAFF_ROLES + [MANAGEMENT, EDITOR, AUTHOR]: - # If the role should be allowed to access the view, we expect a successful result - assert response.status_code == 302 - page_tree = reverse( - "pages", kwargs={"region_slug": "augsburg", "language_slug": "en"} - ) - redirect_location = response.headers.get("Location") - # If errors occur, we get redirected to the page tree - assert redirect_location != page_tree - # Check if xliff import view is correctly rendered - response = client.get(redirect_location) - print(response.headers) - assert response.status_code == 200 - assert file_1 in response.content.decode("utf-8") - assert file_2 in response.content.decode("utf-8") - # Check if XLIFF import is correctly confirmed - # (perform test twice to check whether unchanged diffs can be imported as well) - for msg in ["successfully", "without changes"]: + # Perform test twice to check whether unchanged diffs can be imported as well + for response, msg in [ + (response1, "successfully"), + (response2, "without changes"), + ]: + # If the role should be allowed to access the view, we expect a successful result + assert response.status_code == 302 + page_tree = reverse( + "pages", kwargs={"region_slug": "augsburg", "language_slug": "en"} + ) + redirect_location = response.headers.get("Location") + # If errors occur, we get redirected to the page tree + assert redirect_location != page_tree + # Check if xliff import view is correctly rendered + response = client.get(redirect_location) + print(response.headers) + assert response.status_code == 200 + assert file_1 in response.content.decode("utf-8") + assert file_2 in response.content.decode("utf-8") + # Check if XLIFF import is correctly confirmed response = client.post(redirect_location) print(response.headers) assert response.status_code == 302 @@ -183,7 +194,7 @@ def test_xliff_import(login_role_user, settings): in response.content.decode("utf-8") ) if translation.version > 1: - # If a translation already exists for this version, asser that the status is inherited + # If a translation already exists for this version, assert that the status is inherited previous_translation = page.translations.get( language__slug="en", version=translation.version - 1 ) @@ -192,12 +203,14 @@ def test_xliff_import(login_role_user, settings): # Else, the status should be inherited from the source translation assert translation.source_translation.status == translation.status elif role == ANONYMOUS: - # For anonymous users, we want to redirect to the login form instead of showing an error - assert response.status_code == 302 - assert ( - response.headers.get("location") - == f"{settings.LOGIN_URL}?next={upload_xliff}" - ) + for response in [response1, response2]: + # For anonymous users, we want to redirect to the login form instead of showing an error + assert response.status_code == 302 + assert ( + response.headers.get("location") + == f"{settings.LOGIN_URL}?next={upload_xliff}" + ) else: - # For logged in users, we want to show an error if they get a permission denied - assert response.status_code == 403 + for response in [response1, response2]: + # For logged in users, we want to show an error if they get a permission denied + assert response.status_code == 403