diff --git a/doc/development.md b/doc/development.md index 0e32335..fe9531b 100644 --- a/doc/development.md +++ b/doc/development.md @@ -62,7 +62,7 @@ flask db upgrade ### Init ```sh -pybabel extract -F babel.cfg -o messages.pot . && pybabel extract -F babel.cfg -k lazy_gettext -o messages.pot . && pybabel init -i messages.pot -d project/translations -l de +pybabel extract -F babel.cfg -o messages.pot . && pybabel extract -F babel.cfg -k lazy_gettext -k dummy_gettext -o messages.pot . && pybabel init -i messages.pot -d project/translations -l de ``` ### Add locale @@ -74,7 +74,7 @@ pybabel init -i messages.pot -d project/translations -l en ### Extract new msgid's and merge into \*.po files ```sh -pybabel extract -F babel.cfg -o messages.pot . && pybabel extract -F babel.cfg -k lazy_gettext -o messages.pot . && pybabel update -N -i messages.pot -d project/translations +pybabel extract -F babel.cfg -o messages.pot . && pybabel extract -F babel.cfg -k lazy_gettext -k dummy_gettext -o messages.pot . && pybabel update -N -i messages.pot -d project/translations ``` #### Compile after translation is done diff --git a/messages.pot b/messages.pot index 2d9ec47..cdef2cc 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-09-30 11:43+0200\n" +"POT-Creation-Date: 2023-11-13 17:25+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,183 +17,183 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.12.1\n" -#: project/i10n.py:15 +#: project/i10n.py:32 msgid "Event_Art" msgstr "" -#: project/i10n.py:16 +#: project/i10n.py:33 msgid "Event_Book" msgstr "" -#: project/i10n.py:17 +#: project/i10n.py:34 msgid "Event_Movie" msgstr "" -#: project/i10n.py:18 +#: project/i10n.py:35 msgid "Event_Family" msgstr "" -#: project/i10n.py:19 +#: project/i10n.py:36 msgid "Event_Festival" msgstr "" -#: project/i10n.py:20 +#: project/i10n.py:37 msgid "Event_Religious" msgstr "" -#: project/i10n.py:21 +#: project/i10n.py:38 msgid "Event_Shopping" msgstr "" -#: project/i10n.py:22 +#: project/i10n.py:39 msgid "Event_Comedy" msgstr "" -#: project/i10n.py:23 +#: project/i10n.py:40 msgid "Event_Music" msgstr "" -#: project/i10n.py:24 +#: project/i10n.py:41 msgid "Event_Dance" msgstr "" -#: project/i10n.py:25 +#: project/i10n.py:42 msgid "Event_Nightlife" msgstr "" -#: project/i10n.py:26 +#: project/i10n.py:43 msgid "Event_Theater" msgstr "" -#: project/i10n.py:27 +#: project/i10n.py:44 msgid "Event_Dining" msgstr "" -#: project/i10n.py:28 +#: project/i10n.py:45 msgid "Event_Conference" msgstr "" -#: project/i10n.py:29 +#: project/i10n.py:46 msgid "Event_Meetup" msgstr "" -#: project/i10n.py:30 +#: project/i10n.py:47 msgid "Event_Fitness" msgstr "" -#: project/i10n.py:31 +#: project/i10n.py:48 msgid "Event_Sports" msgstr "" -#: project/i10n.py:32 +#: project/i10n.py:49 msgid "Event_Other" msgstr "" -#: project/i10n.py:33 +#: project/i10n.py:50 msgid "Event_Exhibition" msgstr "" -#: project/i10n.py:34 +#: project/i10n.py:51 msgid "Event_Culture" msgstr "" -#: project/i10n.py:35 +#: project/i10n.py:52 msgid "Event_Tour" msgstr "" -#: project/i10n.py:36 +#: project/i10n.py:53 msgid "Event_OpenAir" msgstr "" -#: project/i10n.py:37 +#: project/i10n.py:54 msgid "Event_Stage" msgstr "" -#: project/i10n.py:38 +#: project/i10n.py:55 msgid "Event_Lecture" msgstr "" -#: project/i10n.py:39 +#: project/i10n.py:56 msgid "Typical Age range" msgstr "" -#: project/i10n.py:40 +#: project/i10n.py:57 msgid "Administrator" msgstr "" -#: project/i10n.py:41 +#: project/i10n.py:58 msgid "Event expert" msgstr "" -#: project/i10n.py:42 +#: project/i10n.py:59 msgid "EventReviewStatus.inbox" msgstr "" -#: project/i10n.py:43 +#: project/i10n.py:60 msgid "EventReviewStatus.verified" msgstr "" -#: project/i10n.py:44 +#: project/i10n.py:61 msgid "EventReviewStatus.rejected" msgstr "" -#: project/i10n.py:45 +#: project/i10n.py:62 msgid "Scope_openid" msgstr "" -#: project/i10n.py:46 +#: project/i10n.py:63 msgid "Scope_profile" msgstr "" -#: project/i10n.py:47 +#: project/i10n.py:64 msgid "Scope_user:read" msgstr "" -#: project/i10n.py:48 +#: project/i10n.py:65 msgid "Scope_user:write" msgstr "" -#: project/i10n.py:49 +#: project/i10n.py:66 msgid "Scope_organizer:write" msgstr "" -#: project/i10n.py:50 +#: project/i10n.py:67 msgid "Scope_place:write" msgstr "" -#: project/i10n.py:51 +#: project/i10n.py:68 msgid "Scope_event:write" msgstr "" -#: project/i10n.py:52 +#: project/i10n.py:69 msgid "Scope_eventlist:write" msgstr "" -#: project/i10n.py:53 +#: project/i10n.py:70 msgid "Scope_eventreference:write" msgstr "" -#: project/i10n.py:54 +#: project/i10n.py:71 msgid "Scope_organization:read" msgstr "" -#: project/i10n.py:55 +#: project/i10n.py:72 msgid "Scope_organization:write" msgstr "" -#: project/i10n.py:56 +#: project/i10n.py:73 msgid "Scope_customwidget:write" msgstr "" -#: project/i10n.py:57 +#: project/i10n.py:74 msgid "There must be no self-reference." msgstr "" -#: project/utils.py:15 +#: project/utils.py:19 msgid "Event_" msgstr "" -#: project/utils.py:19 +#: project/utils.py:23 msgid "." msgstr "" @@ -201,11 +201,6 @@ msgstr "" msgid "message" msgstr "" -#: project/api/organization/resources.py:647 -#: project/views/admin_unit_member_invitation.py:89 -msgid "You have received an invitation" -msgstr "" - #: project/forms/admin.py:11 project/templates/layout.html:344 #: project/views/root.py:55 msgid "Terms of service" @@ -237,7 +232,7 @@ msgid "Announcement" msgstr "" #: project/forms/admin.py:18 project/forms/oauth2_client.py:24 -#: project/forms/user.py:13 +#: project/forms/user.py:18 project/forms/user.py:32 msgid "Save" msgstr "" @@ -267,7 +262,7 @@ msgstr "" #: project/forms/admin_unit_member.py:12 project/forms/admin_unit_member.py:25 #: project/forms/admin_unit_member.py:30 project/forms/event.py:112 #: project/forms/event_suggestion.py:38 project/forms/organizer.py:33 -#: project/forms/user.py:18 project/forms/user.py:23 +#: project/forms/user.py:37 project/forms/user.py:42 #: project/templates/_macros.html:246 project/templates/_macros.html:1586 #: project/templates/admin/admin.html:27 project/templates/admin/email.html:4 #: project/templates/admin/email.html:66 project/templates/admin/users.html:19 @@ -475,7 +470,7 @@ msgid "Verification requests postal codes" msgstr "" #: project/forms/admin_unit.py:93 -msgid "Accept verification requests to organizations with these postal codes." +msgid "Limit verification requests to organizations with these postal codes." msgstr "" #: project/forms/admin_unit.py:109 @@ -523,11 +518,11 @@ msgstr "" msgid "Link Color" msgstr "" -#: project/forms/admin_unit.py:165 project/forms/user.py:17 +#: project/forms/admin_unit.py:165 project/forms/user.py:36 msgid "Request deletion" msgstr "" -#: project/forms/admin_unit.py:170 project/forms/user.py:22 +#: project/forms/admin_unit.py:170 project/forms/user.py:41 #: project/templates/admin_unit/cancel_deletion.html:6 #: project/templates/admin_unit/update.html:44 #: project/templates/manage/events.html:49 project/templates/profile.html:13 @@ -1356,13 +1351,21 @@ msgstr "" msgid "Confirm" msgstr "" -#: project/forms/user.py:9 project/templates/admin/admin.html:31 +#: project/forms/user.py:9 +msgid "Language" +msgstr "" + +#: project/forms/user.py:11 +msgid "Default" +msgstr "" + +#: project/forms/user.py:28 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 +#: project/forms/user.py:29 msgid "Information about new features and improvements." msgstr "" @@ -1850,7 +1853,7 @@ msgstr "" #: project/templates/developer/read.html:4 #: project/templates/developer/read.html:8 project/templates/layout.html:361 -#: project/templates/profile.html:45 +#: project/templates/profile.html:49 msgid "Developer" msgstr "" @@ -1864,20 +1867,25 @@ msgstr "" msgid "Delete account" msgstr "" -#: project/templates/profile.html:36 +#: project/templates/profile.html:36 project/templates/user/general.html:4 +#: project/templates/user/general.html:8 +msgid "General" +msgstr "" + +#: project/templates/profile.html:40 #: project/templates/user/notifications.html:4 #: project/templates/user/notifications.html:8 msgid "Notifications" msgstr "" -#: project/templates/profile.html:40 +#: project/templates/profile.html:44 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:49 +#: project/templates/profile.html:53 msgid "OAuth2 clients" msgstr "" @@ -1912,7 +1920,7 @@ msgstr "" msgid "User" msgstr "" -#: project/templates/admin/email.html:47 project/views/admin.py:165 +#: project/templates/admin/email.html:47 project/views/admin.py:163 msgid "Mail sent successfully" msgstr "" @@ -2297,7 +2305,7 @@ msgstr "" #: project/templates/manage/reference_requests_incoming.html:20 #: project/templates/reference/read.html:22 #: project/templates/reference_request/review_status.html:17 -#: project/views/reference_request_review.py:42 +#: project/views/reference_request_review.py:38 msgid "View event" msgstr "" @@ -2408,7 +2416,7 @@ msgid "Reviews" msgstr "" #: project/templates/manage/verification_requests_incoming.html:19 -#: project/views/verification_request_review.py:36 +#: project/views/verification_request_review.py:32 msgid "View organization" msgstr "" @@ -2537,7 +2545,7 @@ msgstr "" msgid "You do not have an account yet? Not a problem!" msgstr "" -#: project/templates/security/login_user.html:44 project/views/widget.py:122 +#: project/templates/security/login_user.html:44 project/views/widget.py:119 msgid "Register for free" msgstr "" @@ -2614,29 +2622,19 @@ msgid "Organization successfully deleted" msgstr "" #: project/views/admin.py:133 project/views/manage.py:485 -#: project/views/user.py:63 +#: project/views/user.py:64 project/views/user.py:83 msgid "Settings successfully updated" msgstr "" -#: project/views/admin.py:153 -#, python-format -msgid "Test mail from %(site_name)s" -msgstr "" - -#: project/views/admin.py:183 -#, python-format -msgid "Newsletter from %(site_name)s" -msgstr "" - -#: project/views/admin.py:232 +#: project/views/admin.py:233 msgid "User successfully updated" msgstr "" -#: project/views/admin.py:252 +#: project/views/admin.py:253 msgid "Entered email does not match user email" msgstr "" -#: project/views/admin.py:256 +#: project/views/admin.py:257 msgid "User successfully deleted" msgstr "" @@ -2654,14 +2652,6 @@ msgstr "" msgid "AdminUnit successfully updated" msgstr "" -#: project/views/admin_unit.py:271 -msgid "Organization invitation accepted" -msgstr "" - -#: project/views/admin_unit.py:285 -msgid "Organization deletion requested" -msgstr "" - #: project/views/admin_unit_member.py:46 msgid "Member successfully updated" msgstr "" @@ -2682,46 +2672,38 @@ msgstr "" msgid "Invitation successfully declined" msgstr "" -#: project/views/admin_unit_member_invitation.py:94 +#: project/views/admin_unit_member_invitation.py:93 msgid "Invitation successfully sent" msgstr "" -#: project/views/admin_unit_member_invitation.py:117 +#: project/views/admin_unit_member_invitation.py:116 msgid "Entered email does not match invitation email" msgstr "" -#: project/views/admin_unit_member_invitation.py:122 +#: project/views/admin_unit_member_invitation.py:121 msgid "Invitation successfully deleted" msgstr "" -#: project/views/event.py:208 +#: project/views/event.py:209 msgid "Event successfully published" msgstr "" -#: project/views/event.py:210 +#: project/views/event.py:211 msgid "Draft successfully saved" msgstr "" -#: project/views/event.py:212 +#: project/views/event.py:213 msgid "Event successfully planned" msgstr "" -#: project/views/event.py:272 +#: project/views/event.py:273 msgid "Event successfully updated" msgstr "" -#: project/views/event.py:298 +#: project/views/event.py:299 msgid "Event successfully deleted" msgstr "" -#: project/views/event.py:463 -msgid "Referenced event changed" -msgstr "" - -#: project/views/event.py:486 -msgid "New event report" -msgstr "" - #: project/views/event_place.py:34 msgid "Place successfully created" msgstr "" @@ -2742,11 +2724,6 @@ msgstr "" msgid "Event suggestion successfully rejected" msgstr "" -#: project/views/event_suggestion.py:87 -#: project/views/reference_request_review.py:139 -msgid "Event review status updated" -msgstr "" - #: project/views/js.py:33 msgid "Short name is already taken" msgstr "" @@ -2847,68 +2824,114 @@ msgid "" "notified after the other organization reviews the event." msgstr "" -#: project/views/reference_request.py:172 -msgid "New reference request" -msgstr "" - -#: project/views/reference_request.py:181 -msgid "New reference automatically verified" -msgstr "" - -#: project/views/reference_request_review.py:40 +#: project/views/reference_request_review.py:36 msgid "Request already verified" msgstr "" -#: project/views/reference_request_review.py:61 +#: project/views/reference_request_review.py:57 msgid "Reference successfully created" msgstr "" -#: project/views/reference_request_review.py:69 +#: project/views/reference_request_review.py:65 msgid "Request successfully updated" msgstr "" -#: project/views/reference_request_review.py:92 -#: project/views/verification_request_review.py:83 +#: project/views/reference_request_review.py:88 +#: project/views/verification_request_review.py:79 #, python-format msgid "" "If all upcoming reference requests of %(admin_unit_name)s should be " "verified automatically." msgstr "" -#: project/views/user.py:107 +#: project/views/user.py:127 msgid "" "You are administrator of at least one organization. Cancel your " "membership to delete your account." msgstr "" -#: project/views/user.py:114 project/views/user.py:141 +#: project/views/user.py:134 project/views/user.py:161 msgid "Entered email does not match your email" msgstr "" -#: project/views/user.py:160 +#: project/views/utils.py:27 +msgid "New event report" +msgstr "" + +#: project/views/utils.py:28 project/views/utils.py:36 +msgid "You have received an invitation" +msgstr "" + +#: project/views/utils.py:29 +#, python-format +msgid "Newsletter from %(site_name)s" +msgstr "" + +#: project/views/utils.py:30 +msgid "Organization deletion requested" +msgstr "" + +#: project/views/utils.py:33 +msgid "Organization invitation accepted" +msgstr "" + +#: project/views/utils.py:37 +msgid "New reference automatically verified" +msgstr "" + +#: project/views/utils.py:40 +msgid "New reference request" +msgstr "" + +#: project/views/utils.py:41 project/views/utils.py:46 +msgid "Event review status updated" +msgstr "" + +#: project/views/utils.py:44 +msgid "Referenced event changed" +msgstr "" + +#: project/views/utils.py:45 +msgid "New event review" +msgstr "" + +#: project/views/utils.py:47 msgid "User deletion requested" msgstr "" -#: project/views/utils.py:91 +#: project/views/utils.py:48 +#, python-format +msgid "Test mail from %(site_name)s" +msgstr "" + +#: project/views/utils.py:49 +msgid "New verification request" +msgstr "" + +#: project/views/utils.py:50 +msgid "Verification request review status updated" +msgstr "" + +#: project/views/utils.py:122 msgid "" "An entry with the entered values ​​already exists. Duplicate entries are " "not allowed." msgstr "" -#: project/views/utils.py:142 +#: project/views/utils.py:173 #, python-format msgid "Error in the %s field - %s" msgstr "" -#: project/views/utils.py:149 +#: project/views/utils.py:180 msgid "Show" msgstr "" -#: project/views/utils.py:157 +#: project/views/utils.py:188 msgid "You do not have permission for this action" msgstr "" -#: project/views/utils.py:306 +#: project/views/utils.py:382 msgid "" "The invitation was issued to another user. Sign in with the email address" " the invitation was sent to." @@ -2924,37 +2947,25 @@ msgstr "" msgid "Verification request successfully deleted" msgstr "" -#: project/views/verification_request.py:207 -msgid "New verification request" -msgstr "" - -#: project/views/verification_request_review.py:34 +#: project/views/verification_request_review.py:30 msgid "Verification request already verified" msgstr "" -#: project/views/verification_request_review.py:64 +#: project/views/verification_request_review.py:60 msgid "Organization successfully verified" msgstr "" -#: project/views/verification_request_review.py:66 +#: project/views/verification_request_review.py:62 msgid "Verification request successfully updated" msgstr "" -#: project/views/verification_request_review.py:119 -msgid "Verification request review status updated" -msgstr "" - -#: project/views/widget.py:114 +#: project/views/widget.py:111 msgid "Thank you so much! The event is being verified." msgstr "" -#: project/views/widget.py:118 +#: project/views/widget.py:115 msgid "" "For more options and your own calendar of events, you can register for " "free." msgstr "" -#: project/views/widget.py:184 -msgid "New event review" -msgstr "" - diff --git a/migrations/versions/182a83a49c1f_.py b/migrations/versions/182a83a49c1f_.py new file mode 100644 index 0000000..295a04d --- /dev/null +++ b/migrations/versions/182a83a49c1f_.py @@ -0,0 +1,26 @@ +"""empty message + +Revision ID: 182a83a49c1f +Revises: c20d6d7575be +Create Date: 2023-11-13 15:50:05.343979 + +""" +import sqlalchemy as sa +import sqlalchemy_utils +from alembic import op + +from project import dbtypes + +# revision identifiers, used by Alembic. +revision = "182a83a49c1f" +down_revision = "c20d6d7575be" +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column("user", sa.Column("locale", sa.String(length=255), nullable=True)) + + +def downgrade(): + op.drop_column("user", "locale") diff --git a/project/__init__.py b/project/__init__.py index 90d3d64..fdedffd 100644 --- a/project/__init__.py +++ b/project/__init__.py @@ -142,7 +142,7 @@ sitemap_path = os.path.join(cache_path, sitemap_file) robots_txt_path = os.path.join(cache_path, robots_txt_file) # i18n -from project.i10n import get_locale +from project.i10n import get_locale, get_locale_from_request app.config["BABEL_DEFAULT_LOCALE"] = "de" app.config["BABEL_DEFAULT_TIMEZONE"] = "Europe/Berlin" @@ -238,6 +238,10 @@ def user_registered_sighandler(app, user, confirm_token, confirmation_token, for set_user_accepted_tos(user) db.session.commit() + if not user.locale: + user.locale = get_locale_from_request() + db.session.commit() + # OAuth2 from project.oauth2 import config_oauth diff --git a/project/api/organization/resources.py b/project/api/organization/resources.py index 68ebfd6..174d927 100644 --- a/project/api/organization/resources.py +++ b/project/api/organization/resources.py @@ -1,6 +1,5 @@ from flask import abort, request from flask_apispec import doc, marshal_with, use_kwargs -from flask_babel import gettext from sqlalchemy import and_ from project import db @@ -131,7 +130,7 @@ from project.views.reference_request import ( handle_request_according_to_relation, send_reference_request_mails, ) -from project.views.utils import get_current_admin_unit_for_api, send_mail_async +from project.views.utils import get_current_admin_unit_for_api, send_template_mail_async from project.views.verification_request import send_verification_request_inbox_mails @@ -642,9 +641,8 @@ class OrganizationOrganizationInvitationListResource(BaseResource): db.session.add(invitation) db.session.commit() - send_mail_async( + send_template_mail_async( invitation.email, - gettext("You have received an invitation"), "organization_invitation_notice", invitation=invitation, ) diff --git a/project/forms/user.py b/project/forms/user.py index 5e85688..9707f1b 100644 --- a/project/forms/user.py +++ b/project/forms/user.py @@ -1,9 +1,28 @@ from flask_babel import lazy_gettext from flask_wtf import FlaskForm -from wtforms import BooleanField, EmailField, SubmitField +from wtforms import BooleanField, EmailField, SelectField, SubmitField from wtforms.validators import DataRequired, Optional +class GeneralForm(FlaskForm): + locale = SelectField( + lazy_gettext("Language"), + choices=[ + ("None", lazy_gettext("Default")), + ("de", "Deutsch"), + ("en", "English"), + ], + default="None", + validators=[Optional()], + ) + submit = SubmitField(lazy_gettext("Save")) + + def populate_obj(self, obj): + super().populate_obj(obj) + if obj.locale == "None": + obj.locale = None + + class NotificationForm(FlaskForm): newsletter_enabled = BooleanField( lazy_gettext("Newsletter"), diff --git a/project/i10n.py b/project/i10n.py index 8fe7b1d..9a81f13 100644 --- a/project/i10n.py +++ b/project/i10n.py @@ -1,14 +1,35 @@ from flask import request -from flask_babel import gettext +from flask_babel import Locale, gettext +from flask_security import current_user from project import app def get_locale(): - if not request: - return app.config["LANGUAGES"][0] + try: + if ( + current_user + and current_user.is_authenticated + and current_user.locale + and Locale.parse(current_user.locale) + ): + return current_user.locale + except Exception: + pass - return request.accept_languages.best_match(app.config["LANGUAGES"]) + if not request: + return app.config["BABEL_DEFAULT_LOCALE"] + + return get_locale_from_request() + + +def get_locale_from_request(): + if not request: # pragma: no cover + return None + + return request.accept_languages.best_match( + app.config["LANGUAGES"], app.config["BABEL_DEFAULT_LOCALE"] + ) def print_dynamic_texts(): diff --git a/project/models/user.py b/project/models/user.py index 0da92ed..ab46b67 100644 --- a/project/models/user.py +++ b/project/models/user.py @@ -65,6 +65,7 @@ class User(db.Model, UserMixin): ) created_at = deferred(Column(DateTime, default=datetime.datetime.utcnow)) deletion_requested_at = deferred(Column(DateTime, nullable=True)) + locale = Column(String(255), nullable=True) @property def is_member_of_verified_admin_unit(self): diff --git a/project/templates/profile.html b/project/templates/profile.html index 06fe6d1..2d5866a 100644 --- a/project/templates/profile.html +++ b/project/templates/profile.html @@ -32,6 +32,10 @@

