From dabaa9aea5d42a749b2c5e8a28f4a2af0e8c5a9f Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 1 Nov 2021 23:40:21 +1100 Subject: [PATCH 1/2] Adds a function to construct an "absolute" URL Useful for providing an external link (e.g. in an email) --- InvenTree/InvenTree/helpers.py | 27 +++++++++++++++++++++++++ InvenTree/report/templatetags/report.py | 14 ++++--------- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/InvenTree/InvenTree/helpers.py b/InvenTree/InvenTree/helpers.py index 319b88cb09..4b0f31a702 100644 --- a/InvenTree/InvenTree/helpers.py +++ b/InvenTree/InvenTree/helpers.py @@ -69,6 +69,33 @@ def getStaticUrl(filename): return os.path.join(STATIC_URL, str(filename)) +def construct_absolute_url(url): + """ + Construct (or attempt to construct) an absolute URL from a relative URL. + + This is useful when (for example) sending an email to a user with a link + to something in the InvenTree web framework. + + This requires the BASE_URL configuration option to be set! + """ + + base = str(InvenTreeSetting.get_setting('INVENTREE_BASE_URL')) + + url = str(url) + + if not base: + return url + + # Strip trailing slash from base url + if base.endswith('/'): + base = base[:-1] + + if url.startswith('/'): + url = url[1:] + + url = f"{base}/{url}" + + def getBlankImage(): """ Return the qualified path for the 'blank image' placeholder. diff --git a/InvenTree/report/templatetags/report.py b/InvenTree/report/templatetags/report.py index 45d11c1d2d..9593db1885 100644 --- a/InvenTree/report/templatetags/report.py +++ b/InvenTree/report/templatetags/report.py @@ -14,6 +14,8 @@ from stock.models import StockItem from common.models import InvenTreeSetting +import InvenTree.helpers + register = template.Library() @@ -119,18 +121,10 @@ def internal_link(link, text): text = str(text) - base_url = InvenTreeSetting.get_setting('INVENTREE_BASE_URL') + url = InvenTree.helpers.construct_absolute_url(link) # If the base URL is not set, just return the text - if not base_url: + if not url: return text - if not base_url.endswith('/'): - base_url += '/' - - if base_url.endswith('/') and link.startswith('/'): - link = link[1:] - - url = f"{base_url}{link}" - return mark_safe(f'{text}') From 6f9ac4a8504988a7803645060e09b4efcdda383c Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 2 Nov 2021 00:40:25 +1100 Subject: [PATCH 2/2] - Fixes for construct_absolute_url function - Refactor notification email generation - Update template file - Add separate templates folder for email --- InvenTree/InvenTree/helpers.py | 6 ++- InvenTree/part/tasks.py | 44 +++++++++++-------- .../part/templatetags/inventree_extras.py | 6 +++ .../stock/low_stock_notification.html | 30 ------------- .../email/low_stock_notification.html | 35 +++++++++++++++ 5 files changed, 70 insertions(+), 51 deletions(-) delete mode 100644 InvenTree/stock/templates/stock/low_stock_notification.html create mode 100644 InvenTree/templates/email/low_stock_notification.html diff --git a/InvenTree/InvenTree/helpers.py b/InvenTree/InvenTree/helpers.py index 4b0f31a702..fd86306627 100644 --- a/InvenTree/InvenTree/helpers.py +++ b/InvenTree/InvenTree/helpers.py @@ -69,7 +69,7 @@ def getStaticUrl(filename): return os.path.join(STATIC_URL, str(filename)) -def construct_absolute_url(url): +def construct_absolute_url(*arg): """ Construct (or attempt to construct) an absolute URL from a relative URL. @@ -81,7 +81,7 @@ def construct_absolute_url(url): base = str(InvenTreeSetting.get_setting('INVENTREE_BASE_URL')) - url = str(url) + url = '/'.join(arg) if not base: return url @@ -95,6 +95,8 @@ def construct_absolute_url(url): url = f"{base}/{url}" + return url + def getBlankImage(): """ diff --git a/InvenTree/part/tasks.py b/InvenTree/part/tasks.py index 9fc05ec3f1..72d996e772 100644 --- a/InvenTree/part/tasks.py +++ b/InvenTree/part/tasks.py @@ -1,12 +1,18 @@ -# Author: Roche Christopher -# Created at 10:26 AM on 31/10/21 +# -*- coding: utf-8 -*- +from __future__ import unicode_literals import logging from django.utils.translation import ugettext_lazy as _ from django.template.loader import render_to_string -from InvenTree import tasks as inventree_tasks +from allauth.account.models import EmailAddress + +from common.models import InvenTree + +import InvenTree.helpers +import InvenTree.tasks + from part.models import Part logger = logging.getLogger("inventree") @@ -17,36 +23,36 @@ def notify_low_stock(part: Part): Notify users who have starred a part when its stock quantity falls below the minimum threshold """ - from allauth.account.models import EmailAddress + logger.info(f"Sending low stock notification email for {part.full_name}") + starred_users_email = EmailAddress.objects.filter(user__starred_parts__part=part) + # TODO: In the future, include the part image in the email template + if len(starred_users_email) > 0: logger.info(f"Notify users regarding low stock of {part.name}") context = { - 'part_name': part.name, - # Part url can be used to open the page of part in application from the email. - # It can be facilitated when the application base url is accessible programmatically. - # 'part_url': f'{application_base_url}/part/{stock_item.part.id}', - - # quantity is in decimal field datatype. Since the same datatype is used in models, - # it is not converted to number/integer, - 'part_quantity': part.total_stock, - 'minimum_quantity': part.minimum_stock + # Pass the "Part" object through to the template context + 'part': part, + 'link': InvenTree.helpers.construct_absolute_url(part.get_absolute_url()), } - subject = _(f'Attention! {part.name} is low on stock') - html_message = render_to_string('stock/low_stock_notification.html', context) + + subject = _(f'[InvenTree] {part.name} is low on stock') + html_message = render_to_string('email/low_stock_notification.html', context) recipients = starred_users_email.values_list('email', flat=True) - inventree_tasks.send_email(subject, '', recipients, html_message=html_message) + + InvenTree.tasks.send_email(subject, '', recipients, html_message=html_message) def notify_low_stock_if_required(part: Part): """ - Check if the stock quantity has fallen below the minimum threshold of part. If yes, notify the users who have - starred the part + Check if the stock quantity has fallen below the minimum threshold of part. + + If true, notify the users who have subscribed to the part """ if part.is_part_low_on_stock(): - inventree_tasks.offload_task( + InvenTree.tasks.offload_task( 'part.tasks.notify_low_stock', part ) diff --git a/InvenTree/part/templatetags/inventree_extras.py b/InvenTree/part/templatetags/inventree_extras.py index 2230077a81..590ea20a6f 100644 --- a/InvenTree/part/templatetags/inventree_extras.py +++ b/InvenTree/part/templatetags/inventree_extras.py @@ -122,6 +122,12 @@ def inventree_title(*args, **kwargs): return version.inventreeInstanceTitle() +@register.simple_tag() +def inventree_base_url(*args, **kwargs): + """ Return the INVENTREE_BASE_URL setting """ + return InvenTreeSetting.get_setting('INVENTREE_BASE_URL') + + @register.simple_tag() def python_version(*args, **kwargs): """ diff --git a/InvenTree/stock/templates/stock/low_stock_notification.html b/InvenTree/stock/templates/stock/low_stock_notification.html deleted file mode 100644 index 3126cd11c1..0000000000 --- a/InvenTree/stock/templates/stock/low_stock_notification.html +++ /dev/null @@ -1,30 +0,0 @@ -{% load i18n %} - -

