From 023aca094fda7fb95d3e8a6783f37417e9acd745 Mon Sep 17 00:00:00 2001 From: Daniel Grams Date: Thu, 29 Apr 2021 15:06:47 +0200 Subject: [PATCH] Send reference mail only when event changed significantly #161 --- .vscode/launch.json | 6 +++ project/api/event/resources.py | 11 +++++- project/api/resources.py | 13 ++++--- project/forms/event.py | 4 ++ project/services/event.py | 21 ++++++++++- project/utils.py | 33 +++++++++++++++++ project/views/event.py | 15 +++++--- tests/api/test_event.py | 58 +++++++++++++++++++++++++++++ tests/seeder.py | 67 +++++++++++++++++++++++++++++++--- tests/views/test_reference.py | 40 +++++++++++++++++--- 10 files changed, 243 insertions(+), 25 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 41dbaa6..326532f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -47,6 +47,12 @@ "request": "launch", "program": "${file}", "console": "integratedTerminal" + }, + { + "name": "Debug Unit Test", + "type": "python", + "request": "test", + "justMyCode": false, } ] } \ No newline at end of file diff --git a/project/api/event/resources.py b/project/api/event/resources.py index 518e17e..c463d11 100644 --- a/project/api/event/resources.py +++ b/project/api/event/resources.py @@ -25,6 +25,7 @@ from project.oauth2 import require_oauth from project.services.event import ( get_event_with_details_or_404, get_events_query, + get_significant_event_changes, update_event, ) from project.services.event_search import EventSearchParams @@ -59,8 +60,11 @@ class EventResource(BaseResource): event = self.update_instance(EventPostRequestSchema, instance=event) update_event(event) + changes = get_significant_event_changes(event) db.session.commit() - send_referenced_event_changed_mails(event) + + if changes: + send_referenced_event_changed_mails(event) return make_response("", 204) @@ -75,8 +79,11 @@ class EventResource(BaseResource): event = self.update_instance(EventPatchRequestSchema, instance=event) update_event(event) + changes = get_significant_event_changes(event) db.session.commit() - send_referenced_event_changed_mails(event) + + if changes: + send_referenced_event_changed_mails(event) return make_response("", 204) diff --git a/project/api/resources.py b/project/api/resources.py index df9f756..17cd3ec 100644 --- a/project/api/resources.py +++ b/project/api/resources.py @@ -37,12 +37,13 @@ class BaseResource(MethodResource): return instance def update_instance(self, schema_cls, instance): - instance = schema_cls().load( - request.json, session=db.session, instance=instance - ) + with db.session.no_autoflush: + instance = schema_cls().load( + request.json, session=db.session, instance=instance + ) - validate = getattr(instance, "validate", None) - if callable(validate): - validate() + validate = getattr(instance, "validate", None) + if callable(validate): + validate() return instance diff --git a/project/forms/event.py b/project/forms/event.py index 05c54bb..33520f9 100644 --- a/project/forms/event.py +++ b/project/forms/event.py @@ -353,6 +353,10 @@ class UpdateEventForm(BaseEventForm): obj.photo = Image() field.populate_obj(obj, name) + if obj.photo and obj.photo.is_empty(): + obj.photo = None + obj.photo_id = None + class DeleteEventForm(FlaskForm): submit = SubmitField(lazy_gettext("Delete event")) diff --git a/project/services/event.py b/project/services/event.py index ce18b9e..cb4c7a7 100644 --- a/project/services/event.py +++ b/project/services/event.py @@ -15,9 +15,11 @@ from project.models import ( EventOrganizer, EventPlace, EventReference, + EventStatus, Image, Location, ) +from project.utils import get_pending_changes def get_event_category(category_name): @@ -278,12 +280,15 @@ def update_event_dates_with_recurrence_rule(event): def insert_event(event): + if not event.status: + event.status = EventStatus.scheduled update_event_dates_with_recurrence_rule(event) db.session.add(event) def update_event(event): - update_event_dates_with_recurrence_rule(event) + with db.session.no_autoflush: + update_event_dates_with_recurrence_rule(event) def get_upcoming_event_dates(event_id): @@ -294,3 +299,17 @@ def get_upcoming_event_dates(event_id): .order_by(EventDate.start) .all() ) + + +def get_significant_event_changes(event) -> dict: + keys = [ + "name", + "start", + "recurrence_rule", + "status", + "attendance_mode", + "booked_up", + "event_place_id", + "organizer_id", + ] + return get_pending_changes(event, include_collections=False, include_keys=keys) diff --git a/project/utils.py b/project/utils.py index 9883a46..57447f7 100644 --- a/project/utils.py +++ b/project/utils.py @@ -4,6 +4,7 @@ import pathlib from flask_babelex import lazy_gettext from psycopg2.errorcodes import CHECK_VIOLATION, UNIQUE_VIOLATION from sqlalchemy.exc import IntegrityError +from sqlalchemy.orm.base import NO_CHANGE, object_state def get_event_category_name(category): @@ -56,3 +57,35 @@ def make_check_violation(message: str = None, statement: str = "") -> IntegrityE def make_unique_violation(message: str = None, statement: str = "") -> IntegrityError: return make_integrity_error(UNIQUE_VIOLATION, message, statement) + + +def get_pending_changes( + instance, include_collections=True, passive=None, include_keys=None +) -> dict: + result = {} + + state = object_state(instance) + + if not state.modified: # pragma: no cover + return result + + dict_ = state.dict + + for attr in state.manager.attributes: + if ( + (include_keys and attr.key not in include_keys) + or (not include_collections and hasattr(attr.impl, "get_collection")) + or not hasattr(attr.impl, "get_history") + ): # pragma: no cover + continue + + (added, unchanged, deleted) = attr.impl.get_history( + state, dict_, passive=NO_CHANGE + ) + + if added or deleted: + old_value = deleted[0] if deleted else None + new_value = added[0] if added else None + result[attr.key] = [new_value, old_value] + + return result diff --git a/project/views/event.py b/project/views/event.py index d7a7d19..b37bab9 100644 --- a/project/views/event.py +++ b/project/views/event.py @@ -30,6 +30,7 @@ from project.models import ( ) from project.services.event import ( get_event_with_details_or_404, + get_significant_event_changes, get_upcoming_event_dates, insert_event, update_event, @@ -179,8 +180,11 @@ def event_update(event_id): try: update_event(event) + changes = get_significant_event_changes(event) db.session.commit() - send_referenced_event_changed_mails(event) + + if changes: + send_referenced_event_changed_mails(event) flash_message( gettext("Event successfully updated"), url_for("event", event_id=event.id), @@ -304,10 +308,11 @@ def prepare_event_form_for_suggestion(form, event_suggestion): def update_event_with_form(event, form, event_suggestion=None): - form.populate_obj(event) - event.categories = EventCategory.query.filter( - EventCategory.id.in_(form.category_ids.data) - ).all() + with db.session.no_autoflush: + form.populate_obj(event) + event.categories = EventCategory.query.filter( + EventCategory.id.in_(form.category_ids.data) + ).all() def get_user_rights(event): diff --git a/tests/api/test_event.py b/tests/api/test_event.py index 838f047..4e2955f 100644 --- a/tests/api/test_event.py +++ b/tests/api/test_event.py @@ -314,6 +314,48 @@ def test_put_dateWithoutTimezone(client, seeder, utils, app): assert event.start == expected +def test_put_referencedEventUpdate_sendsMail(client, seeder, utils, app, mocker): + user_id, admin_unit_id = seeder.setup_api_access() + event_id = seeder.create_event_via_api(admin_unit_id) + place_id = seeder.upsert_default_event_place(admin_unit_id) + organizer_id = seeder.upsert_default_event_organizer(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") + seeder.create_reference(event_id, other_admin_unit_id) + + mail_mock = utils.mock_send_mails(mocker) + url = utils.get_url("api_v1_event", id=event_id) + put = create_put(place_id, organizer_id) + put["name"] = "Changed name" + response = utils.put_json(url, put) + + utils.assert_response_no_content(response) + utils.assert_send_mail_called(mail_mock, "other@test.de") + + +def test_put_referencedEventNonDirtyUpdate_doesNotSendMail( + client, seeder, utils, app, mocker +): + user_id, admin_unit_id = seeder.setup_api_access() + event_id = seeder.create_event_via_api(admin_unit_id) + place_id = seeder.upsert_default_event_place(admin_unit_id) + organizer_id = seeder.upsert_default_event_organizer(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") + seeder.create_reference(event_id, other_admin_unit_id) + + mail_mock = utils.mock_send_mails(mocker) + url = utils.get_url("api_v1_event", id=event_id) + put = create_put(place_id, organizer_id) + put["name"] = "Name" + response = utils.put_json(url, put) + + utils.assert_response_no_content(response) + mail_mock.assert_not_called() + + def test_patch(client, seeder, utils, app): user_id, admin_unit_id = seeder.setup_api_access() event_id = seeder.create_event(admin_unit_id) @@ -342,6 +384,22 @@ def test_patch_startAfterEnd(client, seeder, utils, app): utils.assert_response_bad_request(response) +def test_patch_referencedEventUpdate_sendsMail(client, seeder, utils, app, mocker): + user_id, admin_unit_id = seeder.setup_api_access() + event_id = seeder.create_event_via_api(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") + seeder.create_reference(event_id, other_admin_unit_id) + + mail_mock = utils.mock_send_mails(mocker) + url = utils.get_url("api_v1_event", id=event_id) + response = utils.patch_json(url, {"name": "Changed name"}) + + utils.assert_response_no_content(response) + utils.assert_send_mail_called(mail_mock, "other@test.de") + + def test_delete(client, seeder, utils, app): user_id, admin_unit_id = seeder.setup_api_access() event_id = seeder.create_event(admin_unit_id) diff --git a/tests/seeder.py b/tests/seeder.py index 6753338..0db143e 100644 --- a/tests/seeder.py +++ b/tests/seeder.py @@ -176,8 +176,7 @@ class Seeder(object): category = get_event_category(category_name) return category.id - def create_event(self, admin_unit_id, recurrence_rule=None, external_link=None): - from project.dateutils import get_now + def create_event(self, admin_unit_id, recurrence_rule="", external_link=""): from project.models import Event from project.services.event import insert_event, upsert_event_category @@ -187,16 +186,65 @@ class Seeder(object): event.categories = [upsert_event_category("Other")] event.name = "Name" event.description = "Beschreibung" - event.start = get_now() + event.start = self.get_now_by_minute() event.event_place_id = self.upsert_default_event_place(admin_unit_id) event.organizer_id = self.upsert_default_event_organizer(admin_unit_id) event.recurrence_rule = recurrence_rule event.external_link = external_link + event.ticket_link = "" + event.tags = "" + event.price_info = "" insert_event(event) self._db.session.commit() event_id = event.id return event_id + def create_event_via_form(self, admin_unit_id: int) -> str: + place_id = self.upsert_default_event_place(admin_unit_id) + organizer_id = self.upsert_default_event_organizer(admin_unit_id) + url = self._utils.get_url("event_create_for_admin_unit_id", id=admin_unit_id) + response = self._utils.get_ok(url) + response = self._utils.post_form( + url, + response, + { + "name": "Name", + "description": "Beschreibung", + "start": ["2030-12-31", "23", "59"], + "event_place_id": place_id, + "organizer_id": organizer_id, + "photo-image_base64": self.get_default_image_upload_base64(), + }, + ) + + with self._app.app_context(): + from project.models import Event + + event = ( + Event.query.filter(Event.admin_unit_id == admin_unit_id) + .filter(Event.name == "Name") + .first() + ) + return event.id + + def create_event_via_api(self, admin_unit_id: int) -> int: + place_id = self.upsert_default_event_place(admin_unit_id) + organizer_id = self.upsert_default_event_organizer(admin_unit_id) + + url = self._utils.get_url("api_v1_organization_event_list", id=admin_unit_id) + response = self._utils.post_json( + url, + { + "name": "Name", + "start": "2021-02-07T11:00:00.000Z", + "place": {"id": place_id}, + "organizer": {"id": organizer_id}, + }, + ) + self._utils.assert_response_created(response) + assert "id" in response.json + return response.json["id"] + def get_default_image_base64(self): return """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""" @@ -217,7 +265,6 @@ class Seeder(object): return image_id def create_event_suggestion(self, admin_unit_id, free_text=False): - from project.dateutils import get_now from project.models import EventSuggestion from project.services.event import upsert_event_category from project.services.event_suggestion import insert_event_suggestion @@ -230,7 +277,7 @@ class Seeder(object): suggestion.contact_email_notice = True suggestion.name = "Vorschlag" suggestion.description = "Beschreibung" - suggestion.start = get_now() + suggestion.start = self.get_now_by_minute() suggestion.photo_id = self.upsert_default_image() suggestion.categories = [upsert_event_category("Other")] @@ -290,3 +337,13 @@ class Seeder(object): event_id = self.create_event(other_admin_unit_id) reference_request_id = self.create_reference_request(event_id, admin_unit_id) return (other_user_id, other_admin_unit_id, event_id, reference_request_id) + + def get_now_by_minute(self): + from datetime import datetime + + from project.dateutils import get_now + + now = get_now() + return datetime( + now.year, now.month, now.day, now.hour, now.minute, tzinfo=now.tzinfo + ) diff --git a/tests/views/test_reference.py b/tests/views/test_reference.py index 2f267e8..8eabdfb 100644 --- a/tests/views/test_reference.py +++ b/tests/views/test_reference.py @@ -199,15 +199,16 @@ def test_admin_unit_references_outgoing(client, seeder, utils): def test_referencedEventUpdate_sendsMail(client, seeder, utils, app, mocker): 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) + other_user_id = seeder.create_user("other@test.de") + other_admin_unit_id = seeder.create_admin_unit(other_user_id, "Other Crew") utils.logout() utils.login("other@test.de") + + # Event per Form anlegen, um dieselben Default-Werte wie im Update zu haben + event_id = seeder.create_event_via_form(other_admin_unit_id) + seeder.create_reference(event_id, admin_unit_id) + url = utils.get_url("event_update", event_id=event_id) response = utils.get_ok(url) @@ -221,3 +222,30 @@ def test_referencedEventUpdate_sendsMail(client, seeder, utils, app, mocker): ) utils.assert_send_mail_called(mail_mock, "test@test.de") + + +def test_referencedEventNonDirtyUpdate_doesNotSendMail( + client, seeder, utils, app, mocker +): + user_id, admin_unit_id = seeder.setup_base() + other_user_id = seeder.create_user("other@test.de") + other_admin_unit_id = seeder.create_admin_unit(other_user_id, "Other Crew") + + utils.logout() + utils.login("other@test.de") + + # Event per Form anlegen, um dieselben Default-Werte wie im Update zu haben + event_id = seeder.create_event_via_form(other_admin_unit_id) + seeder.create_reference(event_id, admin_unit_id) + + url = utils.get_url("event_update", event_id=event_id) + response = utils.get_ok(url) + + mail_mock = utils.mock_send_mails(mocker) + response = utils.post_form( + url, + response, + {}, + ) + + mail_mock.assert_not_called()