Notification fix (#3939)

* Fix docstring for NotificationMessage class

* Fix for 'refresh' button in notification table

* Simplify API for marking notifications as 'read'

- Simply update the detail serializer
- No requirement for extra API endpoints
- Same updates for news feed entry
- Hide 'read' news on the home page
- Add ability to mark news items as read via table

* Bug fix for build.js

* Fix for part category template
This commit is contained in:
Oliver 2022-11-17 08:26:19 +11:00 committed by GitHub
parent 54e7dd28e5
commit 95645c7b14
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 72 additions and 87 deletions

View File

@ -19,8 +19,8 @@ import common.models
import common.serializers
from InvenTree.api import BulkDeleteMixin
from InvenTree.helpers import inheritors
from InvenTree.mixins import (CreateAPI, ListAPI, RetrieveAPI,
RetrieveUpdateAPI, RetrieveUpdateDestroyAPI)
from InvenTree.mixins import (ListAPI, RetrieveAPI, RetrieveUpdateAPI,
RetrieveUpdateDestroyAPI)
from plugin.models import NotificationUserSetting
from plugin.serializers import NotificationUserSettingSerializer
@ -319,36 +319,6 @@ class NotificationDetail(NotificationMessageMixin, RetrieveUpdateDestroyAPI):
"""
class NotificationReadEdit(NotificationMessageMixin, CreateAPI):
"""General API endpoint to manipulate read state of a notification."""
def get_serializer_context(self):
"""Add instance to context so it can be accessed in the serializer."""
context = super().get_serializer_context()
if self.request:
context['instance'] = self.get_object()
return context
def perform_create(self, serializer):
"""Set the `read` status to the target value."""
message = self.get_object()
try:
message.read = self.target
message.save()
except Exception as exc:
raise serializers.ValidationError(detail=serializers.as_serializer_error(exc))
class NotificationRead(NotificationReadEdit):
"""API endpoint to mark a notification as read."""
target = True
class NotificationUnread(NotificationReadEdit):
"""API endpoint to mark a notification as unread."""
target = False
class NotificationReadAll(NotificationMessageMixin, RetrieveAPI):
"""API endpoint to mark all notifications as read."""
@ -390,11 +360,6 @@ class NewsFeedEntryDetail(NewsFeedMixin, RetrieveUpdateDestroyAPI):
"""Detail view for an individual news feed object."""
class NewsFeedEntryRead(NewsFeedMixin, NotificationReadEdit):
"""API endpoint to mark a news item as read."""
target = True
settings_api_urls = [
# User settings
re_path(r'^user/', include([
@ -432,8 +397,6 @@ common_api_urls = [
re_path(r'^notifications/', include([
# Individual purchase order detail URLs
re_path(r'^(?P<pk>\d+)/', include([
re_path(r'^read/', NotificationRead.as_view(), name='api-notifications-read'),
re_path(r'^unread/', NotificationUnread.as_view(), name='api-notifications-unread'),
re_path(r'.*$', NotificationDetail.as_view(), name='api-notifications-detail'),
])),
# Read all
@ -446,7 +409,6 @@ common_api_urls = [
# News
re_path(r'^news/', include([
re_path(r'^(?P<pk>\d+)/', include([
re_path(r'^read/', NewsFeedEntryRead.as_view(), name='api-news-read'),
re_path(r'.*$', NewsFeedEntryDetail.as_view(), name='api-news-detail'),
])),
re_path(r'^.*$', NewsFeedEntryList.as_view(), name='api-news-list'),

View File

@ -2199,14 +2199,13 @@ class NotificationEntry(models.Model):
class NotificationMessage(models.Model):
"""A NotificationEntry records the last time a particular notifaction was sent out.
"""A NotificationMessage is a message sent to a particular user, notifying them of some *important information*
It is recorded to ensure that notifications are not sent out "too often" to users.
Notification messages can be generated by a variety of sources.
Attributes:
- key: A text entry describing the notification e.g. 'part.notify_low_stock'
- uid: An (optional) numerical ID for a particular instance
- date: The last time this notification was sent
target_object: The 'target' of the notification message
source_object: The 'source' of the notification message
"""
# generic link to target

View File

@ -158,7 +158,7 @@ class NotificationMessageSerializer(InvenTreeModelSerializer):
age_human = serializers.CharField(read_only=True)
read = serializers.BooleanField(read_only=True)
read = serializers.BooleanField()
def get_target(self, obj):
"""Function to resolve generic object reference to target."""
@ -203,20 +203,10 @@ class NotificationMessageSerializer(InvenTreeModelSerializer):
]
class NotificationReadSerializer(NotificationMessageSerializer):
"""Serializer for reading a notification."""
def is_valid(self, raise_exception=False):
"""Ensure instance data is available for view and let validation pass."""
self.instance = self.context['instance'] # set instance that should be returned
self._validated_data = True
return True
class NewsFeedEntrySerializer(InvenTreeModelSerializer):
"""Serializer for the NewsFeedEntry model."""
read = serializers.BooleanField(read_only=True)
read = serializers.BooleanField()
class Meta:
"""Meta options for NewsFeedEntrySerializer."""

View File

@ -25,7 +25,7 @@
{% endblock %}
{% block actions %}
{% if user.is_staff and roles.part_category.change %}
{% if category and user.is_staff and roles.part_category.change %}
{% url 'admin:part_partcategory_change' category.pk as url %}
{% include "admin_button.html" with url=url %}
{% endif %}

View File

@ -2679,7 +2679,7 @@ function loadBuildTable(table, options) {
treeColumn: 1,
});
table.treegrid('expandAll');
$(table).treegrid('expandAll');
} else if (display_mode == 'calendar') {
if (!loaded_calendar) {

View File

@ -17,6 +17,7 @@ function loadNewsFeedTable(table, options={}, enableDelete=false) {
groupBy: false,
queryParams: {
ordering: 'published',
read: false,
},
paginationVAlign: 'bottom',
formatNoMatches: function() {
@ -49,10 +50,31 @@ function loadNewsFeedTable(table, options={}, enableDelete=false) {
field: 'published',
title: '{% trans "Published" %}',
sortable: 'true',
formatter: function(value) {
return renderDate(value);
formatter: function(value, row) {
var html = renderDate(value);
var buttons = getReadEditButton(row.pk, row.read);
html += `<div class='btn-group float-right' role='group'>${buttons}</div>`;
return html;
}
},
]
});
$(table).on('click', '.notification-read', function() {
var pk = $(this).attr('pk');
var url = `/api/news/${pk}/`;
inventreePut(url,
{
read: true,
},
{
method: 'PATCH',
success: function() {
$(table).bootstrapTable('refresh');
}
}
);
});
}

View File

@ -17,7 +17,7 @@ function loadNotificationTable(table, options={}, enableDelete=false) {
var params = options.params || {};
var read = typeof(params.read) === 'undefined' ? true : params.read;
setupFilterList(`notifications-${options.name}`, table);
setupFilterList(`notifications-${options.name}`, $(table));
$(table).inventreeTable({
url: options.url,
@ -157,38 +157,50 @@ function notificationCheck(force = false) {
* - panel_caller: this button was clicked in the notification panel
**/
function updateNotificationReadState(btn, panel_caller=false) {
var url = `/api/notifications/${btn.attr('pk')}/${btn.attr('target')}/`;
inventreePut(url, {}, {
method: 'POST',
success: function() {
// update the notification tables if they were declared
if (window.updateNotifications) {
window.updateNotifications();
}
// Determine 'read' status of the notification
var status = btn.attr('target') == 'read';
var pk = btn.attr('pk');
// update current notification count
var count = parseInt($('#notification-counter').html());
if (btn.attr('target') == 'read') {
count = count - 1;
} else {
count = count + 1;
}
var url = `/api/notifications/${pk}/`;
// Prevent negative notification count
if (count < 0) {
count = 0;
}
inventreePut(
url,
{
read: status,
},
{
method: 'PATCH',
success: function() {
// update the notification tables if they were declared
if (window.updateNotifications) {
window.updateNotifications();
}
// update notification indicator now
updateNotificationIndicator(count);
// update current notification count
var count = parseInt($('#notification-counter').html());
// remove notification if called from notification panel
if (panel_caller) {
btn.parent().parent().remove();
if (status) {
count = count - 1;
} else {
count = count + 1;
}
// Prevent negative notification count
if (count < 0) {
count = 0;
}
// update notification indicator now
updateNotificationIndicator(count);
// remove notification if called from notification panel
if (panel_caller) {
btn.parent().parent().remove();
}
}
}
});
);
};
/**