{% trans "Hi, " %} {{ part_name }} {% trans "is low on stock. Kindly do the needful." %}

- - - - - - - - - - - - - - - - - - - - - - - -
{% trans "Part low on stock" %}
{% trans "Part Name" %}{% trans "Available Quantity" %}{% trans "Minimum Quantity" %}
{{ part_name }}{{ part_quantity }}{{ minimum_quantity }}
{% trans "You are receiving this mail because you have starred the part " %} {{ part_name }} - {% trans "Inventree application" %} -
- diff --git a/InvenTree/templates/email/low_stock_notification.html b/InvenTree/templates/email/low_stock_notification.html new file mode 100644 index 0000000000..4fa4e55295 --- /dev/null +++ b/InvenTree/templates/email/low_stock_notification.html @@ -0,0 +1,35 @@ +{% load i18n %} +{% load inventree_extras %} + + + + + + + + + + + + + + + + + + + + + + + +
+

{% blocktrans with part=part.name %} The available stock for {{ part }} has fallen below the configured minimum level{% endblocktrans %}

+ {% if link %} +

{% trans "Click on the following link to view this part" %}: {{ link }}

+ {% endif %} +
{% trans "Part Name" %}{% trans "Available Quantity" %}{% trans "Minimum Quantity" %}
{{ part.full_name }}{{ part.total_stock }}{{ part.minimum_stock }}
+

{% blocktrans with part=part.name %}You are receiving this email because you are subscribed to notifications for this part {% endblocktrans %}.

+

{% trans "InvenTree version" %}: {% inventree_version %}

+
+