From fb5a94a778b012f508ff6a05267d4aac6da059fd Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 16 Feb 2021 21:30:20 +1100 Subject: [PATCH 1/8] Support for email settings --- InvenTree/InvenTree/settings.py | 45 +++++++++++++++++++++++++++++++++ InvenTree/config_template.yaml | 23 +++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index 8b5400e374..fd57167dd4 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -495,6 +495,51 @@ CURRENCIES = CONFIG.get( # TODO - Allow live web-based backends in the future EXCHANGE_BACKEND = 'InvenTree.exchange.InvenTreeManualExchangeBackend' +# Extract email settings from the config file +email_config = CONFIG.get('email', {}) + +EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' + +# Email backend settings +EMAIL_HOST = get_setting( + 'INVENTREE_EMAIL_HOST', + email_config.get('host', '') +) + +EMAIL_PORT = get_setting( + 'INVENTREE_EMAIL_PORT', + email_config.get('port', 25) +) + +EMAIL_HOST_USER = get_setting( + 'INVENTREE_EMAIL_USERNAME', + email_config.get('username', ''), +) + +EMAIL_HOST_PASSWORD = get_setting( + 'INVENTREE_EMAIL_PASSWORD', + email_config.get('password', ''), +) + +EMAIL_SUBJECT_PREFIX = get_setting( + 'INVENTREE_EMAIL_PREFIX', + email_config.get('prefix', '[InvenTree] '), +) + +EMAIL_USE_LOCALTIME = False + +EMAIL_USE_TLS = get_setting( + 'INVENTREE_EMAIL_TLS', + email_config.get('tls', False), +) + +EMAIL_USE_SSL = get_setting( + 'INVENTREE_EMAIL_SSL', + email_config.get('ssl', False), +) + +EMAIL_TIMEOUT = 60 + LOCALE_PATHS = ( os.path.join(BASE_DIR, 'locale/'), ) diff --git a/InvenTree/config_template.yaml b/InvenTree/config_template.yaml index a64e6d42c0..506788a5f1 100644 --- a/InvenTree/config_template.yaml +++ b/InvenTree/config_template.yaml @@ -63,6 +63,29 @@ currencies: - NZD - USD +# Email backend configuration +# Ref: https://docs.djangoproject.com/en/dev/topics/email/ +# Available options: +# host: Email server host address +# port: Email port +# username: Account username +# password: Account password +# prefix: Email subject prefix +# tls: Enable TLS support +# ssl: Enable SSL support + +# Alternatively, these options can all be set using environment variables +# Refer to the InvenTree documentation for more information + +email: + host: '' + port: 25 + username: '' + password: '' + prefix: '[InvenTree] ' + tls: False + ssl: False + # Set debug to False to run in production mode # Use the environment variable INVENTREE_DEBUG debug: True From d243ff6b37d19d8dd656a3325794f49180ace656 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Mon, 12 Apr 2021 19:18:47 +1000 Subject: [PATCH 2/8] Offload email task to background worker --- InvenTree/InvenTree/tasks.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/InvenTree/InvenTree/tasks.py b/InvenTree/InvenTree/tasks.py index 4829514f19..3c27f0749d 100644 --- a/InvenTree/InvenTree/tasks.py +++ b/InvenTree/InvenTree/tasks.py @@ -51,6 +51,24 @@ def schedule_task(taskname, **kwargs): pass +def offload_task(taskname, *args, **kwargs): + """ + Create an AsyncTask. + This is different to a 'scheduled' task, + in that it only runs once! + """ + + try: + from django_q.tasks import AsyncTask + except (AppRegistryNotReady): + logger.warning("Could not offload task - app registry not ready") + return + + task = AsyncTask(taskname, *args, **kwargs) + + task.run() + + def heartbeat(): """ Simple task which runs at 5 minute intervals, @@ -141,3 +159,21 @@ def check_for_updates(): tag, None ) + + +def send_email(subject, body, recipients, from_email=None): + """ + Send an email with the specified subject and body, + to the specified recipients list. + """ + + if type(recipients) == str: + recipients = [recipients] + + offload_task( + 'django.core.mail.send_mail', + subject, body, + from_email, + recipients, + ) + From a9d490b7166a11a711173281dfa8e0ef7c9d0a33 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Mon, 12 Apr 2021 19:20:41 +1000 Subject: [PATCH 3/8] PEP fixes --- InvenTree/InvenTree/tasks.py | 1 - 1 file changed, 1 deletion(-) diff --git a/InvenTree/InvenTree/tasks.py b/InvenTree/InvenTree/tasks.py index 3c27f0749d..6c01e03aa6 100644 --- a/InvenTree/InvenTree/tasks.py +++ b/InvenTree/InvenTree/tasks.py @@ -176,4 +176,3 @@ def send_email(subject, body, recipients, from_email=None): from_email, recipients, ) - From 12a4c22a9b28b1924e396f265f443edfe13e1d72 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Mon, 12 Apr 2021 20:02:05 +1000 Subject: [PATCH 4/8] Password reset templates --- InvenTree/InvenTree/middleware.py | 4 ++ InvenTree/InvenTree/urls.py | 1 + InvenTree/templates/registration/login.html | 2 + .../registration/password_reset_complete.html | 59 ++++++++++++++++ .../registration/password_reset_confirm.html | 69 +++++++++++++++++++ .../registration/password_reset_done.html | 65 +++++++++++++++++ .../registration/password_reset_form.html | 68 ++++++++++++++++++ 7 files changed, 268 insertions(+) create mode 100644 InvenTree/templates/registration/password_reset_complete.html create mode 100644 InvenTree/templates/registration/password_reset_confirm.html create mode 100644 InvenTree/templates/registration/password_reset_done.html create mode 100644 InvenTree/templates/registration/password_reset_form.html diff --git a/InvenTree/InvenTree/middleware.py b/InvenTree/InvenTree/middleware.py index a34df4b7bd..f30d77ad3b 100644 --- a/InvenTree/InvenTree/middleware.py +++ b/InvenTree/InvenTree/middleware.py @@ -52,6 +52,10 @@ class AuthRequiredMiddleware(object): if request.path_info.startswith('/static/'): authorized = True + # Unauthorized users can access the login page + elif request.path_info.startswith('/accounts/'): + authorized = True + elif 'Authorization' in request.headers.keys(): auth = request.headers['Authorization'].strip() diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index 7c22639e65..28854e311d 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -143,6 +143,7 @@ urlpatterns = [ url(r'^admin/error_log/', include('error_report.urls')), url(r'^admin/shell/', include('django_admin_shell.urls')), url(r'^admin/', admin.site.urls, name='inventree-admin'), + url(r'accounts/', include('django.contrib.auth.urls')), url(r'^index/', IndexView.as_view(), name='index'), url(r'^search/', SearchView.as_view(), name='search'), diff --git a/InvenTree/templates/registration/login.html b/InvenTree/templates/registration/login.html index a3f7b91e35..746d3c86fa 100644 --- a/InvenTree/templates/registration/login.html +++ b/InvenTree/templates/registration/login.html @@ -89,6 +89,8 @@ +

+

{% trans "Forgotten your password?" %} - {% trans "Click here to reset" %}

diff --git a/InvenTree/templates/registration/password_reset_complete.html b/InvenTree/templates/registration/password_reset_complete.html new file mode 100644 index 0000000000..7fc83d85ba --- /dev/null +++ b/InvenTree/templates/registration/password_reset_complete.html @@ -0,0 +1,59 @@ +{% load static %} +{% load i18n %} +{% load crispy_forms_tags %} + + + + + + + + + + + + + + + + + + + + + + + + + InvenTree + + + + + + + + \ No newline at end of file diff --git a/InvenTree/templates/registration/password_reset_confirm.html b/InvenTree/templates/registration/password_reset_confirm.html new file mode 100644 index 0000000000..455baea698 --- /dev/null +++ b/InvenTree/templates/registration/password_reset_confirm.html @@ -0,0 +1,69 @@ +{% load static %} +{% load i18n %} +{% load crispy_forms_tags %} + + + + + + + + + + + + + + + + + + + + + + + + + InvenTree + + + + + + + + \ No newline at end of file diff --git a/InvenTree/templates/registration/password_reset_done.html b/InvenTree/templates/registration/password_reset_done.html new file mode 100644 index 0000000000..04e0aec69b --- /dev/null +++ b/InvenTree/templates/registration/password_reset_done.html @@ -0,0 +1,65 @@ +{% load static %} +{% load i18n %} +{% load crispy_forms_tags %} + + + + + + + + + + + + + + + + + + + + + + + + + InvenTree + + + + + + + + \ No newline at end of file diff --git a/InvenTree/templates/registration/password_reset_form.html b/InvenTree/templates/registration/password_reset_form.html new file mode 100644 index 0000000000..dabada5651 --- /dev/null +++ b/InvenTree/templates/registration/password_reset_form.html @@ -0,0 +1,68 @@ +{% load static %} +{% load i18n %} +{% load crispy_forms_tags %} + + + + + + + + + + + + + + + + + + + + + + + + + InvenTree + + + + + + + + \ No newline at end of file From f902b79d79cb43838ac31594dece0618a3ab88aa Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Mon, 12 Apr 2021 20:07:38 +1000 Subject: [PATCH 5/8] And more templates --- InvenTree/InvenTree/urls.py | 2 +- .../templates/registration/logged_out.html | 59 +++++++++++++++++++ InvenTree/templates/registration/logout.html | 8 --- 3 files changed, 60 insertions(+), 9 deletions(-) create mode 100644 InvenTree/templates/registration/logged_out.html delete mode 100644 InvenTree/templates/registration/logout.html diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index 28854e311d..88160e76c1 100644 --- a/InvenTree/InvenTree/urls.py +++ b/InvenTree/InvenTree/urls.py @@ -133,7 +133,7 @@ urlpatterns = [ url(r'^auth/', include('rest_framework.urls', namespace='rest_framework')), url(r'^login/?', auth_views.LoginView.as_view(), name='login'), - url(r'^logout/', auth_views.LogoutView.as_view(template_name='registration/logout.html'), name='logout'), + url(r'^logout/', auth_views.LogoutView.as_view(template_name='registration/logged_out.html'), name='logout'), url(r'^settings/', include(settings_urls)), diff --git a/InvenTree/templates/registration/logged_out.html b/InvenTree/templates/registration/logged_out.html new file mode 100644 index 0000000000..b23ba001e2 --- /dev/null +++ b/InvenTree/templates/registration/logged_out.html @@ -0,0 +1,59 @@ +{% load static %} +{% load i18n %} +{% load crispy_forms_tags %} + + + + + + + + + + + + + + + + + + + + + + + + + InvenTree + + + + + + + + \ No newline at end of file diff --git a/InvenTree/templates/registration/logout.html b/InvenTree/templates/registration/logout.html deleted file mode 100644 index 1ce04b6472..0000000000 --- a/InvenTree/templates/registration/logout.html +++ /dev/null @@ -1,8 +0,0 @@ -{% extends "base.html" %} -{% load i18n %} - -{% block content %} -

{% trans "Logout" %}

-

{% trans "You have been logged out" %}

-

{% trans 'Click' %} {% trans 'here to log in

' %} -{% endblock %} \ No newline at end of file From 96efb0eb28d102df8d6e0ff12f7d1740f79a9f40 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 13 Apr 2021 20:02:20 +1000 Subject: [PATCH 6/8] Remove "forgot password" link if the email backend is not configured --- InvenTree/InvenTree/context.py | 1 + InvenTree/InvenTree/settings.py | 5 +++- InvenTree/InvenTree/status.py | 31 +++++++++++++++++++++ InvenTree/config_template.yaml | 4 ++- InvenTree/templates/registration/login.html | 3 ++ InvenTree/templates/stats.html | 21 ++++++++++---- 6 files changed, 58 insertions(+), 7 deletions(-) diff --git a/InvenTree/InvenTree/context.py b/InvenTree/InvenTree/context.py index 3e9af2f751..e072f5a5ea 100644 --- a/InvenTree/InvenTree/context.py +++ b/InvenTree/InvenTree/context.py @@ -32,6 +32,7 @@ def health_status(request): status = { 'django_q_running': InvenTree.status.is_worker_running(), + 'email_configured': InvenTree.status.is_email_configured(), } all_healthy = True diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index fd57167dd4..5ee79753ca 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -498,7 +498,10 @@ EXCHANGE_BACKEND = 'InvenTree.exchange.InvenTreeManualExchangeBackend' # Extract email settings from the config file email_config = CONFIG.get('email', {}) -EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' +EMAIL_BACKEND = get_setting( + 'django.core.mail.backends.smtp.EmailBackend', + email_config.get('backend', '') +) # Email backend settings EMAIL_HOST = get_setting( diff --git a/InvenTree/InvenTree/status.py b/InvenTree/InvenTree/status.py index 88acc69a7a..e9846e445a 100644 --- a/InvenTree/InvenTree/status.py +++ b/InvenTree/InvenTree/status.py @@ -11,6 +11,9 @@ from datetime import datetime, timedelta from django_q.models import Success from django_q.monitor import Stat +from django.conf import settings + + logger = logging.getLogger("inventree") @@ -43,6 +46,30 @@ def is_worker_running(**kwargs): return results.exists() +def is_email_configured(): + """ + Check if email backend is configured. + + NOTE: This does not check if the configuration is valid! + """ + + configured = True + + if not settings.EMAIL_HOST: + logger.warning("EMAIL_HOST is not configured") + configured = False + + if not settings.EMAIL_HOST_USER: + logger.warning("EMAIL_HOST_USER is not configured") + configured = False + + if not settings.EMAIL_HOST_PASSWORD: + logger.warning("EMAIL_HOST_PASSWORD is not configured") + configured = False + + return configured + + def check_system_health(**kwargs): """ Check that the InvenTree system is running OK. @@ -56,6 +83,10 @@ def check_system_health(**kwargs): result = False logger.warning(_("Background worker check failed")) + if not is_email_configured(): + result = False + logger.warning(_("Email backend not configured")) + if not result: logger.warning(_("InvenTree system health checks failed")) diff --git a/InvenTree/config_template.yaml b/InvenTree/config_template.yaml index 506788a5f1..81411dee55 100644 --- a/InvenTree/config_template.yaml +++ b/InvenTree/config_template.yaml @@ -74,7 +74,9 @@ currencies: # tls: Enable TLS support # ssl: Enable SSL support -# Alternatively, these options can all be set using environment variables +# Alternatively, these options can all be set using environment variables, +# with the INVENTREE_EMAIL_ prefix: +# e.g. INVENTREE_EMAIL_HOST / INVENTREE_EMAIL_PORT / INVENTREE_EMAIL_USERNAME # Refer to the InvenTree documentation for more information email: diff --git a/InvenTree/templates/registration/login.html b/InvenTree/templates/registration/login.html index 746d3c86fa..37eda9a103 100644 --- a/InvenTree/templates/registration/login.html +++ b/InvenTree/templates/registration/login.html @@ -89,9 +89,12 @@ + + {% if email_configured %}

{% trans "Forgotten your password?" %} - {% trans "Click here to reset" %}

+ {% endif %} diff --git a/InvenTree/templates/stats.html b/InvenTree/templates/stats.html index 30d3f3d881..eff9c4504f 100644 --- a/InvenTree/templates/stats.html +++ b/InvenTree/templates/stats.html @@ -25,18 +25,29 @@ {% endif %} + {% if not django_q_running %} {% trans "Background Worker" %} - {% if django_q_running %} - {% trans "Operational" %} - {% else %} - {% trans "Not running" %} - {% endif %} + + {% trans "Background worker not running" %} + {% endif %} + {% if not email_configured %} + + + {% trans "Email Settings" %} + + + {% trans "Email settings not configured" %} + + + + {% endif %} + {% endif %} {% if not system_healthy %} {% for issue in system_issues %} From 04318c6d70141727820741a9dd7984892e8271df Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 13 Apr 2021 20:15:09 +1000 Subject: [PATCH 7/8] Adjust default email settings --- InvenTree/InvenTree/settings.py | 5 +---- InvenTree/config_template.yaml | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index 5ee79753ca..e2bd5ebc83 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -524,10 +524,7 @@ EMAIL_HOST_PASSWORD = get_setting( email_config.get('password', ''), ) -EMAIL_SUBJECT_PREFIX = get_setting( - 'INVENTREE_EMAIL_PREFIX', - email_config.get('prefix', '[InvenTree] '), -) +EMAIL_SUBJECT_PREFIX = '[InvenTree] ') EMAIL_USE_LOCALTIME = False diff --git a/InvenTree/config_template.yaml b/InvenTree/config_template.yaml index 81411dee55..e9f32f382e 100644 --- a/InvenTree/config_template.yaml +++ b/InvenTree/config_template.yaml @@ -80,11 +80,11 @@ currencies: # Refer to the InvenTree documentation for more information email: + # backend: 'django.core.mail.backends.smtp.EmailBackend' host: '' port: 25 username: '' password: '' - prefix: '[InvenTree] ' tls: False ssl: False From d5034ece5168977aa3dc258f53f4d657ece3afba Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Tue, 13 Apr 2021 20:21:33 +1000 Subject: [PATCH 8/8] typo fix --- InvenTree/InvenTree/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/InvenTree/settings.py b/InvenTree/InvenTree/settings.py index e2bd5ebc83..1b84f0c51a 100644 --- a/InvenTree/InvenTree/settings.py +++ b/InvenTree/InvenTree/settings.py @@ -524,7 +524,7 @@ EMAIL_HOST_PASSWORD = get_setting( email_config.get('password', ''), ) -EMAIL_SUBJECT_PREFIX = '[InvenTree] ') +EMAIL_SUBJECT_PREFIX = '[InvenTree] ' EMAIL_USE_LOCALTIME = False