From 5fd75d9c1fa5f38ab8ae4d69197da1b7e55c1a8a Mon Sep 17 00:00:00 2001 From: Daniel Grams Date: Thu, 27 Apr 2023 15:37:59 +0200 Subject: [PATCH] Delete organization #454 --- messages.pot | 142 ++++--- migrations/versions/00daa8c472ba_.py | 10 +- migrations/versions/021f602d9965_.py | 4 +- migrations/versions/3c5b34fd1156_.py | 6 +- migrations/versions/41512b20e07c_.py | 3 +- migrations/versions/4a5c083c649b_.py | 4 +- migrations/versions/6b7016f73688_.py | 12 +- migrations/versions/92f37474ad62_.py | 15 +- migrations/versions/975c22ae802b_.py | 2 +- migrations/versions/bbad7e33a780_.py | 5 +- migrations/versions/cbac4166f9c0_.py | 348 ++++++++++++++++++ project/__init__.py | 11 +- project/celery_tasks.py | 34 ++ project/forms/admin_unit.py | 10 + project/models/admin_unit.py | 14 +- project/models/event_suggestion.py | 6 +- project/services/admin_unit.py | 12 + project/templates/admin/admin_units.html | 2 + .../templates/admin_unit/cancel_deletion.html | 24 ++ .../admin_unit/request_deletion.html | 26 ++ project/templates/admin_unit/update.html | 13 + ...rganization_deletion_requested_notice.html | 6 + ...organization_deletion_requested_notice.txt | 3 + project/templates/manage/events.html | 6 + .../translations/de/LC_MESSAGES/messages.mo | Bin 37812 -> 38528 bytes .../translations/de/LC_MESSAGES/messages.po | 142 ++++--- .../translations/en/LC_MESSAGES/messages.mo | Bin 4079 -> 4079 bytes .../translations/en/LC_MESSAGES/messages.po | 142 ++++--- project/views/admin.py | 4 +- project/views/admin_unit.py | 90 ++++- project/views/utils.py | 28 +- tests/services/test_admin_unit.py | 17 + tests/test_models.py | 31 ++ tests/views/test_admin_unit.py | 111 ++++++ 34 files changed, 1106 insertions(+), 177 deletions(-) create mode 100644 migrations/versions/cbac4166f9c0_.py create mode 100644 project/templates/admin_unit/cancel_deletion.html create mode 100644 project/templates/admin_unit/request_deletion.html create mode 100644 project/templates/email/organization_deletion_requested_notice.html create mode 100644 project/templates/email/organization_deletion_requested_notice.txt create mode 100644 tests/services/test_admin_unit.py diff --git a/messages.pot b/messages.pot index 06f56dd..889c9bb 100644 --- a/messages.pot +++ b/messages.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2023-04-21 12:29+0200\n" +"POT-Creation-Date: 2023-04-27 15:10+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -211,7 +211,7 @@ msgstr "" #: project/forms/admin.py:13 project/templates/_macros.html:1473 #: project/templates/layout.html:311 #: project/templates/widget/event_suggestion/create.html:204 -#: project/views/admin_unit.py:73 project/views/root.py:71 +#: project/views/admin_unit.py:82 project/views/root.py:71 msgid "Contact" msgstr "" @@ -302,10 +302,13 @@ msgid "Update organization" msgstr "" #: project/forms/admin.py:68 project/templates/admin/delete_admin_unit.html:6 +#: project/templates/admin_unit/request_deletion.html:6 +#: project/templates/admin_unit/update.html:93 msgid "Delete organization" msgstr "" #: project/forms/admin.py:69 project/forms/admin_unit.py:28 +#: project/forms/admin_unit.py:134 project/forms/admin_unit.py:139 #: project/forms/event.py:85 project/forms/event.py:114 #: project/forms/event_place.py:25 project/forms/event_place.py:50 #: project/forms/event_suggestion.py:26 project/forms/oauth2_client.py:66 @@ -471,6 +474,17 @@ msgstr "" msgid "Link Color" msgstr "" +#: project/forms/admin_unit.py:133 +msgid "Request deletion" +msgstr "" + +#: project/forms/admin_unit.py:138 +#: project/templates/admin_unit/cancel_deletion.html:6 +#: project/templates/admin_unit/update.html:26 +#: project/templates/manage/events.html:49 +msgid "Cancel deletion" +msgstr "" + #: project/forms/admin_unit_member.py:13 msgid "Invite" msgstr "" @@ -577,7 +591,7 @@ msgstr "" msgid "100 km" msgstr "" -#: project/forms/event.py:38 project/templates/manage/events.html:94 +#: project/forms/event.py:38 project/templates/manage/events.html:100 msgid "Start" msgstr "" @@ -762,8 +776,8 @@ msgid "Choose how people can attend the event." msgstr "" #: project/forms/event.py:225 project/forms/event_place.py:27 -#: project/templates/manage/events.html:98 -#: project/templates/manage/events.html:133 +#: project/templates/manage/events.html:104 +#: project/templates/manage/events.html:139 #: project/templates/manage/places.html:21 #: project/templates/manage/places.html:39 #: project/templates/widget/event_suggestion/create.html:258 @@ -829,7 +843,7 @@ msgstr "" #: project/templates/event_place/create.html:31 #: project/templates/event_place/delete.html:13 #: project/templates/event_place/update.html:31 -#: project/templates/manage/events.html:97 +#: project/templates/manage/events.html:103 msgid "Place" msgstr "" @@ -846,7 +860,7 @@ msgstr "" #: project/forms/event_suggestion.py:60 project/templates/_macros.html:475 #: project/templates/_macros.html:647 project/templates/event/create.html:253 #: project/templates/event/update.html:156 -#: project/templates/manage/events.html:96 +#: project/templates/manage/events.html:102 #: project/templates/organizer/create.html:27 #: project/templates/organizer/delete.html:13 #: project/templates/organizer/update.html:27 @@ -971,7 +985,7 @@ msgstr "" #: project/forms/event.py:446 project/forms/event_date.py:24 #: project/forms/planing.py:22 project/templates/_macros.html:305 #: project/templates/admin_unit/create.html:38 -#: project/templates/admin_unit/update.html:39 +#: project/templates/admin_unit/update.html:46 #: project/templates/event_place/create.html:40 #: project/templates/event_place/update.html:40 #: project/templates/manage/organizers.html:19 @@ -1121,8 +1135,10 @@ msgstr "" #: project/forms/reference.py:11 project/forms/reference_request.py:16 #: project/templates/_macros.html:491 project/templates/_macros.html:664 #: project/templates/admin/delete_admin_unit.html:13 +#: project/templates/admin_unit/cancel_deletion.html:13 #: project/templates/admin_unit/create.html:28 -#: project/templates/admin_unit/update.html:29 +#: project/templates/admin_unit/request_deletion.html:15 +#: project/templates/admin_unit/update.html:36 #: project/templates/layout.html:247 msgid "Organization" msgstr "" @@ -1264,7 +1280,7 @@ msgstr "" #: project/templates/event/actions.html:25 #: project/templates/event/create.html:230 #: project/templates/event/update.html:122 -#: project/templates/manage/events.html:95 +#: project/templates/manage/events.html:101 #: project/templates/widget/event_suggestion/create.html:229 msgid "Event" msgstr "" @@ -1307,9 +1323,9 @@ msgstr "" #: project/templates/_macros.html:590 project/templates/_macros.html:633 #: project/templates/_macros.html:765 -#: project/templates/admin/admin_units.html:34 +#: project/templates/admin/admin_units.html:36 #: project/templates/admin/users.html:32 -#: project/templates/manage/events.html:110 +#: project/templates/manage/events.html:116 #: project/templates/manage/members.html:35 #: project/templates/manage/organizers.html:33 #: project/templates/manage/places.html:31 @@ -1325,7 +1341,7 @@ msgstr "" #: project/templates/_macros.html:703 project/templates/_macros.html:1297 #: project/templates/event/actions.html:38 -#: project/templates/manage/events.html:117 +#: project/templates/manage/events.html:123 #: project/templates/manage/references_incoming.html:10 msgid "Reference event" msgstr "" @@ -1338,7 +1354,7 @@ msgstr "" #: project/templates/_macros.html:723 project/templates/_macros.html:1294 #: project/templates/event/actions.html:32 -#: project/templates/manage/events.html:115 +#: project/templates/manage/events.html:121 msgid "Request reference" msgstr "" @@ -1423,11 +1439,11 @@ msgid "Duplicate event" msgstr "" #: project/templates/_macros.html:1301 project/templates/event/actions.html:44 -#: project/templates/manage/events.html:121 +#: project/templates/manage/events.html:127 msgid "Add to list" msgstr "" -#: project/templates/_macros.html:1304 project/templates/manage/events.html:124 +#: project/templates/_macros.html:1304 project/templates/manage/events.html:130 msgid "More" msgstr "" @@ -1482,7 +1498,7 @@ msgstr "" msgid "Enter list name" msgstr "" -#: project/templates/admin/admin_units.html:32 project/templates/home.html:25 +#: project/templates/admin/admin_units.html:34 project/templates/home.html:25 msgid "Manage" msgstr "" @@ -1500,7 +1516,7 @@ msgstr "" #: project/templates/layout.html:157 project/templates/layout.html:205 #: project/templates/manage/events.html:6 -#: project/templates/manage/events.html:48 +#: project/templates/manage/events.html:54 #: project/templates/manage/events_vue.html:4 msgid "Events" msgstr "" @@ -1546,7 +1562,7 @@ msgstr "" #: project/templates/event/create.html:5 #: project/templates/event/create.html:221 project/templates/layout.html:212 -#: project/templates/manage/events.html:49 +#: project/templates/manage/events.html:55 #: project/templates/manage/organizers.html:38 msgid "Create event" msgstr "" @@ -1623,7 +1639,7 @@ msgstr "" #: project/templates/admin/settings.html:4 #: project/templates/admin/settings.html:11 #: project/templates/admin_unit/update.html:6 -#: project/templates/admin_unit/update.html:23 +#: project/templates/admin_unit/update.html:30 #: project/templates/layout.html:259 project/templates/manage/widgets.html:11 #: project/templates/manage/widgets.html:15 project/templates/profile.html:19 msgid "Settings" @@ -1677,17 +1693,17 @@ msgstr "" msgid "Users" msgstr "" -#: project/templates/admin/admin_units.html:33 -#: project/templates/manage/events.html:109 +#: project/templates/admin/admin_units.html:35 +#: project/templates/manage/events.html:115 #: project/templates/manage/organizers.html:32 #: project/templates/manage/references_incoming.html:19 #: project/templates/manage/references_outgoing.html:19 msgid "View" msgstr "" -#: project/templates/admin/admin_units.html:35 +#: project/templates/admin/admin_units.html:37 #: project/templates/admin/users.html:33 -#: project/templates/manage/events.html:111 +#: project/templates/manage/events.html:117 #: project/templates/manage/members.html:21 #: project/templates/manage/members.html:36 #: project/templates/manage/organizers.html:34 @@ -1701,7 +1717,7 @@ msgstr "" msgid "User" msgstr "" -#: project/templates/admin/email.html:47 project/views/admin.py:155 +#: project/templates/admin/email.html:47 project/views/admin.py:144 msgid "Mail sent successfully" msgstr "" @@ -1718,7 +1734,7 @@ msgid "Mails sent successfully" msgstr "" #: project/templates/admin_unit/create.html:58 -#: project/templates/admin_unit/update.html:59 +#: project/templates/admin_unit/update.html:66 #: project/templates/event/create.html:347 #: project/templates/event/update.html:204 #: project/templates/event_place/create.html:57 @@ -1738,7 +1754,18 @@ msgstr "" msgid "Invite user" msgstr "" -#: project/templates/admin_unit/update.html:72 +#: project/templates/admin_unit/request_deletion.html:8 +msgid "" +"The organization is not deleted immediately. After a period of time, the " +"organization will be deleted. Until then, the deletion can be canceled." +msgstr "" + +#: project/templates/admin_unit/update.html:25 +#: project/templates/manage/events.html:48 +msgid "The organization is scheduled for deletion." +msgstr "" + +#: project/templates/admin_unit/update.html:79 #: project/templates/manage/verification_requests_outgoing.html:6 #: project/templates/manage/verification_requests_outgoing.html:11 msgid "Verification requests" @@ -1773,6 +1800,7 @@ msgid "You have been invited to join %(admin_unit_name)s." msgstr "" #: project/templates/email/invitation_notice.html:5 +#: project/templates/email/organization_deletion_requested_notice.html:5 #: project/templates/email/organization_invitation_notice.html:5 msgid "Click here to view the invitation" msgstr "" @@ -1790,6 +1818,11 @@ msgstr "" msgid "Notification settings" msgstr "" +#: project/templates/email/organization_deletion_requested_notice.html:4 +#, python-format +msgid "%(admin_unit_name)s is scheduled for deletion." +msgstr "" + #: project/templates/email/organization_invitation_accepted_notice.html:4 #, python-format msgid "" @@ -1887,7 +1920,7 @@ msgstr "" #: project/templates/event/actions.html:74 project/templates/event/read.html:32 #: project/templates/event_date/read.html:34 -#: project/templates/manage/events.html:145 +#: project/templates/manage/events.html:151 msgid "Add event to list" msgstr "" @@ -2008,21 +2041,21 @@ msgstr "" msgid "Verify organization" msgstr "" -#: project/templates/manage/events.html:85 +#: project/templates/manage/events.html:91 msgid "More filters" msgstr "" -#: project/templates/manage/events.html:99 -#: project/templates/manage/events.html:134 +#: project/templates/manage/events.html:105 +#: project/templates/manage/events.html:140 msgid "Number of references" msgstr "" -#: project/templates/manage/events.html:99 -#: project/templates/manage/events.html:134 +#: project/templates/manage/events.html:105 +#: project/templates/manage/events.html:140 msgid "Number of reference requests." msgstr "" -#: project/templates/manage/events.html:112 +#: project/templates/manage/events.html:118 msgid "Duplicate" msgstr "" @@ -2192,63 +2225,68 @@ msgstr "" msgid "Preview" msgstr "" -#: project/views/admin.py:60 +#: project/views/admin.py:63 msgid "Organization successfully updated" msgstr "" -#: project/views/admin.py:82 +#: project/views/admin.py:85 project/views/admin_unit.py:182 +#: project/views/admin_unit.py:215 msgid "Entered name does not match organization name" msgstr "" -#: project/views/admin.py:87 +#: project/views/admin.py:89 msgid "Organization successfully deleted" msgstr "" -#: project/views/admin.py:111 project/views/manage.py:443 +#: project/views/admin.py:113 project/views/manage.py:432 #: project/views/user.py:28 msgid "Settings successfully updated" msgstr "" -#: project/views/admin.py:144 +#: project/views/admin.py:133 #, python-format msgid "Test mail from %(site_name)s" msgstr "" -#: project/views/admin.py:187 +#: project/views/admin.py:162 #, python-format msgid "Newsletter from %(site_name)s" msgstr "" -#: project/views/admin.py:237 +#: project/views/admin.py:212 msgid "User successfully updated" msgstr "" -#: project/views/admin.py:257 +#: project/views/admin.py:232 msgid "Entered email does not match user email" msgstr "" -#: project/views/admin.py:262 +#: project/views/admin.py:237 msgid "User successfully deleted" msgstr "" -#: project/views/admin_unit.py:69 +#: project/views/admin_unit.py:78 msgid "" "Organizations cannot currently be created. The project is in a closed " "test phase. If you are interested, you can contact us." msgstr "" -#: project/views/admin_unit.py:119 +#: project/views/admin_unit.py:128 msgid "Organization successfully created" msgstr "" -#: project/views/admin_unit.py:149 +#: project/views/admin_unit.py:158 msgid "AdminUnit successfully updated" msgstr "" -#: project/views/admin_unit.py:171 +#: project/views/admin_unit.py:245 msgid "Organization invitation accepted" msgstr "" +#: project/views/admin_unit.py:259 +msgid "Organization deletion requested" +msgstr "" + #: project/views/admin_unit_member.py:43 msgid "Member successfully updated" msgstr "" @@ -2432,26 +2470,26 @@ msgid "" "verified automatically." msgstr "" -#: project/views/utils.py:65 +#: project/views/utils.py:71 msgid "" "An entry with the entered values ​​already exists. Duplicate entries are " "not allowed." msgstr "" -#: project/views/utils.py:116 +#: project/views/utils.py:122 #, python-format msgid "Error in the %s field - %s" msgstr "" -#: project/views/utils.py:123 +#: project/views/utils.py:129 msgid "Show" msgstr "" -#: project/views/utils.py:131 +#: project/views/utils.py:137 msgid "You do not have permission for this action" msgstr "" -#: project/views/utils.py:252 +#: project/views/utils.py:258 msgid "" "The invitation was issued to another user. Sign in with the email address" " the invitation was sent to." diff --git a/migrations/versions/00daa8c472ba_.py b/migrations/versions/00daa8c472ba_.py index 2e46508..2a77de8 100644 --- a/migrations/versions/00daa8c472ba_.py +++ b/migrations/versions/00daa8c472ba_.py @@ -21,9 +21,15 @@ depends_on = None def upgrade(): # ### commands auto generated by Alembic - please adjust! ### # op.drop_table('spatial_ref_sys') - op.drop_constraint( - "eventsuggestion_event_id_fkey", "eventsuggestion", type_="foreignkey" + op.execute( + "ALTER TABLE eventsuggestion DROP CONSTRAINT IF EXISTS eventsuggestion_event_id_fkey;" ) + op.execute( + "ALTER TABLE eventsuggestion DROP CONSTRAINT IF EXISTS fk_eventsuggestion_event_id_event;" + ) + # op.drop_constraint( + # "eventsuggestion_event_id_fkey", "eventsuggestion", type_="foreignkey" + # ) op.create_foreign_key( None, "eventsuggestion", "event", ["event_id"], ["id"], ondelete="SET NULL" ) diff --git a/migrations/versions/021f602d9965_.py b/migrations/versions/021f602d9965_.py index 5780c03..936b5c2 100644 --- a/migrations/versions/021f602d9965_.py +++ b/migrations/versions/021f602d9965_.py @@ -68,7 +68,9 @@ def upgrade(): sa.PrimaryKeyConstraint("id"), ) # op.drop_table('spatial_ref_sys') - op.drop_constraint("event_contact_id_fkey", "event", type_="foreignkey") + op.execute("ALTER TABLE event DROP CONSTRAINT IF EXISTS event_contact_id_fkey;") + op.execute("ALTER TABLE event DROP CONSTRAINT IF EXISTS fk_event_event_contact_id;") + # op.drop_constraint("event_contact_id_fkey", "event", type_="foreignkey") op.drop_column("event", "review_status") op.drop_column("event", "rejection_resaon") op.drop_column("event", "contact_id") diff --git a/migrations/versions/3c5b34fd1156_.py b/migrations/versions/3c5b34fd1156_.py index cc7d037..fe159fe 100644 --- a/migrations/versions/3c5b34fd1156_.py +++ b/migrations/versions/3c5b34fd1156_.py @@ -67,8 +67,12 @@ def upgrade(): upgrade_category_to_categories() + op.execute("ALTER TABLE event DROP CONSTRAINT IF EXISTS event_category_id_fkey;") + op.execute( + "ALTER TABLE event DROP CONSTRAINT IF EXISTS fk_event_event_category_id;" + ) # op.drop_table('spatial_ref_sys') - op.drop_constraint("event_category_id_fkey", "event", type_="foreignkey") + # op.drop_constraint("event_category_id_fkey", "event", type_="foreignkey") op.drop_column("event", "category_id") # ### end Alembic commands ### diff --git a/migrations/versions/41512b20e07c_.py b/migrations/versions/41512b20e07c_.py index fe37fd6..e394030 100644 --- a/migrations/versions/41512b20e07c_.py +++ b/migrations/versions/41512b20e07c_.py @@ -20,7 +20,8 @@ depends_on = None def upgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.drop_constraint("event_event_place_id_fkey", "event", type_="foreignkey") + op.execute("ALTER TABLE event DROP CONSTRAINT IF EXISTS event_event_place_id_fkey;") + # op.drop_constraint("event_event_place_id_fkey", "event", type_="foreignkey") op.create_foreign_key(None, "event", "eventplace", ["event_place_id"], ["id"]) # ### end Alembic commands ### diff --git a/migrations/versions/4a5c083c649b_.py b/migrations/versions/4a5c083c649b_.py index 3c10d42..47ce76b 100644 --- a/migrations/versions/4a5c083c649b_.py +++ b/migrations/versions/4a5c083c649b_.py @@ -33,7 +33,9 @@ def upgrade(): sa.Column("updated_at", sa.DateTime(), nullable=True), sa.Column("created_by_id", sa.Integer(), nullable=True), sa.Column("updated_by_id", sa.Integer(), nullable=True), - sa.CheckConstraint("source_admin_unit_id != target_admin_unit_id"), + sa.CheckConstraint( + "source_admin_unit_id != target_admin_unit_id", name="source_neq_target" + ), sa.ForeignKeyConstraint( ["created_by_id"], ["user.id"], diff --git a/migrations/versions/6b7016f73688_.py b/migrations/versions/6b7016f73688_.py index 74035ab..e25abcc 100644 --- a/migrations/versions/6b7016f73688_.py +++ b/migrations/versions/6b7016f73688_.py @@ -21,8 +21,16 @@ depends_on = None def upgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.drop_constraint("event_place_id_fkey", "event", type_="foreignkey") - op.drop_constraint("event_host_id_fkey", "event", type_="foreignkey") + op.execute("ALTER TABLE event DROP CONSTRAINT IF EXISTS event_place_id_fkey;") + op.execute( + "ALTER TABLE event DROP CONSTRAINT IF EXISTS fk_event_event_place_id_place;" + ) + op.execute("ALTER TABLE event DROP CONSTRAINT IF EXISTS event_host_id_fkey;") + op.execute( + "ALTER TABLE event DROP CONSTRAINT IF EXISTS fk_event_event_host_id_place;" + ) + # op.drop_constraint("event_place_id_fkey", "event", type_="foreignkey") + # op.drop_constraint("event_host_id_fkey", "event", type_="foreignkey") op.drop_column("event", "host_id") op.drop_column("event", "place_id") op.drop_table("eventsuggestiondate") diff --git a/migrations/versions/92f37474ad62_.py b/migrations/versions/92f37474ad62_.py index 063bc57..037f0fc 100644 --- a/migrations/versions/92f37474ad62_.py +++ b/migrations/versions/92f37474ad62_.py @@ -18,10 +18,19 @@ depends_on = None def upgrade(): # ### commands auto generated by Alembic - please adjust! ### # op.drop_table('spatial_ref_sys') - op.drop_constraint( - "eventplace_name_organizer_id_admin_unit_id_key", "eventplace", type_="unique" + op.execute( + "ALTER TABLE eventplace DROP CONSTRAINT IF EXISTS eventplace_name_organizer_id_admin_unit_id_key;" ) - op.drop_constraint("eventplace_organizer_id_fkey", "eventplace", type_="foreignkey") + # op.drop_constraint( + # "eventplace_name_organizer_id_admin_unit_id_key", "eventplace", type_="unique" + # ) + op.execute( + "ALTER TABLE eventplace DROP CONSTRAINT IF EXISTS eventplace_organizer_id_fkey;" + ) + op.execute( + "ALTER TABLE eventplace DROP CONSTRAINT IF EXISTS fk_eventplace_organizer_id;" + ) + # op.drop_constraint("eventplace_organizer_id_fkey", "eventplace", type_="foreignkey") op.drop_column("eventplace", "public") op.drop_column("eventplace", "organizer_id") # ### end Alembic commands ### diff --git a/migrations/versions/975c22ae802b_.py b/migrations/versions/975c22ae802b_.py index 3ddd39d..9482452 100644 --- a/migrations/versions/975c22ae802b_.py +++ b/migrations/versions/975c22ae802b_.py @@ -40,7 +40,7 @@ def upgrade(): op.alter_column("event", "host_id", existing_type=sa.INTEGER(), nullable=True) op.alter_column("event", "place_id", existing_type=sa.INTEGER(), nullable=True) op.create_foreign_key(None, "event", "eventorganizer", ["organizer_id"], ["id"]) - op.drop_constraint("place_name_key", "place", type_="unique") + op.execute("ALTER TABLE place DROP CONSTRAINT IF EXISTS place_name_key;") # ### end Alembic commands ### diff --git a/migrations/versions/bbad7e33a780_.py b/migrations/versions/bbad7e33a780_.py index 32393ad..150bfb9 100644 --- a/migrations/versions/bbad7e33a780_.py +++ b/migrations/versions/bbad7e33a780_.py @@ -294,7 +294,10 @@ def upgrade(): sa.Column("id", sa.Integer(), nullable=False), sa.Column("organization_id", sa.Integer(), nullable=True), sa.Column("admin_unit_id", sa.Integer(), nullable=True), - sa.CheckConstraint("NOT(organization_id IS NULL AND admin_unit_id IS NULL)"), + sa.CheckConstraint( + "NOT(organization_id IS NULL AND admin_unit_id IS NULL)", + name="org_or_adminunit_check", + ), sa.ForeignKeyConstraint( ["admin_unit_id"], ["adminunit.id"], diff --git a/migrations/versions/cbac4166f9c0_.py b/migrations/versions/cbac4166f9c0_.py new file mode 100644 index 0000000..f00f4c7 --- /dev/null +++ b/migrations/versions/cbac4166f9c0_.py @@ -0,0 +1,348 @@ +"""empty message + +Revision ID: cbac4166f9c0 +Revises: 30650020b4b7 +Create Date: 2023-04-27 11:02:04.294121 + +""" +import sqlalchemy as sa +import sqlalchemy_utils +from alembic import op + +from project import dbtypes + +# revision identifiers, used by Alembic. +revision = "cbac4166f9c0" +down_revision = "30650020b4b7" +branch_labels = None +depends_on = None + + +def upgrade(): + op.execute("ALTER TABLE adminunit DROP CONSTRAINT IF EXISTS adminunit_name_key;") + op.execute("ALTER TABLE adminunit DROP CONSTRAINT IF EXISTS uq_adminunit_name;") + op.execute( + "ALTER TABLE adminunit DROP CONSTRAINT IF EXISTS adminunit_short_name_key;" + ) + op.execute( + "ALTER TABLE adminunit DROP CONSTRAINT IF EXISTS uq_adminunit_short_name;" + ) + + with op.batch_alter_table("adminunit", schema=None) as batch_op: + batch_op.add_column( + sa.Column("deletion_requested_at", sa.DateTime(), nullable=True) + ) + batch_op.add_column( + sa.Column("deletion_requested_by_id", sa.Integer(), nullable=True) + ) + batch_op.create_unique_constraint(batch_op.f("uq_adminunit_name"), ["name"]) + batch_op.create_unique_constraint( + batch_op.f("uq_adminunit_short_name"), ["short_name"] + ) + batch_op.create_foreign_key( + batch_op.f("fk_adminunit_deletion_requested_by_id_user"), + "user", + ["deletion_requested_by_id"], + ["id"], + ) + + op.execute( + "ALTER TABLE adminunitmemberinvitation DROP CONSTRAINT IF EXISTS adminunitmemberinvitation_email_admin_unit_id_key;" + ) + op.execute( + "ALTER TABLE adminunitmemberinvitation DROP CONSTRAINT IF EXISTS uq_adminunitmemberinvitation_email;" + ) + with op.batch_alter_table("adminunitmemberinvitation", schema=None) as batch_op: + batch_op.create_unique_constraint( + batch_op.f("uq_adminunitmemberinvitation_email"), ["email", "admin_unit_id"] + ) + + op.execute( + "ALTER TABLE adminunitmemberrole DROP CONSTRAINT IF EXISTS adminunitmemberrole_name_key;" + ) + op.execute( + "ALTER TABLE adminunitmemberrole DROP CONSTRAINT IF EXISTS uq_adminunitmemberrole_name;" + ) + with op.batch_alter_table("adminunitmemberrole", schema=None) as batch_op: + batch_op.create_unique_constraint( + batch_op.f("uq_adminunitmemberrole_name"), ["name"] + ) + + op.execute( + "ALTER TABLE adminunitrelation DROP CONSTRAINT IF EXISTS adminunitrelation_source_admin_unit_id_target_admin_unit_id_key;" + ) + op.execute( + "ALTER TABLE adminunitrelation DROP CONSTRAINT IF EXISTS uq_adminunitrelation_source_admin_unit_id;" + ) + with op.batch_alter_table("adminunitrelation", schema=None) as batch_op: + batch_op.create_unique_constraint( + batch_op.f("uq_adminunitrelation_source_admin_unit_id"), + ["source_admin_unit_id", "target_admin_unit_id"], + ) + + op.execute( + "ALTER TABLE event_coorganizers DROP CONSTRAINT IF EXISTS event_coorganizers_event_id_organizer_id_key;" + ) + op.execute( + "ALTER TABLE event_coorganizers DROP CONSTRAINT IF EXISTS uq_event_coorganizers_event_id;" + ) + with op.batch_alter_table("event_coorganizers", schema=None) as batch_op: + batch_op.create_unique_constraint( + batch_op.f("uq_event_coorganizers_event_id"), ["event_id", "organizer_id"] + ) + + op.execute( + "ALTER TABLE event_eventcategories DROP CONSTRAINT IF EXISTS event_eventcategories_event_id_category_id;" + ) + op.execute( + "ALTER TABLE event_eventcategories DROP CONSTRAINT IF EXISTS event_eventcategories_event_id_category_id_key;" + ) + op.execute( + "ALTER TABLE event_eventcategories DROP CONSTRAINT IF EXISTS uq_event_eventcategories_event_id;" + ) + + with op.batch_alter_table("event_eventcategories", schema=None) as batch_op: + batch_op.create_unique_constraint( + batch_op.f("uq_event_eventcategories_event_id"), ["event_id", "category_id"] + ) + + op.execute( + "ALTER TABLE event_eventlists DROP CONSTRAINT IF EXISTS event_eventlists_event_id_list_id_key;" + ) + op.execute( + "ALTER TABLE event_eventlists DROP CONSTRAINT IF EXISTS uq_event_eventlists_event_id;" + ) + with op.batch_alter_table("event_eventlists", schema=None) as batch_op: + batch_op.create_unique_constraint( + batch_op.f("uq_event_eventlists_event_id"), ["event_id", "list_id"] + ) + + op.execute( + "ALTER TABLE eventcategory DROP CONSTRAINT IF EXISTS eventcategory_name_key;" + ) + op.execute( + "ALTER TABLE eventcategory DROP CONSTRAINT IF EXISTS uq_eventcategory_name;" + ) + with op.batch_alter_table("eventcategory", schema=None) as batch_op: + batch_op.create_unique_constraint(batch_op.f("uq_eventcategory_name"), ["name"]) + + op.execute( + "ALTER TABLE eventorganizer DROP CONSTRAINT IF EXISTS eventorganizer_name_admin_unit_id_key;" + ) + op.execute( + "ALTER TABLE eventorganizer DROP CONSTRAINT IF EXISTS uq_eventorganizer_name;" + ) + with op.batch_alter_table("eventorganizer", schema=None) as batch_op: + batch_op.create_unique_constraint( + batch_op.f("uq_eventorganizer_name"), ["name", "admin_unit_id"] + ) + + op.execute( + "ALTER TABLE eventplace DROP CONSTRAINT IF EXISTS eventplace_name_admin_unit_id;" + ) + op.execute( + "ALTER TABLE eventplace DROP CONSTRAINT IF EXISTS eventplace_name_admin_unit_id_key;" + ) + op.execute("ALTER TABLE eventplace DROP CONSTRAINT IF EXISTS uq_eventplace_name;") + with op.batch_alter_table("eventplace", schema=None) as batch_op: + batch_op.create_unique_constraint( + batch_op.f("uq_eventplace_name"), ["name", "admin_unit_id"] + ) + + op.execute( + "ALTER TABLE eventsuggestion_eventcategories DROP CONSTRAINT IF EXISTS eventsuggestion_eventcategori_event_suggestion_id_category__key;" + ) + op.execute( + "ALTER TABLE eventsuggestion_eventcategories DROP CONSTRAINT IF EXISTS eventsuggestion_eventcategories_event_suggestion_id_category_id;" + ) + op.execute( + "ALTER TABLE eventsuggestion_eventcategories DROP CONSTRAINT IF EXISTS uq_eventsuggestion_eventcategories_event_suggestion_id;" + ) + with op.batch_alter_table( + "eventsuggestion_eventcategories", schema=None + ) as batch_op: + batch_op.create_unique_constraint( + batch_op.f("uq_eventsuggestion_eventcategories_event_suggestion_id"), + ["event_suggestion_id", "category_id"], + ) + + op.execute( + "ALTER TABLE flask_dance_oauth DROP CONSTRAINT IF EXISTS flask_dance_oauth_provider_user_id_key;" + ) + op.execute( + "ALTER TABLE flask_dance_oauth DROP CONSTRAINT IF EXISTS uq_flask_dance_oauth_provider_user_id;" + ) + with op.batch_alter_table("flask_dance_oauth", schema=None) as batch_op: + batch_op.create_unique_constraint( + batch_op.f("uq_flask_dance_oauth_provider_user_id"), ["provider_user_id"] + ) + + op.execute( + "ALTER TABLE oauth2_code DROP CONSTRAINT IF EXISTS oauth2_code_code_key;" + ) + op.execute("ALTER TABLE oauth2_code DROP CONSTRAINT IF EXISTS uq_oauth2_code_code;") + with op.batch_alter_table("oauth2_code", schema=None) as batch_op: + batch_op.create_unique_constraint(batch_op.f("uq_oauth2_code_code"), ["code"]) + + op.execute( + "ALTER TABLE oauth2_token DROP CONSTRAINT IF EXISTS oauth2_token_access_token_key;" + ) + op.execute( + "ALTER TABLE oauth2_token DROP CONSTRAINT IF EXISTS uq_oauth2_token_access_token;" + ) + with op.batch_alter_table("oauth2_token", schema=None) as batch_op: + batch_op.create_unique_constraint( + batch_op.f("uq_oauth2_token_access_token"), ["access_token"] + ) + + op.execute("ALTER TABLE role DROP CONSTRAINT IF EXISTS role_name_key;") + op.execute("ALTER TABLE role DROP CONSTRAINT IF EXISTS uq_role_name;") + with op.batch_alter_table("role", schema=None) as batch_op: + batch_op.create_unique_constraint(batch_op.f("uq_role_name"), ["name"]) + + op.execute("ALTER TABLE public.user DROP CONSTRAINT IF EXISTS user_email_key;") + op.execute("ALTER TABLE public.user DROP CONSTRAINT IF EXISTS uq_user_email;") + with op.batch_alter_table("user", schema=None) as batch_op: + batch_op.create_unique_constraint(batch_op.f("uq_user_email"), ["email"]) + + op.execute( + "ALTER TABLE user_favoriteevents DROP CONSTRAINT IF EXISTS user_favoriteevents_user_id_event_id_key;" + ) + op.execute( + "ALTER TABLE user_favoriteevents DROP CONSTRAINT IF EXISTS uq_user_favoriteevents_user_id;" + ) + with op.batch_alter_table("user_favoriteevents", schema=None) as batch_op: + batch_op.create_unique_constraint( + batch_op.f("uq_user_favoriteevents_user_id"), ["user_id", "event_id"] + ) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("user_favoriteevents", schema=None) as batch_op: + batch_op.drop_constraint( + batch_op.f("uq_user_favoriteevents_user_id"), type_="unique" + ) + batch_op.create_unique_constraint( + "user_favoriteevents_user_id_event_id_key", ["user_id", "event_id"] + ) + + with op.batch_alter_table("user", schema=None) as batch_op: + batch_op.drop_constraint(batch_op.f("uq_user_email"), type_="unique") + batch_op.create_unique_constraint("user_email_key", ["email"]) + + with op.batch_alter_table("role", schema=None) as batch_op: + batch_op.drop_constraint(batch_op.f("uq_role_name"), type_="unique") + batch_op.create_unique_constraint("role_name_key", ["name"]) + + with op.batch_alter_table("oauth2_token", schema=None) as batch_op: + batch_op.drop_constraint( + batch_op.f("uq_oauth2_token_access_token"), type_="unique" + ) + batch_op.create_unique_constraint( + "oauth2_token_access_token_key", ["access_token"] + ) + + with op.batch_alter_table("oauth2_code", schema=None) as batch_op: + batch_op.drop_constraint(batch_op.f("uq_oauth2_code_code"), type_="unique") + batch_op.create_unique_constraint("oauth2_code_code_key", ["code"]) + + with op.batch_alter_table("flask_dance_oauth", schema=None) as batch_op: + batch_op.drop_constraint( + batch_op.f("uq_flask_dance_oauth_provider_user_id"), type_="unique" + ) + batch_op.create_unique_constraint( + "flask_dance_oauth_provider_user_id_key", ["provider_user_id"] + ) + + with op.batch_alter_table( + "eventsuggestion_eventcategories", schema=None + ) as batch_op: + batch_op.drop_constraint( + batch_op.f("uq_eventsuggestion_eventcategories_event_suggestion_id"), + type_="unique", + ) + batch_op.create_unique_constraint( + "eventsuggestion_eventcategori_event_suggestion_id_category__key", + ["event_suggestion_id", "category_id"], + ) + + with op.batch_alter_table("eventplace", schema=None) as batch_op: + batch_op.drop_constraint(batch_op.f("uq_eventplace_name"), type_="unique") + batch_op.create_unique_constraint( + "eventplace_name_admin_unit_id_key", ["name", "admin_unit_id"] + ) + + with op.batch_alter_table("eventorganizer", schema=None) as batch_op: + batch_op.drop_constraint(batch_op.f("uq_eventorganizer_name"), type_="unique") + batch_op.create_unique_constraint( + "eventorganizer_name_admin_unit_id_key", ["name", "admin_unit_id"] + ) + + with op.batch_alter_table("eventcategory", schema=None) as batch_op: + batch_op.drop_constraint(batch_op.f("uq_eventcategory_name"), type_="unique") + batch_op.create_unique_constraint("eventcategory_name_key", ["name"]) + + with op.batch_alter_table("event_eventlists", schema=None) as batch_op: + batch_op.drop_constraint( + batch_op.f("uq_event_eventlists_event_id"), type_="unique" + ) + batch_op.create_unique_constraint( + "event_eventlists_event_id_list_id_key", ["event_id", "list_id"] + ) + + with op.batch_alter_table("event_eventcategories", schema=None) as batch_op: + batch_op.drop_constraint( + batch_op.f("uq_event_eventcategories_event_id"), type_="unique" + ) + batch_op.create_unique_constraint( + "event_eventcategories_event_id_category_id_key", + ["event_id", "category_id"], + ) + + with op.batch_alter_table("event_coorganizers", schema=None) as batch_op: + batch_op.drop_constraint( + batch_op.f("uq_event_coorganizers_event_id"), type_="unique" + ) + batch_op.create_unique_constraint( + "event_coorganizers_event_id_organizer_id_key", ["event_id", "organizer_id"] + ) + + with op.batch_alter_table("adminunitrelation", schema=None) as batch_op: + batch_op.drop_constraint( + batch_op.f("uq_adminunitrelation_source_admin_unit_id"), type_="unique" + ) + batch_op.create_unique_constraint( + "adminunitrelation_source_admin_unit_id_target_admin_unit_id_key", + ["source_admin_unit_id", "target_admin_unit_id"], + ) + + with op.batch_alter_table("adminunitmemberrole", schema=None) as batch_op: + batch_op.drop_constraint( + batch_op.f("uq_adminunitmemberrole_name"), type_="unique" + ) + batch_op.create_unique_constraint("adminunitmemberrole_name_key", ["name"]) + + with op.batch_alter_table("adminunitmemberinvitation", schema=None) as batch_op: + batch_op.drop_constraint( + batch_op.f("uq_adminunitmemberinvitation_email"), type_="unique" + ) + batch_op.create_unique_constraint( + "adminunitmemberinvitation_email_admin_unit_id_key", + ["email", "admin_unit_id"], + ) + + with op.batch_alter_table("adminunit", schema=None) as batch_op: + batch_op.drop_constraint( + batch_op.f("fk_adminunit_deletion_requested_by_id_user"), type_="foreignkey" + ) + batch_op.drop_constraint(batch_op.f("uq_adminunit_short_name"), type_="unique") + batch_op.drop_constraint(batch_op.f("uq_adminunit_name"), type_="unique") + batch_op.create_unique_constraint("adminunit_short_name_key", ["short_name"]) + batch_op.create_unique_constraint("adminunit_name_key", ["name"]) + batch_op.drop_column("deletion_requested_by_id") + batch_op.drop_column("deletion_requested_at") + + # ### end Alembic commands ### diff --git a/project/__init__.py b/project/__init__.py index c01a27b..9e8159f 100644 --- a/project/__init__.py +++ b/project/__init__.py @@ -12,6 +12,7 @@ from flask_qrcode import QRcode from flask_security import Security, SQLAlchemySessionUserDatastore from flask_sqlalchemy import SQLAlchemy from flask_wtf.csrf import CSRFProtect +from sqlalchemy import MetaData from project.custom_session_interface import CustomSessionInterface @@ -183,7 +184,15 @@ if app.config["MAIL_SUPPRESS_SEND"]: email_dispatched.connect(log_message) # Create db -db = SQLAlchemy(app) +convention = { + "ix": "ix_%(column_0_label)s", + "uq": "uq_%(table_name)s_%(column_0_name)s", + "ck": "ck_%(table_name)s_%(constraint_name)s", + "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s", + "pk": "pk_%(table_name)s", +} +metadata = MetaData(naming_convention=convention) +db = SQLAlchemy(app, metadata=metadata) migrate = Migrate(app, db) # Celery tasks diff --git a/project/celery_tasks.py b/project/celery_tasks.py index e6daea8..883f244 100644 --- a/project/celery_tasks.py +++ b/project/celery_tasks.py @@ -1,3 +1,4 @@ +from celery import group from celery.schedules import crontab from project import celery @@ -7,6 +8,9 @@ from project import celery def setup_periodic_tasks(sender, **kwargs): sender.add_periodic_task(crontab(hour=0, minute=0), clear_images_task) sender.add_periodic_task(crontab(hour=0, minute=5), clear_admin_unit_dumps_task) + sender.add_periodic_task( + crontab(hour=0, minute=30), delete_admin_units_with_due_request_task + ) sender.add_periodic_task(crontab(hour=1, minute=0), update_recurring_dates_task) sender.add_periodic_task(crontab(hour=2, minute=0), dump_all_task) sender.add_periodic_task(crontab(hour=3, minute=0), seo_generate_sitemap_task) @@ -63,6 +67,36 @@ def clear_admin_unit_dumps_task(): clear_admin_unit_dumps() +@celery.task( + acks_late=True, + reject_on_worker_lost=True, +) +def delete_admin_units_with_due_request_task(): + from project.services.admin_unit import get_admin_units_with_due_delete_request + + admin_units = get_admin_units_with_due_delete_request() + + if not admin_units: + return + + group(delete_admin_unit_task.s(admin_unit.id) for admin_unit in admin_units).delay() + + +@celery.task( + acks_late=True, + reject_on_worker_lost=True, +) +def delete_admin_unit_task(admin_unit_id): + from project.services.admin_unit import delete_admin_unit, get_admin_unit_by_id + + admin_unit = get_admin_unit_by_id(admin_unit_id) + + if not admin_unit: + return + + delete_admin_unit(admin_unit) + + @celery.task( acks_late=True, reject_on_worker_lost=True, diff --git a/project/forms/admin_unit.py b/project/forms/admin_unit.py index d4ae022..4e2af7e 100644 --- a/project/forms/admin_unit.py +++ b/project/forms/admin_unit.py @@ -127,3 +127,13 @@ class UpdateAdminUnitWidgetForm(FlaskForm): validators=[Optional()], ) submit = SubmitField(lazy_gettext("Update settings")) + + +class RequestAdminUnitDeletionForm(FlaskForm): + submit = SubmitField(lazy_gettext("Request deletion")) + name = StringField(lazy_gettext("Name"), validators=[DataRequired()]) + + +class CancelAdminUnitDeletionForm(FlaskForm): + submit = SubmitField(lazy_gettext("Cancel deletion")) + name = StringField(lazy_gettext("Name"), validators=[DataRequired()]) diff --git a/project/models/admin_unit.py b/project/models/admin_unit.py index 7575565..4c5624f 100644 --- a/project/models/admin_unit.py +++ b/project/models/admin_unit.py @@ -2,6 +2,7 @@ from flask_security import AsaList, RoleMixin from sqlalchemy import ( Boolean, Column, + DateTime, ForeignKey, Integer, String, @@ -88,7 +89,9 @@ class AdminUnitRelation(db.Model, TrackableMixin): __tablename__ = "adminunitrelation" __table_args__ = ( UniqueConstraint("source_admin_unit_id", "target_admin_unit_id"), - CheckConstraint("source_admin_unit_id != target_admin_unit_id"), + CheckConstraint( + "source_admin_unit_id != target_admin_unit_id", name="source_neq_target" + ), ) id = Column(Integer(), primary_key=True) source_admin_unit_id = db.Column( @@ -148,6 +151,15 @@ class AdminUnit(db.Model, TrackableMixin): id = Column(Integer(), primary_key=True) name = Column(Unicode(255), unique=True) short_name = Column(Unicode(100), unique=True) + deletion_requested_at = deferred(Column(DateTime, nullable=True), group="deletion") + deletion_requested_by_id = deferred( + Column(ForeignKey("user.id"), nullable=True), group="deletion" + ) + deletion_requested_by = relationship( + "User", + primaryjoin="User.id == AdminUnit.deletion_requested_by_id", + remote_side="User.id", + ) members = relationship( "AdminUnitMember", cascade="all, delete-orphan", diff --git a/project/models/event_suggestion.py b/project/models/event_suggestion.py index 975906e..a71fa84 100644 --- a/project/models/event_suggestion.py +++ b/project/models/event_suggestion.py @@ -30,8 +30,12 @@ class EventSuggestion(db.Model, TrackableMixin, EventMixin): __table_args__ = ( CheckConstraint( "NOT(event_place_id IS NULL AND event_place_text IS NULL)", + name="place_not_null", + ), + CheckConstraint( + "NOT(organizer_id IS NULL AND organizer_text IS NULL)", + name="organizer_not_null", ), - CheckConstraint("NOT(organizer_id IS NULL AND organizer_text IS NULL)"), ) id = Column(Integer(), primary_key=True) diff --git a/project/services/admin_unit.py b/project/services/admin_unit.py index bf3d1ea..681308f 100644 --- a/project/services/admin_unit.py +++ b/project/services/admin_unit.py @@ -1,3 +1,5 @@ +import datetime + from sqlalchemy import and_, func, or_ from project import db @@ -312,3 +314,13 @@ def create_ical_events_for_admin_unit( params.can_read_private_events = False return create_ical_events_for_search(params) + + +def get_admin_units_with_due_delete_request(): + due = datetime.datetime.utcnow() - datetime.timedelta(days=3) + return AdminUnit.query.filter(AdminUnit.deletion_requested_at < due).all() + + +def delete_admin_unit(admin_unit: AdminUnit): + db.session.delete(admin_unit) + db.session.commit() diff --git a/project/templates/admin/admin_units.html b/project/templates/admin/admin_units.html index adacb24..d7835e1 100644 --- a/project/templates/admin/admin_units.html +++ b/project/templates/admin/admin_units.html @@ -19,6 +19,7 @@ {{ _('Name') }} created_at + deletion_requested_at @@ -28,6 +29,7 @@ {{ admin_unit.name }} {{ render_admin_unit_badges(admin_unit) }} {% if admin_unit.created_at %}{{ admin_unit.created_at | dateformat }}{% endif %} + {% if admin_unit.deletion_requested_at %}{{ admin_unit.deletion_requested_at | dateformat }}{% endif %} {{ _('Manage') }} {{ _('View') }} diff --git a/project/templates/admin_unit/cancel_deletion.html b/project/templates/admin_unit/cancel_deletion.html new file mode 100644 index 0000000..a3006dd --- /dev/null +++ b/project/templates/admin_unit/cancel_deletion.html @@ -0,0 +1,24 @@ +{% extends "layout.html" %} +{% from "_macros.html" import render_field_with_errors, render_field %} + +{% block content %} + +

