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/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/settings.py b/InvenTree/InvenTree/settings.py index 8b5400e374..1b84f0c51a 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 = get_setting( + 'django.core.mail.backends.smtp.EmailBackend', + email_config.get('backend', '') +) + +# 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 = '[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/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/InvenTree/tasks.py b/InvenTree/InvenTree/tasks.py index 4829514f19..6c01e03aa6 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,20 @@ 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, + ) diff --git a/InvenTree/InvenTree/urls.py b/InvenTree/InvenTree/urls.py index 7c22639e65..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)), @@ -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/config_template.yaml b/InvenTree/config_template.yaml index a64e6d42c0..e9f32f382e 100644 --- a/InvenTree/config_template.yaml +++ b/InvenTree/config_template.yaml @@ -63,6 +63,31 @@ 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, +# 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: + # backend: 'django.core.mail.backends.smtp.EmailBackend' + host: '' + port: 25 + username: '' + password: '' + tls: False + ssl: False + # Set debug to False to run in production mode # Use the environment variable INVENTREE_DEBUG debug: True 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 + + + + + +
+ +
+
+
+ +
+ +
+

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

+

{% trans "Return to login screen" %}

+
+
+
+
+ +
+ + \ No newline at end of file diff --git a/InvenTree/templates/registration/login.html b/InvenTree/templates/registration/login.html index a3f7b91e35..37eda9a103 100644 --- a/InvenTree/templates/registration/login.html +++ b/InvenTree/templates/registration/login.html @@ -89,7 +89,12 @@ + + {% if email_configured %} +

+

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

+ {% endif %} 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 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 + + + + + +
+ +
+
+
+ +
+ +
+

{% trans "Password reset complete" %}

+

{% trans "Return to login screen" %}

+
+
+
+
+ +
+ + \ 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 + + + + + +
+ +
+
+
+ +
+ +
+ + {% if validlink %} +

{% trans "Change password" %}

+
+ {% csrf_token %} + {{ form.as_p }} + +
+ {% else %} +

+ {% trans "The password reset link was invalid, possibly because it has already been used. Please request a new password reset." %} +

+ {% endif %} +
+
+
+
+
+ + \ 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 + + + + + +
+ +
+
+
+ +
+
+ +

+ {% trans "We've emailed you instructions for setting your password, if an account exists with the email you entered. You should receive them shortly." %} +

+

+ {% trans "If you don't receive an email, please make sure you've entered the address you registered with, and check your spam folder." %} +

+ +
+ {% trans "Return to login screen" %} +
+
+
+
+
+ + \ 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 + + + + + +
+ +
+
+
+ +
+ +
+ +

{% trans "Forgotten your password?" %}

+

{% trans "Enter your email address below." %}

+

{% trans "An email will be sent with password reset instructions." %}

+ +
+ {% csrf_token %} + {{ form.as_p }} + +
+ +
+
+
+
+ +
+ + \ No newline at end of file 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 %}