diff --git a/InvenTree/build/templates/build/detail.html b/InvenTree/build/templates/build/detail.html index d53122cdd1..31e9f38080 100644 --- a/InvenTree/build/templates/build/detail.html +++ b/InvenTree/build/templates/build/detail.html @@ -247,7 +247,9 @@ diff --git a/InvenTree/part/api.py b/InvenTree/part/api.py index 20447a4d26..dc521b42c6 100644 --- a/InvenTree/part/api.py +++ b/InvenTree/part/api.py @@ -177,6 +177,17 @@ class CategoryDetail(generics.RetrieveUpdateDestroyAPIView): return ctx + def update(self, request, *args, **kwargs): + + if 'starred' in request.data: + starred = str2bool(request.data.get('starred', False)) + + self.get_object().set_starred(request.user, starred) + + response = super().update(request, *args, **kwargs) + + return response + class CategoryParameterList(generics.ListAPIView): """ API endpoint for accessing a list of PartCategoryParameterTemplate objects. @@ -446,7 +457,7 @@ class PartDetail(generics.RetrieveUpdateDestroyAPIView): """ if 'starred' in request.data: - starred = str2bool(request.data.get('starred', None)) + starred = str2bool(request.data.get('starred', False)) self.get_object().set_starred(request.user, starred) diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index 981d143507..3b6d823ddc 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -35,8 +35,6 @@ class CategorySerializer(InvenTreeModelSerializer): def __init__(self, *args, **kwargs): - self.starred_categories = kwargs.pop('starred_categories', []) - super().__init__(*args, **kwargs) def get_starred(self, category): @@ -44,7 +42,7 @@ class CategorySerializer(InvenTreeModelSerializer): Return True if the category is directly "starred" by the current user """ - return category in self.starred_categories + return category in self.context.get('starred_categories', []) url = serializers.CharField(source='get_absolute_url', read_only=True) diff --git a/InvenTree/part/templates/part/category.html b/InvenTree/part/templates/part/category.html index 03369b093d..48677ee71d 100644 --- a/InvenTree/part/templates/part/category.html +++ b/InvenTree/part/templates/part/category.html @@ -20,15 +20,37 @@ {% include "admin_button.html" with url=url %} {% endif %} {% if category %} -{% if roles.part_category.change %} - +{% elif starred %} + +{% else %} + {% endif %} -{% if roles.part_category.delete %} - +{% if roles.part_category.change or roles.part_category.delete %} +
+ + +
{% endif %} {% endif %} {% if roles.part_category.add %} @@ -198,6 +220,14 @@ data: {{ parameters|safe }}, } ); + + $("#toggle-starred").click(function() { + toggleStar({ + url: '{% url "api-part-category-detail" category.pk %}', + button: '#category-star-icon' + }); + }); + {% endif %} enableSidebar('category'); diff --git a/InvenTree/part/templates/part/part_base.html b/InvenTree/part/templates/part/part_base.html index 21e26c64c6..a4087a3ece 100644 --- a/InvenTree/part/templates/part/part_base.html +++ b/InvenTree/part/templates/part/part_base.html @@ -320,7 +320,7 @@ $("#toggle-starred").click(function() { toggleStar({ - part: {{ part.id }}, + url: '{% url "api-part-detail" part.pk %}', button: '#part-star-icon', }); }); diff --git a/InvenTree/part/views.py b/InvenTree/part/views.py index de4bbf5443..56ab98004d 100644 --- a/InvenTree/part/views.py +++ b/InvenTree/part/views.py @@ -1470,18 +1470,29 @@ class CategoryDetail(InvenTreeRoleMixin, DetailView): if category: cascade = kwargs.get('cascade', True) + # Prefetch parts parameters parts_parameters = category.prefetch_parts_parameters(cascade=cascade) + # Get table headers (unique parameters names) context['headers'] = category.get_unique_parameters(cascade=cascade, prefetch=parts_parameters) + # Insert part information context['headers'].insert(0, 'description') context['headers'].insert(0, 'part') + # Get parameters data context['parameters'] = category.get_parts_parameters(cascade=cascade, prefetch=parts_parameters) + # Insert "starred" information + context['starred'] = category.is_starred_by(self.request.user) + context['starred_directly'] = context['starred'] and category.is_starred_by( + self.request.user, + include_parents=False, + ) + return context diff --git a/InvenTree/templates/js/translated/part.js b/InvenTree/templates/js/translated/part.js index b87e90dcc8..e00f04aebd 100644 --- a/InvenTree/templates/js/translated/part.js +++ b/InvenTree/templates/js/translated/part.js @@ -378,19 +378,18 @@ function duplicatePart(pk, options={}) { * * options: * - button: ID of the button (default = '#part-star-icon') - * - part: pk of the part object + * - URL: API url of the object * - user: pk of the user */ function toggleStar(options) { - var url = `/api/part/${options.part}/`; - - inventreeGet(url, {}, { + inventreeGet(options.url, {}, { success: function(response) { + var starred = response.starred; inventreePut( - url, + options.url, { starred: !starred, }, @@ -399,16 +398,16 @@ function toggleStar(options) { success: function(response) { if (response.starred) { $(options.button).removeClass('fa fa-bell-slash').addClass('fas fa-bell icon-green'); - $(options.button).attr('title', '{% trans "You are subscribed to notifications for this part" %}'); + $(options.button).attr('title', '{% trans "You are subscribed to notifications for this item" %}'); - showMessage('{% trans "You have subscribed to notifications for this part" %}', { + showMessage('{% trans "You have subscribed to notifications for this item" %}', { style: 'success', }); } else { $(options.button).removeClass('fas fa-bell icon-green').addClass('fa fa-bell-slash'); - $(options.button).attr('title', '{% trans "Subscribe to notifications for this part" %}'); + $(options.button).attr('title', '{% trans "Subscribe to notifications for this item" %}'); - showMessage('{% trans "You have unsubscribed to notifications for this part" %}', { + showMessage('{% trans "You have unsubscribed to notifications for this item" %}', { style: 'warning', }); } @@ -453,7 +452,7 @@ function makePartIcons(part) { } if (part.starred) { - html += makeIconBadge('fa-star', '{% trans "Starred part" %}'); + html += makeIconBadge('fa-bell icon-green', '{% trans "Subscribed part" %}'); } if (part.salable) { @@ -461,7 +460,7 @@ function makePartIcons(part) { } if (!part.active) { - html += `{% trans "Inactive" %}`; + html += `{% trans "Inactive" %} `; } return html; @@ -1268,10 +1267,17 @@ function loadPartCategoryTable(table, options) { switchable: true, sortable: true, formatter: function(value, row) { - return renderLink( + + var html = renderLink( value, `/part/category/${row.pk}/` ); + + if (row.starred) { + html += makeIconBadge('fa-bell icon-green', '{% trans "Subscribed category" %}'); + } + + return html; } }, { diff --git a/InvenTree/templates/js/translated/table_filters.js b/InvenTree/templates/js/translated/table_filters.js index 4d12f69780..537adefee9 100644 --- a/InvenTree/templates/js/translated/table_filters.js +++ b/InvenTree/templates/js/translated/table_filters.js @@ -103,6 +103,10 @@ function getAvailableTableFilters(tableKey) { title: '{% trans "Include subcategories" %}', description: '{% trans "Include subcategories" %}', }, + starred: { + type: 'bool', + title: '{% trans "Subscribed" %}', + }, }; } @@ -368,7 +372,7 @@ function getAvailableTableFilters(tableKey) { }, starred: { type: 'bool', - title: '{% trans "Starred" %}', + title: '{% trans "Subscribed" %}', }, salable: { type: 'bool',