Merge pull request #1304 from SchrodingersGat/email-support

Support for email settings
This commit is contained in:
Oliver 2021-04-13 20:42:23 +10:00 committed by GitHub
commit 79dc66e840
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 484 additions and 14 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,59 @@
{% load static %}
{% load i18n %}
{% load crispy_forms_tags %}
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- CSS -->
<link rel="stylesheet" href="{% static 'css/bootstrap_3.3.7_css_bootstrap.min.css' %}">
<link rel="stylesheet" href="{% static 'css/select2.css' %}">
<link rel="stylesheet" href="{% static 'css/bootstrap-table.css' %}">
<link rel="stylesheet" href="{% static 'css/inventree.css' %}">
<link rel="stylesheet" href="{% static 'fontawesome/css/brands.css' %}">
<link rel="stylesheet" href="{% static 'fontawesome/css/solid.css' %}">
<script type='text/javascript' src="{% static 'fontawesome/js/solid.js' %}"></script>
<script type='text/javascript' src="{% static 'fontawesome/js/brands.js' %}"></script>
<script type='text/javascript' src="{% static 'fontawesome/js/fontawesome.js' %}"></script>
<style>
.login-error {
color: #F88;
}
</style>
<title>
InvenTree
</title>
</head>
<body class='login-screen'>
<div class='main body-wrapper login-screen'>
<div class='login-container'>
<div class="row">
<div class='container-fluid'>
<div class='clearfix content-heading login-header'>
<img class="pull-left" src="{% static 'img/inventree.png' %}" width="60" height="60"/>
<span><h3>InvenTree</h3></span>
</div>
<hr>
<div class='container-fluid'>
<p>{% trans "You have been logged out" %}</p>
<p><a href='{% url "login" %}'>{% trans "Return to login screen" %}</a></p>
</div>
</div>
</div>
</div>
</div>
</body>

View File

@ -89,7 +89,12 @@
<button class='pull-right btn btn-primary login-button' type="submit">{% trans "Login" %}</button>
</form>
{% if email_configured %}
<hr><br>
<p>{% trans "Forgotten your password?" %} - <a href='{% url "password_reset" %}'>{% trans "Click here to reset" %}</a></p>
</div>
{% endif %}
</div>
</div>
</div>

View File

@ -1,8 +0,0 @@
{% extends "base.html" %}
{% load i18n %}
{% block content %}
<h4>{% trans "Logout" %}</h4>
<p>{% trans "You have been logged out" %}</p>
<p>{% trans 'Click' %} <a href="{% url 'login' %}"> {% trans 'here</a> to log in</p>' %}
{% endblock %}

View File

@ -0,0 +1,59 @@
{% load static %}
{% load i18n %}
{% load crispy_forms_tags %}
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- CSS -->
<link rel="stylesheet" href="{% static 'css/bootstrap_3.3.7_css_bootstrap.min.css' %}">
<link rel="stylesheet" href="{% static 'css/select2.css' %}">
<link rel="stylesheet" href="{% static 'css/bootstrap-table.css' %}">
<link rel="stylesheet" href="{% static 'css/inventree.css' %}">
<link rel="stylesheet" href="{% static 'fontawesome/css/brands.css' %}">
<link rel="stylesheet" href="{% static 'fontawesome/css/solid.css' %}">
<script type='text/javascript' src="{% static 'fontawesome/js/solid.js' %}"></script>
<script type='text/javascript' src="{% static 'fontawesome/js/brands.js' %}"></script>
<script type='text/javascript' src="{% static 'fontawesome/js/fontawesome.js' %}"></script>
<style>
.login-error {
color: #F88;
}
</style>
<title>
InvenTree
</title>
</head>
<body class='login-screen'>
<div class='main body-wrapper login-screen'>
<div class='login-container'>
<div class="row">
<div class='container-fluid'>
<div class='clearfix content-heading login-header'>
<img class="pull-left" src="{% static 'img/inventree.png' %}" width="60" height="60"/>
<span><h3>InvenTree</h3></span>
</div>
<hr>
<div class='container-fluid'>
<p>{% trans "Password reset complete" %}</p>
<p><a href='{% url "login" %}'>{% trans "Return to login screen" %}</a></p>
</div>
</div>
</div>
</div>
</div>
</body>

View File

@ -0,0 +1,69 @@
{% load static %}
{% load i18n %}
{% load crispy_forms_tags %}
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- CSS -->
<link rel="stylesheet" href="{% static 'css/bootstrap_3.3.7_css_bootstrap.min.css' %}">
<link rel="stylesheet" href="{% static 'css/select2.css' %}">
<link rel="stylesheet" href="{% static 'css/bootstrap-table.css' %}">
<link rel="stylesheet" href="{% static 'css/inventree.css' %}">
<link rel="stylesheet" href="{% static 'fontawesome/css/brands.css' %}">
<link rel="stylesheet" href="{% static 'fontawesome/css/solid.css' %}">
<script type='text/javascript' src="{% static 'fontawesome/js/solid.js' %}"></script>
<script type='text/javascript' src="{% static 'fontawesome/js/brands.js' %}"></script>
<script type='text/javascript' src="{% static 'fontawesome/js/fontawesome.js' %}"></script>
<style>
.login-error {
color: #F88;
}
</style>
<title>
InvenTree
</title>
</head>
<body class='login-screen'>
<div class='main body-wrapper login-screen'>
<div class='login-container'>
<div class="row">
<div class='container-fluid'>
<div class='clearfix content-heading login-header'>
<img class="pull-left" src="{% static 'img/inventree.png' %}" width="60" height="60"/>
<span><h3>InvenTree</h3></span>
</div>
<hr>
<div class='container-fluid'>
{% if validlink %}
<h3>{% trans "Change password" %}</h3>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button class="btn btn-primary" type="submit">{% trans "Change password" %}</button>
</form>
{% else %}
<p>
{% trans "The password reset link was invalid, possibly because it has already been used. Please request a new password reset." %}
</p>
{% endif %}
</div>
</div>
</div>
</div>
</div>
</body>

