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
 [](https://codecov.io/gh/eventcally/eventcally) [](https://dashboard.cypress.io/projects/32g194/runs) [](https://github.com/psf/black) [](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')}}
+
+
+
+
+
+
+
+
+
+{% 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') }}
+