From a6d452c20b861b8fb3400d96726f0ffc8f536ca3 Mon Sep 17 00:00:00 2001 From: Daniel Grams Date: Tue, 28 Mar 2023 22:50:07 +0200 Subject: [PATCH] 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 %} + + + +
+ {{ form.hidden_tag() }} +
+ {{ render_field_with_errors(form.recipient_choice, ri='radio') }} +
+
+ {{ render_field_with_errors(form.test_recipient) }} +
+ {{ render_field_with_errors(form.message) }} + {{ render_field(form.submit) }} +
+ +

+

+ + +

+ + + +{% 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') }}

+ +
+ {{ form.hidden_tag() }} + {{ render_field_with_errors(form.newsletter_enabled, ri="switch") }} + {{ render_field(form.submit) }} +
+ +{% 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