diff --git a/InvenTree/common/api.py b/InvenTree/common/api.py index 4b825549a5..e87194446e 100644 --- a/InvenTree/common/api.py +++ b/InvenTree/common/api.py @@ -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\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\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'), diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index a3c5a112c8..6bf4bd68f4 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -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 diff --git a/InvenTree/common/serializers.py b/InvenTree/common/serializers.py index 3965998950..b39d19cfc0 100644 --- a/InvenTree/common/serializers.py +++ b/InvenTree/common/serializers.py @@ -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.""" diff --git a/InvenTree/part/templates/part/category.html b/InvenTree/part/templates/part/category.html index 530e68d47f..b27ad51560 100644 --- a/InvenTree/part/templates/part/category.html +++ b/InvenTree/part/templates/part/category.html @@ -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 %} diff --git a/InvenTree/templates/js/translated/build.js b/InvenTree/templates/js/translated/build.js index ec483da3b4..f23b24e817 100644 --- a/InvenTree/templates/js/translated/build.js +++ b/InvenTree/templates/js/translated/build.js @@ -2679,7 +2679,7 @@ function loadBuildTable(table, options) { treeColumn: 1, }); - table.treegrid('expandAll'); + $(table).treegrid('expandAll'); } else if (display_mode == 'calendar') { if (!loaded_calendar) { diff --git a/InvenTree/templates/js/translated/news.js b/InvenTree/templates/js/translated/news.js index 4069177c60..ccbb5e607e 100644 --- a/InvenTree/templates/js/translated/news.js +++ b/InvenTree/templates/js/translated/news.js @@ -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 += `
${buttons}
`; + 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'); + } + } + ); + }); } diff --git a/InvenTree/templates/js/translated/notification.js b/InvenTree/templates/js/translated/notification.js index c511010b5c..b6957741b4 100644 --- a/InvenTree/templates/js/translated/notification.js +++ b/InvenTree/templates/js/translated/notification.js @@ -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(); + } } } - }); + ); }; /**