diff --git a/migrations/versions/35a6577b6af8_.py b/migrations/versions/35a6577b6af8_.py new file mode 100644 index 0000000..89886f9 --- /dev/null +++ b/migrations/versions/35a6577b6af8_.py @@ -0,0 +1,46 @@ +"""empty message + +Revision ID: 35a6577b6af8 +Revises: a0a248667cd8 +Create Date: 2021-01-25 10:37:41.116909 + +""" +from alembic import op +import sqlalchemy as sa +import sqlalchemy_utils +from project import dbtypes + + +# revision identifiers, used by Alembic. +revision = "35a6577b6af8" +down_revision = "a0a248667cd8" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_unique_constraint( + "eventreference_event_id_admin_unit_id", + "eventreference", + ["event_id", "admin_unit_id"], + ) + op.create_unique_constraint( + "eventreferencerequest_event_id_admin_unit_id", + "eventreferencerequest", + ["event_id", "admin_unit_id"], + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint( + "eventreferencerequest_event_id_admin_unit_id", + "eventreferencerequest", + type_="unique", + ) + op.drop_constraint( + "eventreference_event_id_admin_unit_id", "eventreference", type_="unique" + ) + # ### end Alembic commands ### diff --git a/project/models.py b/project/models.py index 384a1cd..9a5660d 100644 --- a/project/models.py +++ b/project/models.py @@ -373,6 +373,11 @@ def purge_event_organizer(mapper, connect, self): class EventReference(db.Model, TrackableMixin): __tablename__ = "eventreference" + __table_args__ = ( + UniqueConstraint( + "event_id", "admin_unit_id", name="eventreference_event_id_admin_unit_id" + ), + ) id = Column(Integer(), primary_key=True) event_id = db.Column(db.Integer, db.ForeignKey("event.id"), nullable=False) admin_unit_id = db.Column(db.Integer, db.ForeignKey("adminunit.id"), nullable=False) @@ -384,6 +389,13 @@ class EventReference(db.Model, TrackableMixin): class EventReferenceRequest(db.Model, TrackableMixin): __tablename__ = "eventreferencerequest" + __table_args__ = ( + UniqueConstraint( + "event_id", + "admin_unit_id", + name="eventreferencerequest_event_id_admin_unit_id", + ), + ) id = Column(Integer(), primary_key=True) event_id = db.Column(db.Integer, db.ForeignKey("event.id"), nullable=False) admin_unit_id = db.Column(db.Integer, db.ForeignKey("adminunit.id"), nullable=False) @@ -431,7 +443,9 @@ class EventMixin(object): class EventSuggestion(db.Model, TrackableMixin, EventMixin): __tablename__ = "eventsuggestion" __table_args__ = ( - CheckConstraint("NOT(event_place_id IS NULL AND event_place_text IS NULL)"), + CheckConstraint( + "NOT(event_place_id IS NULL AND event_place_text IS NULL)", + ), CheckConstraint("NOT(organizer_id IS NULL AND organizer_text IS NULL)"), ) id = Column(Integer(), primary_key=True) diff --git a/project/translations/de/LC_MESSAGES/messages.mo b/project/translations/de/LC_MESSAGES/messages.mo index 8679638..77be93a 100644 Binary files a/project/translations/de/LC_MESSAGES/messages.mo and b/project/translations/de/LC_MESSAGES/messages.mo differ diff --git a/project/translations/de/LC_MESSAGES/messages.po b/project/translations/de/LC_MESSAGES/messages.po index 992dc0d..298f193 100644 --- a/project/translations/de/LC_MESSAGES/messages.po +++ b/project/translations/de/LC_MESSAGES/messages.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2021-01-25 09:17+0100\n" +"POT-Creation-Date: 2021-01-25 10:17+0100\n" "PO-Revision-Date: 2020-06-07 18:51+0200\n" "Last-Translator: FULL NAME \n" "Language: de\n" @@ -1634,12 +1634,18 @@ msgstr "Empfehlung erfolgreich erstellt" msgid "Request successfully updated" msgstr "Empfehlungsanfrage erfolgreich aktualisiert" -#: project/views/utils.py:53 +#: project/views/utils.py:30 +msgid "" +"An entry with the entered values ​​already exists. Duplicate entries are " +"not allowed." +msgstr "Ein Eintrag mit den eingegebenen Werten existiert bereits. Doppelte Einträge sind nicht erlaubt." + +#: project/views/utils.py:61 #, python-format msgid "Error in the %s field - %s" msgstr "Fehler im Feld %s: %s" -#: project/views/utils.py:61 +#: project/views/utils.py:69 msgid "Show" msgstr "Anzeigen" diff --git a/project/views/utils.py b/project/views/utils.py index 733f69d..4d24c06 100644 --- a/project/views/utils.py +++ b/project/views/utils.py @@ -4,6 +4,7 @@ from flask_babelex import gettext from flask import request, url_for, render_template, flash, redirect, Markup from flask_mail import Message from sqlalchemy.exc import SQLAlchemyError +from psycopg2.errorcodes import UNIQUE_VIOLATION def track_analytics(key, value1, value2): @@ -19,12 +20,21 @@ def track_analytics(key, value1, value2): def handleSqlError(e: SQLAlchemyError) -> str: - if e.orig: - message = str(e.orig) - else: - message = str(e) - print(message) - return message + if not e.orig: + return str(e) + + prefix = None + message = str(e.orig) + + if e.orig.pgcode == UNIQUE_VIOLATION: + prefix = gettext( + "An entry with the entered values ​​already exists. Duplicate entries are not allowed." + ) + + if not prefix: + return message + + return "%s (%s)" % (prefix, message) def get_pagination_urls(pagination, **kwargs): diff --git a/tests/utils.py b/tests/utils.py index 51983e7..e405712 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -86,10 +86,10 @@ class UtilActions(object): assert response.content_type == "application/json" return response.json - def mock_db_commit(self, mocker): + def mock_db_commit(self, mocker, orig=None): mocked_commit = mocker.patch("project.db.session.commit") mocked_commit.side_effect = IntegrityError( - "MockException", "MockException", None + "MockException", "MockException", orig ) def mock_send_mails(self, mocker): diff --git a/tests/views/test_event.py b/tests/views/test_event.py index 0828982..ddaaa93 100644 --- a/tests/views/test_event.py +++ b/tests/views/test_event.py @@ -1,4 +1,5 @@ import pytest +from psycopg2.errors import UniqueViolation def test_read(client, seeder, utils): @@ -19,7 +20,7 @@ def test_create(client, app, utils, seeder, mocker, db_error): response = utils.get_ok(url) if db_error: - utils.mock_db_commit(mocker) + utils.mock_db_commit(mocker, UniqueViolation("MockException", "MockException")) response = utils.post_form( url, diff --git a/tests/views/test_reference.py b/tests/views/test_reference.py index 7e7e833..2f267e8 100644 --- a/tests/views/test_reference.py +++ b/tests/views/test_reference.py @@ -49,6 +49,27 @@ def test_create(client, app, utils, seeder, mocker, db_error): assert reference is not None +def test_create_duplicateNotAllowed(client, seeder, utils, app): + user_id, admin_unit_id = seeder.setup_base() + ( + other_user_id, + other_admin_unit_id, + event_id, + reference_id, + ) = seeder.create_any_reference(admin_unit_id) + + url = utils.get_url("event_reference_create", event_id=event_id) + response = utils.get_ok(url) + + response = utils.post_form( + url, + response, + {"admin_unit_id": admin_unit_id}, + ) + utils.assert_response_ok(response) + assert b"duplicate key" in response.data + + def test_create_401(client, app, utils, seeder, mocker): seeder.create_user() seeder._utils.login() diff --git a/tests/views/test_reference_request.py b/tests/views/test_reference_request.py index c528e54..06e2ae5 100644 --- a/tests/views/test_reference_request.py +++ b/tests/views/test_reference_request.py @@ -50,6 +50,36 @@ def test_create(client, app, utils, seeder, mocker, db_error): ) +def test_create_duplicateNotAllowed(client, app, utils, seeder): + user_id, admin_unit_id = seeder.setup_base() + event_id = seeder.create_event(admin_unit_id) + other_user_id = seeder.create_user("other@test.de") + other_admin_unit_id = seeder.create_admin_unit(other_user_id, "Other Crew") + + # First + url = utils.get_url("event_reference_request_create", event_id=event_id) + response = utils.get_ok(url) + response = utils.post_form( + url, + response, + {"admin_unit_id": other_admin_unit_id}, + ) + utils.assert_response_redirect( + response, "manage_admin_unit_reference_requests_outgoing", id=admin_unit_id + ) + + # Second + url = utils.get_url("event_reference_request_create", event_id=event_id) + response = utils.get_ok(url) + response = utils.post_form( + url, + response, + {"admin_unit_id": other_admin_unit_id}, + ) + utils.assert_response_ok(response) + assert b"duplicate key" in response.data + + def test_admin_unit_reference_requests_incoming(client, seeder, utils): user_id, admin_unit_id = seeder.setup_base() seeder.create_incoming_reference_request(admin_unit_id)