Fix update notification deduplication and release link (#12424)
This commit is contained in:
parent
fcbe4302b9
commit
3ddf792a59
|
|
@ -856,6 +856,7 @@ def check_for_updates():
|
|||
data = json.loads(response.text)
|
||||
|
||||
tag = data.get('tag_name', None)
|
||||
release_url = data.get('html_url')
|
||||
|
||||
if not tag:
|
||||
raise ValueError("'tag_name' missing from GitHub response") # pragma: no cover
|
||||
|
|
@ -885,11 +886,13 @@ def check_for_updates():
|
|||
trigger_notification(
|
||||
None,
|
||||
'update_available',
|
||||
notification_uid=0,
|
||||
targets=get_user_model().objects.filter(is_superuser=True),
|
||||
delivery_methods={InvenTreeUINotifications},
|
||||
context={
|
||||
'name': _('Update Available'),
|
||||
'message': _('An update for InvenTree is available'),
|
||||
'link': release_url,
|
||||
},
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import os
|
||||
from datetime import timedelta
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
|
|
@ -11,6 +12,7 @@ from django.db.utils import NotSupportedError
|
|||
from django.test import TestCase
|
||||
from django.utils import timezone
|
||||
|
||||
import requests_mock
|
||||
from django_q.models import OrmQ, Schedule, Task
|
||||
from error_report.models import Error
|
||||
|
||||
|
|
@ -168,19 +170,57 @@ class InvenTreeTaskTests(PluginRegistryMixin, TestCase):
|
|||
errors = Error.objects.filter(when__lte=threshold)
|
||||
self.assertEqual(len(errors), 0)
|
||||
|
||||
def test_task_check_for_updates(self):
|
||||
"""Test the task check_for_updates."""
|
||||
# Check that setting should be empty
|
||||
@requests_mock.Mocker()
|
||||
def test_task_check_for_updates(self, requests_mocker):
|
||||
"""Test update notification creation and deduplication."""
|
||||
from common.models import NotificationEntry, NotificationMessage
|
||||
from common.serializers import NotificationMessageSerializer
|
||||
|
||||
self.ensurePluginsLoaded(force=True)
|
||||
|
||||
user = User.objects.create_superuser(
|
||||
username='update_admin',
|
||||
email='update@example.com',
|
||||
password='test-password',
|
||||
)
|
||||
|
||||
release_url = 'https://github.com/InvenTree/InvenTree/releases/tag/99.99.99'
|
||||
|
||||
requests_mocker.get(
|
||||
'https://api.github.com/repos/inventree/inventree/releases/latest',
|
||||
json={'tag_name': '99.99.99', 'html_url': release_url},
|
||||
)
|
||||
|
||||
InvenTreeSetting.set_setting('_INVENTREE_LATEST_VERSION', '')
|
||||
self.assertEqual(InvenTreeSetting.get_setting('_INVENTREE_LATEST_VERSION'), '')
|
||||
|
||||
# Get new version
|
||||
InvenTree.tasks.offload_task(InvenTree.tasks.check_for_updates)
|
||||
with (
|
||||
patch(
|
||||
'InvenTree.tasks.isInvenTreeUpToDate', return_value=False
|
||||
) as mock_up_to_date,
|
||||
patch('InvenTree.tasks.check_daily_holdoff', return_value=True),
|
||||
):
|
||||
InvenTree.tasks.check_for_updates()
|
||||
|
||||
# Check that setting is not empty
|
||||
response = InvenTreeSetting.get_setting('_INVENTREE_LATEST_VERSION')
|
||||
self.assertNotEqual(response, '')
|
||||
self.assertTrue(bool(response))
|
||||
self.assertEqual(
|
||||
InvenTreeSetting.get_setting('_INVENTREE_LATEST_VERSION'), '99.99.99'
|
||||
)
|
||||
self.assertEqual(NotificationMessage.objects.count(), 1)
|
||||
self.assertEqual(NotificationEntry.objects.count(), 1)
|
||||
|
||||
message = NotificationMessage.objects.get(user=user)
|
||||
entry = NotificationEntry.objects.get()
|
||||
|
||||
self.assertEqual(message.link, release_url)
|
||||
self.assertEqual(entry.uid, 0)
|
||||
|
||||
serialized = NotificationMessageSerializer(message).data
|
||||
self.assertEqual(serialized['target']['link'], release_url)
|
||||
|
||||
InvenTree.tasks.check_for_updates()
|
||||
|
||||
self.assertEqual(NotificationMessage.objects.count(), 1)
|
||||
self.assertEqual(NotificationEntry.objects.count(), 1)
|
||||
mock_up_to_date.assert_called()
|
||||
|
||||
def test_task_check_for_migrations(self):
|
||||
"""Test the task check_for_migrations."""
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
"""Add link field to notification messages."""
|
||||
|
||||
# Generated by Django 5.2.15 on 2026-07-20 01:09
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
"""Add the optional notification link field."""
|
||||
|
||||
dependencies = [('common', '0047_parametertemplate_unique')]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='notificationmessage',
|
||||
name='link',
|
||||
field=models.URLField(
|
||||
blank=True,
|
||||
help_text='Optional explicit URL associated with this notification',
|
||||
max_length=500,
|
||||
null=True,
|
||||
),
|
||||
)
|
||||
]
|
||||
|
|
@ -1723,6 +1723,13 @@ class NotificationMessage(models.Model):
|
|||
|
||||
message = models.CharField(max_length=250, blank=True, null=True)
|
||||
|
||||
link = models.URLField(
|
||||
max_length=500,
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text=_('Optional explicit URL associated with this notification'),
|
||||
)
|
||||
|
||||
creation = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
read = models.BooleanField(default=False)
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@ def trigger_notification(obj: Model, category: str = '', obj_ref: str = 'pk', **
|
|||
obj: The object (model instance) that is triggering the notification
|
||||
category: The category (label) for the notification
|
||||
obj_ref: The reference to the object that should be used for the notification
|
||||
notification_uid: Explicit deduplication identifier for notifications without a model instance
|
||||
kwargs: Additional arguments to pass to the notification method
|
||||
"""
|
||||
# Check if data is importing currently
|
||||
|
|
@ -105,6 +106,7 @@ def trigger_notification(obj: Model, category: str = '', obj_ref: str = 'pk', **
|
|||
context = kwargs.get('context', {})
|
||||
delivery_methods = kwargs.get('delivery_methods')
|
||||
check_recent = kwargs.get('check_recent', True)
|
||||
notification_uid = kwargs.get('notification_uid')
|
||||
|
||||
# Resolve object reference
|
||||
refs = [obj_ref, 'pk', 'id', 'uid']
|
||||
|
|
@ -122,6 +124,8 @@ def trigger_notification(obj: Model, category: str = '', obj_ref: str = 'pk', **
|
|||
raise KeyError(
|
||||
f"Could not resolve an object reference for '{obj!s}' with {','.join(set(refs))}"
|
||||
)
|
||||
elif notification_uid is not None:
|
||||
obj_ref_value = notification_uid
|
||||
|
||||
# Check if we have notified recently...
|
||||
delta = timedelta(days=1)
|
||||
|
|
|
|||
|
|
@ -318,7 +318,9 @@ class NotificationMessageSerializer(InvenTreeModelSerializer):
|
|||
"""Function to resolve generic object reference to target."""
|
||||
target = get_objectreference(obj, 'target_content_type', 'target_object_id')
|
||||
|
||||
if target and 'link' not in target:
|
||||
if target and obj.link:
|
||||
target['link'] = obj.link
|
||||
elif target and 'link' not in target:
|
||||
# Check if object has an absolute_url function
|
||||
if hasattr(obj.target_object, 'get_absolute_url'):
|
||||
target['link'] = obj.target_object.get_absolute_url()
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ class InvenTreeUINotifications(NotificationMixin, InvenTreePlugin):
|
|||
category=category,
|
||||
name=ctx.get('name'),
|
||||
message=ctx.get('message'),
|
||||
link=ctx.get('link'),
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue