From a6d452c20b861b8fb3400d96726f0ffc8f536ca3 Mon Sep 17 00:00:00 2001
From: Daniel Grams
Date: Tue, 28 Mar 2023 22:50:07 +0200
Subject: [PATCH 1/2] Add newsletter functionality #391
---
.flake8 | 2 +-
.github/workflows/test.yml | 14 ++
messages.pot | 212 +++++++++++-------
migrations/versions/47c334ba5b86_.py | 31 +++
project/base_tasks.py | 4 +-
project/forms/admin.py | 17 +-
project/forms/user.py | 13 ++
project/models.py | 8 +
project/templates/_macros.html | 2 +
project/templates/admin/admin.html | 4 +
project/templates/admin/email.html | 4 +-
project/templates/admin/newsletter.html | 123 ++++++++++
project/templates/email/newsletter.html | 9 +
project/templates/email/newsletter.txt | 3 +
project/templates/profile.html | 4 +
project/templates/user/notifications.html | 16 ++
.../translations/de/LC_MESSAGES/messages.mo | Bin 35508 -> 36153 bytes
.../translations/de/LC_MESSAGES/messages.po | 204 ++++++++++-------
.../translations/en/LC_MESSAGES/messages.mo | Bin 3565 -> 3565 bytes
.../translations/en/LC_MESSAGES/messages.po | 212 +++++++++++-------
project/views/admin.py | 47 ++++
project/views/user.py | 32 ++-
tests/conftest.py | 1 +
tests/views/test_admin.py | 22 ++
tests/views/test_user.py | 23 ++
25 files changed, 754 insertions(+), 253 deletions(-)
create mode 100644 migrations/versions/47c334ba5b86_.py
create mode 100644 project/forms/user.py
create mode 100644 project/templates/admin/newsletter.html
create mode 100644 project/templates/email/newsletter.html
create mode 100644 project/templates/email/newsletter.txt
create mode 100644 project/templates/user/notifications.html
diff --git a/.flake8 b/.flake8
index 7663589..e44c8df 100644
--- a/.flake8
+++ b/.flake8
@@ -1,5 +1,5 @@
[flake8]
-extend-ignore = E501, E203
+extend-ignore = E501, E203, E711
exclude =
.git,
.github,
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 1db9fdf..e8f251d 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -35,6 +35,19 @@ jobs:
# Maps tcp port 5432 on service container to the host
- 5432:5432
+ redis:
+ # Docker Hub image
+ image: redis
+ # Set health checks to wait until redis has started
+ options: >-
+ --health-cmd "redis-cli ping"
+ --health-interval 10s
+ --health-timeout 5s
+ --health-retries 5
+ ports:
+ # Maps port 6379 on service container to the host
+ - 6379:6379
+
steps:
- name: Check out repository code
uses: actions/checkout@v2
@@ -53,6 +66,7 @@ jobs:
run: pytest --cov=project --splits 4 --group ${{ matrix.group }}
env:
TEST_DATABASE_URL: postgresql://postgres:postgres@localhost/eventcally_tests
+ TEST_REDIS_URL: redis://redis:6379
- name: Upload coverage
uses: actions/upload-artifact@v2
diff --git a/messages.pot b/messages.pot
index 3306229..0240f75 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-03-24 21:24+0100\n"
+"POT-Creation-Date: 2023-03-28 17:04+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -208,7 +208,7 @@ msgstr ""
msgid "Legal notice"
msgstr ""
-#: project/forms/admin.py:13 project/templates/_macros.html:1395
+#: project/forms/admin.py:13 project/templates/_macros.html:1397
#: project/templates/layout.html:302
#: project/templates/widget/event_suggestion/create.html:204
#: project/views/admin_unit.py:73 project/views/root.py:69
@@ -225,6 +225,7 @@ msgid "Start page"
msgstr ""
#: project/forms/admin.py:17 project/forms/oauth2_client.py:24
+#: project/forms/user.py:13
msgid "Save"
msgstr ""
@@ -285,7 +286,7 @@ msgstr ""
msgid "Update organization"
msgstr ""
-#: project/forms/admin.py:63
+#: project/forms/admin.py:63 project/forms/admin.py:70
msgid "Recipient"
msgstr ""
@@ -293,6 +294,22 @@ msgstr ""
msgid "Send test mail synchronously"
msgstr ""
+#: project/forms/admin.py:72 project/forms/admin.py:78
+msgid "Test recipient"
+msgstr ""
+
+#: project/forms/admin.py:73
+msgid "All users with enabled newsletter setting"
+msgstr ""
+
+#: project/forms/admin.py:79
+msgid "Message"
+msgstr ""
+
+#: project/forms/admin.py:80
+msgid "Send newsletter"
+msgstr ""
+
#: project/forms/admin_unit.py:15 project/forms/event_place.py:12
#: project/forms/organizer.py:12
msgid "Street"
@@ -342,7 +359,7 @@ msgstr ""
msgid "The short name is used to create a unique identifier for your events"
msgstr ""
-#: project/forms/admin_unit.py:41 project/templates/_macros.html:1531
+#: project/forms/admin_unit.py:41 project/templates/_macros.html:1533
msgid "Short name must contain only letters numbers or underscore"
msgstr ""
@@ -355,20 +372,21 @@ msgstr ""
#: project/forms/admin_unit.py:48 project/forms/admin_unit_member.py:11
#: project/forms/admin_unit_member.py:23 project/forms/admin_unit_member.py:28
#: project/forms/event.py:107 project/forms/event_suggestion.py:38
-#: project/forms/organizer.py:27 project/templates/_macros.html:235
-#: project/templates/_macros.html:1491 project/templates/admin/admin.html:27
+#: project/forms/organizer.py:27 project/templates/_macros.html:237
+#: project/templates/_macros.html:1493 project/templates/admin/admin.html:27
+#: project/templates/admin/email.html:4 project/templates/admin/email.html:66
#: project/templates/admin/users.html:19
msgid "Email"
msgstr ""
#: project/forms/admin_unit.py:49 project/forms/event.py:108
#: project/forms/event_suggestion.py:31 project/forms/organizer.py:28
-#: project/templates/_macros.html:288
+#: project/templates/_macros.html:290
msgid "Phone"
msgstr ""
#: project/forms/admin_unit.py:50 project/forms/event.py:109
-#: project/forms/organizer.py:29 project/templates/_macros.html:296
+#: project/forms/organizer.py:29 project/templates/_macros.html:298
msgid "Fax"
msgstr ""
@@ -566,16 +584,16 @@ msgstr ""
msgid "All-day"
msgstr ""
-#: project/forms/event.py:54 project/templates/_macros.html:1711
+#: project/forms/event.py:54 project/templates/_macros.html:1713
#: project/templates/widget/event_suggestion/create.html:240
msgid "Recurring event"
msgstr ""
-#: project/forms/event.py:61 project/templates/_macros.html:1252
+#: project/forms/event.py:61 project/templates/_macros.html:1254
msgid "The start must be before the end."
msgstr ""
-#: project/forms/event.py:67 project/templates/_macros.html:1269
+#: project/forms/event.py:67 project/templates/_macros.html:1271
msgid "An event can last a maximum of 14 days."
msgstr ""
@@ -609,7 +627,7 @@ msgstr ""
msgid "Enter a link where tickets can be purchased."
msgstr ""
-#: project/forms/event.py:136 project/templates/_macros.html:217
+#: project/forms/event.py:136 project/templates/_macros.html:219
msgid "Tags"
msgstr ""
@@ -659,7 +677,7 @@ msgstr ""
msgid "If the participants needs to register for the event."
msgstr ""
-#: project/forms/event.py:170 project/templates/_macros.html:249
+#: project/forms/event.py:170 project/templates/_macros.html:251
#: project/templates/layout.html:110
msgid "Booked up"
msgstr ""
@@ -739,8 +757,8 @@ msgid ""
" course it works without it."
msgstr ""
-#: project/forms/event.py:242 project/templates/_macros.html:396
-#: project/templates/_macros.html:559
+#: project/forms/event.py:242 project/templates/_macros.html:398
+#: project/templates/_macros.html:561
msgid "Previous start date"
msgstr ""
@@ -786,7 +804,7 @@ msgstr ""
#: project/forms/event.py:286 project/forms/event.py:295
#: project/forms/event.py:368 project/forms/event_suggestion.py:50
-#: project/templates/_macros.html:436 project/templates/_macros.html:599
+#: project/templates/_macros.html:438 project/templates/_macros.html:601
#: project/templates/event/create.html:284
#: project/templates/event/update.html:166
#: project/templates/event_place/create.html:31
@@ -805,8 +823,8 @@ msgstr ""
#: project/forms/event.py:302 project/forms/event.py:311
#: project/forms/event.py:376 project/forms/event.py:439
-#: project/forms/event_suggestion.py:60 project/templates/_macros.html:473
-#: project/templates/_macros.html:636 project/templates/event/create.html:253
+#: project/forms/event_suggestion.py:60 project/templates/_macros.html:475
+#: project/templates/_macros.html:638 project/templates/event/create.html:253
#: project/templates/event/update.html:156
#: project/templates/organizer/create.html:27
#: project/templates/organizer/delete.html:13
@@ -891,7 +909,7 @@ msgstr ""
msgid "PublicStatus.published"
msgstr ""
-#: project/forms/event.py:402 project/templates/_macros.html:255
+#: project/forms/event.py:402 project/templates/_macros.html:257
msgid "PublicStatus.draft"
msgstr ""
@@ -904,7 +922,7 @@ msgstr ""
msgid "Update event"
msgstr ""
-#: project/forms/event.py:423 project/templates/_macros.html:1224
+#: project/forms/event.py:423 project/templates/_macros.html:1226
#: project/templates/event/actions.html:66
#: project/templates/event/delete.html:6
msgid "Delete event"
@@ -925,7 +943,7 @@ msgid "Keyword"
msgstr ""
#: project/forms/event.py:436 project/forms/event_date.py:21
-#: project/forms/planing.py:19 project/templates/_macros.html:369
+#: project/forms/planing.py:19 project/templates/_macros.html:371
msgid "Category"
msgstr ""
@@ -934,7 +952,7 @@ msgid "Find events"
msgstr ""
#: project/forms/event_date.py:24 project/forms/planing.py:22
-#: project/templates/_macros.html:303
+#: project/templates/_macros.html:305
#: project/templates/admin_unit/create.html:38
#: project/templates/admin_unit/update.html:39
#: project/templates/event_place/create.html:40
@@ -1077,7 +1095,7 @@ msgid "Weekdays"
msgstr ""
#: project/forms/reference.py:11 project/forms/reference_request.py:16
-#: project/templates/_macros.html:489 project/templates/_macros.html:652
+#: project/templates/_macros.html:491 project/templates/_macros.html:654
#: project/templates/admin_unit/create.html:28
#: project/templates/admin_unit/update.html:29
#: project/templates/layout.html:242
@@ -1105,7 +1123,7 @@ msgstr ""
msgid "Delete request"
msgstr ""
-#: project/forms/reference_request.py:28 project/templates/_macros.html:1407
+#: project/forms/reference_request.py:28 project/templates/_macros.html:1409
#: project/templates/event_suggestion/review_status.html:18
#: project/templates/reference_request/review_status.html:12
msgid "Review status"
@@ -1163,43 +1181,53 @@ msgstr ""
msgid "Deny"
msgstr ""
+#: project/forms/user.py:9 project/templates/admin/admin.html:31
+#: project/templates/admin/newsletter.html:4
+#: project/templates/admin/newsletter.html:93
+msgid "Newsletter"
+msgstr ""
+
+#: project/forms/user.py:10
+msgid "Information about new features and improvements."
+msgstr ""
+
#: project/forms/widgets.py:154
msgid "This field is required."
msgstr ""
-#: project/templates/_macros.html:147
+#: project/templates/_macros.html:149
msgid "Show on Google Maps"
msgstr ""
-#: project/templates/_macros.html:226
+#: project/templates/_macros.html:228
msgid "Link"
msgstr ""
-#: project/templates/_macros.html:282
+#: project/templates/_macros.html:284
msgid "Verified"
msgstr ""
-#: project/templates/_macros.html:346
-#, python-format
-msgid "Created at %(created_at)s by %(created_by)s."
-msgstr ""
-
#: project/templates/_macros.html:348
#, python-format
-msgid "Created at %(created_at)s."
+msgid "Created at %(created_at)s by %(created_by)s."
msgstr ""
-#: project/templates/_macros.html:353
+#: project/templates/_macros.html:350
#, python-format
-msgid "Last updated at %(updated_at)s by %(updated_by)s."
+msgid "Created at %(created_at)s."
msgstr ""
#: project/templates/_macros.html:355
#, python-format
+msgid "Last updated at %(updated_at)s by %(updated_by)s."
+msgstr ""
+
+#: project/templates/_macros.html:357
+#, python-format
msgid "Last updated at %(updated_at)s."
msgstr ""
-#: project/templates/_macros.html:385 project/templates/_macros.html:555
+#: project/templates/_macros.html:387 project/templates/_macros.html:557
#: project/templates/event/actions.html:25
#: project/templates/event/create.html:230
#: project/templates/event/update.html:122
@@ -1207,63 +1235,63 @@ msgstr ""
msgid "Event"
msgstr ""
-#: project/templates/_macros.html:391 project/templates/_macros.html:920
+#: project/templates/_macros.html:393 project/templates/_macros.html:922
msgid "Date"
msgstr ""
-#: project/templates/_macros.html:418 project/templates/_macros.html:577
-#: project/templates/_macros.html:1476 project/templates/event/actions.html:51
+#: project/templates/_macros.html:420 project/templates/_macros.html:579
+#: project/templates/_macros.html:1478 project/templates/event/actions.html:51
msgid "Share"
msgstr ""
-#: project/templates/_macros.html:422 project/templates/_macros.html:581
-#: project/templates/_macros.html:1506
+#: project/templates/_macros.html:424 project/templates/_macros.html:583
+#: project/templates/_macros.html:1508
msgid "Add to calendar"
msgstr ""
-#: project/templates/_macros.html:430 project/templates/_macros.html:592
+#: project/templates/_macros.html:432 project/templates/_macros.html:594
#: project/templates/event/report.html:4
msgid "Report event"
msgstr ""
-#: project/templates/_macros.html:457 project/templates/_macros.html:618
+#: project/templates/_macros.html:459 project/templates/_macros.html:620
msgid "Show directions"
msgstr ""
-#: project/templates/_macros.html:462 project/templates/_macros.html:623
+#: project/templates/_macros.html:464 project/templates/_macros.html:625
msgid "The event takes place online."
msgstr ""
-#: project/templates/_macros.html:464 project/templates/_macros.html:625
+#: project/templates/_macros.html:466 project/templates/_macros.html:627
msgid "The event takes place both offline and online."
msgstr ""
-#: project/templates/_macros.html:585 project/templates/layout.html:168
+#: project/templates/_macros.html:587 project/templates/layout.html:168
#: project/templates/user/favorite_events.html:4
msgid "Favorite events"
msgstr ""
-#: project/templates/_macros.html:679 project/templates/event_date/list.html:5
+#: project/templates/_macros.html:681 project/templates/event_date/list.html:5
#: project/templates/event_date/list.html:299
#: project/templates/reference_request/review.html:32
msgid "Event Dates"
msgstr ""
-#: project/templates/_macros.html:771
+#: project/templates/_macros.html:773
msgid "Search location on Google"
msgstr ""
-#: project/templates/_macros.html:837
+#: project/templates/_macros.html:839
#, python-format
msgid "%(count)d event dates"
msgstr ""
-#: project/templates/_macros.html:860 project/templates/_macros.html:862
+#: project/templates/_macros.html:862 project/templates/_macros.html:864
#: project/templates/event_date/list.html:321
msgid "First"
msgstr ""
-#: project/templates/_macros.html:865 project/templates/_macros.html:867
+#: project/templates/_macros.html:867 project/templates/_macros.html:869
#: project/templates/event_date/list.html:322
#: project/templates/widget/event_suggestion/create.html:193
#: project/templates/widget/event_suggestion/create.html:218
@@ -1274,12 +1302,12 @@ msgstr ""
msgid "Previous"
msgstr ""
-#: project/templates/_macros.html:869
+#: project/templates/_macros.html:871
#, python-format
msgid "Page %(page)d of %(pages)d (%(total)d total)"
msgstr ""
-#: project/templates/_macros.html:871 project/templates/_macros.html:873
+#: project/templates/_macros.html:873 project/templates/_macros.html:875
#: project/templates/event_date/list.html:324
#: project/templates/widget/event_suggestion/create.html:194
#: project/templates/widget/event_suggestion/create.html:219
@@ -1289,88 +1317,88 @@ msgstr ""
msgid "Next"
msgstr ""
-#: project/templates/_macros.html:876 project/templates/_macros.html:878
+#: project/templates/_macros.html:878 project/templates/_macros.html:880
#: project/templates/event_date/list.html:325
msgid "Last"
msgstr ""
-#: project/templates/_macros.html:943
+#: project/templates/_macros.html:945
msgid "Radius"
msgstr ""
-#: project/templates/_macros.html:1153
+#: project/templates/_macros.html:1155
msgid "Edit image"
msgstr ""
-#: project/templates/_macros.html:1174
+#: project/templates/_macros.html:1176
msgid "Close"
msgstr ""
-#: project/templates/_macros.html:1175
+#: project/templates/_macros.html:1177
msgid "Okay"
msgstr ""
-#: project/templates/_macros.html:1187
+#: project/templates/_macros.html:1189
msgid "Choose image file"
msgstr ""
-#: project/templates/_macros.html:1223 project/templates/event/actions.html:65
+#: project/templates/_macros.html:1225 project/templates/event/actions.html:65
#: project/templates/event/delete.html:12
msgid "Edit event"
msgstr ""
-#: project/templates/_macros.html:1226 project/templates/manage/events.html:66
+#: project/templates/_macros.html:1228 project/templates/manage/events.html:66
msgid "More"
msgstr ""
-#: project/templates/_macros.html:1273
+#: project/templates/_macros.html:1275
msgid "Please enter a valid time, between 00:00 and 23:59."
msgstr ""
-#: project/templates/_macros.html:1301
+#: project/templates/_macros.html:1303
#, python-format
msgid "Just use %(term)s"
msgstr ""
-#: project/templates/_macros.html:1367
+#: project/templates/_macros.html:1369
msgid "Event suggestion"
msgstr ""
-#: project/templates/_macros.html:1485
+#: project/templates/_macros.html:1487
msgid "Link copied"
msgstr ""
-#: project/templates/_macros.html:1485
+#: project/templates/_macros.html:1487
msgid "Copy link"
msgstr ""
-#: project/templates/_macros.html:1514
+#: project/templates/_macros.html:1516
msgid "Google calendar"
msgstr ""
-#: project/templates/_macros.html:1515
+#: project/templates/_macros.html:1517
msgid "Apple calendar"
msgstr ""
-#: project/templates/_macros.html:1516
+#: project/templates/_macros.html:1518
msgid "Yahoo calendar"
msgstr ""
-#: project/templates/_macros.html:1517
+#: project/templates/_macros.html:1519
msgid "Other calendar"
msgstr ""
-#: project/templates/_macros.html:1712
+#: project/templates/_macros.html:1714
msgid "Remove event date"
msgstr ""
-#: project/templates/_macros.html:1741 project/templates/event/create.html:176
+#: project/templates/_macros.html:1743 project/templates/event/create.html:176
#: project/templates/event/update.html:99
#: project/templates/widget/event_suggestion/create.html:129
msgid "Enter organizer"
msgstr ""
-#: project/templates/_macros.html:1765
+#: project/templates/_macros.html:1767
msgid "Enter list name"
msgstr ""
@@ -1414,6 +1442,7 @@ msgstr ""
#: project/templates/admin/admin.html:3 project/templates/admin/admin.html:9
#: project/templates/admin/admin_units.html:10
#: project/templates/admin/email.html:65
+#: project/templates/admin/newsletter.html:92
#: project/templates/admin/settings.html:10
#: project/templates/admin/users.html:10 project/templates/layout.html:171
msgid "Admin"
@@ -1501,8 +1530,7 @@ msgstr ""
msgid "Organization invitations"
msgstr ""
-#: project/templates/admin/admin.html:15 project/templates/admin/email.html:4
-#: project/templates/admin/email.html:66
+#: project/templates/admin/admin.html:15
#: project/templates/admin/settings.html:4
#: project/templates/admin/settings.html:11
#: project/templates/admin_unit/update.html:6
@@ -1528,18 +1556,24 @@ msgid "Switch organization"
msgstr ""
#: project/templates/developer/read.html:4 project/templates/layout.html:310
-#: project/templates/profile.html:29
+#: project/templates/profile.html:33
msgid "Developer"
msgstr ""
#: project/templates/profile.html:23
+#: project/templates/user/notifications.html:4
+#: project/templates/user/notifications.html:8
+msgid "Notifications"
+msgstr ""
+
+#: project/templates/profile.html:27
msgid "Applications"
msgstr ""
#: project/templates/oauth2_client/list.html:4
#: project/templates/oauth2_client/list.html:11
#: project/templates/oauth2_client/read.html:11
-#: project/templates/profile.html:33
+#: project/templates/profile.html:37
msgid "OAuth2 clients"
msgstr ""
@@ -1560,7 +1594,7 @@ msgstr ""
msgid "Edit"
msgstr ""
-#: project/templates/admin/email.html:47 project/views/admin.py:119
+#: project/templates/admin/email.html:47 project/views/admin.py:122
msgid "Mail sent successfully"
msgstr ""
@@ -1572,6 +1606,10 @@ msgstr ""
msgid "Send test mail asynchronously"
msgstr ""
+#: project/templates/admin/newsletter.html:59
+msgid "Mails sent successfully"
+msgstr ""
+
#: project/templates/admin_unit/create.html:58
#: project/templates/admin_unit/update.html:59
#: project/templates/event/create.html:347
@@ -1626,6 +1664,10 @@ msgstr ""
msgid "this is a message from %(site_name)s."
msgstr ""
+#: project/templates/email/newsletter.html:7
+msgid "Notification settings"
+msgstr ""
+
#: project/templates/email/organization_invitation_accepted_notice.html:4
#, python-format
msgid ""
@@ -2035,20 +2077,26 @@ msgstr ""
msgid "Preview"
msgstr ""
-#: project/views/admin.py:55
+#: project/views/admin.py:58
msgid "Organization successfully updated"
msgstr ""
-#: project/views/admin.py:79 project/views/manage.py:361
+#: project/views/admin.py:82 project/views/manage.py:361
+#: project/views/user.py:27
msgid "Settings successfully updated"
msgstr ""
-#: project/views/admin.py:108
+#: project/views/admin.py:111
#, python-format
msgid "Test mail from %(site_name)s"
msgstr ""
-#: project/views/admin.py:152
+#: project/views/admin.py:150
+#, python-format
+msgid "Newsletter from %(site_name)s"
+msgstr ""
+
+#: project/views/admin.py:200
msgid "User successfully updated"
msgstr ""
diff --git a/migrations/versions/47c334ba5b86_.py b/migrations/versions/47c334ba5b86_.py
new file mode 100644
index 0000000..db18259
--- /dev/null
+++ b/migrations/versions/47c334ba5b86_.py
@@ -0,0 +1,31 @@
+"""empty message
+
+Revision ID: 47c334ba5b86
+Revises: 3f77c8693ae3
+Create Date: 2023-03-26 23:03:44.678745
+
+"""
+import sqlalchemy as sa
+import sqlalchemy_utils
+from alembic import op
+
+from project import dbtypes
+
+# revision identifiers, used by Alembic.
+revision = "47c334ba5b86"
+down_revision = "3f77c8693ae3"
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+ op.add_column(
+ "user",
+ sa.Column(
+ "newsletter_enabled", sa.Boolean(), server_default="1", nullable=False
+ ),
+ )
+
+
+def downgrade():
+ op.drop_column("user", "newsletter_enabled")
diff --git a/project/base_tasks.py b/project/base_tasks.py
index 0280a39..cbdf04c 100644
--- a/project/base_tasks.py
+++ b/project/base_tasks.py
@@ -7,6 +7,6 @@ from project.views.utils import send_mail
base=getattr(app, "celery_http_task_cls"),
priority=0,
)
-def send_mail_task(recipient, subject, template):
+def send_mail_task(recipient, subject, template, **context):
with force_locale():
- send_mail(recipient, subject, template)
+ send_mail(recipient, subject, template, **context)
diff --git a/project/forms/admin.py b/project/forms/admin.py
index e7251f3..2899fa8 100644
--- a/project/forms/admin.py
+++ b/project/forms/admin.py
@@ -1,6 +1,6 @@
from flask_babelex import lazy_gettext
from flask_wtf import FlaskForm
-from wtforms import BooleanField, SubmitField, TextAreaField
+from wtforms import BooleanField, RadioField, SubmitField, TextAreaField
from wtforms.fields.html5 import EmailField
from wtforms.validators import DataRequired, Optional
@@ -63,3 +63,18 @@ class AdminTestEmailForm(FlaskForm):
recipient = EmailField(lazy_gettext("Recipient"), validators=[DataRequired()])
submit = SubmitField(lazy_gettext("Send test mail synchronously"))
+
+
+class AdminNewsletterForm(FlaskForm):
+ recipient_choice = RadioField(
+ lazy_gettext("Recipient"),
+ choices=[
+ (1, lazy_gettext("Test recipient")),
+ (2, lazy_gettext("All users with enabled newsletter setting")),
+ ],
+ default=1,
+ coerce=int,
+ )
+ test_recipient = EmailField(lazy_gettext("Test recipient"), validators=[Optional()])
+ message = TextAreaField(lazy_gettext("Message"), validators=[DataRequired()])
+ submit = SubmitField(lazy_gettext("Send newsletter"))
diff --git a/project/forms/user.py b/project/forms/user.py
new file mode 100644
index 0000000..cd56c5b
--- /dev/null
+++ b/project/forms/user.py
@@ -0,0 +1,13 @@
+from flask_babelex import lazy_gettext
+from flask_wtf import FlaskForm
+from wtforms import BooleanField, SubmitField
+from wtforms.validators import Optional
+
+
+class NotificationForm(FlaskForm):
+ newsletter_enabled = BooleanField(
+ lazy_gettext("Newsletter"),
+ description=lazy_gettext("Information about new features and improvements."),
+ validators=[Optional()],
+ )
+ submit = SubmitField(lazy_gettext("Save"))
diff --git a/project/models.py b/project/models.py
index c361b2e..0d19a43 100644
--- a/project/models.py
+++ b/project/models.py
@@ -199,6 +199,14 @@ class User(db.Model, UserMixin):
secondary="user_favoriteevents",
backref=backref("favored_by_users", lazy=True),
)
+ newsletter_enabled = deferred(
+ Column(
+ Boolean(),
+ nullable=True,
+ default=True,
+ server_default="1",
+ )
+ )
def get_user_id(self):
return self.id
diff --git a/project/templates/_macros.html b/project/templates/_macros.html
index d535743..8b3140c 100644
--- a/project/templates/_macros.html
+++ b/project/templates/_macros.html
@@ -70,6 +70,8 @@
{% endfor %}
+ {% elif 'ri' in kwargs and kwargs['ri'] == 'radio' %}
+ {{ render_radio_buttons(field) }}
{% else %}
{% if 'class' in kwargs %}
{% set _dummy=kwargs.pop('class') %}
diff --git a/project/templates/admin/admin.html b/project/templates/admin/admin.html
index 024b0e1..adff26d 100644
--- a/project/templates/admin/admin.html
+++ b/project/templates/admin/admin.html
@@ -27,6 +27,10 @@
{{ _('Email') }}
+
+ {{ _('Newsletter') }}
+
+
{% endblock %}
\ No newline at end of file
diff --git a/project/templates/admin/email.html b/project/templates/admin/email.html
index 88ad55c..3b3297c 100644
--- a/project/templates/admin/email.html
+++ b/project/templates/admin/email.html
@@ -1,7 +1,7 @@
{% extends "layout.html" %}
{% from "_macros.html" import render_field, render_field_with_errors %}
{%- block title -%}
-{{ _('Settings') }}
+{{ _('Email') }}
{%- endblock -%}
{% block header %}
+{% endblock %}
+{% block content %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/project/templates/email/newsletter.html b/project/templates/email/newsletter.html
new file mode 100644
index 0000000..fdedd25
--- /dev/null
+++ b/project/templates/email/newsletter.html
@@ -0,0 +1,9 @@
+{% extends "email/layout.html" %}
+{% block content %}
+
+ {{ message|safe }}
+
+
+ {{ _('Notification settings') }}
+
+{% endblock %}
\ No newline at end of file
diff --git a/project/templates/email/newsletter.txt b/project/templates/email/newsletter.txt
new file mode 100644
index 0000000..5e788d3
--- /dev/null
+++ b/project/templates/email/newsletter.txt
@@ -0,0 +1,3 @@
+{{ message|safe }}
+
+{{ _('Notification settings') }}: {{ url_for('user_notifications', _external=True) }}
diff --git a/project/templates/profile.html b/project/templates/profile.html
index 4472bcb..c864fcb 100644
--- a/project/templates/profile.html
+++ b/project/templates/profile.html
@@ -19,6 +19,10 @@
{{ _('Settings') }}
+
+ {{ _('Notifications') }}
+
+
{{ _('Applications') }}
diff --git a/project/templates/user/notifications.html b/project/templates/user/notifications.html
new file mode 100644
index 0000000..04fdc24
--- /dev/null
+++ b/project/templates/user/notifications.html
@@ -0,0 +1,16 @@
+{% extends "layout.html" %}
+{% from "_macros.html" import render_field_with_errors, render_field %}
+{%- block title -%}
+{{ _('Notifications') }}
+{%- endblock -%}
+{% block content %}
+
+{{ _('Notifications') }}
+
+
+
+{% endblock %}
diff --git a/project/translations/de/LC_MESSAGES/messages.mo b/project/translations/de/LC_MESSAGES/messages.mo
index 0f63114b2bdde6e381ea3668fc22f2e2e4e27e59..43c97226579deb75085b25250e18f18e946cc541 100644
GIT binary patch
delta 8623
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
MO#KbtI*rl)1LMqxZ2$lO
delta 8039
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
QrF}^=Tl@KD%-R?FKTJH(8vp\n"
"Language: de\n"
@@ -209,7 +209,7 @@ msgstr "Nutzungsbedingungen"
msgid "Legal notice"
msgstr "Impressum"
-#: project/forms/admin.py:13 project/templates/_macros.html:1395
+#: project/forms/admin.py:13 project/templates/_macros.html:1397
#: project/templates/layout.html:302
#: project/templates/widget/event_suggestion/create.html:204
#: project/views/admin_unit.py:73 project/views/root.py:69
@@ -226,6 +226,7 @@ msgid "Start page"
msgstr "Startseite"
#: project/forms/admin.py:17 project/forms/oauth2_client.py:24
+#: project/forms/user.py:13
msgid "Save"
msgstr "Speichern"
@@ -294,7 +295,7 @@ msgstr ""
msgid "Update organization"
msgstr "Organisation aktualisieren"
-#: project/forms/admin.py:63
+#: project/forms/admin.py:63 project/forms/admin.py:70
msgid "Recipient"
msgstr "Empfänger"
@@ -302,6 +303,22 @@ msgstr "Empfänger"
msgid "Send test mail synchronously"
msgstr "Test-Mail synchron senden"
+#: project/forms/admin.py:72 project/forms/admin.py:78
+msgid "Test recipient"
+msgstr "Test-Empfänger"
+
+#: project/forms/admin.py:73
+msgid "All users with enabled newsletter setting"
+msgstr "Alle Nutzer mit aktiviertem Newsletter"
+
+#: project/forms/admin.py:79
+msgid "Message"
+msgstr "Nachricht"
+
+#: project/forms/admin.py:80
+msgid "Send newsletter"
+msgstr "Newsletter senden"
+
#: project/forms/admin_unit.py:15 project/forms/event_place.py:12
#: project/forms/organizer.py:12
msgid "Street"
@@ -354,7 +371,7 @@ msgstr ""
"eindeutig zu identifizieren. Der Kurzname darf nur Buchstaben, Nummern "
"und Unterstriche enthalten."
-#: project/forms/admin_unit.py:41 project/templates/_macros.html:1531
+#: project/forms/admin_unit.py:41 project/templates/_macros.html:1533
msgid "Short name must contain only letters numbers or underscore"
msgstr "Der Kurzname darf nur Buchstaben, Nummern und Unterstriche enthalten"
@@ -367,20 +384,21 @@ msgstr "Link URL"
#: project/forms/admin_unit.py:48 project/forms/admin_unit_member.py:11
#: project/forms/admin_unit_member.py:23 project/forms/admin_unit_member.py:28
#: project/forms/event.py:107 project/forms/event_suggestion.py:38
-#: project/forms/organizer.py:27 project/templates/_macros.html:235
-#: project/templates/_macros.html:1491 project/templates/admin/admin.html:27
+#: project/forms/organizer.py:27 project/templates/_macros.html:237
+#: project/templates/_macros.html:1493 project/templates/admin/admin.html:27
+#: project/templates/admin/email.html:4 project/templates/admin/email.html:66
#: project/templates/admin/users.html:19
msgid "Email"
msgstr "Email"
#: project/forms/admin_unit.py:49 project/forms/event.py:108
#: project/forms/event_suggestion.py:31 project/forms/organizer.py:28
-#: project/templates/_macros.html:288
+#: project/templates/_macros.html:290
msgid "Phone"
msgstr "Telefon"
#: project/forms/admin_unit.py:50 project/forms/event.py:109
-#: project/forms/organizer.py:29 project/templates/_macros.html:296
+#: project/forms/organizer.py:29 project/templates/_macros.html:298
msgid "Fax"
msgstr "Fax"
@@ -589,16 +607,16 @@ msgstr "Gib an, wann der Termin endet. Ein Termin darf maximal 14 Tage dauern."
msgid "All-day"
msgstr "Ganztägig"
-#: project/forms/event.py:54 project/templates/_macros.html:1711
+#: project/forms/event.py:54 project/templates/_macros.html:1713
#: project/templates/widget/event_suggestion/create.html:240
msgid "Recurring event"
msgstr "Serientermin"
-#: project/forms/event.py:61 project/templates/_macros.html:1252
+#: project/forms/event.py:61 project/templates/_macros.html:1254
msgid "The start must be before the end."
msgstr "Der Start muss vor dem Ende sein."
-#: project/forms/event.py:67 project/templates/_macros.html:1269
+#: project/forms/event.py:67 project/templates/_macros.html:1271
msgid "An event can last a maximum of 14 days."
msgstr "Eine Veranstaltung darf maximal 14 Tage dauern."
@@ -634,7 +652,7 @@ msgstr "Ticket Link"
msgid "Enter a link where tickets can be purchased."
msgstr "Gib einen Link ein, über den Tickets gekauft werden können."
-#: project/forms/event.py:136 project/templates/_macros.html:217
+#: project/forms/event.py:136 project/templates/_macros.html:219
msgid "Tags"
msgstr "Stichworte"
@@ -687,7 +705,7 @@ msgstr "Anmeldung erforderlich"
msgid "If the participants needs to register for the event."
msgstr "Wenn sich die Teilnehmer für die Veranstaltung anmelden müssen."
-#: project/forms/event.py:170 project/templates/_macros.html:249
+#: project/forms/event.py:170 project/templates/_macros.html:251
#: project/templates/layout.html:110
msgid "Booked up"
msgstr "Ausgebucht"
@@ -773,8 +791,8 @@ msgstr ""
"Wir empfehlen dir, ein Foto für die Veranstaltung hochzuladen. Es macht "
"schon deutlich mehr her, aber es geht natürlich auch ohne."
-#: project/forms/event.py:242 project/templates/_macros.html:396
-#: project/templates/_macros.html:559
+#: project/forms/event.py:242 project/templates/_macros.html:398
+#: project/templates/_macros.html:561
msgid "Previous start date"
msgstr "Vorheriges Startdatum"
@@ -826,7 +844,7 @@ msgstr "Ungültiger Mitveranstalter."
#: project/forms/event.py:286 project/forms/event.py:295
#: project/forms/event.py:368 project/forms/event_suggestion.py:50
-#: project/templates/_macros.html:436 project/templates/_macros.html:599
+#: project/templates/_macros.html:438 project/templates/_macros.html:601
#: project/templates/event/create.html:284
#: project/templates/event/update.html:166
#: project/templates/event_place/create.html:31
@@ -845,8 +863,8 @@ msgstr "Neuen Ort eingeben"
#: project/forms/event.py:302 project/forms/event.py:311
#: project/forms/event.py:376 project/forms/event.py:439
-#: project/forms/event_suggestion.py:60 project/templates/_macros.html:473
-#: project/templates/_macros.html:636 project/templates/event/create.html:253
+#: project/forms/event_suggestion.py:60 project/templates/_macros.html:475
+#: project/templates/_macros.html:638 project/templates/event/create.html:253
#: project/templates/event/update.html:156
#: project/templates/organizer/create.html:27
#: project/templates/organizer/delete.html:13
@@ -935,7 +953,7 @@ msgstr "Öffentlicher Status"
msgid "PublicStatus.published"
msgstr "Veröffentlicht"
-#: project/forms/event.py:402 project/templates/_macros.html:255
+#: project/forms/event.py:402 project/templates/_macros.html:257
msgid "PublicStatus.draft"
msgstr "Entwurf"
@@ -948,7 +966,7 @@ msgstr "Wähle den öffentlichen Status der Veranstaltung."
msgid "Update event"
msgstr "Veranstaltung aktualisieren"
-#: project/forms/event.py:423 project/templates/_macros.html:1224
+#: project/forms/event.py:423 project/templates/_macros.html:1226
#: project/templates/event/actions.html:66
#: project/templates/event/delete.html:6
msgid "Delete event"
@@ -969,7 +987,7 @@ msgid "Keyword"
msgstr "Stichwort"
#: project/forms/event.py:436 project/forms/event_date.py:21
-#: project/forms/planing.py:19 project/templates/_macros.html:369
+#: project/forms/planing.py:19 project/templates/_macros.html:371
msgid "Category"
msgstr "Kategorie"
@@ -978,7 +996,7 @@ msgid "Find events"
msgstr "Veranstaltungen finden"
#: project/forms/event_date.py:24 project/forms/planing.py:22
-#: project/templates/_macros.html:303
+#: project/templates/_macros.html:305
#: project/templates/admin_unit/create.html:38
#: project/templates/admin_unit/update.html:39
#: project/templates/event_place/create.html:40
@@ -1125,7 +1143,7 @@ msgid "Weekdays"
msgstr "Wochentage"
#: project/forms/reference.py:11 project/forms/reference_request.py:16
-#: project/templates/_macros.html:489 project/templates/_macros.html:652
+#: project/templates/_macros.html:491 project/templates/_macros.html:654
#: project/templates/admin_unit/create.html:28
#: project/templates/admin_unit/update.html:29
#: project/templates/layout.html:242
@@ -1153,7 +1171,7 @@ msgstr "Anfrage speichern"
msgid "Delete request"
msgstr "Anfrage löschen"
-#: project/forms/reference_request.py:28 project/templates/_macros.html:1407
+#: project/forms/reference_request.py:28 project/templates/_macros.html:1409
#: project/templates/event_suggestion/review_status.html:18
#: project/templates/reference_request/review_status.html:12
msgid "Review status"
@@ -1211,43 +1229,53 @@ msgstr "Erlauben"
msgid "Deny"
msgstr "Ablehnen"
+#: project/forms/user.py:9 project/templates/admin/admin.html:31
+#: project/templates/admin/newsletter.html:4
+#: project/templates/admin/newsletter.html:93
+msgid "Newsletter"
+msgstr "Newsletter"
+
+#: project/forms/user.py:10
+msgid "Information about new features and improvements."
+msgstr "Informationen über neue Features und Verbesserungen."
+
#: project/forms/widgets.py:154
msgid "This field is required."
msgstr "Dieses Feld ist erforderlich."
-#: project/templates/_macros.html:147
+#: project/templates/_macros.html:149
msgid "Show on Google Maps"
msgstr "Auf Google Maps anzeigen"
-#: project/templates/_macros.html:226
+#: project/templates/_macros.html:228
msgid "Link"
msgstr "Link"
-#: project/templates/_macros.html:282
+#: project/templates/_macros.html:284
msgid "Verified"
msgstr "Verifiziert"
-#: project/templates/_macros.html:346
+#: project/templates/_macros.html:348
#, python-format
msgid "Created at %(created_at)s by %(created_by)s."
msgstr "Erstellt am %(created_at)s von %(created_by)s."
-#: project/templates/_macros.html:348
+#: project/templates/_macros.html:350
#, python-format
msgid "Created at %(created_at)s."
msgstr "Erstellt am %(created_at)s."
-#: project/templates/_macros.html:353
+#: project/templates/_macros.html:355
#, python-format
msgid "Last updated at %(updated_at)s by %(updated_by)s."
msgstr "Zuletzt aktualisiert am %(updated_at)s von %(updated_by)s."
-#: project/templates/_macros.html:355
+#: project/templates/_macros.html:357
#, python-format
msgid "Last updated at %(updated_at)s."
msgstr "Zuletzt aktualisiert am %(updated_at)s."
-#: project/templates/_macros.html:385 project/templates/_macros.html:555
+#: project/templates/_macros.html:387 project/templates/_macros.html:557
#: project/templates/event/actions.html:25
#: project/templates/event/create.html:230
#: project/templates/event/update.html:122
@@ -1255,65 +1283,65 @@ msgstr "Zuletzt aktualisiert am %(updated_at)s."
msgid "Event"
msgstr "Veranstaltung"
-#: project/templates/_macros.html:391 project/templates/_macros.html:920
+#: project/templates/_macros.html:393 project/templates/_macros.html:922
msgid "Date"
msgstr "Datum"
-#: project/templates/_macros.html:418 project/templates/_macros.html:577
-#: project/templates/_macros.html:1476 project/templates/event/actions.html:51
+#: project/templates/_macros.html:420 project/templates/_macros.html:579
+#: project/templates/_macros.html:1478 project/templates/event/actions.html:51
msgid "Share"
msgstr "Teilen"
-#: project/templates/_macros.html:422 project/templates/_macros.html:581
-#: project/templates/_macros.html:1506
+#: project/templates/_macros.html:424 project/templates/_macros.html:583
+#: project/templates/_macros.html:1508
msgid "Add to calendar"
msgstr "Zum Kalender"
-#: project/templates/_macros.html:430 project/templates/_macros.html:592
+#: project/templates/_macros.html:432 project/templates/_macros.html:594
#: project/templates/event/report.html:4
msgid "Report event"
msgstr "Veranstaltung melden"
-#: project/templates/_macros.html:457 project/templates/_macros.html:618
+#: project/templates/_macros.html:459 project/templates/_macros.html:620
msgid "Show directions"
msgstr "Anreise planen"
-#: project/templates/_macros.html:462 project/templates/_macros.html:623
+#: project/templates/_macros.html:464 project/templates/_macros.html:625
msgid "The event takes place online."
msgstr "Die Veranstaltung findet online statt."
-#: project/templates/_macros.html:464 project/templates/_macros.html:625
+#: project/templates/_macros.html:466 project/templates/_macros.html:627
msgid "The event takes place both offline and online."
msgstr ""
"Die Veranstaltung findet sowohl als Präsenzveranstaltung als auch online "
"statt."
-#: project/templates/_macros.html:585 project/templates/layout.html:168
+#: project/templates/_macros.html:587 project/templates/layout.html:168
#: project/templates/user/favorite_events.html:4
msgid "Favorite events"
msgstr "Merkzettel"
-#: project/templates/_macros.html:679 project/templates/event_date/list.html:5
+#: project/templates/_macros.html:681 project/templates/event_date/list.html:5
#: project/templates/event_date/list.html:299
#: project/templates/reference_request/review.html:32
msgid "Event Dates"
msgstr "Termine"
-#: project/templates/_macros.html:771
+#: project/templates/_macros.html:773
msgid "Search location on Google"
msgstr "Ort bei Google suchen"
-#: project/templates/_macros.html:837
+#: project/templates/_macros.html:839
#, python-format
msgid "%(count)d event dates"
msgstr "%(count)d Termine"
-#: project/templates/_macros.html:860 project/templates/_macros.html:862
+#: project/templates/_macros.html:862 project/templates/_macros.html:864
#: project/templates/event_date/list.html:321
msgid "First"
msgstr "Letzte"
-#: project/templates/_macros.html:865 project/templates/_macros.html:867
+#: project/templates/_macros.html:867 project/templates/_macros.html:869
#: project/templates/event_date/list.html:322
#: project/templates/widget/event_suggestion/create.html:193
#: project/templates/widget/event_suggestion/create.html:218
@@ -1324,12 +1352,12 @@ msgstr "Letzte"
msgid "Previous"
msgstr "Zurück"
-#: project/templates/_macros.html:869
+#: project/templates/_macros.html:871
#, python-format
msgid "Page %(page)d of %(pages)d (%(total)d total)"
msgstr "Seite %(page)d von %(pages)d (%(total)d insgesamt)"
-#: project/templates/_macros.html:871 project/templates/_macros.html:873
+#: project/templates/_macros.html:873 project/templates/_macros.html:875
#: project/templates/event_date/list.html:324
#: project/templates/widget/event_suggestion/create.html:194
#: project/templates/widget/event_suggestion/create.html:219
@@ -1339,88 +1367,88 @@ msgstr "Seite %(page)d von %(pages)d (%(total)d insgesamt)"
msgid "Next"
msgstr "Weiter"
-#: project/templates/_macros.html:876 project/templates/_macros.html:878
+#: project/templates/_macros.html:878 project/templates/_macros.html:880
#: project/templates/event_date/list.html:325
msgid "Last"
msgstr "Erste"
-#: project/templates/_macros.html:943
+#: project/templates/_macros.html:945
msgid "Radius"
msgstr "Umkreis"
-#: project/templates/_macros.html:1153
+#: project/templates/_macros.html:1155
msgid "Edit image"
msgstr "Bild bearbeiten"
-#: project/templates/_macros.html:1174
+#: project/templates/_macros.html:1176
msgid "Close"
msgstr "Schließen"
-#: project/templates/_macros.html:1175
+#: project/templates/_macros.html:1177
msgid "Okay"
msgstr "OK"
-#: project/templates/_macros.html:1187
+#: project/templates/_macros.html:1189
msgid "Choose image file"
msgstr "Bild-Datei auswählen"
-#: project/templates/_macros.html:1223 project/templates/event/actions.html:65
+#: project/templates/_macros.html:1225 project/templates/event/actions.html:65
#: project/templates/event/delete.html:12
msgid "Edit event"
msgstr "Veranstaltung bearbeiten"
-#: project/templates/_macros.html:1226 project/templates/manage/events.html:66
+#: project/templates/_macros.html:1228 project/templates/manage/events.html:66
msgid "More"
msgstr "Mehr"
-#: project/templates/_macros.html:1273
+#: project/templates/_macros.html:1275
msgid "Please enter a valid time, between 00:00 and 23:59."
msgstr "Bitte gib eine gültige Uhrzeit zwischen 00:00 und 23:59 ein."
-#: project/templates/_macros.html:1301
+#: project/templates/_macros.html:1303
#, python-format
msgid "Just use %(term)s"
msgstr "Verwende einfach %(term)s"
-#: project/templates/_macros.html:1367
+#: project/templates/_macros.html:1369
msgid "Event suggestion"
msgstr "Veranstaltungsvorschlag"
-#: project/templates/_macros.html:1485
+#: project/templates/_macros.html:1487
msgid "Link copied"
msgstr "Link kopiert"
-#: project/templates/_macros.html:1485
+#: project/templates/_macros.html:1487
msgid "Copy link"
msgstr "Link kopieren"
-#: project/templates/_macros.html:1514
+#: project/templates/_macros.html:1516
msgid "Google calendar"
msgstr "Google Kalender"
-#: project/templates/_macros.html:1515
+#: project/templates/_macros.html:1517
msgid "Apple calendar"
msgstr "Apple Kalender"
-#: project/templates/_macros.html:1516
+#: project/templates/_macros.html:1518
msgid "Yahoo calendar"
msgstr "Yahoo Kalender"
-#: project/templates/_macros.html:1517
+#: project/templates/_macros.html:1519
msgid "Other calendar"
msgstr "Anderer Kalender"
-#: project/templates/_macros.html:1712
+#: project/templates/_macros.html:1714
msgid "Remove event date"
msgstr "Termin entfernen"
-#: project/templates/_macros.html:1741 project/templates/event/create.html:176
+#: project/templates/_macros.html:1743 project/templates/event/create.html:176
#: project/templates/event/update.html:99
#: project/templates/widget/event_suggestion/create.html:129
msgid "Enter organizer"
msgstr "Veranstalter eingeben"
-#: project/templates/_macros.html:1765
+#: project/templates/_macros.html:1767
msgid "Enter list name"
msgstr "Listenname eingeben"
@@ -1464,6 +1492,7 @@ msgstr "Profil"
#: project/templates/admin/admin.html:3 project/templates/admin/admin.html:9
#: project/templates/admin/admin_units.html:10
#: project/templates/admin/email.html:65
+#: project/templates/admin/newsletter.html:92
#: project/templates/admin/settings.html:10
#: project/templates/admin/users.html:10 project/templates/layout.html:171
msgid "Admin"
@@ -1551,8 +1580,7 @@ msgstr "Beziehungen"
msgid "Organization invitations"
msgstr "Organisationseinladungen"
-#: project/templates/admin/admin.html:15 project/templates/admin/email.html:4
-#: project/templates/admin/email.html:66
+#: project/templates/admin/admin.html:15
#: project/templates/admin/settings.html:4
#: project/templates/admin/settings.html:11
#: project/templates/admin_unit/update.html:6
@@ -1578,18 +1606,24 @@ msgid "Switch organization"
msgstr "Organisation wechseln"
#: project/templates/developer/read.html:4 project/templates/layout.html:310
-#: project/templates/profile.html:29
+#: project/templates/profile.html:33
msgid "Developer"
msgstr "Entwickler"
#: project/templates/profile.html:23
+#: project/templates/user/notifications.html:4
+#: project/templates/user/notifications.html:8
+msgid "Notifications"
+msgstr "Benachrichtigungen"
+
+#: project/templates/profile.html:27
msgid "Applications"
msgstr "Apps"
#: project/templates/oauth2_client/list.html:4
#: project/templates/oauth2_client/list.html:11
#: project/templates/oauth2_client/read.html:11
-#: project/templates/profile.html:33
+#: project/templates/profile.html:37
msgid "OAuth2 clients"
msgstr "OAuth2 Clients"
@@ -1610,7 +1644,7 @@ msgstr "Benutzer"
msgid "Edit"
msgstr "Bearbeiten"
-#: project/templates/admin/email.html:47 project/views/admin.py:119
+#: project/templates/admin/email.html:47 project/views/admin.py:122
msgid "Mail sent successfully"
msgstr "Mail erfolgreich gesendet"
@@ -1622,6 +1656,10 @@ msgstr "Test-Mail"
msgid "Send test mail asynchronously"
msgstr "Test-Mail asynchron senden"
+#: project/templates/admin/newsletter.html:59
+msgid "Mails sent successfully"
+msgstr "Mails erfolgreich gesendet"
+
#: project/templates/admin_unit/create.html:58
#: project/templates/admin_unit/update.html:59
#: project/templates/event/create.html:347
@@ -1676,6 +1714,10 @@ msgstr "Moin"
msgid "this is a message from %(site_name)s."
msgstr "das ist eine Nachricht von %(site_name)s."
+#: project/templates/email/newsletter.html:7
+msgid "Notification settings"
+msgstr "Benachrichtigungseinstellungen"
+
#: project/templates/email/organization_invitation_accepted_notice.html:4
#, python-format
msgid ""
@@ -2096,20 +2138,26 @@ msgstr "Optionale Details"
msgid "Preview"
msgstr "Vorschau"
-#: project/views/admin.py:55
+#: project/views/admin.py:58
msgid "Organization successfully updated"
msgstr "Organisation erfolgreich aktualisiert"
-#: project/views/admin.py:79 project/views/manage.py:361
+#: project/views/admin.py:82 project/views/manage.py:361
+#: project/views/user.py:27
msgid "Settings successfully updated"
msgstr "Einstellungen erfolgreich aktualisiert"
-#: project/views/admin.py:108
+#: project/views/admin.py:111
#, python-format
msgid "Test mail from %(site_name)s"
msgstr "Test-Mail von %(site_name)s"
-#: project/views/admin.py:152
+#: project/views/admin.py:150
+#, python-format
+msgid "Newsletter from %(site_name)s"
+msgstr "Newsletter von %(site_name)s"
+
+#: project/views/admin.py:200
msgid "User successfully updated"
msgstr "Nutzer erfolgreich aktualisiert"
diff --git a/project/translations/en/LC_MESSAGES/messages.mo b/project/translations/en/LC_MESSAGES/messages.mo
index f1fcd1de63b22f2729b92260be565e04a7704b17..bab21eea6c06486d7565ab6a2cf0334068daee1f 100644
GIT binary patch
delta 23
ecmaDW{Z@LzY)&o<1w(Ty0~2inqs>b>Jy-x?Q3nSA
delta 23
ecmaDW{Z@LzY)&o{1tUW%BNJ@{!_7-MJy-x>=m!4)
diff --git a/project/translations/en/LC_MESSAGES/messages.po b/project/translations/en/LC_MESSAGES/messages.po
index e38fe4b..fc96199 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-03-24 21:24+0100\n"
+"POT-Creation-Date: 2023-03-28 17:04+0200\n"
"PO-Revision-Date: 2021-04-30 15:04+0200\n"
"Last-Translator: FULL NAME \n"
"Language: en\n"
@@ -209,7 +209,7 @@ msgstr ""
msgid "Legal notice"
msgstr ""
-#: project/forms/admin.py:13 project/templates/_macros.html:1395
+#: project/forms/admin.py:13 project/templates/_macros.html:1397
#: project/templates/layout.html:302
#: project/templates/widget/event_suggestion/create.html:204
#: project/views/admin_unit.py:73 project/views/root.py:69
@@ -226,6 +226,7 @@ msgid "Start page"
msgstr ""
#: project/forms/admin.py:17 project/forms/oauth2_client.py:24
+#: project/forms/user.py:13
msgid "Save"
msgstr ""
@@ -286,7 +287,7 @@ msgstr ""
msgid "Update organization"
msgstr ""
-#: project/forms/admin.py:63
+#: project/forms/admin.py:63 project/forms/admin.py:70
msgid "Recipient"
msgstr ""
@@ -294,6 +295,22 @@ msgstr ""
msgid "Send test mail synchronously"
msgstr ""
+#: project/forms/admin.py:72 project/forms/admin.py:78
+msgid "Test recipient"
+msgstr ""
+
+#: project/forms/admin.py:73
+msgid "All users with enabled newsletter setting"
+msgstr ""
+
+#: project/forms/admin.py:79
+msgid "Message"
+msgstr ""
+
+#: project/forms/admin.py:80
+msgid "Send newsletter"
+msgstr ""
+
#: project/forms/admin_unit.py:15 project/forms/event_place.py:12
#: project/forms/organizer.py:12
msgid "Street"
@@ -343,7 +360,7 @@ msgstr ""
msgid "The short name is used to create a unique identifier for your events"
msgstr ""
-#: project/forms/admin_unit.py:41 project/templates/_macros.html:1531
+#: project/forms/admin_unit.py:41 project/templates/_macros.html:1533
msgid "Short name must contain only letters numbers or underscore"
msgstr ""
@@ -356,20 +373,21 @@ msgstr ""
#: project/forms/admin_unit.py:48 project/forms/admin_unit_member.py:11
#: project/forms/admin_unit_member.py:23 project/forms/admin_unit_member.py:28
#: project/forms/event.py:107 project/forms/event_suggestion.py:38
-#: project/forms/organizer.py:27 project/templates/_macros.html:235
-#: project/templates/_macros.html:1491 project/templates/admin/admin.html:27
+#: project/forms/organizer.py:27 project/templates/_macros.html:237
+#: project/templates/_macros.html:1493 project/templates/admin/admin.html:27
+#: project/templates/admin/email.html:4 project/templates/admin/email.html:66
#: project/templates/admin/users.html:19
msgid "Email"
msgstr ""
#: project/forms/admin_unit.py:49 project/forms/event.py:108
#: project/forms/event_suggestion.py:31 project/forms/organizer.py:28
-#: project/templates/_macros.html:288
+#: project/templates/_macros.html:290
msgid "Phone"
msgstr ""
#: project/forms/admin_unit.py:50 project/forms/event.py:109
-#: project/forms/organizer.py:29 project/templates/_macros.html:296
+#: project/forms/organizer.py:29 project/templates/_macros.html:298
msgid "Fax"
msgstr ""
@@ -567,16 +585,16 @@ msgstr ""
msgid "All-day"
msgstr ""
-#: project/forms/event.py:54 project/templates/_macros.html:1711
+#: project/forms/event.py:54 project/templates/_macros.html:1713
#: project/templates/widget/event_suggestion/create.html:240
msgid "Recurring event"
msgstr ""
-#: project/forms/event.py:61 project/templates/_macros.html:1252
+#: project/forms/event.py:61 project/templates/_macros.html:1254
msgid "The start must be before the end."
msgstr ""
-#: project/forms/event.py:67 project/templates/_macros.html:1269
+#: project/forms/event.py:67 project/templates/_macros.html:1271
msgid "An event can last a maximum of 14 days."
msgstr ""
@@ -610,7 +628,7 @@ msgstr ""
msgid "Enter a link where tickets can be purchased."
msgstr ""
-#: project/forms/event.py:136 project/templates/_macros.html:217
+#: project/forms/event.py:136 project/templates/_macros.html:219
msgid "Tags"
msgstr ""
@@ -660,7 +678,7 @@ msgstr ""
msgid "If the participants needs to register for the event."
msgstr ""
-#: project/forms/event.py:170 project/templates/_macros.html:249
+#: project/forms/event.py:170 project/templates/_macros.html:251
#: project/templates/layout.html:110
msgid "Booked up"
msgstr ""
@@ -740,8 +758,8 @@ msgid ""
" course it works without it."
msgstr ""
-#: project/forms/event.py:242 project/templates/_macros.html:396
-#: project/templates/_macros.html:559
+#: project/forms/event.py:242 project/templates/_macros.html:398
+#: project/templates/_macros.html:561
msgid "Previous start date"
msgstr ""
@@ -787,7 +805,7 @@ msgstr ""
#: project/forms/event.py:286 project/forms/event.py:295
#: project/forms/event.py:368 project/forms/event_suggestion.py:50
-#: project/templates/_macros.html:436 project/templates/_macros.html:599
+#: project/templates/_macros.html:438 project/templates/_macros.html:601
#: project/templates/event/create.html:284
#: project/templates/event/update.html:166
#: project/templates/event_place/create.html:31
@@ -806,8 +824,8 @@ msgstr ""
#: project/forms/event.py:302 project/forms/event.py:311
#: project/forms/event.py:376 project/forms/event.py:439
-#: project/forms/event_suggestion.py:60 project/templates/_macros.html:473
-#: project/templates/_macros.html:636 project/templates/event/create.html:253
+#: project/forms/event_suggestion.py:60 project/templates/_macros.html:475
+#: project/templates/_macros.html:638 project/templates/event/create.html:253
#: project/templates/event/update.html:156
#: project/templates/organizer/create.html:27
#: project/templates/organizer/delete.html:13
@@ -892,7 +910,7 @@ msgstr ""
msgid "PublicStatus.published"
msgstr ""
-#: project/forms/event.py:402 project/templates/_macros.html:255
+#: project/forms/event.py:402 project/templates/_macros.html:257
msgid "PublicStatus.draft"
msgstr ""
@@ -905,7 +923,7 @@ msgstr ""
msgid "Update event"
msgstr ""
-#: project/forms/event.py:423 project/templates/_macros.html:1224
+#: project/forms/event.py:423 project/templates/_macros.html:1226
#: project/templates/event/actions.html:66
#: project/templates/event/delete.html:6
msgid "Delete event"
@@ -926,7 +944,7 @@ msgid "Keyword"
msgstr ""
#: project/forms/event.py:436 project/forms/event_date.py:21
-#: project/forms/planing.py:19 project/templates/_macros.html:369
+#: project/forms/planing.py:19 project/templates/_macros.html:371
msgid "Category"
msgstr ""
@@ -935,7 +953,7 @@ msgid "Find events"
msgstr ""
#: project/forms/event_date.py:24 project/forms/planing.py:22
-#: project/templates/_macros.html:303
+#: project/templates/_macros.html:305
#: project/templates/admin_unit/create.html:38
#: project/templates/admin_unit/update.html:39
#: project/templates/event_place/create.html:40
@@ -1078,7 +1096,7 @@ msgid "Weekdays"
msgstr ""
#: project/forms/reference.py:11 project/forms/reference_request.py:16
-#: project/templates/_macros.html:489 project/templates/_macros.html:652
+#: project/templates/_macros.html:491 project/templates/_macros.html:654
#: project/templates/admin_unit/create.html:28
#: project/templates/admin_unit/update.html:29
#: project/templates/layout.html:242
@@ -1106,7 +1124,7 @@ msgstr ""
msgid "Delete request"
msgstr ""
-#: project/forms/reference_request.py:28 project/templates/_macros.html:1407
+#: project/forms/reference_request.py:28 project/templates/_macros.html:1409
#: project/templates/event_suggestion/review_status.html:18
#: project/templates/reference_request/review_status.html:12
msgid "Review status"
@@ -1164,43 +1182,53 @@ msgstr ""
msgid "Deny"
msgstr ""
+#: project/forms/user.py:9 project/templates/admin/admin.html:31
+#: project/templates/admin/newsletter.html:4
+#: project/templates/admin/newsletter.html:93
+msgid "Newsletter"
+msgstr ""
+
+#: project/forms/user.py:10
+msgid "Information about new features and improvements."
+msgstr ""
+
#: project/forms/widgets.py:154
msgid "This field is required."
msgstr ""
-#: project/templates/_macros.html:147
+#: project/templates/_macros.html:149
msgid "Show on Google Maps"
msgstr ""
-#: project/templates/_macros.html:226
+#: project/templates/_macros.html:228
msgid "Link"
msgstr ""
-#: project/templates/_macros.html:282
+#: project/templates/_macros.html:284
msgid "Verified"
msgstr ""
-#: project/templates/_macros.html:346
-#, python-format
-msgid "Created at %(created_at)s by %(created_by)s."
-msgstr ""
-
#: project/templates/_macros.html:348
#, python-format
-msgid "Created at %(created_at)s."
+msgid "Created at %(created_at)s by %(created_by)s."
msgstr ""
-#: project/templates/_macros.html:353
+#: project/templates/_macros.html:350
#, python-format
-msgid "Last updated at %(updated_at)s by %(updated_by)s."
+msgid "Created at %(created_at)s."
msgstr ""
#: project/templates/_macros.html:355
#, python-format
+msgid "Last updated at %(updated_at)s by %(updated_by)s."
+msgstr ""
+
+#: project/templates/_macros.html:357
+#, python-format
msgid "Last updated at %(updated_at)s."
msgstr ""
-#: project/templates/_macros.html:385 project/templates/_macros.html:555
+#: project/templates/_macros.html:387 project/templates/_macros.html:557
#: project/templates/event/actions.html:25
#: project/templates/event/create.html:230
#: project/templates/event/update.html:122
@@ -1208,63 +1236,63 @@ msgstr ""
msgid "Event"
msgstr ""
-#: project/templates/_macros.html:391 project/templates/_macros.html:920
+#: project/templates/_macros.html:393 project/templates/_macros.html:922
msgid "Date"
msgstr ""
-#: project/templates/_macros.html:418 project/templates/_macros.html:577
-#: project/templates/_macros.html:1476 project/templates/event/actions.html:51
+#: project/templates/_macros.html:420 project/templates/_macros.html:579
+#: project/templates/_macros.html:1478 project/templates/event/actions.html:51
msgid "Share"
msgstr ""
-#: project/templates/_macros.html:422 project/templates/_macros.html:581
-#: project/templates/_macros.html:1506
+#: project/templates/_macros.html:424 project/templates/_macros.html:583
+#: project/templates/_macros.html:1508
msgid "Add to calendar"
msgstr ""
-#: project/templates/_macros.html:430 project/templates/_macros.html:592
+#: project/templates/_macros.html:432 project/templates/_macros.html:594
#: project/templates/event/report.html:4
msgid "Report event"
msgstr ""
-#: project/templates/_macros.html:457 project/templates/_macros.html:618
+#: project/templates/_macros.html:459 project/templates/_macros.html:620
msgid "Show directions"
msgstr ""
-#: project/templates/_macros.html:462 project/templates/_macros.html:623
+#: project/templates/_macros.html:464 project/templates/_macros.html:625
msgid "The event takes place online."
msgstr ""
-#: project/templates/_macros.html:464 project/templates/_macros.html:625
+#: project/templates/_macros.html:466 project/templates/_macros.html:627
msgid "The event takes place both offline and online."
msgstr ""
-#: project/templates/_macros.html:585 project/templates/layout.html:168
+#: project/templates/_macros.html:587 project/templates/layout.html:168
#: project/templates/user/favorite_events.html:4
msgid "Favorite events"
msgstr ""
-#: project/templates/_macros.html:679 project/templates/event_date/list.html:5
+#: project/templates/_macros.html:681 project/templates/event_date/list.html:5
#: project/templates/event_date/list.html:299
#: project/templates/reference_request/review.html:32
msgid "Event Dates"
msgstr ""
-#: project/templates/_macros.html:771
+#: project/templates/_macros.html:773
msgid "Search location on Google"
msgstr ""
-#: project/templates/_macros.html:837
+#: project/templates/_macros.html:839
#, python-format
msgid "%(count)d event dates"
msgstr ""
-#: project/templates/_macros.html:860 project/templates/_macros.html:862
+#: project/templates/_macros.html:862 project/templates/_macros.html:864
#: project/templates/event_date/list.html:321
msgid "First"
msgstr ""
-#: project/templates/_macros.html:865 project/templates/_macros.html:867
+#: project/templates/_macros.html:867 project/templates/_macros.html:869
#: project/templates/event_date/list.html:322
#: project/templates/widget/event_suggestion/create.html:193
#: project/templates/widget/event_suggestion/create.html:218
@@ -1275,12 +1303,12 @@ msgstr ""
msgid "Previous"
msgstr ""
-#: project/templates/_macros.html:869
+#: project/templates/_macros.html:871
#, python-format
msgid "Page %(page)d of %(pages)d (%(total)d total)"
msgstr ""
-#: project/templates/_macros.html:871 project/templates/_macros.html:873
+#: project/templates/_macros.html:873 project/templates/_macros.html:875
#: project/templates/event_date/list.html:324
#: project/templates/widget/event_suggestion/create.html:194
#: project/templates/widget/event_suggestion/create.html:219
@@ -1290,88 +1318,88 @@ msgstr ""
msgid "Next"
msgstr ""
-#: project/templates/_macros.html:876 project/templates/_macros.html:878
+#: project/templates/_macros.html:878 project/templates/_macros.html:880
#: project/templates/event_date/list.html:325
msgid "Last"
msgstr ""
-#: project/templates/_macros.html:943
+#: project/templates/_macros.html:945
msgid "Radius"
msgstr ""
-#: project/templates/_macros.html:1153
+#: project/templates/_macros.html:1155
msgid "Edit image"
msgstr ""
-#: project/templates/_macros.html:1174
+#: project/templates/_macros.html:1176
msgid "Close"
msgstr ""
-#: project/templates/_macros.html:1175
+#: project/templates/_macros.html:1177
msgid "Okay"
msgstr ""
-#: project/templates/_macros.html:1187
+#: project/templates/_macros.html:1189
msgid "Choose image file"
msgstr ""
-#: project/templates/_macros.html:1223 project/templates/event/actions.html:65
+#: project/templates/_macros.html:1225 project/templates/event/actions.html:65
#: project/templates/event/delete.html:12
msgid "Edit event"
msgstr ""
-#: project/templates/_macros.html:1226 project/templates/manage/events.html:66
+#: project/templates/_macros.html:1228 project/templates/manage/events.html:66
msgid "More"
msgstr ""
-#: project/templates/_macros.html:1273
+#: project/templates/_macros.html:1275
msgid "Please enter a valid time, between 00:00 and 23:59."
msgstr ""
-#: project/templates/_macros.html:1301
+#: project/templates/_macros.html:1303
#, python-format
msgid "Just use %(term)s"
msgstr ""
-#: project/templates/_macros.html:1367
+#: project/templates/_macros.html:1369
msgid "Event suggestion"
msgstr ""
-#: project/templates/_macros.html:1485
+#: project/templates/_macros.html:1487
msgid "Link copied"
msgstr ""
-#: project/templates/_macros.html:1485
+#: project/templates/_macros.html:1487
msgid "Copy link"
msgstr ""
-#: project/templates/_macros.html:1514
+#: project/templates/_macros.html:1516
msgid "Google calendar"
msgstr ""
-#: project/templates/_macros.html:1515
+#: project/templates/_macros.html:1517
msgid "Apple calendar"
msgstr ""
-#: project/templates/_macros.html:1516
+#: project/templates/_macros.html:1518
msgid "Yahoo calendar"
msgstr ""
-#: project/templates/_macros.html:1517
+#: project/templates/_macros.html:1519
msgid "Other calendar"
msgstr ""
-#: project/templates/_macros.html:1712
+#: project/templates/_macros.html:1714
msgid "Remove event date"
msgstr ""
-#: project/templates/_macros.html:1741 project/templates/event/create.html:176
+#: project/templates/_macros.html:1743 project/templates/event/create.html:176
#: project/templates/event/update.html:99
#: project/templates/widget/event_suggestion/create.html:129
msgid "Enter organizer"
msgstr ""
-#: project/templates/_macros.html:1765
+#: project/templates/_macros.html:1767
msgid "Enter list name"
msgstr ""
@@ -1415,6 +1443,7 @@ msgstr ""
#: project/templates/admin/admin.html:3 project/templates/admin/admin.html:9
#: project/templates/admin/admin_units.html:10
#: project/templates/admin/email.html:65
+#: project/templates/admin/newsletter.html:92
#: project/templates/admin/settings.html:10
#: project/templates/admin/users.html:10 project/templates/layout.html:171
msgid "Admin"
@@ -1502,8 +1531,7 @@ msgstr ""
msgid "Organization invitations"
msgstr ""
-#: project/templates/admin/admin.html:15 project/templates/admin/email.html:4
-#: project/templates/admin/email.html:66
+#: project/templates/admin/admin.html:15
#: project/templates/admin/settings.html:4
#: project/templates/admin/settings.html:11
#: project/templates/admin_unit/update.html:6
@@ -1529,18 +1557,24 @@ msgid "Switch organization"
msgstr ""
#: project/templates/developer/read.html:4 project/templates/layout.html:310
-#: project/templates/profile.html:29
+#: project/templates/profile.html:33
msgid "Developer"
msgstr ""
#: project/templates/profile.html:23
+#: project/templates/user/notifications.html:4
+#: project/templates/user/notifications.html:8
+msgid "Notifications"
+msgstr ""
+
+#: project/templates/profile.html:27
msgid "Applications"
msgstr ""
#: project/templates/oauth2_client/list.html:4
#: project/templates/oauth2_client/list.html:11
#: project/templates/oauth2_client/read.html:11
-#: project/templates/profile.html:33
+#: project/templates/profile.html:37
msgid "OAuth2 clients"
msgstr ""
@@ -1561,7 +1595,7 @@ msgstr ""
msgid "Edit"
msgstr ""
-#: project/templates/admin/email.html:47 project/views/admin.py:119
+#: project/templates/admin/email.html:47 project/views/admin.py:122
msgid "Mail sent successfully"
msgstr ""
@@ -1573,6 +1607,10 @@ msgstr ""
msgid "Send test mail asynchronously"
msgstr ""
+#: project/templates/admin/newsletter.html:59
+msgid "Mails sent successfully"
+msgstr ""
+
#: project/templates/admin_unit/create.html:58
#: project/templates/admin_unit/update.html:59
#: project/templates/event/create.html:347
@@ -1627,6 +1665,10 @@ msgstr ""
msgid "this is a message from %(site_name)s."
msgstr ""
+#: project/templates/email/newsletter.html:7
+msgid "Notification settings"
+msgstr ""
+
#: project/templates/email/organization_invitation_accepted_notice.html:4
#, python-format
msgid ""
@@ -2036,20 +2078,26 @@ msgstr ""
msgid "Preview"
msgstr ""
-#: project/views/admin.py:55
+#: project/views/admin.py:58
msgid "Organization successfully updated"
msgstr ""
-#: project/views/admin.py:79 project/views/manage.py:361
+#: project/views/admin.py:82 project/views/manage.py:361
+#: project/views/user.py:27
msgid "Settings successfully updated"
msgstr ""
-#: project/views/admin.py:108
+#: project/views/admin.py:111
#, python-format
msgid "Test mail from %(site_name)s"
msgstr ""
-#: project/views/admin.py:152
+#: project/views/admin.py:150
+#, python-format
+msgid "Newsletter from %(site_name)s"
+msgstr ""
+
+#: project/views/admin.py:200
msgid "User successfully updated"
msgstr ""
diff --git a/project/views/admin.py b/project/views/admin.py
index b31ba16..e36c89e 100644
--- a/project/views/admin.py
+++ b/project/views/admin.py
@@ -1,3 +1,4 @@
+from celery import group
from flask import flash, redirect, render_template, request, url_for
from flask_babelex import gettext
from flask_security import roles_required
@@ -7,6 +8,7 @@ from sqlalchemy.sql import func
from project import app, celery, db
from project.base_tasks import send_mail_task
from project.forms.admin import (
+ AdminNewsletterForm,
AdminSettingsForm,
AdminTestEmailForm,
UpdateAdminUnitForm,
@@ -125,6 +127,51 @@ def admin_email():
return render_template("admin/email.html", form=form)
+@app.route("/admin/newsletter", methods=["GET", "POST"])
+@roles_required("admin")
+def admin_newsletter():
+ form = AdminNewsletterForm()
+
+ if "poll" in request.args: # pragma: no cover
+ try:
+ result = celery.GroupResult.restore(request.args["poll"])
+ ready = result.ready()
+ return {
+ "ready": ready,
+ "count": len(result.children),
+ "completed": result.completed_count(),
+ "successful": result.successful() if ready else None,
+ }
+ except Exception as e:
+ return {"ready": True, "successful": False, "error": str(e)}
+
+ if form.validate_on_submit():
+ subject = gettext(
+ "Newsletter from %(site_name)s",
+ site_name=app.config["SITE_NAME"],
+ )
+
+ if form.recipient_choice.data == 1: # pragma: no cover
+ recipients = [form.test_recipient.data]
+ else:
+ users = (
+ User.query.filter(User.email != None)
+ .filter(User.confirmed_at != None)
+ .filter(User.newsletter_enabled)
+ .all()
+ )
+ recipients = [u.email for u in users]
+
+ result = group(
+ send_mail_task.s(r, subject, "newsletter", message=form.message.data)
+ for r in recipients
+ ).delay()
+ result.save()
+ return {"result_id": result.id}
+
+ return render_template("admin/newsletter.html", form=form)
+
+
@app.route("/admin/users")
@roles_required("admin")
def admin_users():
diff --git a/project/views/user.py b/project/views/user.py
index f75e21d..4878dda 100644
--- a/project/views/user.py
+++ b/project/views/user.py
@@ -1,9 +1,12 @@
-from flask import render_template
-from flask_security import auth_required
+from flask import flash, redirect, render_template, url_for
+from flask_babelex import gettext
+from flask_security import auth_required, current_user
+from sqlalchemy.exc import SQLAlchemyError
-from project import app
-from project.models import AdminUnitInvitation
-from project.views.utils import get_invitation_access_result
+from project import app, db
+from project.forms.user import NotificationForm
+from project.models import AdminUnitInvitation, User
+from project.views.utils import get_invitation_access_result, handleSqlError
@app.route("/profile")
@@ -12,6 +15,25 @@ def profile():
return render_template("profile.html")
+@app.route("/user/notifications", methods=("GET", "POST"))
+@auth_required()
+def user_notifications():
+ user = User.query.get_or_404(current_user.id)
+ form = NotificationForm(obj=user)
+
+ if form.validate_on_submit():
+ try:
+ form.populate_obj(user)
+ db.session.commit()
+ flash(gettext("Settings successfully updated"), "success")
+ return redirect(url_for("profile"))
+ except SQLAlchemyError as e: # pragma: no cover
+ db.session.rollback()
+ flash(handleSqlError(e), "danger")
+
+ return render_template("user/notifications.html", form=form)
+
+
@app.route("/user/organization-invitations/")
def user_organization_invitation(id):
invitation = AdminUnitInvitation.query.get_or_404(id)
diff --git a/tests/conftest.py b/tests/conftest.py
index d13c2df..fc462ab 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -14,6 +14,7 @@ def pytest_generate_tests(metafunc):
os.environ["DATABASE_URL"] = os.environ.get(
"TEST_DATABASE_URL", "postgresql://postgres@localhost/eventcally_tests"
)
+ os.environ["REDIS_URL"] = os.environ.get("TEST_REDIS_URL", "redis://")
os.environ["AUTHLIB_INSECURE_TRANSPORT"] = "1"
os.environ[
"JWT_PRIVATE_KEY"
diff --git a/tests/views/test_admin.py b/tests/views/test_admin.py
index 058971d..37aec2a 100644
--- a/tests/views/test_admin.py
+++ b/tests/views/test_admin.py
@@ -79,6 +79,28 @@ def test_admin_email(client, seeder, utils, app, mocker):
utils.assert_send_mail_called(mail_mock, "test@test.de")
+def test_newsletter(app, utils, seeder):
+ user_id, admin_unit_id = seeder.setup_base(True)
+
+ for i in range(10):
+ seeder.create_user(f"test{i}@test.de")
+
+ url = utils.get_url("admin_newsletter")
+ response = utils.get_ok(url)
+
+ response = utils.post_form(
+ url,
+ response,
+ {
+ "recipient_choice": 2,
+ "message": "Message",
+ },
+ )
+
+ utils.assert_response_ok(response)
+ assert "result_id" in response.json
+
+
def test_admin_users(client, seeder, utils, app):
seeder.create_user(admin=True)
user = utils.login()
diff --git a/tests/views/test_user.py b/tests/views/test_user.py
index b709df2..d9add97 100644
--- a/tests/views/test_user.py
+++ b/tests/views/test_user.py
@@ -65,3 +65,26 @@ def test_user_favorite_events(client, seeder, utils):
url = utils.get_url("user_favorite_events")
utils.get_ok(url)
+
+
+def test_user_notifications(client, seeder, utils, app):
+ user_id, admin_unit_id = seeder.setup_base()
+
+ url = utils.get_url("user_notifications")
+ response = utils.get_ok(url)
+
+ response = utils.post_form(
+ url,
+ response,
+ {
+ "newsletter_enabled": None,
+ },
+ )
+
+ utils.assert_response_redirect(response, "profile")
+
+ with app.app_context():
+ from project.models import User
+
+ place = User.query.get(user_id)
+ assert not place.newsletter_enabled
From 81f25446095bd8a75c9c3c72e8dd35fdfa703c4c Mon Sep 17 00:00:00 2001
From: Daniel Grams
Date: Tue, 28 Mar 2023 23:05:57 +0200
Subject: [PATCH 2/2] Add newsletter functionality #391
---
.github/workflows/test.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index e8f251d..6cefec5 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -66,7 +66,7 @@ jobs:
run: pytest --cov=project --splits 4 --group ${{ matrix.group }}
env:
TEST_DATABASE_URL: postgresql://postgres:postgres@localhost/eventcally_tests
- TEST_REDIS_URL: redis://redis:6379
+ TEST_REDIS_URL: redis://localhost:6379
- name: Upload coverage
uses: actions/upload-artifact@v2