{{ _('Cancel deletion') }} "{{ admin_unit.name }}"

+ +
+ {{ form.hidden_tag() }} + +
+
+ {{ _('Organization') }} +
+
+ {{ render_field_with_errors(form.name) }} +
+
+ + {{ render_field(form.submit) }} + +
+ +{% endblock %} diff --git a/project/templates/admin_unit/request_deletion.html b/project/templates/admin_unit/request_deletion.html new file mode 100644 index 0000000..7f4ee93 --- /dev/null +++ b/project/templates/admin_unit/request_deletion.html @@ -0,0 +1,26 @@ +{% extends "layout.html" %} +{% from "_macros.html" import render_field_with_errors, render_field %} + +{% block content %} + +

{{ _('Delete organization') }} "{{ admin_unit.name }}"

+ +

{{ _('The organization is not deleted immediately. After a period of time, the organization will be deleted. Until then, the deletion can be canceled.') }}

+ +
+ {{ form.hidden_tag() }} + +
+
+ {{ _('Organization') }} +
+
+ {{ render_field_with_errors(form.name) }} +
+
+ + {{ render_field(form.submit) }} + +
+ +{% endblock %} diff --git a/project/templates/admin_unit/update.html b/project/templates/admin_unit/update.html index ff53f5b..fd3cfeb 100644 --- a/project/templates/admin_unit/update.html +++ b/project/templates/admin_unit/update.html @@ -20,6 +20,13 @@ {% block content %} +{% if admin_unit.deletion_requested_at %} + +{% endif %} +