{{ _('Settings') }}

+ + {{ _('General') }} + + {{ _('Notifications') }} diff --git a/project/templates/user/general.html b/project/templates/user/general.html new file mode 100644 index 0000000..8d33f50 --- /dev/null +++ b/project/templates/user/general.html @@ -0,0 +1,21 @@ +{% extends "layout.html" %} +{% from "_macros.html" import render_field_with_errors, render_field %} +{%- block title -%} +{{ _('General') }} +{%- endblock -%} +{% block content %} + + + +
+ {{ form.hidden_tag() }} + {{ render_field_with_errors(form.locale) }} + {{ render_field(form.submit) }} +
+ +{% endblock %} diff --git a/project/templates/user/notifications.html b/project/templates/user/notifications.html index 04fdc24..0e5c828 100644 --- a/project/templates/user/notifications.html +++ b/project/templates/user/notifications.html @@ -5,7 +5,12 @@ {%- endblock -%} {% block content %} -

{{ _('Notifications') }}

+
{{ form.hidden_tag() }} diff --git a/project/translations/de/LC_MESSAGES/messages.mo b/project/translations/de/LC_MESSAGES/messages.mo index 25b886b..69d5983 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 f29622b..077b19e 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-09-30 11:43+0200\n" +"POT-Creation-Date: 2023-11-13 17:25+0100\n" "PO-Revision-Date: 2020-06-07 18:51+0200\n" "Last-Translator: FULL NAME \n" "Language: de\n" @@ -18,183 +18,183 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.12.1\n" -#: project/i10n.py:15 +#: project/i10n.py:32 msgid "Event_Art" msgstr "Kunst" -#: project/i10n.py:16 +#: project/i10n.py:33 msgid "Event_Book" msgstr "Literatur" -#: project/i10n.py:17 +#: project/i10n.py:34 msgid "Event_Movie" msgstr "Film" -#: project/i10n.py:18 +#: project/i10n.py:35 msgid "Event_Family" msgstr "Familie" -#: project/i10n.py:19 +#: project/i10n.py:36 msgid "Event_Festival" msgstr "Festival" -#: project/i10n.py:20 +#: project/i10n.py:37 msgid "Event_Religious" msgstr "Religion" -#: project/i10n.py:21 +#: project/i10n.py:38 msgid "Event_Shopping" msgstr "Shopping" -#: project/i10n.py:22 +#: project/i10n.py:39 msgid "Event_Comedy" msgstr "Comedy" -#: project/i10n.py:23 +#: project/i10n.py:40 msgid "Event_Music" msgstr "Musik" -#: project/i10n.py:24 +#: project/i10n.py:41 msgid "Event_Dance" msgstr "Tanz" -#: project/i10n.py:25 +#: project/i10n.py:42 msgid "Event_Nightlife" msgstr "Party" -#: project/i10n.py:26 +#: project/i10n.py:43 msgid "Event_Theater" msgstr "Theater" -#: project/i10n.py:27 +#: project/i10n.py:44 msgid "Event_Dining" msgstr "Essen" -#: project/i10n.py:28 +#: project/i10n.py:45 msgid "Event_Conference" msgstr "Konferenz" -#: project/i10n.py:29 +#: project/i10n.py:46 msgid "Event_Meetup" msgstr "Networking" -#: project/i10n.py:30 +#: project/i10n.py:47 msgid "Event_Fitness" msgstr "Fitness" -#: project/i10n.py:31 +#: project/i10n.py:48 msgid "Event_Sports" msgstr "Sport" -#: project/i10n.py:32 +#: project/i10n.py:49 msgid "Event_Other" msgstr "Sonstiges" -#: project/i10n.py:33 +#: project/i10n.py:50 msgid "Event_Exhibition" msgstr "Ausstellung" -#: project/i10n.py:34 +#: project/i10n.py:51 msgid "Event_Culture" msgstr "Kultur" -#: project/i10n.py:35 +#: project/i10n.py:52 msgid "Event_Tour" msgstr "Führung" -#: project/i10n.py:36 +#: project/i10n.py:53 msgid "Event_OpenAir" msgstr "Open Air" -#: project/i10n.py:37 +#: project/i10n.py:54 msgid "Event_Stage" msgstr "Bühne" -#: project/i10n.py:38 +#: project/i10n.py:55 msgid "Event_Lecture" msgstr "Vortrag" -#: project/i10n.py:39 +#: project/i10n.py:56 msgid "Typical Age range" msgstr "Typische Altersspanne" -#: project/i10n.py:40 +#: project/i10n.py:57 msgid "Administrator" msgstr "Administrator:in" -#: project/i10n.py:41 +#: project/i10n.py:58 msgid "Event expert" msgstr "Veranstaltungsexpert:in" -#: project/i10n.py:42 +#: project/i10n.py:59 msgid "EventReviewStatus.inbox" msgstr "Ungeprüft" -#: project/i10n.py:43 +#: project/i10n.py:60 msgid "EventReviewStatus.verified" msgstr "Verifiziert" -#: project/i10n.py:44 +#: project/i10n.py:61 msgid "EventReviewStatus.rejected" msgstr "Abgelehnt" -#: project/i10n.py:45 +#: project/i10n.py:62 msgid "Scope_openid" msgstr "Identität verifizieren" -#: project/i10n.py:46 +#: project/i10n.py:63 msgid "Scope_profile" msgstr "Profil-Informationen" -#: project/i10n.py:47 +#: project/i10n.py:64 msgid "Scope_user:read" msgstr "Nutzer-Einstellungen lesen" -#: project/i10n.py:48 +#: project/i10n.py:65 msgid "Scope_user:write" msgstr "Nutzer-Einstellungen anlegen, ändern und löschen" -#: project/i10n.py:49 +#: project/i10n.py:66 msgid "Scope_organizer:write" msgstr "Veranstalter anlegen, ändern und löschen" -#: project/i10n.py:50 +#: project/i10n.py:67 msgid "Scope_place:write" msgstr "Orte anlegen, ändern und löschen" -#: project/i10n.py:51 +#: project/i10n.py:68 msgid "Scope_event:write" msgstr "Veranstaltungen anlegen, ändern und löschen" -#: project/i10n.py:52 +#: project/i10n.py:69 msgid "Scope_eventlist:write" msgstr "Veranstaltungslisten anlegen, ändern und löschen" -#: project/i10n.py:53 +#: project/i10n.py:70 msgid "Scope_eventreference:write" msgstr "Empfehlungen anlegen, ändern und löschen" -#: project/i10n.py:54 +#: project/i10n.py:71 msgid "Scope_organization:read" msgstr "Organisationen lesen" -#: project/i10n.py:55 +#: project/i10n.py:72 msgid "Scope_organization:write" msgstr "Organisationen anlegen, ändern und löschen" -#: project/i10n.py:56 +#: project/i10n.py:73 msgid "Scope_customwidget:write" msgstr "Widgets anlegen, ändern und löschen" -#: project/i10n.py:57 +#: project/i10n.py:74 msgid "There must be no self-reference." msgstr "Es darf keine Selbstreferenz geben." -#: project/utils.py:15 +#: project/utils.py:19 msgid "Event_" msgstr "Event_" -#: project/utils.py:19 +#: project/utils.py:23 msgid "." msgstr "." @@ -202,11 +202,6 @@ msgstr "." msgid "message" msgstr "message" -#: project/api/organization/resources.py:647 -#: project/views/admin_unit_member_invitation.py:89 -msgid "You have received an invitation" -msgstr "Du hast eine Einladung erhalten" - #: project/forms/admin.py:11 project/templates/layout.html:344 #: project/views/root.py:55 msgid "Terms of service" @@ -238,7 +233,7 @@ msgid "Announcement" msgstr "Ankündigung" #: project/forms/admin.py:18 project/forms/oauth2_client.py:24 -#: project/forms/user.py:13 +#: project/forms/user.py:18 project/forms/user.py:32 msgid "Save" msgstr "Speichern" @@ -268,7 +263,7 @@ msgstr "Nutzer löschen" #: project/forms/admin_unit_member.py:12 project/forms/admin_unit_member.py:25 #: project/forms/admin_unit_member.py:30 project/forms/event.py:112 #: project/forms/event_suggestion.py:38 project/forms/organizer.py:33 -#: project/forms/user.py:18 project/forms/user.py:23 +#: project/forms/user.py:37 project/forms/user.py:42 #: project/templates/_macros.html:246 project/templates/_macros.html:1586 #: project/templates/admin/admin.html:27 project/templates/admin/email.html:4 #: project/templates/admin/email.html:66 project/templates/admin/users.html:19 @@ -492,7 +487,9 @@ msgstr "Postleitzahlen für Verifizierungsanfragen" #: project/forms/admin_unit.py:93 msgid "Limit verification requests to organizations with these postal codes." -msgstr "Limitiere Verifizierungsanfragen auf Oranisationen mit diesen Postleitzahlen" +msgstr "" +"Limitiere Verifizierungsanfragen auf Oranisationen mit diesen " +"Postleitzahlen" #: project/forms/admin_unit.py:109 msgid "Verify new organization" @@ -543,11 +540,11 @@ msgstr "Hauptfarbe" msgid "Link Color" msgstr "Linkfarbe" -#: project/forms/admin_unit.py:165 project/forms/user.py:17 +#: project/forms/admin_unit.py:165 project/forms/user.py:36 msgid "Request deletion" msgstr "Löschung beantragen" -#: project/forms/admin_unit.py:170 project/forms/user.py:22 +#: project/forms/admin_unit.py:170 project/forms/user.py:41 #: project/templates/admin_unit/cancel_deletion.html:6 #: project/templates/admin_unit/update.html:44 #: project/templates/manage/events.html:49 project/templates/profile.html:13 @@ -1415,13 +1412,21 @@ msgstr "Ablehnen" msgid "Confirm" msgstr "Bestätigen" -#: project/forms/user.py:9 project/templates/admin/admin.html:31 +#: project/forms/user.py:9 +msgid "Language" +msgstr "Sprache" + +#: project/forms/user.py:11 +msgid "Default" +msgstr "Standard" + +#: project/forms/user.py:28 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 +#: project/forms/user.py:29 msgid "Information about new features and improvements." msgstr "Informationen über neue Features und Verbesserungen." @@ -1911,7 +1916,7 @@ msgstr "Organisation wechseln" #: project/templates/developer/read.html:4 #: project/templates/developer/read.html:8 project/templates/layout.html:361 -#: project/templates/profile.html:45 +#: project/templates/profile.html:49 msgid "Developer" msgstr "Entwickler" @@ -1925,20 +1930,25 @@ msgstr "Dein Account ist zur Löschung vorgesehen." msgid "Delete account" msgstr "Account löschen" -#: project/templates/profile.html:36 +#: project/templates/profile.html:36 project/templates/user/general.html:4 +#: project/templates/user/general.html:8 +msgid "General" +msgstr "Allgemein" + +#: project/templates/profile.html:40 #: project/templates/user/notifications.html:4 #: project/templates/user/notifications.html:8 msgid "Notifications" msgstr "Benachrichtigungen" -#: project/templates/profile.html:40 +#: project/templates/profile.html:44 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:49 +#: project/templates/profile.html:53 msgid "OAuth2 clients" msgstr "OAuth2 Clients" @@ -1973,7 +1983,7 @@ msgstr "Löschen" msgid "User" msgstr "Nutzer" -#: project/templates/admin/email.html:47 project/views/admin.py:165 +#: project/templates/admin/email.html:47 project/views/admin.py:163 msgid "Mail sent successfully" msgstr "Mail erfolgreich gesendet" @@ -2369,7 +2379,7 @@ msgstr "Veranstaltungsvorschlag prüfen" #: project/templates/manage/reference_requests_incoming.html:20 #: project/templates/reference/read.html:22 #: project/templates/reference_request/review_status.html:17 -#: project/views/reference_request_review.py:42 +#: project/views/reference_request_review.py:38 msgid "View event" msgstr "Veranstaltung anzeigen" @@ -2482,7 +2492,7 @@ msgid "Reviews" msgstr "Prüfungen" #: project/templates/manage/verification_requests_incoming.html:19 -#: project/views/verification_request_review.py:36 +#: project/views/verification_request_review.py:32 msgid "View organization" msgstr "Organisation anzeigen" @@ -2508,7 +2518,9 @@ msgstr "" msgid "" "No organizations were found that can verify your organization with postal" " code %(postal_code)s." -msgstr "Es wurden keine Organisationen gefunden, die deine Organisation mit der Postleitzahl %(postal_code)s verifizieren können." +msgstr "" +"Es wurden keine Organisationen gefunden, die deine Organisation mit der " +"Postleitzahl %(postal_code)s verifizieren können." #: project/templates/manage/widgets.html:87 msgid "Link, um Veranstaltungen vorzuschlagen" @@ -2613,7 +2625,7 @@ msgstr "Angemeldet bleiben" msgid "You do not have an account yet? Not a problem!" msgstr "Du hast noch keinen Account? Kein Problem!" -#: project/templates/security/login_user.html:44 project/views/widget.py:122 +#: project/templates/security/login_user.html:44 project/views/widget.py:119 msgid "Register for free" msgstr "Kostenlos registrieren" @@ -2692,29 +2704,19 @@ msgid "Organization successfully deleted" msgstr "Organisation erfolgreich gelöscht" #: project/views/admin.py:133 project/views/manage.py:485 -#: project/views/user.py:63 +#: project/views/user.py:64 project/views/user.py:83 msgid "Settings successfully updated" msgstr "Einstellungen erfolgreich aktualisiert" -#: project/views/admin.py:153 -#, python-format -msgid "Test mail from %(site_name)s" -msgstr "Test-Mail von %(site_name)s" - -#: project/views/admin.py:183 -#, python-format -msgid "Newsletter from %(site_name)s" -msgstr "Newsletter von %(site_name)s" - -#: project/views/admin.py:232 +#: project/views/admin.py:233 msgid "User successfully updated" msgstr "Nutzer erfolgreich aktualisiert" -#: project/views/admin.py:252 +#: project/views/admin.py:253 msgid "Entered email does not match user email" msgstr "Die eingegebene Email passt nicht zur Email des Nutzers" -#: project/views/admin.py:256 +#: project/views/admin.py:257 msgid "User successfully deleted" msgstr "Nutzer erfolgreich gelöscht" @@ -2735,14 +2737,6 @@ msgstr "Organisation erfolgreich erstellt" msgid "AdminUnit successfully updated" msgstr "Organisation erfolgreich aktualisiert" -#: project/views/admin_unit.py:271 -msgid "Organization invitation accepted" -msgstr "Organisationseinladung akzeptiert" - -#: project/views/admin_unit.py:285 -msgid "Organization deletion requested" -msgstr "Löschung der Organisation beantragt" - #: project/views/admin_unit_member.py:46 msgid "Member successfully updated" msgstr "Mitglied erfolgreich aktualisiert" @@ -2763,46 +2757,38 @@ msgstr "Einladung erfolgreich akzeptiert" msgid "Invitation successfully declined" msgstr "Einladung erfolgreich abgelehnt" -#: project/views/admin_unit_member_invitation.py:94 +#: project/views/admin_unit_member_invitation.py:93 msgid "Invitation successfully sent" msgstr "Einladung erfolgreich gesendet" -#: project/views/admin_unit_member_invitation.py:117 +#: project/views/admin_unit_member_invitation.py:116 msgid "Entered email does not match invitation email" msgstr "Die eingegebene Email passt nicht zur Email der Einladung" -#: project/views/admin_unit_member_invitation.py:122 +#: project/views/admin_unit_member_invitation.py:121 msgid "Invitation successfully deleted" msgstr "Einladung erfolgreich gelöscht" -#: project/views/event.py:208 +#: project/views/event.py:209 msgid "Event successfully published" msgstr "Veranstaltung erfolgreich veröffentlicht" -#: project/views/event.py:210 +#: project/views/event.py:211 msgid "Draft successfully saved" msgstr "Entwurf erfolgreich gespeichert" -#: project/views/event.py:212 +#: project/views/event.py:213 msgid "Event successfully planned" msgstr "Veranstaltung erfolgreich geplant" -#: project/views/event.py:272 +#: project/views/event.py:273 msgid "Event successfully updated" msgstr "Veranstaltung erfolgreich aktualisiert" -#: project/views/event.py:298 +#: project/views/event.py:299 msgid "Event successfully deleted" msgstr "Veranstaltung erfolgreich gelöscht" -#: project/views/event.py:463 -msgid "Referenced event changed" -msgstr "Empfohlene Veranstaltung wurde geändert" - -#: project/views/event.py:486 -msgid "New event report" -msgstr "Neue Meldung zu einer Veranstaltung" - #: project/views/event_place.py:34 msgid "Place successfully created" msgstr "Ort erfolgreich erstellt" @@ -2823,11 +2809,6 @@ msgstr "Ort erfolgreich gelöscht" msgid "Event suggestion successfully rejected" msgstr "Veranstaltungsvorschlag erfolgreich abgelehnt" -#: project/views/event_suggestion.py:87 -#: project/views/reference_request_review.py:139 -msgid "Event review status updated" -msgstr "Prüfungsstatus aktualisiert" - #: project/views/js.py:33 msgid "Short name is already taken" msgstr "Der Kurzname ist bereits vergeben" @@ -2933,28 +2914,20 @@ msgstr "" "benachrichtigt, nachdem die andere Organisation die Veranstaltung geprüft" " hat." -#: project/views/reference_request.py:172 -msgid "New reference request" -msgstr "Neue Empfehlungsanfrage" - -#: project/views/reference_request.py:181 -msgid "New reference automatically verified" -msgstr "Neue automatisch verifizierte Empfehlung" - -#: project/views/reference_request_review.py:40 +#: project/views/reference_request_review.py:36 msgid "Request already verified" msgstr "Empfehlungsanfrage ist bereits verifiziert" -#: project/views/reference_request_review.py:61 +#: project/views/reference_request_review.py:57 msgid "Reference successfully created" msgstr "Empfehlung erfolgreich erstellt" -#: project/views/reference_request_review.py:69 +#: project/views/reference_request_review.py:65 msgid "Request successfully updated" msgstr "Empfehlungsanfrage erfolgreich aktualisiert" -#: project/views/reference_request_review.py:92 -#: project/views/verification_request_review.py:83 +#: project/views/reference_request_review.py:88 +#: project/views/verification_request_review.py:79 #, python-format msgid "" "If all upcoming reference requests of %(admin_unit_name)s should be " @@ -2963,7 +2936,7 @@ msgstr "" "Ob alle zukünftigen Empfehlungsanfragen von %(admin_unit_name)s " "automatisch verifiziert werden sollen." -#: project/views/user.py:107 +#: project/views/user.py:127 msgid "" "You are administrator of at least one organization. Cancel your " "membership to delete your account." @@ -2971,15 +2944,69 @@ msgstr "" "Du bist Administrator von mindestens einer Organisation. Beende deine " "Mitgliedschaft, um deinen Account zu löschen." -#: project/views/user.py:114 project/views/user.py:141 +#: project/views/user.py:134 project/views/user.py:161 msgid "Entered email does not match your email" msgstr "Die eingegebene Email entspricht nicht deiner Email" -#: project/views/user.py:160 +#: project/views/utils.py:27 +msgid "New event report" +msgstr "Neue Meldung zu einer Veranstaltung" + +#: project/views/utils.py:28 project/views/utils.py:36 +msgid "You have received an invitation" +msgstr "Du hast eine Einladung erhalten" + +#: project/views/utils.py:29 +#, python-format +msgid "Newsletter from %(site_name)s" +msgstr "Newsletter von %(site_name)s" + +#: project/views/utils.py:30 +msgid "Organization deletion requested" +msgstr "Löschung der Organisation beantragt" + +#: project/views/utils.py:33 +msgid "Organization invitation accepted" +msgstr "Organisationseinladung akzeptiert" + +#: project/views/utils.py:37 +msgid "New reference automatically verified" +msgstr "Neue automatisch verifizierte Empfehlung" + +#: project/views/utils.py:40 +msgid "New reference request" +msgstr "Neue Empfehlungsanfrage" + +#: project/views/utils.py:41 project/views/utils.py:46 +msgid "Event review status updated" +msgstr "Prüfungsstatus aktualisiert" + +#: project/views/utils.py:44 +msgid "Referenced event changed" +msgstr "Empfohlene Veranstaltung wurde geändert" + +#: project/views/utils.py:45 +msgid "New event review" +msgstr "Neue Veranstaltung zu prüfen" + +#: project/views/utils.py:47 msgid "User deletion requested" msgstr "Löschung des Nutzers beantragt" -#: project/views/utils.py:91 +#: project/views/utils.py:48 +#, python-format +msgid "Test mail from %(site_name)s" +msgstr "Test-Mail von %(site_name)s" + +#: project/views/utils.py:49 +msgid "New verification request" +msgstr "Neue Verifizierungsanfrage" + +#: project/views/utils.py:50 +msgid "Verification request review status updated" +msgstr "Prüfungsstatus der Verifizierungsanfrage aktualisiert" + +#: project/views/utils.py:122 msgid "" "An entry with the entered values ​​already exists. Duplicate entries are " "not allowed." @@ -2987,20 +3014,20 @@ msgstr "" "Ein Eintrag mit den eingegebenen Werten existiert bereits. Doppelte " "Einträge sind nicht erlaubt." -#: project/views/utils.py:142 +#: project/views/utils.py:173 #, python-format msgid "Error in the %s field - %s" msgstr "Fehler im Feld %s: %s" -#: project/views/utils.py:149 +#: project/views/utils.py:180 msgid "Show" msgstr "Anzeigen" -#: project/views/utils.py:157 +#: project/views/utils.py:188 msgid "You do not have permission for this action" msgstr "Du hast keine Berechtigung für diese Aktion" -#: project/views/utils.py:306 +#: project/views/utils.py:382 msgid "" "The invitation was issued to another user. Sign in with the email address" " the invitation was sent to." @@ -3020,31 +3047,23 @@ msgstr "" msgid "Verification request successfully deleted" msgstr "Verifizierungsanfrage erfolgreich gelöscht" -#: project/views/verification_request.py:207 -msgid "New verification request" -msgstr "Neue Verifizierungsanfrage" - -#: project/views/verification_request_review.py:34 +#: project/views/verification_request_review.py:30 msgid "Verification request already verified" msgstr "Verifizierungsanfrage ist bereits verifiziert" -#: project/views/verification_request_review.py:64 +#: project/views/verification_request_review.py:60 msgid "Organization successfully verified" msgstr "Organisation erfolgreich verifiziert" -#: project/views/verification_request_review.py:66 +#: project/views/verification_request_review.py:62 msgid "Verification request successfully updated" msgstr "Verifizierungsanfrage erfolgreich aktualisiert" -#: project/views/verification_request_review.py:119 -msgid "Verification request review status updated" -msgstr "Prüfungsstatus der Verifizierungsanfrage aktualisiert" - -#: project/views/widget.py:114 +#: project/views/widget.py:111 msgid "Thank you so much! The event is being verified." msgstr "Vielen Dank! Die Veranstaltung wird geprüft." -#: project/views/widget.py:118 +#: project/views/widget.py:115 msgid "" "For more options and your own calendar of events, you can register for " "free." @@ -3052,10 +3071,6 @@ msgstr "" "Für mehr Optionen und einen eigenen Veranstaltungskalender, kannst du " "dich kostenlos registrieren." -#: project/views/widget.py:184 -msgid "New event review" -msgstr "Neue Veranstaltung zu prüfen" - #~ msgid "" #~ "Indicate when the event will end. " #~ "An event can last a maximum of " diff --git a/project/translations/en/LC_MESSAGES/messages.mo b/project/translations/en/LC_MESSAGES/messages.mo index 9a79561..289dac7 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 bba3a7b..869cf65 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-09-30 11:43+0200\n" +"POT-Creation-Date: 2023-11-13 17:25+0100\n" "PO-Revision-Date: 2021-04-30 15:04+0200\n" "Last-Translator: FULL NAME \n" "Language: en\n" @@ -18,183 +18,183 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.12.1\n" -#: project/i10n.py:15 +#: project/i10n.py:32 msgid "Event_Art" msgstr "Art" -#: project/i10n.py:16 +#: project/i10n.py:33 msgid "Event_Book" msgstr "Book" -#: project/i10n.py:17 +#: project/i10n.py:34 msgid "Event_Movie" msgstr "Movie" -#: project/i10n.py:18 +#: project/i10n.py:35 msgid "Event_Family" msgstr "Family" -#: project/i10n.py:19 +#: project/i10n.py:36 msgid "Event_Festival" msgstr "Festival" -#: project/i10n.py:20 +#: project/i10n.py:37 msgid "Event_Religious" msgstr "Religious" -#: project/i10n.py:21 +#: project/i10n.py:38 msgid "Event_Shopping" msgstr "Shopping" -#: project/i10n.py:22 +#: project/i10n.py:39 msgid "Event_Comedy" msgstr "Comedy" -#: project/i10n.py:23 +#: project/i10n.py:40 msgid "Event_Music" msgstr "Music" -#: project/i10n.py:24 +#: project/i10n.py:41 msgid "Event_Dance" msgstr "Dance" -#: project/i10n.py:25 +#: project/i10n.py:42 msgid "Event_Nightlife" msgstr "Nightlife" -#: project/i10n.py:26 +#: project/i10n.py:43 msgid "Event_Theater" msgstr "Theater" -#: project/i10n.py:27 +#: project/i10n.py:44 msgid "Event_Dining" msgstr "Dining" -#: project/i10n.py:28 +#: project/i10n.py:45 msgid "Event_Conference" msgstr "Conference" -#: project/i10n.py:29 +#: project/i10n.py:46 msgid "Event_Meetup" msgstr "Meetup" -#: project/i10n.py:30 +#: project/i10n.py:47 msgid "Event_Fitness" msgstr "Fitness" -#: project/i10n.py:31 +#: project/i10n.py:48 msgid "Event_Sports" msgstr "Sports" -#: project/i10n.py:32 +#: project/i10n.py:49 msgid "Event_Other" msgstr "Other" -#: project/i10n.py:33 +#: project/i10n.py:50 msgid "Event_Exhibition" msgstr "Exhibition" -#: project/i10n.py:34 +#: project/i10n.py:51 msgid "Event_Culture" msgstr "Culture" -#: project/i10n.py:35 +#: project/i10n.py:52 msgid "Event_Tour" msgstr "Tour" -#: project/i10n.py:36 +#: project/i10n.py:53 msgid "Event_OpenAir" msgstr "Open Air" -#: project/i10n.py:37 +#: project/i10n.py:54 msgid "Event_Stage" msgstr "Stage" -#: project/i10n.py:38 +#: project/i10n.py:55 msgid "Event_Lecture" msgstr "Lecture" -#: project/i10n.py:39 +#: project/i10n.py:56 msgid "Typical Age range" msgstr "" -#: project/i10n.py:40 +#: project/i10n.py:57 msgid "Administrator" msgstr "" -#: project/i10n.py:41 +#: project/i10n.py:58 msgid "Event expert" msgstr "" -#: project/i10n.py:42 +#: project/i10n.py:59 msgid "EventReviewStatus.inbox" msgstr "Inbox" -#: project/i10n.py:43 +#: project/i10n.py:60 msgid "EventReviewStatus.verified" msgstr "Verified" -#: project/i10n.py:44 +#: project/i10n.py:61 msgid "EventReviewStatus.rejected" msgstr "Rejected" -#: project/i10n.py:45 +#: project/i10n.py:62 msgid "Scope_openid" msgstr "Verify identity" -#: project/i10n.py:46 +#: project/i10n.py:63 msgid "Scope_profile" msgstr "Profile information" -#: project/i10n.py:47 +#: project/i10n.py:64 msgid "Scope_user:read" msgstr "Read user settings" -#: project/i10n.py:48 +#: project/i10n.py:65 msgid "Scope_user:write" msgstr "Create, update and delete user settings" -#: project/i10n.py:49 +#: project/i10n.py:66 msgid "Scope_organizer:write" msgstr "Create, update and delete organizers" -#: project/i10n.py:50 +#: project/i10n.py:67 msgid "Scope_place:write" msgstr "Create, update and delete places" -#: project/i10n.py:51 +#: project/i10n.py:68 msgid "Scope_event:write" msgstr "Create, update and delete events" -#: project/i10n.py:52 +#: project/i10n.py:69 msgid "Scope_eventlist:write" msgstr "Create, update and delete event lists" -#: project/i10n.py:53 +#: project/i10n.py:70 msgid "Scope_eventreference:write" msgstr "" -#: project/i10n.py:54 +#: project/i10n.py:71 msgid "Scope_organization:read" msgstr "Read organizations" -#: project/i10n.py:55 +#: project/i10n.py:72 msgid "Scope_organization:write" msgstr "Create, update and delete organizations" -#: project/i10n.py:56 +#: project/i10n.py:73 msgid "Scope_customwidget:write" msgstr "" -#: project/i10n.py:57 +#: project/i10n.py:74 msgid "There must be no self-reference." msgstr "" -#: project/utils.py:15 +#: project/utils.py:19 msgid "Event_" msgstr "" -#: project/utils.py:19 +#: project/utils.py:23 msgid "." msgstr "" @@ -202,11 +202,6 @@ msgstr "" msgid "message" msgstr "" -#: project/api/organization/resources.py:647 -#: project/views/admin_unit_member_invitation.py:89 -msgid "You have received an invitation" -msgstr "" - #: project/forms/admin.py:11 project/templates/layout.html:344 #: project/views/root.py:55 msgid "Terms of service" @@ -238,7 +233,7 @@ msgid "Announcement" msgstr "" #: project/forms/admin.py:18 project/forms/oauth2_client.py:24 -#: project/forms/user.py:13 +#: project/forms/user.py:18 project/forms/user.py:32 msgid "Save" msgstr "" @@ -268,7 +263,7 @@ msgstr "" #: project/forms/admin_unit_member.py:12 project/forms/admin_unit_member.py:25 #: project/forms/admin_unit_member.py:30 project/forms/event.py:112 #: project/forms/event_suggestion.py:38 project/forms/organizer.py:33 -#: project/forms/user.py:18 project/forms/user.py:23 +#: project/forms/user.py:37 project/forms/user.py:42 #: project/templates/_macros.html:246 project/templates/_macros.html:1586 #: project/templates/admin/admin.html:27 project/templates/admin/email.html:4 #: project/templates/admin/email.html:66 project/templates/admin/users.html:19 @@ -476,7 +471,7 @@ msgid "Verification requests postal codes" msgstr "" #: project/forms/admin_unit.py:93 -msgid "Accept verification requests to organizations with these postal codes." +msgid "Limit verification requests to organizations with these postal codes." msgstr "" #: project/forms/admin_unit.py:109 @@ -524,11 +519,11 @@ msgstr "" msgid "Link Color" msgstr "" -#: project/forms/admin_unit.py:165 project/forms/user.py:17 +#: project/forms/admin_unit.py:165 project/forms/user.py:36 msgid "Request deletion" msgstr "" -#: project/forms/admin_unit.py:170 project/forms/user.py:22 +#: project/forms/admin_unit.py:170 project/forms/user.py:41 #: project/templates/admin_unit/cancel_deletion.html:6 #: project/templates/admin_unit/update.html:44 #: project/templates/manage/events.html:49 project/templates/profile.html:13 @@ -1364,13 +1359,21 @@ msgstr "" msgid "Confirm" msgstr "" -#: project/forms/user.py:9 project/templates/admin/admin.html:31 +#: project/forms/user.py:9 +msgid "Language" +msgstr "" + +#: project/forms/user.py:11 +msgid "Default" +msgstr "" + +#: project/forms/user.py:28 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 +#: project/forms/user.py:29 msgid "Information about new features and improvements." msgstr "" @@ -1858,7 +1861,7 @@ msgstr "" #: project/templates/developer/read.html:4 #: project/templates/developer/read.html:8 project/templates/layout.html:361 -#: project/templates/profile.html:45 +#: project/templates/profile.html:49 msgid "Developer" msgstr "" @@ -1872,20 +1875,25 @@ msgstr "" msgid "Delete account" msgstr "" -#: project/templates/profile.html:36 +#: project/templates/profile.html:36 project/templates/user/general.html:4 +#: project/templates/user/general.html:8 +msgid "General" +msgstr "" + +#: project/templates/profile.html:40 #: project/templates/user/notifications.html:4 #: project/templates/user/notifications.html:8 msgid "Notifications" msgstr "" -#: project/templates/profile.html:40 +#: project/templates/profile.html:44 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:49 +#: project/templates/profile.html:53 msgid "OAuth2 clients" msgstr "" @@ -1920,7 +1928,7 @@ msgstr "" msgid "User" msgstr "" -#: project/templates/admin/email.html:47 project/views/admin.py:165 +#: project/templates/admin/email.html:47 project/views/admin.py:163 msgid "Mail sent successfully" msgstr "" @@ -2305,7 +2313,7 @@ msgstr "" #: project/templates/manage/reference_requests_incoming.html:20 #: project/templates/reference/read.html:22 #: project/templates/reference_request/review_status.html:17 -#: project/views/reference_request_review.py:42 +#: project/views/reference_request_review.py:38 msgid "View event" msgstr "" @@ -2416,7 +2424,7 @@ msgid "Reviews" msgstr "" #: project/templates/manage/verification_requests_incoming.html:19 -#: project/views/verification_request_review.py:36 +#: project/views/verification_request_review.py:32 msgid "View organization" msgstr "" @@ -2545,7 +2553,7 @@ msgstr "" msgid "You do not have an account yet? Not a problem!" msgstr "" -#: project/templates/security/login_user.html:44 project/views/widget.py:122 +#: project/templates/security/login_user.html:44 project/views/widget.py:119 msgid "Register for free" msgstr "" @@ -2622,29 +2630,19 @@ msgid "Organization successfully deleted" msgstr "" #: project/views/admin.py:133 project/views/manage.py:485 -#: project/views/user.py:63 +#: project/views/user.py:64 project/views/user.py:83 msgid "Settings successfully updated" msgstr "" -#: project/views/admin.py:153 -#, python-format -msgid "Test mail from %(site_name)s" -msgstr "" - -#: project/views/admin.py:183 -#, python-format -msgid "Newsletter from %(site_name)s" -msgstr "" - -#: project/views/admin.py:232 +#: project/views/admin.py:233 msgid "User successfully updated" msgstr "" -#: project/views/admin.py:252 +#: project/views/admin.py:253 msgid "Entered email does not match user email" msgstr "" -#: project/views/admin.py:256 +#: project/views/admin.py:257 msgid "User successfully deleted" msgstr "" @@ -2662,14 +2660,6 @@ msgstr "" msgid "AdminUnit successfully updated" msgstr "" -#: project/views/admin_unit.py:271 -msgid "Organization invitation accepted" -msgstr "" - -#: project/views/admin_unit.py:285 -msgid "Organization deletion requested" -msgstr "" - #: project/views/admin_unit_member.py:46 msgid "Member successfully updated" msgstr "" @@ -2690,46 +2680,38 @@ msgstr "" msgid "Invitation successfully declined" msgstr "" -#: project/views/admin_unit_member_invitation.py:94 +#: project/views/admin_unit_member_invitation.py:93 msgid "Invitation successfully sent" msgstr "" -#: project/views/admin_unit_member_invitation.py:117 +#: project/views/admin_unit_member_invitation.py:116 msgid "Entered email does not match invitation email" msgstr "" -#: project/views/admin_unit_member_invitation.py:122 +#: project/views/admin_unit_member_invitation.py:121 msgid "Invitation successfully deleted" msgstr "" -#: project/views/event.py:208 +#: project/views/event.py:209 msgid "Event successfully published" msgstr "" -#: project/views/event.py:210 +#: project/views/event.py:211 msgid "Draft successfully saved" msgstr "" -#: project/views/event.py:212 +#: project/views/event.py:213 msgid "Event successfully planned" msgstr "" -#: project/views/event.py:272 +#: project/views/event.py:273 msgid "Event successfully updated" msgstr "" -#: project/views/event.py:298 +#: project/views/event.py:299 msgid "Event successfully deleted" msgstr "" -#: project/views/event.py:463 -msgid "Referenced event changed" -msgstr "" - -#: project/views/event.py:486 -msgid "New event report" -msgstr "" - #: project/views/event_place.py:34 msgid "Place successfully created" msgstr "" @@ -2750,11 +2732,6 @@ msgstr "" msgid "Event suggestion successfully rejected" msgstr "" -#: project/views/event_suggestion.py:87 -#: project/views/reference_request_review.py:139 -msgid "Event review status updated" -msgstr "" - #: project/views/js.py:33 msgid "Short name is already taken" msgstr "" @@ -2855,68 +2832,114 @@ msgid "" "notified after the other organization reviews the event." msgstr "" -#: project/views/reference_request.py:172 -msgid "New reference request" -msgstr "" - -#: project/views/reference_request.py:181 -msgid "New reference automatically verified" -msgstr "" - -#: project/views/reference_request_review.py:40 +#: project/views/reference_request_review.py:36 msgid "Request already verified" msgstr "" -#: project/views/reference_request_review.py:61 +#: project/views/reference_request_review.py:57 msgid "Reference successfully created" msgstr "" -#: project/views/reference_request_review.py:69 +#: project/views/reference_request_review.py:65 msgid "Request successfully updated" msgstr "" -#: project/views/reference_request_review.py:92 -#: project/views/verification_request_review.py:83 +#: project/views/reference_request_review.py:88 +#: project/views/verification_request_review.py:79 #, python-format msgid "" "If all upcoming reference requests of %(admin_unit_name)s should be " "verified automatically." msgstr "" -#: project/views/user.py:107 +#: project/views/user.py:127 msgid "" "You are administrator of at least one organization. Cancel your " "membership to delete your account." msgstr "" -#: project/views/user.py:114 project/views/user.py:141 +#: project/views/user.py:134 project/views/user.py:161 msgid "Entered email does not match your email" msgstr "" -#: project/views/user.py:160 +#: project/views/utils.py:27 +msgid "New event report" +msgstr "" + +#: project/views/utils.py:28 project/views/utils.py:36 +msgid "You have received an invitation" +msgstr "" + +#: project/views/utils.py:29 +#, python-format +msgid "Newsletter from %(site_name)s" +msgstr "" + +#: project/views/utils.py:30 +msgid "Organization deletion requested" +msgstr "" + +#: project/views/utils.py:33 +msgid "Organization invitation accepted" +msgstr "" + +#: project/views/utils.py:37 +msgid "New reference automatically verified" +msgstr "" + +#: project/views/utils.py:40 +msgid "New reference request" +msgstr "" + +#: project/views/utils.py:41 project/views/utils.py:46 +msgid "Event review status updated" +msgstr "" + +#: project/views/utils.py:44 +msgid "Referenced event changed" +msgstr "" + +#: project/views/utils.py:45 +msgid "New event review" +msgstr "" + +#: project/views/utils.py:47 msgid "User deletion requested" msgstr "" -#: project/views/utils.py:91 +#: project/views/utils.py:48 +#, python-format +msgid "Test mail from %(site_name)s" +msgstr "" + +#: project/views/utils.py:49 +msgid "New verification request" +msgstr "" + +#: project/views/utils.py:50 +msgid "Verification request review status updated" +msgstr "" + +#: project/views/utils.py:122 msgid "" "An entry with the entered values ​​already exists. Duplicate entries are " "not allowed." msgstr "" -#: project/views/utils.py:142 +#: project/views/utils.py:173 #, python-format msgid "Error in the %s field - %s" msgstr "" -#: project/views/utils.py:149 +#: project/views/utils.py:180 msgid "Show" msgstr "" -#: project/views/utils.py:157 +#: project/views/utils.py:188 msgid "You do not have permission for this action" msgstr "" -#: project/views/utils.py:306 +#: project/views/utils.py:382 msgid "" "The invitation was issued to another user. Sign in with the email address" " the invitation was sent to." @@ -2932,40 +2955,28 @@ msgstr "" msgid "Verification request successfully deleted" msgstr "" -#: project/views/verification_request.py:207 -msgid "New verification request" -msgstr "" - -#: project/views/verification_request_review.py:34 +#: project/views/verification_request_review.py:30 msgid "Verification request already verified" msgstr "" -#: project/views/verification_request_review.py:64 +#: project/views/verification_request_review.py:60 msgid "Organization successfully verified" msgstr "" -#: project/views/verification_request_review.py:66 +#: project/views/verification_request_review.py:62 msgid "Verification request successfully updated" msgstr "" -#: project/views/verification_request_review.py:119 -msgid "Verification request review status updated" -msgstr "" - -#: project/views/widget.py:114 +#: project/views/widget.py:111 msgid "Thank you so much! The event is being verified." msgstr "" -#: project/views/widget.py:118 +#: project/views/widget.py:115 msgid "" "For more options and your own calendar of events, you can register for " "free." msgstr "" -#: project/views/widget.py:184 -msgid "New event review" -msgstr "" - #~ msgid "" #~ "Indicate when the event will end. " #~ "An event can last a maximum of " @@ -3052,3 +3063,6 @@ msgstr "" #~ msgid "Switch to place search" #~ msgstr "" +#~ msgid "Accept verification requests to organizations with these postal codes." +#~ msgstr "" + diff --git a/project/utils.py b/project/utils.py index 2f22e46..251b1ef 100644 --- a/project/utils.py +++ b/project/utils.py @@ -7,6 +7,10 @@ from sqlalchemy.exc import IntegrityError from sqlalchemy.orm.base import NO_CHANGE, object_state +def dummy_gettext(message: str): + return message + + def strings_are_equal_ignoring_case(string1: str, string2: str): return string1 and string2 and string1.casefold() == string2.casefold() diff --git a/project/views/admin.py b/project/views/admin.py index ef742a4..d62d473 100644 --- a/project/views/admin.py +++ b/project/views/admin.py @@ -25,9 +25,9 @@ from project.views.utils import ( get_pagination_urls, handleSqlError, non_match_for_deletion, - send_mail, - send_mail_async, - send_mails_async, + send_template_mail, + send_template_mail_async, + send_template_mails_to_users_async, ) @@ -150,18 +150,16 @@ def admin_email(): return get_celery_poll_group_result() if form.validate_on_submit(): - subject = gettext( - "Test mail from %(site_name)s", - site_name=app.config["SITE_NAME"], - ) + template = "test_email" + context = {"site_name": app.config["SITE_NAME"]} if "async" in request.args: # pragma: no cover - result = send_mail_async(form.recipient.data, subject, "test_email") + result = send_template_mail_async(form.recipient.data, template, **context) result.save() return {"result_id": result.id} try: - send_mail(form.recipient.data, subject, "test_email") + send_template_mail(form.recipient.data, template, **context) flash(gettext("Mail sent successfully"), "success") except Exception as e: # pragma: no cover flash(str(e), "danger") @@ -180,13 +178,15 @@ def admin_newsletter(): return get_celery_poll_group_result() if form.validate_on_submit(): - subject = gettext( - "Newsletter from %(site_name)s", - site_name=app.config["SITE_NAME"], - ) + template = "newsletter" + context = {"site_name": app.config["SITE_NAME"], "message": form.message.data} if form.recipient_choice.data == 1: # pragma: no cover - recipients = [form.test_recipient.data] + result = send_template_mail_async( + form.test_recipient.data, + template, + **context, + ) else: users = ( User.query.filter(User.email != None) @@ -194,11 +194,12 @@ def admin_newsletter(): .filter(User.newsletter_enabled) .all() ) - recipients = [u.email for u in users] + result = send_template_mails_to_users_async( + users, + template, + **context, + ) - result = send_mails_async( - recipients, subject, "newsletter", message=form.message.data - ) result.save() return {"result_id": result.id} diff --git a/project/views/admin_unit.py b/project/views/admin_unit.py index b044dd4..d91f2c7 100644 --- a/project/views/admin_unit.py +++ b/project/views/admin_unit.py @@ -9,7 +9,6 @@ from project import app, db from project.access import ( can_create_admin_unit, get_admin_unit_for_manage_or_404, - get_admin_unit_members_with_permission, has_access, ) from project.forms.admin_unit import ( @@ -32,7 +31,7 @@ from project.views.utils import ( manage_required, non_match_for_deletion, permission_missing, - send_mails_async, + send_template_mails_to_admin_unit_members_async, ) @@ -261,14 +260,9 @@ def send_admin_unit_invitation_accepted_mails( invitation: AdminUnitInvitation, relation: AdminUnitRelation, admin_unit: AdminUnit ): # Benachrichtige alle Mitglieder der AdminUnit, die diese Einladung erstellt hatte - members = get_admin_unit_members_with_permission( - invitation.admin_unit_id, "admin_unit:update" - ) - emails = list(map(lambda member: member.user.email, members)) - - send_mails_async( - emails, - gettext("Organization invitation accepted"), + send_template_mails_to_admin_unit_members_async( + invitation.admin_unit_id, + "admin_unit:update", "organization_invitation_accepted_notice", invitation=invitation, relation=relation, @@ -277,12 +271,9 @@ def send_admin_unit_invitation_accepted_mails( def send_admin_unit_deletion_requested_mails(admin_unit: AdminUnit): - members = get_admin_unit_members_with_permission(admin_unit.id, "admin_unit:update") - emails = list(map(lambda member: member.user.email, members)) - - send_mails_async( - emails, - gettext("Organization deletion requested"), + send_template_mails_to_admin_unit_members_async( + admin_unit.id, + "admin_unit:update", "organization_deletion_requested_notice", admin_unit=admin_unit, ) diff --git a/project/views/admin_unit_member_invitation.py b/project/views/admin_unit_member_invitation.py index 71a0839..3d74a8d 100644 --- a/project/views/admin_unit_member_invitation.py +++ b/project/views/admin_unit_member_invitation.py @@ -18,7 +18,7 @@ from project.views.utils import ( handleSqlError, non_match_for_deletion, permission_missing, - send_mail_async, + send_template_mail_async, ) @@ -84,9 +84,8 @@ def manage_admin_unit_member_invite(id): db.session.add(invitation) db.session.commit() - send_mail_async( + send_template_mail_async( invitation.email, - gettext("You have received an invitation"), "invitation_notice", invitation=invitation, ) diff --git a/project/views/event.py b/project/views/event.py index 80ae70d..a84a936 100644 --- a/project/views/event.py +++ b/project/views/event.py @@ -55,7 +55,8 @@ from project.views.utils import ( get_calendar_links_for_event, get_share_links, handleSqlError, - send_mails_async, + send_template_mails_to_admin_unit_members_async, + send_template_mails_to_users_async, set_current_admin_unit, ) @@ -453,14 +454,9 @@ def send_referenced_event_changed_mails(event): references = EventReference.query.filter(EventReference.event_id == event.id).all() for reference in references: # Alle Mitglieder der AdminUnit, die das Recht haben, Requests zu verifizieren - members = get_admin_unit_members_with_permission( - reference.admin_unit_id, "reference_request:verify" - ) - emails = list(map(lambda member: member.user.email, members)) - - send_mails_async( - emails, - gettext("Referenced event changed"), + send_template_mails_to_admin_unit_members_async( + reference.admin_unit_id, + "reference_request:verify", "referenced_event_changed_notice", event=event, reference=reference, @@ -474,16 +470,16 @@ def send_event_report_mails(event: Event, report: dict): members = get_admin_unit_members_with_permission( event.admin_unit_id, "event:update" ) - emails = list(map(lambda member: member.user.email, members)) + users = [member.user for member in members] # Alle globalen Admins admins = find_all_users_with_role("admin") - admin_emails = list(map(lambda admin: admin.email, admins)) - emails.extend(x for x in admin_emails if x not in emails) + users.extend( + admin for admin in admins if all(user.id != admin.id for user in users) + ) - send_mails_async( - emails, - gettext("New event report"), + send_template_mails_to_users_async( + users, "event_report_notice", event=event, report=report, diff --git a/project/views/event_suggestion.py b/project/views/event_suggestion.py index b5a3557..393b1cb 100644 --- a/project/views/event_suggestion.py +++ b/project/views/event_suggestion.py @@ -7,7 +7,7 @@ from project import app, db from project.access import access_or_401 from project.forms.event_suggestion import RejectEventSuggestionForm from project.models import EventReviewStatus, EventSuggestion -from project.views.utils import flash_errors, handleSqlError, send_mail_async +from project.views.utils import flash_errors, handleSqlError, send_template_mail_async @app.route("/event_suggestion//review") @@ -82,9 +82,8 @@ def event_suggestion_review_status(event_suggestion_id): def send_event_suggestion_review_status_mail(event_suggestion): if event_suggestion.contact_email and event_suggestion.contact_email_notice: - send_mail_async( + send_template_mail_async( event_suggestion.contact_email, - gettext("Event review status updated"), "review_status_notice", event_suggestion=event_suggestion, ) diff --git a/project/views/reference_request.py b/project/views/reference_request.py index 4eb236a..42cbf2f 100644 --- a/project/views/reference_request.py +++ b/project/views/reference_request.py @@ -4,11 +4,7 @@ from flask_security import auth_required from sqlalchemy.exc import SQLAlchemyError from project import app, db -from project.access import ( - can_request_event_reference, - get_admin_unit_for_manage_or_404, - get_admin_unit_members_with_permission, -) +from project.access import can_request_event_reference, get_admin_unit_for_manage_or_404 from project.forms.reference_request import CreateEventReferenceRequestForm from project.models import ( Event, @@ -31,7 +27,7 @@ from project.views.utils import ( flash_errors, get_pagination_urls, handleSqlError, - send_mails_async, + send_template_mails_to_admin_unit_members_async, ) @@ -169,7 +165,6 @@ def send_reference_request_mails( def _send_reference_request_inbox_mails(request): _send_member_reference_request_verify_mails( request.admin_unit_id, - gettext("New reference request"), "reference_request_notice", request=request, ) @@ -178,19 +173,13 @@ def _send_reference_request_inbox_mails(request): def _send_auto_reference_mails(reference): _send_member_reference_request_verify_mails( reference.admin_unit_id, - gettext("New reference automatically verified"), "reference_auto_verified_notice", reference=reference, ) -def _send_member_reference_request_verify_mails( - admin_unit_id, subject, template, **context -): +def _send_member_reference_request_verify_mails(admin_unit_id, template, **context): # Benachrichtige alle Mitglieder der AdminUnit, die Requests verifizieren können - members = get_admin_unit_members_with_permission( - admin_unit_id, "reference_request:verify" + send_template_mails_to_admin_unit_members_async( + admin_unit_id, "reference_request:verify", template, **context ) - emails = list(map(lambda member: member.user.email, members)) - - send_mails_async(emails, subject, template, **context) diff --git a/project/views/reference_request_review.py b/project/views/reference_request_review.py index 7c295a1..2da1511 100644 --- a/project/views/reference_request_review.py +++ b/project/views/reference_request_review.py @@ -4,11 +4,7 @@ from flask_security import auth_required from sqlalchemy.exc import SQLAlchemyError from project import app, db -from project.access import ( - access_or_401, - get_admin_unit_members_with_permission, - has_access, -) +from project.access import access_or_401, has_access from project.dateutils import get_today from project.forms.reference_request import ReferenceRequestReviewForm from project.models import ( @@ -25,7 +21,7 @@ from project.views.utils import ( flash_errors, flash_message, handleSqlError, - send_mails_async, + send_template_mails_to_admin_unit_members_async, ) @@ -129,14 +125,9 @@ def event_reference_request_review_status(id): def send_reference_request_review_status_mails(request): # Benachrichtige alle Mitglieder der AdminUnit, die diesen Request erstellt hatte - members = get_admin_unit_members_with_permission( - request.event.admin_unit_id, "reference_request:create" - ) - emails = list(map(lambda member: member.user.email, members)) - - send_mails_async( - emails, - gettext("Event review status updated"), + send_template_mails_to_admin_unit_members_async( + request.event.admin_unit_id, + "reference_request:create", "reference_request_review_status_notice", request=request, ) diff --git a/project/views/user.py b/project/views/user.py index 68b73b6..e8aa09a 100644 --- a/project/views/user.py +++ b/project/views/user.py @@ -10,6 +10,7 @@ from project import app, db from project.forms.security import AcceptTosForm from project.forms.user import ( CancelUserDeletionForm, + GeneralForm, NotificationForm, RequestUserDeletionForm, ) @@ -20,7 +21,7 @@ from project.views.utils import ( get_invitation_access_result, handleSqlError, non_match_for_deletion, - send_mail_async, + send_template_mails_to_users_async, ) @@ -50,6 +51,25 @@ def user_accept_tos(): return render_template("user/accept_tos.html", form=form) +@app.route("/user/general", methods=("GET", "POST")) +@auth_required() +def user_general(): + user = User.query.get_or_404(current_user.id) + form = GeneralForm(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/general.html", form=form) + + @app.route("/user/notifications", methods=("GET", "POST")) @auth_required() def user_notifications(): @@ -155,9 +175,8 @@ def user_cancel_deletion(): def send_user_deletion_requested_mail(user): - send_mail_async( - user.email, - gettext("User deletion requested"), + send_template_mails_to_users_async( + [user], "user_deletion_requested_notice", user=user, ) diff --git a/project/views/utils.py b/project/views/utils.py index 21f1e51..38d1c1b 100644 --- a/project/views/utils.py +++ b/project/views/utils.py @@ -1,8 +1,9 @@ from functools import wraps +from itertools import groupby from urllib.parse import quote_plus from flask import Markup, flash, g, redirect, render_template, request, url_for -from flask_babel import gettext +from flask_babel import force_locale, gettext from flask_login.utils import decode_cookie from flask_mail import Message from flask_security import current_user @@ -14,12 +15,42 @@ from project import app, celery, mail from project.access import ( get_admin_unit_for_manage, get_admin_unit_for_manage_or_404, + get_admin_unit_members_with_permission, get_admin_units_for_manage, has_access, ) from project.dateutils import berlin_tz, round_to_next_day from project.models import Event, EventAttendanceMode, EventDate -from project.utils import get_place_str, strings_are_equal_ignoring_case +from project.utils import dummy_gettext, get_place_str, strings_are_equal_ignoring_case + +mail_template_subject_mapping = { + "event_report_notice": dummy_gettext("New event report"), + "invitation_notice": dummy_gettext("You have received an invitation"), + "newsletter": dummy_gettext("Newsletter from %(site_name)s"), + "organization_deletion_requested_notice": dummy_gettext( + "Organization deletion requested" + ), + "organization_invitation_accepted_notice": dummy_gettext( + "Organization invitation accepted" + ), + "organization_invitation_notice": dummy_gettext("You have received an invitation"), + "reference_auto_verified_notice": dummy_gettext( + "New reference automatically verified" + ), + "reference_request_notice": dummy_gettext("New reference request"), + "reference_request_review_status_notice": dummy_gettext( + "Event review status updated" + ), + "referenced_event_changed_notice": dummy_gettext("Referenced event changed"), + "review_notice": dummy_gettext("New event review"), + "review_status_notice": dummy_gettext("Event review status updated"), + "user_deletion_requested_notice": dummy_gettext("User deletion requested"), + "test_email": dummy_gettext("Test mail from %(site_name)s"), + "verification_request_notice": dummy_gettext("New verification request"), + "verification_request_review_status_notice": dummy_gettext( + "Verification request review status updated" + ), +} def set_current_admin_unit(admin_unit): @@ -160,37 +191,86 @@ def permission_missing(redirect_location, message=None): return redirect(redirect_location) -def send_mail(recipient, subject, template, **context): - send_mails([recipient], subject, template, **context) +def send_template_mail(recipient, template, **context): + send_template_mails([recipient], template, **context) -def send_mails(recipients, subject, template, **context): +def send_template_mails(recipients, template, **context): if len(recipients) == 0: # pragma: no cover return - body, html = render_mail_body(template, **context) + subject, body, html = render_mail_body_with_subject(template, **context) send_mails_with_body(recipients, subject, body, html) -def send_mail_async(recipient, subject, template, **context): - return send_mails_async([recipient], subject, template, **context) +def send_template_mail_async(recipient, template, **context): + return send_template_mails_async([recipient], template, **context) -def send_mails_async(recipients, subject, template, **context): +def send_template_mails_async(recipients, template, **context): if len(recipients) == 0: # pragma: no cover return - body, html = render_mail_body(template, **context) + subject, body, html = render_mail_body_with_subject(template, **context) return send_mails_with_body_async(recipients, subject, body, html) +def render_mail_body_with_subject(template, **context): + subject_key = mail_template_subject_mapping.get(template) + locale = context.get("locale", None) or app.config["BABEL_DEFAULT_LOCALE"] + + with force_locale(locale): + subject = gettext(subject_key, **context) + body, html = render_mail_body(template, **context) + + return subject, body, html + + +def send_template_mails_to_admin_unit_members_async( + admin_unit_id, permissions, template, **context +): + members = get_admin_unit_members_with_permission(admin_unit_id, permissions) + users = [member.user for member in members] + + return send_template_mails_to_users_async(users, template, **context) + + +def send_template_mails_to_users_async(users, template, **context): + if len(users) == 0: # pragma: no cover + return + + # Group by locale + def locale_func(user): + return user.locale if user.locale else "" + + sorted_users = sorted(users, key=locale_func) + grouped_users = groupby(sorted_users, locale_func) + + signatures = list() + + for locale, locale_users in grouped_users: + context["locale"] = locale + subject, body, html = render_mail_body_with_subject(template, **context) + signatures.extend([(user.email, subject, body, html) for user in locale_users]) + + return send_mails_with_signatures_async(signatures) + + def send_mails_with_body_async(recipients, subject, body, html): + signatures = [(recipient, subject, body, html) for recipient in recipients] + return send_mails_with_signatures_async(signatures) + + +def send_mails_with_signatures_async(signatures): from celery import group from project.base_tasks import send_mail_with_body_task + if len(signatures) == 0: # pragma: no cover + return + result = group( - send_mail_with_body_task.s(r, subject, body, html) for r in recipients + send_mail_with_body_task.s(*signature) for signature in signatures ).delay() return result diff --git a/project/views/verification_request.py b/project/views/verification_request.py index 9301e61..6b331ca 100644 --- a/project/views/verification_request.py +++ b/project/views/verification_request.py @@ -4,7 +4,7 @@ from flask_security import auth_required from sqlalchemy.exc import SQLAlchemyError from project import app, db -from project.access import access_or_401, get_admin_unit_members_with_permission +from project.access import access_or_401 from project.forms.verification_request import ( CreateAdminUnitVerificationRequestForm, DeleteVerificationRequestForm, @@ -30,7 +30,7 @@ from project.views.utils import ( handleSqlError, manage_required, non_match_for_deletion, - send_mails_async, + send_template_mails_to_admin_unit_members_async, ) @@ -189,22 +189,12 @@ def admin_unit_verification_request_delete(id): ) -def send_member_verification_request_verify_mails( - admin_unit_id, subject, template, **context -): - # Benachrichtige alle Mitglieder der AdminUnit, die Requests verifizieren können - members = get_admin_unit_members_with_permission( - admin_unit_id, "verification_request:verify" - ) - emails = list(map(lambda member: member.user.email, members)) - - send_mails_async(emails, subject, template, **context) - - def send_verification_request_inbox_mails(request): - send_member_verification_request_verify_mails( - request.target_admin_unit_id or request.target_admin_unit.id, - gettext("New verification request"), + # Benachrichtige alle Mitglieder der AdminUnit, die Requests verifizieren können + admin_unit_id = request.target_admin_unit_id or request.target_admin_unit.id + send_template_mails_to_admin_unit_members_async( + admin_unit_id, + "verification_request:verify", "verification_request_notice", request=request, ) diff --git a/project/views/verification_request_review.py b/project/views/verification_request_review.py index cc7f82e..6232dc5 100644 --- a/project/views/verification_request_review.py +++ b/project/views/verification_request_review.py @@ -4,11 +4,7 @@ from flask_security import auth_required from sqlalchemy.exc import SQLAlchemyError from project import app, db -from project.access import ( - access_or_401, - get_admin_unit_members_with_permission, - has_access, -) +from project.access import access_or_401, has_access from project.forms.verification_request import VerificationRequestReviewForm from project.models import ( AdminUnitVerificationRequest, @@ -19,7 +15,7 @@ from project.views.utils import ( flash_errors, flash_message, handleSqlError, - send_mails_async, + send_template_mails_to_admin_unit_members_async, ) @@ -109,14 +105,9 @@ def admin_unit_verification_request_review_status(id): def send_verification_request_review_status_mails(request): # Benachrichtige alle Mitglieder der AdminUnit, die diesen Request erstellt hatte - members = get_admin_unit_members_with_permission( - request.source_admin_unit_id, "verification_request:create" - ) - emails = list(map(lambda member: member.user.email, members)) - - send_mails_async( - emails, - gettext("Verification request review status updated"), + send_template_mails_to_admin_unit_members_async( + request.source_admin_unit_id, + "verification_request:create", "verification_request_review_status_notice", request=request, ) diff --git a/project/views/widget.py b/project/views/widget.py index 363e811..2f82d01 100644 --- a/project/views/widget.py +++ b/project/views/widget.py @@ -5,10 +5,7 @@ from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.sql import func from project import app, db -from project.access import ( - admin_unit_suggestions_enabled_or_404, - get_admin_unit_members_with_permission, -) +from project.access import admin_unit_suggestions_enabled_or_404 from project.dateutils import get_next_full_hour from project.forms.event_date import FindEventDateWidgetForm from project.forms.event_suggestion import CreateEventSuggestionForm @@ -23,7 +20,7 @@ from project.views.utils import ( flash_message, get_pagination_urls, handleSqlError, - send_mails_async, + send_template_mails_to_admin_unit_members_async, ) @@ -176,12 +173,9 @@ def get_styles(admin_unit): def send_event_inbox_mails(admin_unit, event_suggestion): - members = get_admin_unit_members_with_permission(admin_unit.id, "event:verify") - emails = list(map(lambda member: member.user.email, members)) - - send_mails_async( - emails, - gettext("New event review"), + send_template_mails_to_admin_unit_members_async( + admin_unit.id, + "event:verify", "review_notice", event_suggestion=event_suggestion, ) diff --git a/tests/seeder.py b/tests/seeder.py index 8afb212..936aec8 100644 --- a/tests/seeder.py +++ b/tests/seeder.py @@ -38,6 +38,7 @@ class Seeder(object): admin=False, confirm=True, tos_accepted=True, + locale=None, ): from flask_security.confirmable import confirm_user @@ -63,6 +64,7 @@ class Seeder(object): if admin: add_admin_roles_to_user(email) + user.locale = locale self._db.session.commit() user_id = user.id diff --git a/tests/test_i10n.py b/tests/test_i10n.py index 8dd73be..a6e992b 100644 --- a/tests/test_i10n.py +++ b/tests/test_i10n.py @@ -1,4 +1,4 @@ -def test_send_mails(client, app, utils): +def test_dynamic_texts(client, app, utils): from project.i10n import print_dynamic_texts print_dynamic_texts() diff --git a/tests/utils.py b/tests/utils.py index b014c77..2c29900 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -206,14 +206,21 @@ class UtilActions(object): return mocker.patch("project.views.utils.send_mails_with_body") def mock_send_mails_async(self, mocker): - return mocker.patch("project.views.utils.send_mails_with_body_async") + return mocker.patch("project.views.utils.send_mails_with_signatures_async") def assert_send_mail_called( self, mock, expected_recipients, expected_contents=None ): mock.assert_called_once() args, kwargs = mock.call_args - send_recipients, subject, body, html = args + + if mock._extract_mock_name() == "send_mails_with_signatures_async": + signatures = args[0] + send_recipients = [s[0] for s in signatures] + first_signature = signatures[0] + _, subject, body, html = first_signature + else: + send_recipients, subject, body, html = args if not isinstance(expected_recipients, list): expected_recipients = [expected_recipients] diff --git a/tests/views/test_admin.py b/tests/views/test_admin.py index 528e3e0..3324ab5 100644 --- a/tests/views/test_admin.py +++ b/tests/views/test_admin.py @@ -86,7 +86,8 @@ 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") + locale = "de" if (i % 3) == 0 else "en" if (i % 3) == 1 else None + seeder.create_user(f"test{i}@test.de", locale=locale) url = utils.get_url("admin_newsletter") response = utils.get_ok(url) diff --git a/tests/views/test_user.py b/tests/views/test_user.py index f95d7a5..09173d6 100644 --- a/tests/views/test_user.py +++ b/tests/views/test_user.py @@ -69,6 +69,35 @@ def test_user_favorite_events(client, seeder, utils): utils.get_ok(url) +@pytest.mark.parametrize("locale", [None, "de"]) +def test_user_general(client, seeder, utils, app, db, locale): + user_id, admin_unit_id = seeder.setup_base() + + url = utils.get_url("user_general") + response = utils.get_ok(url) + + if locale is None: + values = dict() + else: + values = { + "locale": locale, + } + + response = utils.post_form( + url, + response, + values, + ) + + utils.assert_response_redirect(response, "profile") + + with app.app_context(): + from project.models import User + + user = db.session.get(User, user_id) + assert user.locale == locale + + def test_user_notifications(client, seeder, utils, app, db): user_id, admin_unit_id = seeder.setup_base() @@ -88,8 +117,8 @@ def test_user_notifications(client, seeder, utils, app, db): with app.app_context(): from project.models import User - place = db.session.get(User, user_id) - assert not place.newsletter_enabled + user = db.session.get(User, user_id) + assert not user.newsletter_enabled def test_login_flash(client, seeder, utils): diff --git a/tests/views/test_utils.py b/tests/views/test_utils.py index 785ce57..335b8fc 100644 --- a/tests/views/test_utils.py +++ b/tests/views/test_utils.py @@ -1,6 +1,6 @@ -def test_send_mails(client, seeder, app, db, utils): +def test_send_template_mails(client, seeder, app, db, utils): from project.models import AdminUnitMemberInvitation - from project.views.utils import send_mail + from project.views.utils import send_template_mail user_id, admin_unit_id = seeder.setup_base() email = "new@member.de" @@ -12,9 +12,8 @@ def test_send_mails(client, seeder, app, db, utils): mail.default_sender = None invitation = db.session.get(AdminUnitMemberInvitation, invitation_id) - send_mail( + send_template_mail( email, - "You have received an invitation", "invitation_notice", invitation=invitation, )