View File

@ -0,0 +1,65 @@
{% load static %}
{% load i18n %}
{% load crispy_forms_tags %}
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- CSS -->
<link rel="stylesheet" href="{% static 'css/bootstrap_3.3.7_css_bootstrap.min.css' %}">
<link rel="stylesheet" href="{% static 'css/select2.css' %}">
<link rel="stylesheet" href="{% static 'css/bootstrap-table.css' %}">
<link rel="stylesheet" href="{% static 'css/inventree.css' %}">
<link rel="stylesheet" href="{% static 'fontawesome/css/brands.css' %}">
<link rel="stylesheet" href="{% static 'fontawesome/css/solid.css' %}">
<script type='text/javascript' src="{% static 'fontawesome/js/solid.js' %}"></script>
<script type='text/javascript' src="{% static 'fontawesome/js/brands.js' %}"></script>
<script type='text/javascript' src="{% static 'fontawesome/js/fontawesome.js' %}"></script>
<style>
.login-error {
color: #F88;
}
</style>
<title>
InvenTree
</title>
</head>
<body class='login-screen'>
<div class='main body-wrapper login-screen'>
<div class='login-container'>
<div class="row">
<div class='container-fluid'>
<div class='clearfix content-heading login-header'>
<img class="pull-left" src="{% static 'img/inventree.png' %}" width="60" height="60"/>
<span><h3>InvenTree</h3></span>
</div>
<hr>
<div class='container-fluid'>
<p>
{% trans "We've emailed you instructions for setting your password, if an account exists with the email you entered. You should receive them shortly." %}
</p>
<p>
{% trans "If you don't receive an email, please make sure you've entered the address you registered with, and check your spam folder." %}
</p>
<hr>
<a href='{% url "login" %}'>{% trans "Return to login screen" %}</a>
</div>
</div>
</div>
</div>
</div>
</body>

View File

@ -0,0 +1,68 @@
{% load static %}
{% load i18n %}
{% load crispy_forms_tags %}
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- CSS -->
<link rel="stylesheet" href="{% static 'css/bootstrap_3.3.7_css_bootstrap.min.css' %}">
<link rel="stylesheet" href="{% static 'css/select2.css' %}">
<link rel="stylesheet" href="{% static 'css/bootstrap-table.css' %}">
<link rel="stylesheet" href="{% static 'css/inventree.css' %}">
<link rel="stylesheet" href="{% static 'fontawesome/css/brands.css' %}">
<link rel="stylesheet" href="{% static 'fontawesome/css/solid.css' %}">
<script type='text/javascript' src="{% static 'fontawesome/js/solid.js' %}"></script>
<script type='text/javascript' src="{% static 'fontawesome/js/brands.js' %}"></script>
<script type='text/javascript' src="{% static 'fontawesome/js/fontawesome.js' %}"></script>
<style>
.login-error {
color: #F88;
}
</style>
<title>
InvenTree
</title>
</head>
<body class='login-screen'>
<div class='main body-wrapper login-screen'>
<div class='login-container'>
<div class="row">
<div class='container-fluid'>
<div class='clearfix content-heading login-header'>
<img class="pull-left" src="{% static 'img/inventree.png' %}" width="60" height="60"/>
<span><h3>InvenTree</h3></span>
</div>
<hr>
<div class='container-fluid'>
<p>{% trans "Forgotten your password?" %}</p>
<p>{% trans "Enter your email address below." %}</p>
<p>{% trans "An email will be sent with password reset instructions." %}</p>
<form method="POST">
{% csrf_token %}
{{ form.as_p }}
<button class="btn btn-primary" type="submit">{% trans "Send email" %}</button>
</form>
</div>
</div>
</div>
</div>
</div>
</body>

View File

@ -25,18 +25,29 @@
{% endif %}
</td>
</tr>
{% if not django_q_running %}
<tr>
<td><span class='fas fa-tasks'></span></td>
<td>{% trans "Background Worker" %}</td>
<td>
{% if django_q_running %}
<span class='label label-green'>{% trans "Operational" %}</span>
{% else %}
<span class='label label-red'>{% trans "Not running" %}</span>
{% endif %}
<a href='https://inventree.readthedocs.io/en/latest/admin/tasks'>
<span class='label label-red'>{% trans "Background worker not running" %}</span>
</a>
</td>
</tr>
{% endif %}
{% if not email_configured %}
<tr>
<td><span class='fas fa-envelope'></span></td>
<td>{% trans "Email Settings" %}</td>
<td>
<a href='https://inventree.readthedocs.io/en/latest/admin/email'>
<span class='label label-red'>{% trans "Email settings not configured" %}</span>
</a>
</td>
</tr>
{% endif %}
{% endif %}
{% if not system_healthy %}
{% for issue in system_issues %}