{{ _('Settings') }}

{{ form.hidden_tag() }} @@ -81,4 +88,10 @@ {{ render_field(form.submit) }}
+{% if not admin_unit.deletion_requested_at %} + +{% endif %} + {% endblock %} diff --git a/project/templates/email/organization_deletion_requested_notice.html b/project/templates/email/organization_deletion_requested_notice.html new file mode 100644 index 0000000..b6a2155 --- /dev/null +++ b/project/templates/email/organization_deletion_requested_notice.html @@ -0,0 +1,6 @@ +{% extends "email/layout.html" %} +{% from "_macros.html" import render_email_button %} +{% block content %} +

{{ _('%(admin_unit_name)s is scheduled for deletion.', admin_unit_name=admin_unit.name) }}

+{{ render_email_button(url_for('admin_unit_cancel_deletion', id=admin_unit.id, _external=True), _('Click here to view the invitation')) }} +{% endblock %} \ No newline at end of file diff --git a/project/templates/email/organization_deletion_requested_notice.txt b/project/templates/email/organization_deletion_requested_notice.txt new file mode 100644 index 0000000..92db255 --- /dev/null +++ b/project/templates/email/organization_deletion_requested_notice.txt @@ -0,0 +1,3 @@ +{{ _('%(admin_unit_name)s is scheduled for deletion.', admin_unit_name=admin_unit.name) }} +{{ _('Click the link below to cancel the deletion') }} +{{ url_for('admin_unit_cancel_deletion', id=admin_unit.id, _external=True) }} diff --git a/project/templates/manage/events.html b/project/templates/manage/events.html index 7b45272..9112b5d 100644 --- a/project/templates/manage/events.html +++ b/project/templates/manage/events.html @@ -43,6 +43,12 @@ var vue_app_data = { eventId: 0 }; {{ _('Verify organization') }} {% endif %} +{% if admin_unit.deletion_requested_at %} + +{% endif %}

