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 0f63114..43c9722 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 a94fbfa..9ff9549 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-03-24 21:24+0100\n"
+"POT-Creation-Date: 2023-03-28 17:04+0200\n"
"PO-Revision-Date: 2020-06-07 18:51+0200\n"
"Last-Translator: FULL NAME \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 f1fcd1d..bab21ee 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 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