diff --git a/.coveragerc b/.coveragerc index 166c774..4a530cf 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,6 +1,7 @@ [run] omit = project/celery.py + project/base_tasks.py project/celery_tasks.py project/cli/test.py project/templates/email/* \ No newline at end of file diff --git a/README.md b/README.md index e72a3b7..090ee8f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Open Event Database +# EventCally - Open event calendar ![Tests](https://github.com/eventcally/eventcally/workflows/Tests/badge.svg) [![codecov](https://codecov.io/gh/eventcally/eventcally/branch/main/graph/badge.svg?token=66CLLWWV7Y)](https://codecov.io/gh/eventcally/eventcally) [![Cypress](https://img.shields.io/endpoint?url=https://dashboard.cypress.io/badge/simple/32g194/main&style=flat&logo=cypress)](https://dashboard.cypress.io/projects/32g194/runs) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![Docker Pulls](https://img.shields.io/docker/pulls/eventcally/eventcally)](https://hub.docker.com/r/eventcally/eventcally) diff --git a/deployment/docker-compose/docker-compose.yml b/deployment/docker-compose/docker-compose.yml index ce20ce9..12deda4 100644 --- a/deployment/docker-compose/docker-compose.yml +++ b/deployment/docker-compose/docker-compose.yml @@ -24,6 +24,7 @@ x-web-env: JWT_PRIVATE_KEY: ${JWT_PRIVATE_KEY} JWT_PUBLIC_JWKS: ${JWT_PUBLIC_JWKS} DOCS_URL: ${DOCS_URL} + SITE_NAME: ${SITE_NAME} x-web: &default-web diff --git a/messages.pot b/messages.pot index 982956a..3306229 100644 --- a/messages.pot +++ b/messages.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2023-03-16 19:11+0100\n" +"POT-Creation-Date: 2023-03-24 21:24+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -198,93 +198,101 @@ msgstr "" msgid "You have received an invitation" msgstr "" -#: project/forms/admin.py:10 project/templates/layout.html:294 -#: project/views/root.py:57 +#: project/forms/admin.py:11 project/templates/layout.html:294 +#: project/views/root.py:53 msgid "Terms of service" msgstr "" -#: project/forms/admin.py:11 project/templates/layout.html:298 -#: project/views/root.py:65 +#: project/forms/admin.py:12 project/templates/layout.html:298 +#: project/views/root.py:61 msgid "Legal notice" msgstr "" -#: project/forms/admin.py:12 project/templates/_macros.html:1395 +#: project/forms/admin.py:13 project/templates/_macros.html:1395 #: project/templates/layout.html:302 #: project/templates/widget/event_suggestion/create.html:204 -#: project/views/admin_unit.py:73 project/views/root.py:73 +#: project/views/admin_unit.py:73 project/views/root.py:69 msgid "Contact" msgstr "" -#: project/forms/admin.py:13 project/templates/layout.html:306 -#: project/views/root.py:81 +#: project/forms/admin.py:14 project/templates/layout.html:306 +#: project/views/root.py:77 msgid "Privacy" msgstr "" -#: project/forms/admin.py:14 +#: project/forms/admin.py:15 msgid "Start page" msgstr "" -#: project/forms/admin.py:16 project/forms/oauth2_client.py:24 +#: project/forms/admin.py:17 project/forms/oauth2_client.py:24 msgid "Save" msgstr "" -#: project/forms/admin.py:20 project/forms/admin_unit_member.py:12 +#: project/forms/admin.py:21 project/forms/admin_unit_member.py:12 #: project/forms/admin_unit_member.py:32 msgid "Roles" msgstr "" -#: project/forms/admin.py:21 project/templates/admin/update_user.html:4 +#: project/forms/admin.py:22 project/templates/admin/update_user.html:4 #: project/templates/admin/update_user.html:8 msgid "Update user" msgstr "" -#: project/forms/admin.py:26 +#: project/forms/admin.py:27 msgid "Incoming reference requests allowed" msgstr "" -#: project/forms/admin.py:27 +#: project/forms/admin.py:28 msgid "" "If set, other organizations can ask this organization to reference their " "event." msgstr "" -#: project/forms/admin.py:33 +#: project/forms/admin.py:34 msgid "Suggestions enabled" msgstr "" -#: project/forms/admin.py:34 +#: project/forms/admin.py:35 msgid "If set, the organization can work with suggestions." msgstr "" -#: project/forms/admin.py:38 +#: project/forms/admin.py:39 msgid "Create other organizations" msgstr "" -#: project/forms/admin.py:39 +#: project/forms/admin.py:40 msgid "If set, members of the organization can create other organizations." msgstr "" -#: project/forms/admin.py:45 +#: project/forms/admin.py:46 msgid "Invite other organizations" msgstr "" -#: project/forms/admin.py:46 +#: project/forms/admin.py:47 msgid "If set, members of the organization can invite other organizations." msgstr "" -#: project/forms/admin.py:52 +#: project/forms/admin.py:53 msgid "Verify other organizations" msgstr "" -#: project/forms/admin.py:53 +#: project/forms/admin.py:54 msgid "If set, members of the organization can verify other organizations." msgstr "" -#: project/forms/admin.py:58 project/templates/admin/update_admin_unit.html:4 +#: project/forms/admin.py:59 project/templates/admin/update_admin_unit.html:4 #: project/templates/admin/update_admin_unit.html:8 msgid "Update organization" msgstr "" +#: project/forms/admin.py:63 +msgid "Recipient" +msgstr "" + +#: project/forms/admin.py:65 +msgid "Send test mail synchronously" +msgstr "" + #: project/forms/admin_unit.py:15 project/forms/event_place.py:12 #: project/forms/organizer.py:12 msgid "Street" @@ -348,7 +356,8 @@ msgstr "" #: project/forms/admin_unit_member.py:23 project/forms/admin_unit_member.py:28 #: project/forms/event.py:107 project/forms/event_suggestion.py:38 #: project/forms/organizer.py:27 project/templates/_macros.html:235 -#: project/templates/_macros.html:1491 project/templates/admin/users.html:19 +#: project/templates/_macros.html:1491 project/templates/admin/admin.html:27 +#: project/templates/admin/users.html:19 msgid "Email" msgstr "" @@ -1365,15 +1374,10 @@ msgstr "" msgid "Enter list name" msgstr "" -#: project/templates/home.html:27 +#: project/templates/home.html:24 msgid "Manage" msgstr "" -#: project/templates/home.html:29 project/templates/security/login_user.html:38 -#: project/views/widget.py:159 -msgid "Register for free" -msgstr "" - #: project/templates/layout.html:152 project/templates/layout.html:200 #: project/templates/manage/events.html:6 #: project/templates/manage/events.html:42 @@ -1409,6 +1413,8 @@ msgstr "" #: project/templates/admin/admin.html:3 project/templates/admin/admin.html:9 #: project/templates/admin/admin_units.html:10 +#: project/templates/admin/email.html:65 +#: project/templates/admin/settings.html:10 #: project/templates/admin/users.html:10 project/templates/layout.html:171 msgid "Admin" msgstr "" @@ -1495,9 +1501,10 @@ msgstr "" msgid "Organization invitations" msgstr "" -#: project/templates/admin/admin.html:15 +#: project/templates/admin/admin.html:15 project/templates/admin/email.html:4 +#: project/templates/admin/email.html:66 #: project/templates/admin/settings.html:4 -#: project/templates/admin/settings.html:8 +#: project/templates/admin/settings.html:11 #: project/templates/admin_unit/update.html:6 #: project/templates/admin_unit/update.html:23 #: project/templates/layout.html:253 project/templates/manage/widgets.html:11 @@ -1553,6 +1560,18 @@ msgstr "" msgid "Edit" msgstr "" +#: project/templates/admin/email.html:47 project/views/admin.py:119 +msgid "Mail sent successfully" +msgstr "" + +#: project/templates/admin/email.html:103 +msgid "Test mail" +msgstr "" + +#: project/templates/admin/email.html:111 +msgid "Send test mail asynchronously" +msgstr "" + #: project/templates/admin_unit/create.html:58 #: project/templates/admin_unit/update.html:59 #: project/templates/event/create.html:347 @@ -1665,6 +1684,14 @@ msgstr "" msgid "The review status of your event has been updated." msgstr "" +#: project/templates/email/test_email.html:4 +msgid "This is a test mail" +msgstr "" + +#: project/templates/email/test_email.html:5 +msgid "Click here to open the site" +msgstr "" + #: project/templates/event/actions.html:5 #: project/templates/event/actions.html:22 msgid "Actions for event" @@ -1984,6 +2011,10 @@ msgstr "" msgid "You do not have an account yet? Not a problem!" msgstr "" +#: project/templates/security/login_user.html:38 project/views/widget.py:159 +msgid "Register for free" +msgstr "" + #: project/templates/widget/event_date/list.html:5 msgid "Widget" msgstr "" @@ -2004,15 +2035,20 @@ msgstr "" msgid "Preview" msgstr "" -#: project/views/admin.py:44 +#: project/views/admin.py:55 msgid "Organization successfully updated" msgstr "" -#: project/views/admin.py:68 project/views/manage.py:361 +#: project/views/admin.py:79 project/views/manage.py:361 msgid "Settings successfully updated" msgstr "" -#: project/views/admin.py:103 +#: project/views/admin.py:108 +#, python-format +msgid "Test mail from %(site_name)s" +msgstr "" + +#: project/views/admin.py:152 msgid "User successfully updated" msgstr "" diff --git a/project/__init__.py b/project/__init__.py index 503c8d4..a4ac6de 100644 --- a/project/__init__.py +++ b/project/__init__.py @@ -16,10 +16,18 @@ from flask_wtf.csrf import CSRFProtect from project.custom_session_interface import CustomSessionInterface -def getenv_bool(name: str, default: str = "False"): # pragma: no cover +def getenv_bool(name: str, default: str = "False"): return os.getenv(name, default).lower() in ("true", "1", "t") +def set_env_to_app(app: Flask, key: str, default: str = None): + if key in os.environ and os.environ[key]: # pragma: no cover + app.config[key] = os.environ[key] + + if default: + app.config[key] = default + + # Create app app = Flask(__name__) app.config["SQLALCHEMY_DATABASE_URI"] = os.environ["DATABASE_URL"] @@ -34,7 +42,6 @@ app.config["SECURITY_RECOVERABLE"] = True app.config["SECURITY_CHANGEABLE"] = True app.config["SECURITY_EMAIL_SENDER"] = os.getenv("MAIL_DEFAULT_SENDER") app.config["LANGUAGES"] = ["en", "de"] -app.config["SITE_NAME"] = os.getenv("SITE_NAME", "eventcally") app.config["SERVER_NAME"] = os.getenv("SERVER_NAME") app.config["DOCS_URL"] = os.getenv("DOCS_URL") app.config["ADMIN_UNIT_CREATE_REQUIRES_ADMIN"] = os.getenv( @@ -42,6 +49,7 @@ app.config["ADMIN_UNIT_CREATE_REQUIRES_ADMIN"] = os.getenv( ) app.config["SEO_SITEMAP_PING_GOOGLE"] = getenv_bool("SEO_SITEMAP_PING_GOOGLE", "False") app.config["GOOGLE_MAPS_API_KEY"] = os.getenv("GOOGLE_MAPS_API_KEY") +set_env_to_app(app, "SITE_NAME", "EventCally") # Proxy handling if os.getenv("PREFERRED_URL_SCHEME"): # pragma: no cover @@ -58,11 +66,8 @@ app.config.update( "broker_url": app.config["REDIS_URL"], "result_backend": app.config["REDIS_URL"], "result_expires": timedelta(hours=1), - "broker_pool_limit": None, - "redis_max_connections": 2, "timezone": "Europe/Berlin", "broker_transport_options": { - "max_connections": 2, "queue_order_strategy": "priority", "priority_steps": list(range(3)), "sep": ":", diff --git a/project/base_tasks.py b/project/base_tasks.py new file mode 100644 index 0000000..f29caf7 --- /dev/null +++ b/project/base_tasks.py @@ -0,0 +1,10 @@ +from project import app, celery +from project.views.utils import send_mail + + +@celery.task( + base=getattr(app, "celery_http_task_cls"), + priority=0, +) +def send_mail_task(recipient, subject, template): + send_mail(recipient, subject, template) diff --git a/project/celery.py b/project/celery.py index 980fa30..e4b6a4a 100644 --- a/project/celery.py +++ b/project/celery.py @@ -2,13 +2,8 @@ from smtplib import SMTPException from urllib.error import URLError from celery import Celery -from celery.signals import ( - after_setup_logger, - after_setup_task_logger, - task_postrun, - worker_ready, -) -from celery_singleton import Singleton, clear_locks +from celery import Task as BaseTask +from celery.signals import after_setup_logger, after_setup_task_logger, task_postrun from requests.exceptions import RequestException @@ -19,7 +14,7 @@ class HttpTaskException(Exception): def create_celery(app): celery = Celery(app.import_name) celery.conf.update(app.config["CELERY_CONFIG"]) - TaskBase = Singleton + TaskBase = BaseTask class ContextTask(TaskBase): abstract = True @@ -71,13 +66,6 @@ def setup_task_logger(logger, *args, **kwargs): init_logger_with_one_line_formatter(logger) -@worker_ready.connect -def unlock_all(**kwargs): - from project import celery - - clear_locks(celery) - - @task_postrun.connect def close_session(*args, **kwargs): from project import app diff --git a/project/forms/admin.py b/project/forms/admin.py index 4898be8..e7251f3 100644 --- a/project/forms/admin.py +++ b/project/forms/admin.py @@ -1,7 +1,8 @@ from flask_babelex import lazy_gettext from flask_wtf import FlaskForm from wtforms import BooleanField, SubmitField, TextAreaField -from wtforms.validators import Optional +from wtforms.fields.html5 import EmailField +from wtforms.validators import DataRequired, Optional from project.forms.widgets import MultiCheckboxField @@ -56,3 +57,9 @@ class UpdateAdminUnitForm(FlaskForm): validators=[Optional()], ) submit = SubmitField(lazy_gettext("Update organization")) + + +class AdminTestEmailForm(FlaskForm): + recipient = EmailField(lazy_gettext("Recipient"), validators=[DataRequired()]) + + submit = SubmitField(lazy_gettext("Send test mail synchronously")) diff --git a/project/jinja_filters.py b/project/jinja_filters.py index 5e61af1..484e70f 100644 --- a/project/jinja_filters.py +++ b/project/jinja_filters.py @@ -26,6 +26,9 @@ def any_dict_value_true(data: dict): def ensure_link_scheme(link: str): + if not link: # pragma: no cover + return link + if link.startswith("http://") or link.startswith("https://"): return link diff --git a/project/templates/admin/admin.html b/project/templates/admin/admin.html index 8e096a5..024b0e1 100644 --- a/project/templates/admin/admin.html +++ b/project/templates/admin/admin.html @@ -23,6 +23,10 @@ {{ _('Users') }} + + {{ _('Email') }} + + {% endblock %} \ No newline at end of file diff --git a/project/templates/admin/email.html b/project/templates/admin/email.html new file mode 100644 index 0000000..88ad55c --- /dev/null +++ b/project/templates/admin/email.html @@ -0,0 +1,124 @@ +{% extends "layout.html" %} +{% from "_macros.html" import render_field, render_field_with_errors %} +{%- block title -%} +{{ _('Settings') }} +{%- endblock -%} +{% block header %} + +{% endblock %} +{% block content %} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MAIL_SUPPRESS_SEND{{ config["MAIL_SUPPRESS_SEND"] }}
MAIL_SERVER{{ config["MAIL_SERVER"] }}
MAIL_PORT{{ config["MAIL_PORT"] }}
MAIL_USE_TLS{{ config["MAIL_USE_TLS"] }}
MAIL_USE_SSL{{ config["MAIL_USE_SSL"] }}
MAIL_USERNAME{{ config["MAIL_USERNAME"] }}
MAIL_PASSWORD{{ config["MAIL_PASSWORD"] }}
+ +

{{ _('Test mail')}}

+
+ {{ form.hidden_tag() }} + {{ render_field_with_errors(form.recipient) }} + {{ render_field(form.submit) }} +
+ +

+ +

+
+
+ + +
+

+ + + +{% endblock %} \ No newline at end of file diff --git a/project/templates/admin/settings.html b/project/templates/admin/settings.html index f06f06b..00887e4 100644 --- a/project/templates/admin/settings.html +++ b/project/templates/admin/settings.html @@ -5,7 +5,12 @@ {%- endblock -%} {% block content %} -

{{ _('Settings') }}

+
{{ form.hidden_tag() }} diff --git a/project/templates/email/test_email.html b/project/templates/email/test_email.html new file mode 100644 index 0000000..e8a6850 --- /dev/null +++ b/project/templates/email/test_email.html @@ -0,0 +1,6 @@ +{% extends "email/layout.html" %} +{% from "_macros.html" import render_email_button %} +{% block content %} +

{{ _('This is a test mail') }}

+{{ render_email_button(url_for('home', _external=True), _('Click here to open the site')) }} +{% endblock %} \ No newline at end of file diff --git a/project/templates/email/test_email.txt b/project/templates/email/test_email.txt new file mode 100644 index 0000000..8383563 --- /dev/null +++ b/project/templates/email/test_email.txt @@ -0,0 +1,3 @@ +{{ _('This is a test mail') }} +{{ _('Click the link below to open the site') }} +{{ url_for('home', _external=True) }} diff --git a/project/translations/de/LC_MESSAGES/messages.mo b/project/translations/de/LC_MESSAGES/messages.mo index 790319e..53cabf6 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 298f3e4..045c90c 100644 --- a/project/translations/de/LC_MESSAGES/messages.po +++ b/project/translations/de/LC_MESSAGES/messages.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2023-03-16 19:11+0100\n" +"POT-Creation-Date: 2023-03-24 21:24+0100\n" "PO-Revision-Date: 2020-06-07 18:51+0200\n" "Last-Translator: FULL NAME \n" "Language: de\n" @@ -199,51 +199,51 @@ msgstr "message" msgid "You have received an invitation" msgstr "Du hast eine Einladung erhalten" -#: project/forms/admin.py:10 project/templates/layout.html:294 -#: project/views/root.py:57 +#: project/forms/admin.py:11 project/templates/layout.html:294 +#: project/views/root.py:53 msgid "Terms of service" msgstr "Nutzungsbedingungen" -#: project/forms/admin.py:11 project/templates/layout.html:298 -#: project/views/root.py:65 +#: project/forms/admin.py:12 project/templates/layout.html:298 +#: project/views/root.py:61 msgid "Legal notice" msgstr "Impressum" -#: project/forms/admin.py:12 project/templates/_macros.html:1395 +#: project/forms/admin.py:13 project/templates/_macros.html:1395 #: project/templates/layout.html:302 #: project/templates/widget/event_suggestion/create.html:204 -#: project/views/admin_unit.py:73 project/views/root.py:73 +#: project/views/admin_unit.py:73 project/views/root.py:69 msgid "Contact" msgstr "Kontakt" -#: project/forms/admin.py:13 project/templates/layout.html:306 -#: project/views/root.py:81 +#: project/forms/admin.py:14 project/templates/layout.html:306 +#: project/views/root.py:77 msgid "Privacy" msgstr "Datenschutz" -#: project/forms/admin.py:14 +#: project/forms/admin.py:15 msgid "Start page" msgstr "Startseite" -#: project/forms/admin.py:16 project/forms/oauth2_client.py:24 +#: project/forms/admin.py:17 project/forms/oauth2_client.py:24 msgid "Save" msgstr "Speichern" -#: project/forms/admin.py:20 project/forms/admin_unit_member.py:12 +#: project/forms/admin.py:21 project/forms/admin_unit_member.py:12 #: project/forms/admin_unit_member.py:32 msgid "Roles" msgstr "Rollen" -#: project/forms/admin.py:21 project/templates/admin/update_user.html:4 +#: project/forms/admin.py:22 project/templates/admin/update_user.html:4 #: project/templates/admin/update_user.html:8 msgid "Update user" msgstr "Nutzer aktualisieren" -#: project/forms/admin.py:26 +#: project/forms/admin.py:27 msgid "Incoming reference requests allowed" msgstr "Eingehende Empfehlungsanfragen erlauben" -#: project/forms/admin.py:27 +#: project/forms/admin.py:28 msgid "" "If set, other organizations can ask this organization to reference their " "event." @@ -251,49 +251,57 @@ msgstr "" "Wenn gesetzt, können andere Organisationen diese Organisation bitten, " "deren Veranstaltungen zu empfehlen." -#: project/forms/admin.py:33 +#: project/forms/admin.py:34 msgid "Suggestions enabled" msgstr "Vorschläge aktiv" -#: project/forms/admin.py:34 +#: project/forms/admin.py:35 msgid "If set, the organization can work with suggestions." msgstr "Wenn gesetzt, kann die Organisation mit Vorschlägen arbeiten." -#: project/forms/admin.py:38 +#: project/forms/admin.py:39 msgid "Create other organizations" msgstr "Andere Organisationen erstellen" -#: project/forms/admin.py:39 +#: project/forms/admin.py:40 msgid "If set, members of the organization can create other organizations." msgstr "" "Wenn gesetzt, können Mitglieder der Organisation andere Organisationen " "erstellen." -#: project/forms/admin.py:45 +#: project/forms/admin.py:46 msgid "Invite other organizations" msgstr "Andere Organisationen einladen" -#: project/forms/admin.py:46 +#: project/forms/admin.py:47 msgid "If set, members of the organization can invite other organizations." msgstr "" "Wenn gesetzt, können Mitglieder der Organisation andere Organisationen " "einladen." -#: project/forms/admin.py:52 +#: project/forms/admin.py:53 msgid "Verify other organizations" msgstr "Andere Organisationen verifizieren" -#: project/forms/admin.py:53 +#: project/forms/admin.py:54 msgid "If set, members of the organization can verify other organizations." msgstr "" "Wenn gesetzt, können Mitglieder der Organisation andere Organisationen " "verifizieren." -#: project/forms/admin.py:58 project/templates/admin/update_admin_unit.html:4 +#: project/forms/admin.py:59 project/templates/admin/update_admin_unit.html:4 #: project/templates/admin/update_admin_unit.html:8 msgid "Update organization" msgstr "Organisation aktualisieren" +#: project/forms/admin.py:63 +msgid "Recipient" +msgstr "Empfänger" + +#: project/forms/admin.py:65 +msgid "Send test mail synchronously" +msgstr "Test-Mail synchron senden" + #: project/forms/admin_unit.py:15 project/forms/event_place.py:12 #: project/forms/organizer.py:12 msgid "Street" @@ -360,7 +368,8 @@ msgstr "Link URL" #: project/forms/admin_unit_member.py:23 project/forms/admin_unit_member.py:28 #: project/forms/event.py:107 project/forms/event_suggestion.py:38 #: project/forms/organizer.py:27 project/templates/_macros.html:235 -#: project/templates/_macros.html:1491 project/templates/admin/users.html:19 +#: project/templates/_macros.html:1491 project/templates/admin/admin.html:27 +#: project/templates/admin/users.html:19 msgid "Email" msgstr "Email" @@ -1415,15 +1424,10 @@ msgstr "Veranstalter eingeben" msgid "Enter list name" msgstr "Listenname eingeben" -#: project/templates/home.html:27 +#: project/templates/home.html:24 msgid "Manage" msgstr "Verwaltung" -#: project/templates/home.html:29 project/templates/security/login_user.html:38 -#: project/views/widget.py:159 -msgid "Register for free" -msgstr "Kostenlos registrieren" - #: project/templates/layout.html:152 project/templates/layout.html:200 #: project/templates/manage/events.html:6 #: project/templates/manage/events.html:42 @@ -1459,6 +1463,8 @@ msgstr "Profil" #: project/templates/admin/admin.html:3 project/templates/admin/admin.html:9 #: project/templates/admin/admin_units.html:10 +#: project/templates/admin/email.html:65 +#: project/templates/admin/settings.html:10 #: project/templates/admin/users.html:10 project/templates/layout.html:171 msgid "Admin" msgstr "Administration" @@ -1545,9 +1551,10 @@ msgstr "Beziehungen" msgid "Organization invitations" msgstr "Organisationseinladungen" -#: project/templates/admin/admin.html:15 +#: project/templates/admin/admin.html:15 project/templates/admin/email.html:4 +#: project/templates/admin/email.html:66 #: project/templates/admin/settings.html:4 -#: project/templates/admin/settings.html:8 +#: project/templates/admin/settings.html:11 #: project/templates/admin_unit/update.html:6 #: project/templates/admin_unit/update.html:23 #: project/templates/layout.html:253 project/templates/manage/widgets.html:11 @@ -1603,6 +1610,18 @@ msgstr "Benutzer" msgid "Edit" msgstr "Bearbeiten" +#: project/templates/admin/email.html:47 project/views/admin.py:119 +msgid "Mail sent successfully" +msgstr "Mail erfolgreich gesendet" + +#: project/templates/admin/email.html:103 +msgid "Test mail" +msgstr "Test-Mail" + +#: project/templates/admin/email.html:111 +msgid "Send test mail asynchronously" +msgstr "Test-Mail asynchron senden" + #: project/templates/admin_unit/create.html:58 #: project/templates/admin_unit/update.html:59 #: project/templates/event/create.html:347 @@ -1717,6 +1736,14 @@ msgstr "Klicke hier, um den Vorschlag zu prüfen" msgid "The review status of your event has been updated." msgstr "Der Prüfungsstatus deiner Veranstaltung wurde aktualisiert" +#: project/templates/email/test_email.html:4 +msgid "This is a test mail" +msgstr "Das ist eine Test-Mail" + +#: project/templates/email/test_email.html:5 +msgid "Click here to open the site" +msgstr "Klicke hier, um die Seite zu öffnen" + #: project/templates/event/actions.html:5 #: project/templates/event/actions.html:22 msgid "Actions for event" @@ -2045,6 +2072,10 @@ 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:38 project/views/widget.py:159 +msgid "Register for free" +msgstr "Kostenlos registrieren" + #: project/templates/widget/event_date/list.html:5 msgid "Widget" msgstr "Widget" @@ -2065,15 +2096,20 @@ msgstr "Optionale Details" msgid "Preview" msgstr "Vorschau" -#: project/views/admin.py:44 +#: project/views/admin.py:55 msgid "Organization successfully updated" msgstr "Organisation erfolgreich aktualisiert" -#: project/views/admin.py:68 project/views/manage.py:361 +#: project/views/admin.py:79 project/views/manage.py:361 msgid "Settings successfully updated" msgstr "Einstellungen erfolgreich aktualisiert" -#: project/views/admin.py:103 +#: project/views/admin.py:108 +#, python-format +msgid "Test mail from %(site_name)s" +msgstr "Test-Mail from %(site_name)s" + +#: project/views/admin.py:152 msgid "User successfully updated" msgstr "Nutzer erfolgreich aktualisiert" diff --git a/project/translations/en/LC_MESSAGES/messages.mo b/project/translations/en/LC_MESSAGES/messages.mo index 2657414..f1fcd1d 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 b62c284..e38fe4b 100644 --- a/project/translations/en/LC_MESSAGES/messages.po +++ b/project/translations/en/LC_MESSAGES/messages.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2023-03-16 19:11+0100\n" +"POT-Creation-Date: 2023-03-24 21:24+0100\n" "PO-Revision-Date: 2021-04-30 15:04+0200\n" "Last-Translator: FULL NAME \n" "Language: en\n" @@ -199,93 +199,101 @@ msgstr "" msgid "You have received an invitation" msgstr "" -#: project/forms/admin.py:10 project/templates/layout.html:294 -#: project/views/root.py:57 +#: project/forms/admin.py:11 project/templates/layout.html:294 +#: project/views/root.py:53 msgid "Terms of service" msgstr "" -#: project/forms/admin.py:11 project/templates/layout.html:298 -#: project/views/root.py:65 +#: project/forms/admin.py:12 project/templates/layout.html:298 +#: project/views/root.py:61 msgid "Legal notice" msgstr "" -#: project/forms/admin.py:12 project/templates/_macros.html:1395 +#: project/forms/admin.py:13 project/templates/_macros.html:1395 #: project/templates/layout.html:302 #: project/templates/widget/event_suggestion/create.html:204 -#: project/views/admin_unit.py:73 project/views/root.py:73 +#: project/views/admin_unit.py:73 project/views/root.py:69 msgid "Contact" msgstr "" -#: project/forms/admin.py:13 project/templates/layout.html:306 -#: project/views/root.py:81 +#: project/forms/admin.py:14 project/templates/layout.html:306 +#: project/views/root.py:77 msgid "Privacy" msgstr "" -#: project/forms/admin.py:14 +#: project/forms/admin.py:15 msgid "Start page" msgstr "" -#: project/forms/admin.py:16 project/forms/oauth2_client.py:24 +#: project/forms/admin.py:17 project/forms/oauth2_client.py:24 msgid "Save" msgstr "" -#: project/forms/admin.py:20 project/forms/admin_unit_member.py:12 +#: project/forms/admin.py:21 project/forms/admin_unit_member.py:12 #: project/forms/admin_unit_member.py:32 msgid "Roles" msgstr "" -#: project/forms/admin.py:21 project/templates/admin/update_user.html:4 +#: project/forms/admin.py:22 project/templates/admin/update_user.html:4 #: project/templates/admin/update_user.html:8 msgid "Update user" msgstr "" -#: project/forms/admin.py:26 +#: project/forms/admin.py:27 msgid "Incoming reference requests allowed" msgstr "" -#: project/forms/admin.py:27 +#: project/forms/admin.py:28 msgid "" "If set, other organizations can ask this organization to reference their " "event." msgstr "" -#: project/forms/admin.py:33 +#: project/forms/admin.py:34 msgid "Suggestions enabled" msgstr "" -#: project/forms/admin.py:34 +#: project/forms/admin.py:35 msgid "If set, the organization can work with suggestions." msgstr "" -#: project/forms/admin.py:38 +#: project/forms/admin.py:39 msgid "Create other organizations" msgstr "" -#: project/forms/admin.py:39 +#: project/forms/admin.py:40 msgid "If set, members of the organization can create other organizations." msgstr "" -#: project/forms/admin.py:45 +#: project/forms/admin.py:46 msgid "Invite other organizations" msgstr "" -#: project/forms/admin.py:46 +#: project/forms/admin.py:47 msgid "If set, members of the organization can invite other organizations." msgstr "" -#: project/forms/admin.py:52 +#: project/forms/admin.py:53 msgid "Verify other organizations" msgstr "" -#: project/forms/admin.py:53 +#: project/forms/admin.py:54 msgid "If set, members of the organization can verify other organizations." msgstr "" -#: project/forms/admin.py:58 project/templates/admin/update_admin_unit.html:4 +#: project/forms/admin.py:59 project/templates/admin/update_admin_unit.html:4 #: project/templates/admin/update_admin_unit.html:8 msgid "Update organization" msgstr "" +#: project/forms/admin.py:63 +msgid "Recipient" +msgstr "" + +#: project/forms/admin.py:65 +msgid "Send test mail synchronously" +msgstr "" + #: project/forms/admin_unit.py:15 project/forms/event_place.py:12 #: project/forms/organizer.py:12 msgid "Street" @@ -349,7 +357,8 @@ msgstr "" #: project/forms/admin_unit_member.py:23 project/forms/admin_unit_member.py:28 #: project/forms/event.py:107 project/forms/event_suggestion.py:38 #: project/forms/organizer.py:27 project/templates/_macros.html:235 -#: project/templates/_macros.html:1491 project/templates/admin/users.html:19 +#: project/templates/_macros.html:1491 project/templates/admin/admin.html:27 +#: project/templates/admin/users.html:19 msgid "Email" msgstr "" @@ -1366,15 +1375,10 @@ msgstr "" msgid "Enter list name" msgstr "" -#: project/templates/home.html:27 +#: project/templates/home.html:24 msgid "Manage" msgstr "" -#: project/templates/home.html:29 project/templates/security/login_user.html:38 -#: project/views/widget.py:159 -msgid "Register for free" -msgstr "" - #: project/templates/layout.html:152 project/templates/layout.html:200 #: project/templates/manage/events.html:6 #: project/templates/manage/events.html:42 @@ -1410,6 +1414,8 @@ msgstr "" #: project/templates/admin/admin.html:3 project/templates/admin/admin.html:9 #: project/templates/admin/admin_units.html:10 +#: project/templates/admin/email.html:65 +#: project/templates/admin/settings.html:10 #: project/templates/admin/users.html:10 project/templates/layout.html:171 msgid "Admin" msgstr "" @@ -1496,9 +1502,10 @@ msgstr "" msgid "Organization invitations" msgstr "" -#: project/templates/admin/admin.html:15 +#: project/templates/admin/admin.html:15 project/templates/admin/email.html:4 +#: project/templates/admin/email.html:66 #: project/templates/admin/settings.html:4 -#: project/templates/admin/settings.html:8 +#: project/templates/admin/settings.html:11 #: project/templates/admin_unit/update.html:6 #: project/templates/admin_unit/update.html:23 #: project/templates/layout.html:253 project/templates/manage/widgets.html:11 @@ -1554,6 +1561,18 @@ msgstr "" msgid "Edit" msgstr "" +#: project/templates/admin/email.html:47 project/views/admin.py:119 +msgid "Mail sent successfully" +msgstr "" + +#: project/templates/admin/email.html:103 +msgid "Test mail" +msgstr "" + +#: project/templates/admin/email.html:111 +msgid "Send test mail asynchronously" +msgstr "" + #: project/templates/admin_unit/create.html:58 #: project/templates/admin_unit/update.html:59 #: project/templates/event/create.html:347 @@ -1666,6 +1685,14 @@ msgstr "" msgid "The review status of your event has been updated." msgstr "" +#: project/templates/email/test_email.html:4 +msgid "This is a test mail" +msgstr "" + +#: project/templates/email/test_email.html:5 +msgid "Click here to open the site" +msgstr "" + #: project/templates/event/actions.html:5 #: project/templates/event/actions.html:22 msgid "Actions for event" @@ -1985,6 +2012,10 @@ msgstr "" msgid "You do not have an account yet? Not a problem!" msgstr "" +#: project/templates/security/login_user.html:38 project/views/widget.py:159 +msgid "Register for free" +msgstr "" + #: project/templates/widget/event_date/list.html:5 msgid "Widget" msgstr "" @@ -2005,15 +2036,20 @@ msgstr "" msgid "Preview" msgstr "" -#: project/views/admin.py:44 +#: project/views/admin.py:55 msgid "Organization successfully updated" msgstr "" -#: project/views/admin.py:68 project/views/manage.py:361 +#: project/views/admin.py:79 project/views/manage.py:361 msgid "Settings successfully updated" msgstr "" -#: project/views/admin.py:103 +#: project/views/admin.py:108 +#, python-format +msgid "Test mail from %(site_name)s" +msgstr "" + +#: project/views/admin.py:152 msgid "User successfully updated" msgstr "" diff --git a/project/views/admin.py b/project/views/admin.py index 5482d0f..b31ba16 100644 --- a/project/views/admin.py +++ b/project/views/admin.py @@ -1,15 +1,26 @@ -from flask import flash, redirect, render_template, url_for +from flask import flash, redirect, render_template, request, url_for from flask_babelex import gettext from flask_security import roles_required from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.sql import func -from project import app, db -from project.forms.admin import AdminSettingsForm, UpdateAdminUnitForm, UpdateUserForm +from project import app, celery, db +from project.base_tasks import send_mail_task +from project.forms.admin import ( + AdminSettingsForm, + AdminTestEmailForm, + UpdateAdminUnitForm, + UpdateUserForm, +) from project.models import AdminUnit, Role, User from project.services.admin import upsert_settings from project.services.user import set_roles_for_user -from project.views.utils import flash_errors, get_pagination_urls, handleSqlError +from project.views.utils import ( + flash_errors, + get_pagination_urls, + handleSqlError, + send_mail, +) @app.route("/admin") @@ -76,6 +87,44 @@ def admin_settings(): return render_template("admin/settings.html", form=form) +@app.route("/admin/email", methods=["GET", "POST"]) +@roles_required("admin") +def admin_email(): + form = AdminTestEmailForm() + + if "poll" in request.args: # pragma: no cover + try: + result = celery.AsyncResult(request.args["poll"]) + ready = result.ready() + return { + "ready": ready, + "successful": result.successful() if ready else None, + "value": result.get() if ready else result.result, + } + except Exception as e: + return {"ready": True, "successful": False, "error": str(e)} + + if form.validate_on_submit(): + subject = gettext( + "Test mail from %(site_name)s", + site_name=app.config["SITE_NAME"], + ) + + if "async" in request.args: # pragma: no cover + result = send_mail_task.delay(form.recipient.data, subject, "test_email") + return {"result_id": result.id} + + try: + send_mail(form.recipient.data, subject, "test_email") + flash(gettext("Mail sent successfully"), "success") + except Exception as e: # pragma: no cover + flash(str(e), "danger") + else: # pragma: no cover + flash_errors(form) + + return render_template("admin/email.html", form=form) + + @app.route("/admin/users") @roles_required("admin") def admin_users(): diff --git a/project/views/utils.py b/project/views/utils.py index a68a33d..746a515 100644 --- a/project/views/utils.py +++ b/project/views/utils.py @@ -27,7 +27,7 @@ def get_current_admin_unit(fallback=True): if admin_unit: return admin_unit - if current_user.is_authenticated: + if current_user and current_user.is_authenticated: admin_unit = get_current_admin_unit_from_cookies() if not admin_unit and fallback: @@ -44,7 +44,7 @@ def get_current_admin_unit(fallback=True): def get_current_admin_unit_from_cookies(): try: - if "manage_admin_unit_id" in request.cookies: + if request and request.cookies and "manage_admin_unit_id" in request.cookies: encoded = request.cookies.get("manage_admin_unit_id") manage_admin_unit_id = int(decode_cookie(encoded)) return get_admin_unit_for_manage(manage_admin_unit_id) diff --git a/requirements.txt b/requirements.txt index c21be42..4fdf330 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,7 +17,6 @@ black==23.1.0 blinker==1.4 cached-property==1.5.2 celery==5.2.7 -celery-singleton==0.3.1 certifi==2020.12.5 cffi==1.14.4 cfgv==3.2.0 @@ -121,7 +120,7 @@ TatSu==4.4.0 toml==0.10.2 tomli==2.0.1 typed-ast==1.5.4 -typing_extensions==4.5.0 +typing-extensions==4.5.0 urllib3==1.26.5 URLObject==2.4.3 validators==0.18.2 diff --git a/tests/views/test_admin.py b/tests/views/test_admin.py index cbcdf4e..058971d 100644 --- a/tests/views/test_admin.py +++ b/tests/views/test_admin.py @@ -60,6 +60,25 @@ def test_admin_settings(client, seeder, utils, app, mocker, db_error): assert settings.privacy == "Mein Datenschutz" +def test_admin_email(client, seeder, utils, app, mocker): + user_id, admin_unit_id = seeder.setup_base(True) + + url = utils.get_url("admin_email") + response = utils.get_ok(url) + + mail_mock = utils.mock_send_mails(mocker) + response = utils.post_form( + url, + response, + { + "recipient": "test@test.de", + }, + ) + + utils.assert_response_ok(response) + utils.assert_send_mail_called(mail_mock, "test@test.de") + + def test_admin_users(client, seeder, utils, app): seeder.create_user(admin=True) user = utils.login() diff --git a/tests/views/test_app.py b/tests/views/test_app.py index 888f824..0f5fc9e 100644 --- a/tests/views/test_app.py +++ b/tests/views/test_app.py @@ -1,3 +1,3 @@ def test_index(client): response = client.get("/") - assert b"eventcally" in response.data + assert b"EventCally" in response.data