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 559f7a3..0ee23a4 100644 Binary files a/project/translations/de/LC_MESSAGES/messages.mo and b/project/translations/de/LC_MESSAGES/messages.mo differ diff --git a/project/translations/de/LC_MESSAGES/messages.po b/project/translations/de/LC_MESSAGES/messages.po index 056cd98..c0f2770 100644 --- a/project/translations/de/LC_MESSAGES/messages.po +++ b/project/translations/de/LC_MESSAGES/messages.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2023-04-21 12:29+0200\n" +"POT-Creation-Date: 2023-04-27 15:10+0200\n" "PO-Revision-Date: 2020-06-07 18:51+0200\n" "Last-Translator: FULL NAME \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 e565d2f..a431fc4 100644 Binary files a/project/translations/en/LC_MESSAGES/messages.mo and b/project/translations/en/LC_MESSAGES/messages.mo differ 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 + )