Notify context (#11481)

* Pass extra HTML context through to "notify_responsible"

* Supply additional context

* Update template

* Add unit test for email template

---------

Co-authored-by: Matthias Mair <code@mjmair.com>
This commit is contained in:
OliverandMatthias Mair authored and GitHub committed 2026-10-14 12:43:28 +11:00
1 parent de962f5144
commit 31bc5a043c
4 files changed
+96 -3

No files matched your search

@@ -345,6 +345,7 @@ def notify_responsible(
instance, instance,
sender, sender,
content: NotificationBody = InvenTreeNotificationBodies.NewOrder, content: NotificationBody = InvenTreeNotificationBodies.NewOrder,
html_context: Optional[dict] = None,
exclude=None, exclude=None,
extra_users: Optional[list] = None, extra_users: Optional[list] = None,
): ):
@@ -357,6 +358,7 @@ def notify_responsible(
instance: The newly created instance instance: The newly created instance
sender: Sender model reference sender: Sender model reference
content (NotificationBody, optional): _description_. Defaults to InvenTreeNotificationBodies.NewOrder. content (NotificationBody, optional): _description_. Defaults to InvenTreeNotificationBodies.NewOrder.
html_context: Optional HTML context to include in the notification. Defaults to None.
exclude (User, optional): User instance that should be excluded. Defaults to None. exclude (User, optional): User instance that should be excluded. Defaults to None.
extra_users (list, optional): List of extra users to notify. Defaults to None. extra_users (list, optional): List of extra users to notify. Defaults to None.
""" """
@@ -370,7 +372,14 @@ def notify_responsible(
if extra_users: if extra_users:
users.extend(extra_users) users.extend(extra_users)
notify_users(users, instance, sender, content=content, exclude=exclude) notify_users(
users,
instance,
sender,
content=content,
html_context=html_context,
exclude=exclude,
)
def notify_users( def notify_users(
@@ -378,6 +387,7 @@ def notify_users(
instance, instance,
sender, sender,
content: NotificationBody = InvenTreeNotificationBodies.NewOrder, content: NotificationBody = InvenTreeNotificationBodies.NewOrder,
html_context: Optional[dict] = None,
exclude=None, exclude=None,
): ):
"""Notify all passed users or groups. """Notify all passed users or groups.
@@ -390,6 +400,7 @@ def notify_users(
instance: The newly created instance instance: The newly created instance
sender: Sender model reference sender: Sender model reference
content (NotificationBody, optional): _description_. Defaults to InvenTreeNotificationBodies.NewOrder. content (NotificationBody, optional): _description_. Defaults to InvenTreeNotificationBodies.NewOrder.
html_context (dict, optional): Optional HTML context to include in the notification. Defaults to None.
exclude (User, optional): User instance that should be excluded. Defaults to None. exclude (User, optional): User instance that should be excluded. Defaults to None.
""" """
# Setup context for notification parsing # Setup context for notification parsing
@@ -401,7 +412,9 @@ def notify_users(
} }
# Setup notification context # Setup notification context
# Extra HTML context is included, but cannot override the standard keys
context = { context = {
**(html_context or {}),
'instance': instance, 'instance': instance,
'name': content.name.format(**content_context), 'name': content.name.format(**content_context),
'message': content.message.format(**content_context), 'message': content.message.format(**content_context),
@@ -409,8 +422,7 @@ def notify_users(
'template': {'subject': content.name.format(**content_context)}, 'template': {'subject': content.name.format(**content_context)},
} }
tmp = content.template if tmp := content.template:
if tmp:
context['template']['html'] = tmp.format(**content_context) context['template']['html'] = tmp.format(**content_context)
# Create notification # Create notification
+1
View File
@@ -1322,6 +1322,7 @@ class PurchaseOrder(TotalPriceMixin, Order):
PurchaseOrder, PurchaseOrder,
exclude=user, exclude=user,
content=InvenTreeNotificationBodies.ItemsReceived, content=InvenTreeNotificationBodies.ItemsReceived,
html_context={'order': self, 'stock_items': stock_items},
extra_users=notify_users, extra_users=notify_users,
) )
+64
View File
@@ -6,6 +6,7 @@ from decimal import Decimal
import django.core.exceptions as django_exceptions import django.core.exceptions as django_exceptions
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
from django.core import mail
from django.test import TestCase from django.test import TestCase
from djmoney.money import Money from djmoney.money import Money
@@ -22,6 +23,7 @@ from InvenTree.unit_test import (
) )
from order.status_codes import PurchaseOrderStatus from order.status_codes import PurchaseOrderStatus
from part.models import Part from part.models import Part
from plugin import registry
from stock.models import StockItem, StockLocation from stock.models import StockItem, StockLocation
from users.models import Owner from users.models import Owner
@@ -556,6 +558,68 @@ class OrderTest(ExchangeRateMixin, PluginRegistryMixin, TestCase):
# However *no* notification should have been generated for the creating user # However *no* notification should have been generated for the creating user
self.assertFalse(messages.filter(user__pk=3).exists()) self.assertFalse(messages.filter(user__pk=3).exists())
def test_received_email_notification(self):
"""Test the email notification sent when items are received against a PurchaseOrder.
- The responsible user should receive an email
- The email should list the received stock items
"""
user = get_user_model().objects.get(pk=4)
user.email = 'sam@demo.inventree.org'
user.save()
# Enable email notifications for the responsible user
plugin = registry.get_plugin('inventree-email-notification')
self.assertIsNotNone(plugin)
plugin.set_user_setting('NOTIFY_BY_EMAIL', True, user)
po = PurchaseOrder.objects.get(pk=1)
po.responsible = Owner.create(obj=user)
po.save()
po.place_order()
# Ignore any emails sent when the order was placed
common.models.NotificationEntry.objects.all().delete()
mail.outbox.clear()
location = StockLocation.objects.get(pk=1)
line_1 = PurchaseOrderLineItem.objects.get(pk=1)
line_2 = PurchaseOrderLineItem.objects.get(pk=3)
stock_items = po.receive_line_items(
location,
[
{'line_item': line_1, 'quantity': 15},
{'line_item': line_2, 'quantity': 37},
],
user=get_user_model().objects.get(pk=1),
)
self.assertEqual(stock_items.count(), 2)
# Exactly one email should have been sent, to the responsible user
self.assertEqual(len(mail.outbox), 1)
email = mail.outbox[0]
self.assertEqual(email.to, [user.email])
self.assertIn('Items Received', email.subject)
html = email.alternatives[0][0]
# Order information
self.assertIn('Items have been received against a purchase order', html)
self.assertIn(po.get_absolute_url(), html)
# Table of received items
self.assertIn('<th>Part</th>', html)
self.assertIn('<th>Quantity</th>', html)
self.assertIn('<th>Location</th>', html)
for item in stock_items:
self.assertIn(f'<td>{item.part.name}</td>', html)
self.assertIn(f'<td>{item.quantity}</td>', html)
self.assertIn(f'<td>{item.location.pathstring}</td>', html)
def test_metadata(self): def test_metadata(self):
"""Unit tests for the metadata field.""" """Unit tests for the metadata field."""
for model in [PurchaseOrder, PurchaseOrderLineItem, PurchaseOrderExtraLine]: for model in [PurchaseOrder, PurchaseOrderLineItem, PurchaseOrderExtraLine]:
@@ -9,3 +9,19 @@
<p>{% trans "Click on the following link to view this order" %}: <a href='{{ link }}'>{{ link }}</a></p> <p>{% trans "Click on the following link to view this order" %}: <a href='{{ link }}'>{{ link }}</a></p>
{% endif %} {% endif %}
{% endblock title %} {% endblock title %}
{% block body %}
<tr style="height: 3rem; border-bottom: 1px solid">
<th>{% trans "Part" %}</th>
<th>{% trans "Quantity" %}</th>
<th>{% trans "Location" %}</th>
</tr>
{% for item in stock_items %}
<tr>
<td>{{ item.part.name }}</td>
<td>{{ item.quantity }}</td>
<td>{{ item.location.pathstring }}</td>
</tr>
{% endfor %}
{% endblock body %}