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:
@ -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',
|
||||
]
|
||||
|
||||
|
||||
|
20
InvenTree/part/migrations/0106_part_tags.py
Normal file
20
InvenTree/part/migrations/0106_part_tags.py
Normal 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'),
|
||||
),
|
||||
]
|
@ -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"""
|
||||
|
@ -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:
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user