User locale #557

This commit is contained in:
Daniel Grams 2023-11-14 16:24:03 +01:00
parent 705bb4bf0b
commit 069160c75d
34 changed files with 828 additions and 607 deletions

View File

@ -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

View File

@ -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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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 ""

View File

@ -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")

View File

@ -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

View File

@ -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,
)

View File

@ -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"),

View File

@ -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():

View File

@ -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):

View File

@ -32,6 +32,10 @@
<h2>{{ _('Settings') }}</h2>
<div class="list-group">
<a href="{{ url_for('user_general') }}" class="list-group-item">
{{ _('General') }}
<i class="fa fa-caret-right"></i>
</a>
<a href="{{ url_for('user_notifications') }}" class="list-group-item">
{{ _('Notifications') }}
<i class="fa fa-caret-right"></i>

View File

@ -0,0 +1,21 @@
{% extends "layout.html" %}
{% from "_macros.html" import render_field_with_errors, render_field %}
{%- block title -%}
{{ _('General') }}
{%- endblock -%}
{% block content %}
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{{ url_for('profile') }}">{{ _('Profile') }}</a></li>
<li class="breadcrumb-item active" aria-current="page">{{ _('General') }}</li>
</ol>
</nav>
<form action="" method="POST">
{{ form.hidden_tag() }}
{{ render_field_with_errors(form.locale) }}
{{ render_field(form.submit) }}
</form>
{% endblock %}

View File

@ -5,7 +5,12 @@
{%- endblock -%}
{% block content %}
<h1>{{ _('Notifications') }}</h1>
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{{ url_for('profile') }}">{{ _('Profile') }}</a></li>
<li class="breadcrumb-item active" aria-current="page">{{ _('Notifications') }}</li>
</ol>
</nav>
<form action="" method="POST">
{{ form.hidden_tag() }}

View File

@ -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 <EMAIL@ADDRESS>\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 "

View File

@ -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 <EMAIL@ADDRESS>\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 ""

View File

@ -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()

View File

@ -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}

View File

@ -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,
)

View File

@ -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,
)

View File

@ -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,

View File

@ -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/<int:event_suggestion_id>/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,
)

View File

@ -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)

View File

@ -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,
)

View File

@ -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,
)

View File

@ -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

View File

@ -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,
)

View File

@ -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,
)

View File

@ -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,
)

View File

@ -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

View File

@ -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()

View File

@ -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]

View File

@ -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)

View File

@ -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):

View File

@ -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,
)