2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-06-18 13:05:42 +00:00

Add 'Tag' management (#4367)

* 'Tag' management
Fixes #83

* Add for ManufacturerPart, SupplierPart

* Add tags for StockLocation, StockItem

* fix serializer definition

* add migrations

* update pre-commit

* bump dependencies

* revert updates

* set version for bugbear

* remove bugbear

* readd bugbear remove isort

* and remove bugbear again

* remove bugbear

* make tag fields not required

* add ruleset

* Merge migrations

* fix migrations

* add unittest for detail

* test tag add

* order api

* reduce database access

* add tag modification test

* use overriden serializer to ensuer the manager is always available

* fix typo

* fix serializer

* increae query thershold by 1

* move tag serializer

* fix migrations

* content_types are changing between tests - removing them

* remove unneeded fixture

* Add basic docs

* bump API version

* add api access to the docs

* add python code

* Add tags to search and filters for all models
This commit is contained in:
Matthias Mair
2023-05-04 01:02:48 +02:00
committed by GitHub
parent baaa147fd0
commit f5c2591fd4
22 changed files with 258 additions and 14 deletions

View File

@ -970,6 +970,10 @@ class PartFilter(rest_filters.FilterSet):
virtual = rest_filters.BooleanFilter()
tags_name = rest_filters.CharFilter(field_name='tags__name', lookup_expr='iexact')
tags_slug = rest_filters.CharFilter(field_name='tags__slug', lookup_expr='iexact')
class PartMixin:
"""Mixin class for Part API endpoints"""
@ -1240,6 +1244,8 @@ class PartList(PartMixin, APIDownloadMixin, ListCreateAPI):
'category__name',
'manufacturer_parts__MPN',
'supplier_parts__SKU',
'tags__name',
'tags__slug',
]

View File

@ -0,0 +1,20 @@
# Generated by Django 3.2.18 on 2023-04-27 20:33
from django.db import migrations
import taggit.managers
class Migration(migrations.Migration):
dependencies = [
('taggit', '0005_auto_20220424_2025'),
('part', '0105_alter_part_notes'),
]
operations = [
migrations.AddField(
model_name='part',
name='tags',
field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags'),
),
]

View File

@ -31,6 +31,7 @@ from mptt.exceptions import InvalidMove
from mptt.managers import TreeManager
from mptt.models import MPTTModel, TreeForeignKey
from stdimage.models import StdImageField
from taggit.managers import TaggableManager
import common.models
import common.settings
@ -336,6 +337,7 @@ class PartManager(TreeManager):
'category__parent',
'stock_items',
'builds',
'tags',
)
@ -378,6 +380,7 @@ class Part(InvenTreeBarcodeMixin, InvenTreeNotesMixin, MetadataMixin, MPTTModel)
"""
objects = PartManager()
tags = TaggableManager()
class Meta:
"""Metaclass defines extra model properties"""

View File

@ -15,6 +15,7 @@ from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from sql_util.utils import SubqueryCount, SubquerySum
from taggit.serializers import TagListSerializerField
import common.models
import company.models
@ -31,8 +32,9 @@ from InvenTree.serializers import (DataFileExtractSerializer,
InvenTreeDecimalField,
InvenTreeImageSerializerField,
InvenTreeModelSerializer,
InvenTreeMoneySerializer, RemoteImageMixin,
UserSerializer)
InvenTreeMoneySerializer,
InvenTreeTagModelSerializer,
RemoteImageMixin, UserSerializer)
from InvenTree.status_codes import BuildStatus
from InvenTree.tasks import offload_task
@ -403,7 +405,7 @@ class InitialSupplierSerializer(serializers.Serializer):
return data
class PartSerializer(RemoteImageMixin, InvenTreeModelSerializer):
class PartSerializer(RemoteImageMixin, InvenTreeTagModelSerializer):
"""Serializer for complete detail information of a part.
Used when displaying all details of a single component.
@ -464,13 +466,17 @@ class PartSerializer(RemoteImageMixin, InvenTreeModelSerializer):
'duplicate',
'initial_stock',
'initial_supplier',
'copy_category_parameters'
'copy_category_parameters',
'tags',
]
read_only_fields = [
'barcode_hash',
]
tags = TagListSerializerField(required=False)
def __init__(self, *args, **kwargs):
"""Custom initialization method for PartSerializer:

View File

@ -1425,6 +1425,7 @@ class PartDetailTests(PartAPITestBase):
'name': 'my test api part',
'description': 'a part created with the API',
'category': 1,
'tags': '["tag1", "tag2"]',
}
)
@ -1438,6 +1439,8 @@ class PartDetailTests(PartAPITestBase):
part = Part.objects.get(pk=pk)
self.assertEqual(part.name, 'my test api part')
self.assertEqual(part.tags.count(), 2)
self.assertEqual([a.name for a in part.tags.order_by('slug')], ['tag1', 'tag2'])
# Edit the part
url = reverse('api-part-detail', kwargs={'pk': pk})
@ -1468,6 +1471,13 @@ class PartDetailTests(PartAPITestBase):
self.assertEqual(response.status_code, 200)
# Try to remove a tag
response = self.patch(url, {
'tags': ['tag1',],
})
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['tags'], ['tag1'])
# Try to remove the part
response = self.delete(url)

View File

@ -152,7 +152,7 @@ class CategoryTest(TestCase):
def test_parameters(self):
"""Test that the Category parameters are correctly fetched."""
# Check number of SQL queries to iterate other parameters
with self.assertNumQueries(8):
with self.assertNumQueries(9):
# Prefetch: 3 queries (parts, parameters and parameters_template)
fasteners = self.fasteners.prefetch_parts_parameters()
# Iterate through all parts and parameters