{{ _('Events') }} diff --git a/project/translations/de/LC_MESSAGES/messages.mo b/project/translations/de/LC_MESSAGES/messages.mo index 559f7a3ab3963ca064dcd8fe08655a9088020738..0ee23a417a082dca0c64491a164156091b70cf57 100644 GIT binary patch delta 9121 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 Zk1u`Iv&##6|L^hs|2VMf&=t>y{S(9Y?{)wH delta 8537 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 ewJrP=^U}g_4@9i|>CI\n" "Language: de\n" @@ -212,7 +212,7 @@ msgstr "Impressum" #: project/forms/admin.py:13 project/templates/_macros.html:1473 #: project/templates/layout.html:311 #: project/templates/widget/event_suggestion/create.html:204 -#: project/views/admin_unit.py:73 project/views/root.py:71 +#: project/views/admin_unit.py:82 project/views/root.py:71 msgid "Contact" msgstr "Kontakt" @@ -311,10 +311,13 @@ msgid "Update organization" msgstr "Organisation aktualisieren" #: project/forms/admin.py:68 project/templates/admin/delete_admin_unit.html:6 +#: project/templates/admin_unit/request_deletion.html:6 +#: project/templates/admin_unit/update.html:93 msgid "Delete organization" msgstr "Organisation löschen" #: project/forms/admin.py:69 project/forms/admin_unit.py:28 +#: project/forms/admin_unit.py:134 project/forms/admin_unit.py:139 #: project/forms/event.py:85 project/forms/event.py:114 #: project/forms/event_place.py:25 project/forms/event_place.py:50 #: project/forms/event_suggestion.py:26 project/forms/oauth2_client.py:66 @@ -491,6 +494,17 @@ msgstr "Hauptfarbe" msgid "Link Color" msgstr "Linkfarbe" +#: project/forms/admin_unit.py:133 +msgid "Request deletion" +msgstr "Löschung beantragen" + +#: project/forms/admin_unit.py:138 +#: project/templates/admin_unit/cancel_deletion.html:6 +#: project/templates/admin_unit/update.html:26 +#: project/templates/manage/events.html:49 +msgid "Cancel deletion" +msgstr "Löschen abbrechen" + #: project/forms/admin_unit_member.py:13 msgid "Invite" msgstr "Einladen" @@ -600,7 +614,7 @@ msgstr "50 km" msgid "100 km" msgstr "100 km" -#: project/forms/event.py:38 project/templates/manage/events.html:94 +#: project/forms/event.py:38 project/templates/manage/events.html:100 msgid "Start" msgstr "Beginn" @@ -794,8 +808,8 @@ msgid "Choose how people can attend the event." msgstr "Wähle aus, wie Personen an der Veranstaltung teilnehmen können." #: project/forms/event.py:225 project/forms/event_place.py:27 -#: project/templates/manage/events.html:98 -#: project/templates/manage/events.html:133 +#: project/templates/manage/events.html:104 +#: project/templates/manage/events.html:139 #: project/templates/manage/places.html:21 #: project/templates/manage/places.html:39 #: project/templates/widget/event_suggestion/create.html:258 @@ -869,7 +883,7 @@ msgstr "Ungültiger Mitveranstalter." #: project/templates/event_place/create.html:31 #: project/templates/event_place/delete.html:13 #: project/templates/event_place/update.html:31 -#: project/templates/manage/events.html:97 +#: project/templates/manage/events.html:103 msgid "Place" msgstr "Ort" @@ -886,7 +900,7 @@ msgstr "Neuen Ort eingeben" #: project/forms/event_suggestion.py:60 project/templates/_macros.html:475 #: project/templates/_macros.html:647 project/templates/event/create.html:253 #: project/templates/event/update.html:156 -#: project/templates/manage/events.html:96 +#: project/templates/manage/events.html:102 #: project/templates/organizer/create.html:27 #: project/templates/organizer/delete.html:13 #: project/templates/organizer/update.html:27 @@ -1015,7 +1029,7 @@ msgstr "Kategorie" #: project/forms/event.py:446 project/forms/event_date.py:24 #: project/forms/planing.py:22 project/templates/_macros.html:305 #: project/templates/admin_unit/create.html:38 -#: project/templates/admin_unit/update.html:39 +#: project/templates/admin_unit/update.html:46 #: project/templates/event_place/create.html:40 #: project/templates/event_place/update.html:40 #: project/templates/manage/organizers.html:19 @@ -1169,8 +1183,10 @@ msgstr "Wochentage" #: project/forms/reference.py:11 project/forms/reference_request.py:16 #: project/templates/_macros.html:491 project/templates/_macros.html:664 #: project/templates/admin/delete_admin_unit.html:13 +#: project/templates/admin_unit/cancel_deletion.html:13 #: project/templates/admin_unit/create.html:28 -#: project/templates/admin_unit/update.html:29 +#: project/templates/admin_unit/request_deletion.html:15 +#: project/templates/admin_unit/update.html:36 #: project/templates/layout.html:247 msgid "Organization" msgstr "Organisation" @@ -1319,7 +1335,7 @@ msgstr "Zuletzt aktualisiert am %(updated_at)s." #: project/templates/event/actions.html:25 #: project/templates/event/create.html:230 #: project/templates/event/update.html:122 -#: project/templates/manage/events.html:95 +#: project/templates/manage/events.html:101 #: project/templates/widget/event_suggestion/create.html:229 msgid "Event" msgstr "Veranstaltung" @@ -1364,9 +1380,9 @@ msgstr "Merkzettel" #: project/templates/_macros.html:590 project/templates/_macros.html:633 #: project/templates/_macros.html:765 -#: project/templates/admin/admin_units.html:34 +#: project/templates/admin/admin_units.html:36 #: project/templates/admin/users.html:32 -#: project/templates/manage/events.html:110 +#: project/templates/manage/events.html:116 #: project/templates/manage/members.html:35 #: project/templates/manage/organizers.html:33 #: project/templates/manage/places.html:31 @@ -1382,7 +1398,7 @@ msgstr "Empfohlen von" #: project/templates/_macros.html:703 project/templates/_macros.html:1297 #: project/templates/event/actions.html:38 -#: project/templates/manage/events.html:117 +#: project/templates/manage/events.html:123 #: project/templates/manage/references_incoming.html:10 msgid "Reference event" msgstr "Veranstaltung empfehlen" @@ -1395,7 +1411,7 @@ msgstr "Empfehlungsanfragen" #: project/templates/_macros.html:723 project/templates/_macros.html:1294 #: project/templates/event/actions.html:32 -#: project/templates/manage/events.html:115 +#: project/templates/manage/events.html:121 msgid "Request reference" msgstr "Empfehlung anfragen" @@ -1480,11 +1496,11 @@ msgid "Duplicate event" msgstr "Veranstaltung duplizieren" #: project/templates/_macros.html:1301 project/templates/event/actions.html:44 -#: project/templates/manage/events.html:121 +#: project/templates/manage/events.html:127 msgid "Add to list" msgstr "Zu Liste hinzufügen" -#: project/templates/_macros.html:1304 project/templates/manage/events.html:124 +#: project/templates/_macros.html:1304 project/templates/manage/events.html:130 msgid "More" msgstr "Mehr" @@ -1539,7 +1555,7 @@ msgstr "Veranstalter eingeben" msgid "Enter list name" msgstr "Listenname eingeben" -#: project/templates/admin/admin_units.html:32 project/templates/home.html:25 +#: project/templates/admin/admin_units.html:34 project/templates/home.html:25 msgid "Manage" msgstr "Verwaltung" @@ -1557,7 +1573,7 @@ msgstr "Features" #: project/templates/layout.html:157 project/templates/layout.html:205 #: project/templates/manage/events.html:6 -#: project/templates/manage/events.html:48 +#: project/templates/manage/events.html:54 #: project/templates/manage/events_vue.html:4 msgid "Events" msgstr "Veranstaltungen" @@ -1603,7 +1619,7 @@ msgstr "Veranstaltungen anzeigen" #: project/templates/event/create.html:5 #: project/templates/event/create.html:221 project/templates/layout.html:212 -#: project/templates/manage/events.html:49 +#: project/templates/manage/events.html:55 #: project/templates/manage/organizers.html:38 msgid "Create event" msgstr "Veranstaltung erstellen" @@ -1680,7 +1696,7 @@ msgstr "Organisationseinladungen" #: project/templates/admin/settings.html:4 #: project/templates/admin/settings.html:11 #: project/templates/admin_unit/update.html:6 -#: project/templates/admin_unit/update.html:23 +#: project/templates/admin_unit/update.html:30 #: project/templates/layout.html:259 project/templates/manage/widgets.html:11 #: project/templates/manage/widgets.html:15 project/templates/profile.html:19 msgid "Settings" @@ -1734,17 +1750,17 @@ msgstr "OAuth2 Clients" msgid "Users" msgstr "Nutzer" -#: project/templates/admin/admin_units.html:33 -#: project/templates/manage/events.html:109 +#: project/templates/admin/admin_units.html:35 +#: project/templates/manage/events.html:115 #: project/templates/manage/organizers.html:32 #: project/templates/manage/references_incoming.html:19 #: project/templates/manage/references_outgoing.html:19 msgid "View" msgstr "Anzeigen" -#: project/templates/admin/admin_units.html:35 +#: project/templates/admin/admin_units.html:37 #: project/templates/admin/users.html:33 -#: project/templates/manage/events.html:111 +#: project/templates/manage/events.html:117 #: project/templates/manage/members.html:21 #: project/templates/manage/members.html:36 #: project/templates/manage/organizers.html:34 @@ -1758,7 +1774,7 @@ msgstr "Löschen" msgid "User" msgstr "Nutzer" -#: project/templates/admin/email.html:47 project/views/admin.py:155 +#: project/templates/admin/email.html:47 project/views/admin.py:144 msgid "Mail sent successfully" msgstr "Mail erfolgreich gesendet" @@ -1775,7 +1791,7 @@ msgid "Mails sent successfully" msgstr "Mails erfolgreich gesendet" #: project/templates/admin_unit/create.html:58 -#: project/templates/admin_unit/update.html:59 +#: project/templates/admin_unit/update.html:66 #: project/templates/event/create.html:347 #: project/templates/event/update.html:204 #: project/templates/event_place/create.html:57 @@ -1795,7 +1811,18 @@ msgstr "Beziehung zu %(admin_unit_name)s" msgid "Invite user" msgstr "Nutzer:in einladen" -#: project/templates/admin_unit/update.html:72 +#: project/templates/admin_unit/request_deletion.html:8 +msgid "" +"The organization is not deleted immediately. After a period of time, the " +"organization will be deleted. Until then, the deletion can be canceled." +msgstr "Die Organisation wird nicht sofort gelöscht. Nach einer Frist wird die Organisation gelöscht. Bis dahin kann die Löschung abgebrochen werden." + +#: project/templates/admin_unit/update.html:25 +#: project/templates/manage/events.html:48 +msgid "The organization is scheduled for deletion." +msgstr "Die Organisation ist zur Löschung vorgesehen." + +#: project/templates/admin_unit/update.html:79 #: project/templates/manage/verification_requests_outgoing.html:6 #: project/templates/manage/verification_requests_outgoing.html:11 msgid "Verification requests" @@ -1830,6 +1857,7 @@ msgid "You have been invited to join %(admin_unit_name)s." msgstr "Du wurdest eingeladen, %(admin_unit_name)s beizutreten." #: project/templates/email/invitation_notice.html:5 +#: project/templates/email/organization_deletion_requested_notice.html:5 #: project/templates/email/organization_invitation_notice.html:5 msgid "Click here to view the invitation" msgstr "Klicke hier, um die Einladung anzunehmen." @@ -1847,6 +1875,11 @@ msgstr "das ist eine Nachricht von %(site_name)s." msgid "Notification settings" msgstr "Benachrichtigungseinstellungen" +#: project/templates/email/organization_deletion_requested_notice.html:4 +#, python-format +msgid "%(admin_unit_name)s is scheduled for deletion." +msgstr "%(admin_unit_name)s ist zur Löschung vorgesehen." + #: project/templates/email/organization_invitation_accepted_notice.html:4 #, python-format msgid "" @@ -1948,7 +1981,7 @@ msgstr "Zeige alle Veranstaltungen von %(admin_unit_name)s" #: project/templates/event/actions.html:74 project/templates/event/read.html:32 #: project/templates/event_date/read.html:34 -#: project/templates/manage/events.html:145 +#: project/templates/manage/events.html:151 msgid "Add event to list" msgstr "Veranstaltung zu Liste hinzufügen" @@ -2074,21 +2107,21 @@ msgstr "" msgid "Verify organization" msgstr "Organisation verifizieren" -#: project/templates/manage/events.html:85 +#: project/templates/manage/events.html:91 msgid "More filters" msgstr "Mehr Filter" -#: project/templates/manage/events.html:99 -#: project/templates/manage/events.html:134 +#: project/templates/manage/events.html:105 +#: project/templates/manage/events.html:140 msgid "Number of references" msgstr "Anzahl an Empfehlungen" -#: project/templates/manage/events.html:99 -#: project/templates/manage/events.html:134 +#: project/templates/manage/events.html:105 +#: project/templates/manage/events.html:140 msgid "Number of reference requests." msgstr "Anzahl an Empfehlungsanfragen" -#: project/templates/manage/events.html:112 +#: project/templates/manage/events.html:118 msgid "Duplicate" msgstr "Duplizieren" @@ -2260,46 +2293,47 @@ msgstr "Optionale Details" msgid "Preview" msgstr "Vorschau" -#: project/views/admin.py:60 +#: project/views/admin.py:63 msgid "Organization successfully updated" msgstr "Organisation erfolgreich aktualisiert" -#: project/views/admin.py:82 +#: project/views/admin.py:85 project/views/admin_unit.py:182 +#: project/views/admin_unit.py:215 msgid "Entered name does not match organization name" msgstr "Der eingegebene Name entspricht nicht dem Namen der Organisation" -#: project/views/admin.py:87 +#: project/views/admin.py:89 msgid "Organization successfully deleted" msgstr "Organisation erfolgreich gelöscht" -#: project/views/admin.py:111 project/views/manage.py:443 +#: project/views/admin.py:113 project/views/manage.py:432 #: project/views/user.py:28 msgid "Settings successfully updated" msgstr "Einstellungen erfolgreich aktualisiert" -#: project/views/admin.py:144 +#: project/views/admin.py:133 #, python-format msgid "Test mail from %(site_name)s" msgstr "Test-Mail von %(site_name)s" -#: project/views/admin.py:187 +#: project/views/admin.py:162 #, python-format msgid "Newsletter from %(site_name)s" msgstr "Newsletter von %(site_name)s" -#: project/views/admin.py:237 +#: project/views/admin.py:212 msgid "User successfully updated" msgstr "Nutzer erfolgreich aktualisiert" -#: project/views/admin.py:257 +#: project/views/admin.py:232 msgid "Entered email does not match user email" msgstr "Die eingegebene Email passt nicht zur Email des Nutzers" -#: project/views/admin.py:262 +#: project/views/admin.py:237 msgid "User successfully deleted" msgstr "Nutzer erfolgreich gelöscht" -#: project/views/admin_unit.py:69 +#: project/views/admin_unit.py:78 msgid "" "Organizations cannot currently be created. The project is in a closed " "test phase. If you are interested, you can contact us." @@ -2308,18 +2342,22 @@ msgstr "" " sich in einer geschlossenen Test-Phase. Bei Interesse kannst du uns " "kontaktieren." -#: project/views/admin_unit.py:119 +#: project/views/admin_unit.py:128 msgid "Organization successfully created" msgstr "Organisation erfolgreich erstellt" -#: project/views/admin_unit.py:149 +#: project/views/admin_unit.py:158 msgid "AdminUnit successfully updated" msgstr "Organisation erfolgreich aktualisiert" -#: project/views/admin_unit.py:171 +#: project/views/admin_unit.py:245 msgid "Organization invitation accepted" msgstr "Organisationseinladung akzeptiert" +#: project/views/admin_unit.py:259 +msgid "Organization deletion requested" +msgstr "Löschung der Organisation beantragt" + #: project/views/admin_unit_member.py:43 msgid "Member successfully updated" msgstr "Mitglied erfolgreich aktualisiert" @@ -2507,7 +2545,7 @@ msgstr "" "Ob alle zukünftigen Empfehlungsanfragen von %(admin_unit_name)s " "automatisch verifiziert werden sollen." -#: project/views/utils.py:65 +#: project/views/utils.py:71 msgid "" "An entry with the entered values ​​already exists. Duplicate entries are " "not allowed." @@ -2515,20 +2553,20 @@ msgstr "" "Ein Eintrag mit den eingegebenen Werten existiert bereits. Doppelte " "Einträge sind nicht erlaubt." -#: project/views/utils.py:116 +#: project/views/utils.py:122 #, python-format msgid "Error in the %s field - %s" msgstr "Fehler im Feld %s: %s" -#: project/views/utils.py:123 +#: project/views/utils.py:129 msgid "Show" msgstr "Anzeigen" -#: project/views/utils.py:131 +#: project/views/utils.py:137 msgid "You do not have permission for this action" msgstr "Du hast keine Berechtigung für diese Aktion" -#: project/views/utils.py:252 +#: project/views/utils.py:258 msgid "" "The invitation was issued to another user. Sign in with the email address" " the invitation was sent to." diff --git a/project/translations/en/LC_MESSAGES/messages.mo b/project/translations/en/LC_MESSAGES/messages.mo index e565d2f6c847dc644217e9049d5e9fa58aa834be..a431fc4a0f070ac49bea6875ef54a69d71396ad0 100644 GIT binary patch delta 20 ccmaDa|6YE>Yfg4^1w&ISLxatqIRCN&09NY<&;S4c delta 20 ccmaDa|6YE>Yfg4U1w$h%Bg@U7IRCN&09M5Z(EtDd diff --git a/project/translations/en/LC_MESSAGES/messages.po b/project/translations/en/LC_MESSAGES/messages.po index ecf2bd2..6ec5d84 100644 --- a/project/translations/en/LC_MESSAGES/messages.po +++ b/project/translations/en/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: 2023-04-21 12:29+0200\n" +"POT-Creation-Date: 2023-04-27 15:10+0200\n" "PO-Revision-Date: 2021-04-30 15:04+0200\n" "Last-Translator: FULL NAME \n" "Language: en\n" @@ -212,7 +212,7 @@ msgstr "" #: project/forms/admin.py:13 project/templates/_macros.html:1473 #: project/templates/layout.html:311 #: project/templates/widget/event_suggestion/create.html:204 -#: project/views/admin_unit.py:73 project/views/root.py:71 +#: project/views/admin_unit.py:82 project/views/root.py:71 msgid "Contact" msgstr "" @@ -303,10 +303,13 @@ msgid "Update organization" msgstr "" #: project/forms/admin.py:68 project/templates/admin/delete_admin_unit.html:6 +#: project/templates/admin_unit/request_deletion.html:6 +#: project/templates/admin_unit/update.html:93 msgid "Delete organization" msgstr "" #: project/forms/admin.py:69 project/forms/admin_unit.py:28 +#: project/forms/admin_unit.py:134 project/forms/admin_unit.py:139 #: project/forms/event.py:85 project/forms/event.py:114 #: project/forms/event_place.py:25 project/forms/event_place.py:50 #: project/forms/event_suggestion.py:26 project/forms/oauth2_client.py:66 @@ -472,6 +475,17 @@ msgstr "" msgid "Link Color" msgstr "" +#: project/forms/admin_unit.py:133 +msgid "Request deletion" +msgstr "" + +#: project/forms/admin_unit.py:138 +#: project/templates/admin_unit/cancel_deletion.html:6 +#: project/templates/admin_unit/update.html:26 +#: project/templates/manage/events.html:49 +msgid "Cancel deletion" +msgstr "" + #: project/forms/admin_unit_member.py:13 msgid "Invite" msgstr "" @@ -578,7 +592,7 @@ msgstr "" msgid "100 km" msgstr "" -#: project/forms/event.py:38 project/templates/manage/events.html:94 +#: project/forms/event.py:38 project/templates/manage/events.html:100 msgid "Start" msgstr "" @@ -763,8 +777,8 @@ msgid "Choose how people can attend the event." msgstr "" #: project/forms/event.py:225 project/forms/event_place.py:27 -#: project/templates/manage/events.html:98 -#: project/templates/manage/events.html:133 +#: project/templates/manage/events.html:104 +#: project/templates/manage/events.html:139 #: project/templates/manage/places.html:21 #: project/templates/manage/places.html:39 #: project/templates/widget/event_suggestion/create.html:258 @@ -830,7 +844,7 @@ msgstr "" #: project/templates/event_place/create.html:31 #: project/templates/event_place/delete.html:13 #: project/templates/event_place/update.html:31 -#: project/templates/manage/events.html:97 +#: project/templates/manage/events.html:103 msgid "Place" msgstr "" @@ -847,7 +861,7 @@ msgstr "" #: project/forms/event_suggestion.py:60 project/templates/_macros.html:475 #: project/templates/_macros.html:647 project/templates/event/create.html:253 #: project/templates/event/update.html:156 -#: project/templates/manage/events.html:96 +#: project/templates/manage/events.html:102 #: project/templates/organizer/create.html:27 #: project/templates/organizer/delete.html:13 #: project/templates/organizer/update.html:27 @@ -972,7 +986,7 @@ msgstr "" #: project/forms/event.py:446 project/forms/event_date.py:24 #: project/forms/planing.py:22 project/templates/_macros.html:305 #: project/templates/admin_unit/create.html:38 -#: project/templates/admin_unit/update.html:39 +#: project/templates/admin_unit/update.html:46 #: project/templates/event_place/create.html:40 #: project/templates/event_place/update.html:40 #: project/templates/manage/organizers.html:19 @@ -1122,8 +1136,10 @@ msgstr "" #: project/forms/reference.py:11 project/forms/reference_request.py:16 #: project/templates/_macros.html:491 project/templates/_macros.html:664 #: project/templates/admin/delete_admin_unit.html:13 +#: project/templates/admin_unit/cancel_deletion.html:13 #: project/templates/admin_unit/create.html:28 -#: project/templates/admin_unit/update.html:29 +#: project/templates/admin_unit/request_deletion.html:15 +#: project/templates/admin_unit/update.html:36 #: project/templates/layout.html:247 msgid "Organization" msgstr "" @@ -1272,7 +1288,7 @@ msgstr "" #: project/templates/event/actions.html:25 #: project/templates/event/create.html:230 #: project/templates/event/update.html:122 -#: project/templates/manage/events.html:95 +#: project/templates/manage/events.html:101 #: project/templates/widget/event_suggestion/create.html:229 msgid "Event" msgstr "" @@ -1315,9 +1331,9 @@ msgstr "" #: project/templates/_macros.html:590 project/templates/_macros.html:633 #: project/templates/_macros.html:765 -#: project/templates/admin/admin_units.html:34 +#: project/templates/admin/admin_units.html:36 #: project/templates/admin/users.html:32 -#: project/templates/manage/events.html:110 +#: project/templates/manage/events.html:116 #: project/templates/manage/members.html:35 #: project/templates/manage/organizers.html:33 #: project/templates/manage/places.html:31 @@ -1333,7 +1349,7 @@ msgstr "" #: project/templates/_macros.html:703 project/templates/_macros.html:1297 #: project/templates/event/actions.html:38 -#: project/templates/manage/events.html:117 +#: project/templates/manage/events.html:123 #: project/templates/manage/references_incoming.html:10 msgid "Reference event" msgstr "" @@ -1346,7 +1362,7 @@ msgstr "" #: project/templates/_macros.html:723 project/templates/_macros.html:1294 #: project/templates/event/actions.html:32 -#: project/templates/manage/events.html:115 +#: project/templates/manage/events.html:121 msgid "Request reference" msgstr "" @@ -1431,11 +1447,11 @@ msgid "Duplicate event" msgstr "" #: project/templates/_macros.html:1301 project/templates/event/actions.html:44 -#: project/templates/manage/events.html:121 +#: project/templates/manage/events.html:127 msgid "Add to list" msgstr "" -#: project/templates/_macros.html:1304 project/templates/manage/events.html:124 +#: project/templates/_macros.html:1304 project/templates/manage/events.html:130 msgid "More" msgstr "" @@ -1490,7 +1506,7 @@ msgstr "" msgid "Enter list name" msgstr "" -#: project/templates/admin/admin_units.html:32 project/templates/home.html:25 +#: project/templates/admin/admin_units.html:34 project/templates/home.html:25 msgid "Manage" msgstr "" @@ -1508,7 +1524,7 @@ msgstr "" #: project/templates/layout.html:157 project/templates/layout.html:205 #: project/templates/manage/events.html:6 -#: project/templates/manage/events.html:48 +#: project/templates/manage/events.html:54 #: project/templates/manage/events_vue.html:4 msgid "Events" msgstr "" @@ -1554,7 +1570,7 @@ msgstr "" #: project/templates/event/create.html:5 #: project/templates/event/create.html:221 project/templates/layout.html:212 -#: project/templates/manage/events.html:49 +#: project/templates/manage/events.html:55 #: project/templates/manage/organizers.html:38 msgid "Create event" msgstr "" @@ -1631,7 +1647,7 @@ msgstr "" #: project/templates/admin/settings.html:4 #: project/templates/admin/settings.html:11 #: project/templates/admin_unit/update.html:6 -#: project/templates/admin_unit/update.html:23 +#: project/templates/admin_unit/update.html:30 #: project/templates/layout.html:259 project/templates/manage/widgets.html:11 #: project/templates/manage/widgets.html:15 project/templates/profile.html:19 msgid "Settings" @@ -1685,17 +1701,17 @@ msgstr "" msgid "Users" msgstr "" -#: project/templates/admin/admin_units.html:33 -#: project/templates/manage/events.html:109 +#: project/templates/admin/admin_units.html:35 +#: project/templates/manage/events.html:115 #: project/templates/manage/organizers.html:32 #: project/templates/manage/references_incoming.html:19 #: project/templates/manage/references_outgoing.html:19 msgid "View" msgstr "" -#: project/templates/admin/admin_units.html:35 +#: project/templates/admin/admin_units.html:37 #: project/templates/admin/users.html:33 -#: project/templates/manage/events.html:111 +#: project/templates/manage/events.html:117 #: project/templates/manage/members.html:21 #: project/templates/manage/members.html:36 #: project/templates/manage/organizers.html:34 @@ -1709,7 +1725,7 @@ msgstr "" msgid "User" msgstr "" -#: project/templates/admin/email.html:47 project/views/admin.py:155 +#: project/templates/admin/email.html:47 project/views/admin.py:144 msgid "Mail sent successfully" msgstr "" @@ -1726,7 +1742,7 @@ msgid "Mails sent successfully" msgstr "" #: project/templates/admin_unit/create.html:58 -#: project/templates/admin_unit/update.html:59 +#: project/templates/admin_unit/update.html:66 #: project/templates/event/create.html:347 #: project/templates/event/update.html:204 #: project/templates/event_place/create.html:57 @@ -1746,7 +1762,18 @@ msgstr "" msgid "Invite user" msgstr "" -#: project/templates/admin_unit/update.html:72 +#: project/templates/admin_unit/request_deletion.html:8 +msgid "" +"The organization is not deleted immediately. After a period of time, the " +"organization will be deleted. Until then, the deletion can be canceled." +msgstr "" + +#: project/templates/admin_unit/update.html:25 +#: project/templates/manage/events.html:48 +msgid "The organization is scheduled for deletion." +msgstr "" + +#: project/templates/admin_unit/update.html:79 #: project/templates/manage/verification_requests_outgoing.html:6 #: project/templates/manage/verification_requests_outgoing.html:11 msgid "Verification requests" @@ -1781,6 +1808,7 @@ msgid "You have been invited to join %(admin_unit_name)s." msgstr "" #: project/templates/email/invitation_notice.html:5 +#: project/templates/email/organization_deletion_requested_notice.html:5 #: project/templates/email/organization_invitation_notice.html:5 msgid "Click here to view the invitation" msgstr "" @@ -1798,6 +1826,11 @@ msgstr "" msgid "Notification settings" msgstr "" +#: project/templates/email/organization_deletion_requested_notice.html:4 +#, python-format +msgid "%(admin_unit_name)s is scheduled for deletion." +msgstr "" + #: project/templates/email/organization_invitation_accepted_notice.html:4 #, python-format msgid "" @@ -1895,7 +1928,7 @@ msgstr "" #: project/templates/event/actions.html:74 project/templates/event/read.html:32 #: project/templates/event_date/read.html:34 -#: project/templates/manage/events.html:145 +#: project/templates/manage/events.html:151 msgid "Add event to list" msgstr "" @@ -2016,21 +2049,21 @@ msgstr "" msgid "Verify organization" msgstr "" -#: project/templates/manage/events.html:85 +#: project/templates/manage/events.html:91 msgid "More filters" msgstr "" -#: project/templates/manage/events.html:99 -#: project/templates/manage/events.html:134 +#: project/templates/manage/events.html:105 +#: project/templates/manage/events.html:140 msgid "Number of references" msgstr "" -#: project/templates/manage/events.html:99 -#: project/templates/manage/events.html:134 +#: project/templates/manage/events.html:105 +#: project/templates/manage/events.html:140 msgid "Number of reference requests." msgstr "" -#: project/templates/manage/events.html:112 +#: project/templates/manage/events.html:118 msgid "Duplicate" msgstr "" @@ -2200,63 +2233,68 @@ msgstr "" msgid "Preview" msgstr "" -#: project/views/admin.py:60 +#: project/views/admin.py:63 msgid "Organization successfully updated" msgstr "" -#: project/views/admin.py:82 +#: project/views/admin.py:85 project/views/admin_unit.py:182 +#: project/views/admin_unit.py:215 msgid "Entered name does not match organization name" msgstr "" -#: project/views/admin.py:87 +#: project/views/admin.py:89 msgid "Organization successfully deleted" msgstr "" -#: project/views/admin.py:111 project/views/manage.py:443 +#: project/views/admin.py:113 project/views/manage.py:432 #: project/views/user.py:28 msgid "Settings successfully updated" msgstr "" -#: project/views/admin.py:144 +#: project/views/admin.py:133 #, python-format msgid "Test mail from %(site_name)s" msgstr "" -#: project/views/admin.py:187 +#: project/views/admin.py:162 #, python-format msgid "Newsletter from %(site_name)s" msgstr "" -#: project/views/admin.py:237 +#: project/views/admin.py:212 msgid "User successfully updated" msgstr "" -#: project/views/admin.py:257 +#: project/views/admin.py:232 msgid "Entered email does not match user email" msgstr "" -#: project/views/admin.py:262 +#: project/views/admin.py:237 msgid "User successfully deleted" msgstr "" -#: project/views/admin_unit.py:69 +#: project/views/admin_unit.py:78 msgid "" "Organizations cannot currently be created. The project is in a closed " "test phase. If you are interested, you can contact us." msgstr "" -#: project/views/admin_unit.py:119 +#: project/views/admin_unit.py:128 msgid "Organization successfully created" msgstr "" -#: project/views/admin_unit.py:149 +#: project/views/admin_unit.py:158 msgid "AdminUnit successfully updated" msgstr "" -#: project/views/admin_unit.py:171 +#: project/views/admin_unit.py:245 msgid "Organization invitation accepted" msgstr "" +#: project/views/admin_unit.py:259 +msgid "Organization deletion requested" +msgstr "" + #: project/views/admin_unit_member.py:43 msgid "Member successfully updated" msgstr "" @@ -2440,26 +2478,26 @@ msgid "" "verified automatically." msgstr "" -#: project/views/utils.py:65 +#: project/views/utils.py:71 msgid "" "An entry with the entered values ​​already exists. Duplicate entries are " "not allowed." msgstr "" -#: project/views/utils.py:116 +#: project/views/utils.py:122 #, python-format msgid "Error in the %s field - %s" msgstr "" -#: project/views/utils.py:123 +#: project/views/utils.py:129 msgid "Show" msgstr "" -#: project/views/utils.py:131 +#: project/views/utils.py:137 msgid "You do not have permission for this action" msgstr "" -#: project/views/utils.py:252 +#: project/views/utils.py:258 msgid "" "The invitation was issued to another user. Sign in with the email address" " the invitation was sent to." diff --git a/project/views/admin.py b/project/views/admin.py index 400d143..d9cf1c5 100644 --- a/project/views/admin.py +++ b/project/views/admin.py @@ -18,6 +18,7 @@ from project.forms.admin import ( ) from project.models import AdminUnit, Role, User from project.services.admin import upsert_settings +from project.services.admin_unit import delete_admin_unit from project.services.user import set_roles_for_user from project.views.utils import ( flash_errors, @@ -84,8 +85,7 @@ def admin_admin_unit_delete(id): flash(gettext("Entered name does not match organization name"), "danger") else: try: - db.session.delete(admin_unit) - db.session.commit() + delete_admin_unit(admin_unit) flash(gettext("Organization successfully deleted"), "success") return redirect(url_for("admin_admin_units")) except SQLAlchemyError as e: diff --git a/project/views/admin_unit.py b/project/views/admin_unit.py index f704e5e..0709adf 100644 --- a/project/views/admin_unit.py +++ b/project/views/admin_unit.py @@ -1,4 +1,6 @@ -from flask import flash, redirect, render_template, request, url_for +import datetime + +from flask import flash, g, redirect, render_template, request, url_for from flask_babel import gettext from flask_security import auth_required, current_user from sqlalchemy.exc import SQLAlchemyError @@ -10,7 +12,12 @@ from project.access import ( get_admin_unit_members_with_permission, has_access, ) -from project.forms.admin_unit import CreateAdminUnitForm, UpdateAdminUnitForm +from project.forms.admin_unit import ( + CancelAdminUnitDeletionForm, + CreateAdminUnitForm, + RequestAdminUnitDeletionForm, + UpdateAdminUnitForm, +) from project.models import AdminUnit, AdminUnitInvitation, AdminUnitRelation, Location from project.services.admin_unit import ( insert_admin_unit_for_user, @@ -22,6 +29,8 @@ from project.views.utils import ( flash_message, get_current_admin_unit, handleSqlError, + manage_required, + non_match_for_deletion, permission_missing, send_mails, ) @@ -157,6 +166,71 @@ def admin_unit_update(id): return render_template("admin_unit/update.html", form=form, admin_unit=admin_unit) +@app.route("/admin_unit//request-deletion", methods=("GET", "POST")) +@auth_required() +@manage_required("admin_unit:update") +def admin_unit_request_deletion(id): + admin_unit = g.admin_unit + + if admin_unit.deletion_requested_at: # pragma: no cover + return redirect(url_for("admin_unit_cancel_deletion", id=admin_unit.id)) + + form = RequestAdminUnitDeletionForm() + + if form.validate_on_submit(): + if non_match_for_deletion(form.name.data, admin_unit.name): + flash(gettext("Entered name does not match organization name"), "danger") + else: + admin_unit.deletion_requested_at = datetime.datetime.utcnow() + admin_unit.deletion_requested_by_id = current_user.id + + try: + db.session.commit() + send_admin_unit_deletion_requested_mails(admin_unit) + return redirect(url_for("manage_admin_unit", id=admin_unit.id)) + except SQLAlchemyError as e: + db.session.rollback() + flash(handleSqlError(e), "danger") + else: + flash_errors(form) + + return render_template( + "admin_unit/request_deletion.html", form=form, admin_unit=admin_unit + ) + + +@app.route("/admin_unit//cancel-deletion", methods=("GET", "POST")) +@auth_required() +@manage_required("admin_unit:update") +def admin_unit_cancel_deletion(id): + admin_unit = g.admin_unit + + if not admin_unit.deletion_requested_at: # pragma: no cover + return redirect(url_for("admin_unit_request_deletion", id=admin_unit.id)) + + form = CancelAdminUnitDeletionForm() + + if form.validate_on_submit(): + if non_match_for_deletion(form.name.data, admin_unit.name): + flash(gettext("Entered name does not match organization name"), "danger") + else: + admin_unit.deletion_requested_at = None + admin_unit.deletion_requested_by_id = None + + try: + db.session.commit() + return redirect(url_for("manage_admin_unit", id=admin_unit.id)) + except SQLAlchemyError as e: + db.session.rollback() + flash(handleSqlError(e), "danger") + else: + flash_errors(form) + + return render_template( + "admin_unit/cancel_deletion.html", form=form, admin_unit=admin_unit + ) + + def send_admin_unit_invitation_accepted_mails( invitation: AdminUnitInvitation, relation: AdminUnitRelation, admin_unit: AdminUnit ): @@ -174,3 +248,15 @@ def send_admin_unit_invitation_accepted_mails( relation=relation, admin_unit=admin_unit, ) + + +def send_admin_unit_deletion_requested_mails(admin_unit: AdminUnit): + members = get_admin_unit_members_with_permission(admin_unit.id, "admin_unit:update") + emails = list(map(lambda member: member.user.email, members)) + + send_mails( + emails, + gettext("Organization deletion requested"), + "organization_deletion_requested_notice", + admin_unit=admin_unit, + ) diff --git a/project/views/utils.py b/project/views/utils.py index 2d093a9..3be861d 100644 --- a/project/views/utils.py +++ b/project/views/utils.py @@ -1,3 +1,4 @@ +from functools import wraps from urllib.parse import quote_plus from flask import Markup, flash, g, redirect, render_template, request, url_for @@ -10,7 +11,12 @@ from sqlalchemy.exc import SQLAlchemyError from wtforms import FormField from project import app, celery, mail -from project.access import get_admin_unit_for_manage, get_admin_units_for_manage +from project.access import ( + get_admin_unit_for_manage, + get_admin_unit_for_manage_or_404, + get_admin_units_for_manage, + has_access, +) from project.dateutils import berlin_tz, round_to_next_day from project.models import Event, EventAttendanceMode, EventDate from project.utils import get_place_str, strings_are_equal_ignoring_case @@ -298,3 +304,23 @@ def get_celery_poll_group_result(): # pragma: no cover "successful": False, "error": getattr(e, "message", "Unknown error"), } + + +def manage_required(permission=None): + def decorator(f): + @wraps(f) + def decorated_function(id, *args, **kwargs): + admin_unit = get_admin_unit_for_manage_or_404(id) + + if permission and not has_access(admin_unit, permission): + return permission_missing( + url_for("manage_admin_unit", id=admin_unit.id) + ) + + g.admin_unit = admin_unit + + return f(id, *args, **kwargs) + + return decorated_function + + return decorator diff --git a/tests/services/test_admin_unit.py b/tests/services/test_admin_unit.py new file mode 100644 index 0000000..f124d0f --- /dev/null +++ b/tests/services/test_admin_unit.py @@ -0,0 +1,17 @@ +def test_get_admin_units_with_due_delete_request(client, seeder, db, utils, app): + user_id, admin_unit_id = seeder.setup_base() + + with app.app_context(): + import datetime + + from project.models import AdminUnit + from project.services.admin_unit import get_admin_units_with_due_delete_request + + admin_unit = db.session.get(AdminUnit, admin_unit_id) + admin_unit.deletion_requested_at = ( + datetime.datetime.utcnow() - datetime.timedelta(days=4) + ) + db.session.commit() + + due_admin_units = get_admin_units_with_due_delete_request() + assert len(due_admin_units) == 1 diff --git a/tests/test_models.py b/tests/test_models.py index eb7e3b6..151af84 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -537,3 +537,34 @@ def test_purge_eventorganizer(client, app, db, seeder): location = db.session.get(Location, location_id) assert location is None + + +def test_delete_admin_unit(client, app, db, seeder): + _, admin_unit_id = seeder.setup_base(log_in=False) + instance_id = seeder.upsert_default_event_organizer(admin_unit_id) + image_id = seeder.upsert_default_image() + location_id = seeder.create_location(street="Street") + + with app.app_context(): + from project.models import AdminUnit, EventOrganizer, Image, Location + from project.services.admin_unit import delete_admin_unit + + instance = db.session.get(EventOrganizer, instance_id) + instance.logo = db.session.get(Image, image_id) + instance.location = db.session.get(Location, location_id) + db.session.commit() + + assert instance.logo is not None + assert instance.location is not None + + admin_unit = db.session.get(AdminUnit, admin_unit_id) + delete_admin_unit(admin_unit) + + admin_unit = db.session.get(AdminUnit, admin_unit_id) + assert admin_unit is None + + image = db.session.get(Image, image_id) + assert image is None + + location = db.session.get(Location, location_id) + assert admin_unit is location diff --git a/tests/views/test_admin_unit.py b/tests/views/test_admin_unit.py index 44f2f80..589212e 100644 --- a/tests/views/test_admin_unit.py +++ b/tests/views/test_admin_unit.py @@ -1,3 +1,6 @@ +import pytest + + def create_form_data(response, utils): return { "csrf_token": utils.get_csrf(response), @@ -297,3 +300,111 @@ def test_list(client, app, utils, seeder): seeder.create_admin_unit(user_id, "Meine Crew") response = client.get("/manage/admin_units") assert b"Meine Crew" in response.data + + +@pytest.mark.parametrize("db_error", [True, False]) +@pytest.mark.parametrize("non_match", [True, False]) +def test_admin_unit_request_deletion( + client, seeder, utils, app, db, mocker, db_error, non_match +): + user_id, admin_unit_id = seeder.setup_base() + + url = utils.get_url("admin_unit_request_deletion", id=admin_unit_id) + response = utils.get_ok(url) + + if db_error: + utils.mock_db_commit(mocker) + + form_name = "Meine Crew" + + if non_match: + form_name = "wrong" + + response = utils.post_form( + url, + response, + { + "name": form_name, + }, + ) + + if non_match: + utils.assert_response_error_message( + response, "Der eingegebene Name entspricht nicht dem Namen der Organisation" + ) + return + + if db_error: + utils.assert_response_db_error(response) + return + + utils.assert_response_redirect(response, "manage_admin_unit", id=admin_unit_id) + + with app.app_context(): + from project.models import AdminUnit + + admin_unit = db.session.get(AdminUnit, admin_unit_id) + assert admin_unit.deletion_requested_at is not None + + +@pytest.mark.parametrize("db_error", [True, False]) +@pytest.mark.parametrize("non_match", [True, False]) +def test_admin_unit_cancel_deletion( + client, seeder, utils, app, db, mocker, db_error, non_match +): + user_id, admin_unit_id = seeder.setup_base() + + with app.app_context(): + import datetime + + from project.models import AdminUnit + + admin_unit = db.session.get(AdminUnit, admin_unit_id) + admin_unit.deletion_requested_at = datetime.datetime.utcnow() + db.session.commit() + + url = utils.get_url("admin_unit_cancel_deletion", id=admin_unit_id) + response = utils.get_ok(url) + + if db_error: + utils.mock_db_commit(mocker) + + form_name = "Meine Crew" + + if non_match: + form_name = "wrong" + + response = utils.post_form( + url, + response, + { + "name": form_name, + }, + ) + + if non_match: + utils.assert_response_error_message( + response, "Der eingegebene Name entspricht nicht dem Namen der Organisation" + ) + return + + if db_error: + utils.assert_response_db_error(response) + return + + utils.assert_response_redirect(response, "manage_admin_unit", id=admin_unit_id) + + with app.app_context(): + from project.models import AdminUnit + + admin_unit = db.session.get(AdminUnit, admin_unit_id) + assert admin_unit.deletion_requested_at is None + + +def test_admin_unit_cancel_deletion_permission_missing(client, seeder, utils, mocker): + owner_id, admin_unit_id, member_id = seeder.setup_base_event_verifier() + + response = utils.get_endpoint("admin_unit_cancel_deletion", id=admin_unit_id) + utils.assert_response_permission_missing( + response, "manage_admin_unit", id=admin_unit_id + )