feat(backend): rename filter (#12672)

* feat(backend): rename filter

* fix filter name

* fix parameter used in test

* remove temporary fix added in https://github.com/inventree/InvenTree/pull/12668

* remove unneeded filter redef

* add changelog

---------

Co-authored-by: Oliver <oliver.henry.walters@gmail.com>
This commit is contained in:
Matthias Mair
2026-08-22 07:46:26 +10:00
committed by GitHub
co-authored by Oliver
parent a435738507
commit 9d19b32b16
11 changed files with 25 additions and 25 deletions
+2
View File
@@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Breaking Changes ### Breaking Changes
- [#12507](https://github.com/inventree/InvenTree/pull/12507) calling an invalid or repeated state transition now raises a ValidationError. Plugins implementing state transitions should evaluate the PR and adapt their usage of transitions to gain the new safeguards. - [#12507](https://github.com/inventree/InvenTree/pull/12507) calling an invalid or repeated state transition now raises a ValidationError. Plugins implementing state transitions should evaluate the PR and adapt their usage of transitions to gain the new safeguards.
- [#12672](https://github.com/inventree/InvenTree/pull/12672) renames the newly added `tags` filter from 1.4.0 (https://github.com/inventree/InvenTree/pull/12077) to `tag_name` to remove a nameclash.
### Added ### Added
@@ -1,11 +1,14 @@
"""InvenTree API version information.""" """InvenTree API version information."""
# InvenTree API version # InvenTree API version
INVENTREE_API_VERSION = 533 INVENTREE_API_VERSION = 534
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about.""" """Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
INVENTREE_API_TEXT = """ INVENTREE_API_TEXT = """
v534 -> 2026-08-21 : https://github.com/inventree/InvenTree/pull/12672
- rename 'tags' filter to 'tag_name' to avoid name clash with the 'tags' field on various API endpoints
v533 -> 2026-08-18 : https://github.com/inventree/InvenTree/pull/12317 v533 -> 2026-08-18 : https://github.com/inventree/InvenTree/pull/12317
- Refactors the PurchaseOrder, PurchaseOrderLineItem and PurchaseOrderExtraLine API endpoints to use DRF viewsets - Refactors the PurchaseOrder, PurchaseOrderLineItem and PurchaseOrderExtraLine API endpoints to use DRF viewsets
+1 -1
View File
@@ -309,7 +309,7 @@ class BuildFilter(FilterSet):
return queryset return queryset
tags = common.filters.TagsFilter() tag_name = common.filters.TagsFilter()
class BuildMixin: class BuildMixin:
+1 -1
View File
@@ -786,7 +786,7 @@ class AttachmentFilter(FilterSet):
return queryset.exclude(attachment=None).exclude(attachment='') return queryset.exclude(attachment=None).exclude(attachment='')
return queryset.filter(Q(attachment=None) | Q(attachment='')).distinct() return queryset.filter(Q(attachment=None) | Q(attachment='')).distinct()
tags = common.filters.TagsFilter() tag_name = common.filters.TagsFilter()
class AttachmentMixin: class AttachmentMixin:
+2 -5
View File
@@ -104,19 +104,16 @@ class TagsFilter(rest_filters.CharFilter):
?tags=apple,banana returns only items tagged with both 'apple' AND 'banana' ?tags=apple,banana returns only items tagged with both 'apple' AND 'banana'
""" """
_is_viewset: bool = False def __init__(self, *args, **kwargs):
def __init__(self, is_viewset: bool = False, *args, **kwargs):
"""Initialize the filter.""" """Initialize the filter."""
if 'label' not in kwargs: if 'label' not in kwargs:
kwargs['label'] = _('Tags') kwargs['label'] = _('Tags')
self._is_viewset = is_viewset
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def filter(self, qs, value): def filter(self, qs, value):
"""Filter queryset to items matching all provided tag names.""" """Filter queryset to items matching all provided tag names."""
if not value or (self._is_viewset and InvenTree.helpers.is_bool(value)): if not value:
return qs return qs
tag_names = [t.strip() for t in value.split(',') if t.strip()] tag_names = [t.strip() for t in value.split(',') if t.strip()]
+6 -6
View File
@@ -1391,7 +1391,7 @@ class TagAPITests(InvenTreeAPITestCase):
"""Filtering parts by a single tag should return only parts with that tag.""" """Filtering parts by a single tag should return only parts with that tag."""
url = reverse('api-part-list') url = reverse('api-part-list')
response = self.get(url, data={'tags': 'apple'}) response = self.get(url, data={'tag_name': 'apple'})
pks = {p['pk'] for p in response.data} pks = {p['pk'] for p in response.data}
self.assertIn(self.part_a.pk, pks) self.assertIn(self.part_a.pk, pks)
@@ -1402,7 +1402,7 @@ class TagAPITests(InvenTreeAPITestCase):
"""Filtering by comma-separated tags should return only parts that have ALL tags.""" """Filtering by comma-separated tags should return only parts that have ALL tags."""
url = reverse('api-part-list') url = reverse('api-part-list')
response = self.get(url, data={'tags': 'apple,banana'}) response = self.get(url, data={'tag_name': 'apple,banana'})
pks = {p['pk'] for p in response.data} pks = {p['pk'] for p in response.data}
self.assertIn(self.part_a.pk, pks) self.assertIn(self.part_a.pk, pks)
@@ -1413,7 +1413,7 @@ class TagAPITests(InvenTreeAPITestCase):
"""Tag filtering should be case-insensitive.""" """Tag filtering should be case-insensitive."""
url = reverse('api-part-list') url = reverse('api-part-list')
response = self.get(url, data={'tags': 'APPLE'}) response = self.get(url, data={'tag_name': 'APPLE'})
pks = {p['pk'] for p in response.data} pks = {p['pk'] for p in response.data}
self.assertIn(self.part_a.pk, pks) self.assertIn(self.part_a.pk, pks)
@@ -1423,14 +1423,14 @@ class TagAPITests(InvenTreeAPITestCase):
"""Filtering by a tag that no part has should return an empty result set.""" """Filtering by a tag that no part has should return an empty result set."""
url = reverse('api-part-list') url = reverse('api-part-list')
response = self.get(url, data={'tags': 'doesnotexist'}) response = self.get(url, data={'tag_name': 'doesnotexist'})
self.assertEqual(len(response.data), 0) self.assertEqual(len(response.data), 0)
def test_part_filter_tag_whitespace(self): def test_part_filter_tag_whitespace(self):
"""Whitespace around comma-separated tag names should be ignored.""" """Whitespace around comma-separated tag names should be ignored."""
url = reverse('api-part-list') url = reverse('api-part-list')
response = self.get(url, data={'tags': ' apple , banana '}) response = self.get(url, data={'tag_name': ' apple , banana '})
pks = {p['pk'] for p in response.data} pks = {p['pk'] for p in response.data}
self.assertIn(self.part_a.pk, pks) self.assertIn(self.part_a.pk, pks)
@@ -1484,7 +1484,7 @@ class TagAPITests(InvenTreeAPITestCase):
""" """
url = reverse('api-part-list') url = reverse('api-part-list')
response = self.get(url, data={'tags': 'true'}, expected_code=200) response = self.get(url, data={'tag_name': 'true'}, expected_code=200)
self.assertEqual(response.data, []) self.assertEqual(response.data, [])
+3 -3
View File
@@ -47,7 +47,7 @@ class CompanyFilter(FilterSet):
model = Company model = Company
fields = ['is_customer', 'is_manufacturer', 'is_supplier', 'name', 'active'] fields = ['is_customer', 'is_manufacturer', 'is_supplier', 'name', 'active']
tags = common.filters.TagsFilter() tag_name = common.filters.TagsFilter()
class CompanyMixin(OutputOptionsMixin): class CompanyMixin(OutputOptionsMixin):
@@ -152,7 +152,7 @@ class ManufacturerPartFilter(FilterSet):
field_name='manufacturer__active', label=_('Manufacturer is Active') field_name='manufacturer__active', label=_('Manufacturer is Active')
) )
tags = common.filters.TagsFilter() tag_name = common.filters.TagsFilter()
class ManufacturerOutputOptions(OutputConfiguration): class ManufacturerOutputOptions(OutputConfiguration):
@@ -308,7 +308,7 @@ class SupplierPartFilter(FilterSet):
else: else:
return queryset.exclude(in_stock__gt=0) return queryset.exclude(in_stock__gt=0)
tags = common.filters.TagsFilter() tag_name = common.filters.TagsFilter()
class SupplierPartOutputOptions(OutputConfiguration): class SupplierPartOutputOptions(OutputConfiguration):
+2 -4
View File
@@ -286,7 +286,7 @@ class OrderFilter(FilterSet):
return queryset.filter(q1 | q2 | q3 | q4).distinct() return queryset.filter(q1 | q2 | q3 | q4).distinct()
tags = common.filters.TagsFilter() tag_name = common.filters.TagsFilter()
class LineItemFilter(FilterSet): class LineItemFilter(FilterSet):
@@ -374,8 +374,6 @@ class PurchaseOrderFilter(OrderFilter):
""" """
return queryset.filter(lines__build_order=build).distinct() return queryset.filter(lines__build_order=build).distinct()
tags = common.filters.TagsFilter(is_viewset=True)
class PurchaseOrderOutputOptions(OutputConfiguration): class PurchaseOrderOutputOptions(OutputConfiguration):
"""Output options for the PurchaseOrder endpoint.""" """Output options for the PurchaseOrder endpoint."""
@@ -1546,7 +1544,7 @@ class SalesOrderShipmentFilter(FilterSet):
return queryset.filter(q1 | q2).distinct() return queryset.filter(q1 | q2).distinct()
tags = common.filters.TagsFilter() tag_name = common.filters.TagsFilter()
class SalesOrderShipmentMixin: class SalesOrderShipmentMixin:
+1 -1
View File
@@ -936,7 +936,7 @@ class PartFilter(FilterSet):
consumable = rest_filters.BooleanFilter() consumable = rest_filters.BooleanFilter()
tags = common.filters.TagsFilter() tag_name = common.filters.TagsFilter()
# Created date filters # Created date filters
created_before = InvenTreeDateFilter( created_before = InvenTreeDateFilter(
+2 -2
View File
@@ -412,7 +412,7 @@ class StockLocationFilter(FilterSet):
return queryset return queryset
tags = common.filters.TagsFilter() tag_name = common.filters.TagsFilter()
class StockLocationMixin(SerializerContextMixin): class StockLocationMixin(SerializerContextMixin):
@@ -1067,7 +1067,7 @@ class StockFilter(FilterSet):
children = loc_obj.getUniqueChildren() children = loc_obj.getUniqueChildren()
return queryset.filter(location__in=children) return queryset.filter(location__in=children)
tags = common.filters.TagsFilter() tag_name = common.filters.TagsFilter()
class StockApiMixin(SerializerContextMixin): class StockApiMixin(SerializerContextMixin):
@@ -408,7 +408,7 @@ export function TagsFilter({
modelType?: ModelType; modelType?: ModelType;
}): TableFilter { }): TableFilter {
return { return {
name: 'tags', name: 'tag_name',
label: t`Tags`, label: t`Tags`,
description: t`Filter by tags`, description: t`Filter by tags`,
placeholder: t`Select tags`, placeholder: t`Select tags`,