2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-28 19:46:46 +00:00

Updates for metadata information (#7343)

* Updates for metadata information

- Override 'label' values with 'verbose_name' values
- Only if 'label' is *not* translated, but 'verbose_name' is
- Allows the translated model fields name to be pushed through to the metadata framework

* Remove unused import

* Add unit testing for metadata lookup

* Update serializer: allow 'category' to be blank

* Bump API version

* Fix for unit test
This commit is contained in:
Oliver 2024-05-27 16:55:08 +10:00 committed by GitHub
parent 797a0c10df
commit ea6bdd3ca6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 96 additions and 15 deletions

View File

@ -1,12 +1,15 @@
"""InvenTree API version information.""" """InvenTree API version information."""
# InvenTree API version # InvenTree API version
INVENTREE_API_VERSION = 201 INVENTREE_API_VERSION = 202
"""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 = """
v202 - 2024-05-27 : https://github.com/inventree/InvenTree/pull/7343
- Adjust "required" attribute of Part.category field to be optional
v201 - 2024-05-21 : https://github.com/inventree/InvenTree/pull/7074 v201 - 2024-05-21 : https://github.com/inventree/InvenTree/pull/7074
- Major refactor of the report template / report printing interface - Major refactor of the report template / report printing interface
- This is a *breaking change* to the report template API - This is a *breaking change* to the report template API

View File

@ -115,6 +115,31 @@ class InvenTreeMetadata(SimpleMetadata):
return metadata return metadata
def override_value(self, field_name, field_value, model_value):
"""Override a value on the serializer with a matching value for the model.
This is used to override the serializer values with model values,
if (and *only* if) the model value should take precedence.
The values are overridden under the following conditions:
- field_value is None
- model_value is callable, and field_value is not (this indicates that the model value is translated)
- model_value is not a string, and field_value is a string (this indicates that the model value is translated)
"""
if model_value and not field_value:
return model_value
if field_value and not model_value:
return field_value
if callable(model_value) and not callable(field_value):
return model_value
if type(model_value) is not str and type(field_value) is str:
return model_value
return field_value
def get_serializer_info(self, serializer): def get_serializer_info(self, serializer):
"""Override get_serializer_info so that we can add 'default' values to any fields whose Meta.model specifies a default value.""" """Override get_serializer_info so that we can add 'default' values to any fields whose Meta.model specifies a default value."""
self.serializer = serializer self.serializer = serializer
@ -134,7 +159,12 @@ class InvenTreeMetadata(SimpleMetadata):
model_class = None model_class = None
# Attributes to copy extra attributes from the model to the field (if they don't exist) # Attributes to copy extra attributes from the model to the field (if they don't exist)
extra_attributes = ['help_text', 'max_length'] # Note that the attributes may be named differently on the underlying model!
extra_attributes = {
'help_text': 'help_text',
'max_length': 'max_length',
'label': 'verbose_name',
}
try: try:
model_class = serializer.Meta.model model_class = serializer.Meta.model
@ -165,10 +195,12 @@ class InvenTreeMetadata(SimpleMetadata):
elif name in model_default_values: elif name in model_default_values:
serializer_info[name]['default'] = model_default_values[name] serializer_info[name]['default'] = model_default_values[name]
for attr in extra_attributes: for field_key, model_key in extra_attributes.items():
if attr not in serializer_info[name]: field_value = serializer_info[name].get(field_key, None)
if hasattr(field, attr): model_value = getattr(field, model_key, None)
serializer_info[name][attr] = getattr(field, attr)
if value := self.override_value(name, field_value, model_value):
serializer_info[name][field_key] = value
# Iterate through relations # Iterate through relations
for name, relation in model_fields.relations.items(): for name, relation in model_fields.relations.items():
@ -186,13 +218,12 @@ class InvenTreeMetadata(SimpleMetadata):
relation.model_field.get_limit_choices_to() relation.model_field.get_limit_choices_to()
) )
for attr in extra_attributes: for field_key, model_key in extra_attributes.items():
if attr not in serializer_info[name] and hasattr( field_value = serializer_info[name].get(field_key, None)
relation.model_field, attr model_value = getattr(relation.model_field, model_key, None)
):
serializer_info[name][attr] = getattr( if value := self.override_value(name, field_value, model_value):
relation.model_field, attr serializer_info[name][field_key] = value
)
if name in model_default_values: if name in model_default_values:
serializer_info[name]['default'] = model_default_values[name] serializer_info[name]['default'] = model_default_values[name]

View File

@ -851,7 +851,9 @@ class PartSerializer(
starred = serializers.SerializerMethodField() starred = serializers.SerializerMethodField()
# PrimaryKeyRelated fields (Note: enforcing field type here results in much faster queries, somehow...) # PrimaryKeyRelated fields (Note: enforcing field type here results in much faster queries, somehow...)
category = serializers.PrimaryKeyRelatedField(queryset=PartCategory.objects.all()) category = serializers.PrimaryKeyRelatedField(
queryset=PartCategory.objects.all(), required=False, allow_null=True
)
# Pricing fields # Pricing fields
pricing_min = InvenTree.serializers.InvenTreeMoneySerializer( pricing_min = InvenTree.serializers.InvenTreeMoneySerializer(

View File

@ -541,13 +541,58 @@ class PartOptionsAPITest(InvenTreeAPITestCase):
category = actions['category'] category = actions['category']
self.assertEqual(category['type'], 'related field') self.assertEqual(category['type'], 'related field')
self.assertTrue(category['required']) self.assertFalse(category['required'])
self.assertFalse(category['read_only']) self.assertFalse(category['read_only'])
self.assertEqual(category['label'], 'Category') self.assertEqual(category['label'], 'Category')
self.assertEqual(category['model'], 'partcategory') self.assertEqual(category['model'], 'partcategory')
self.assertEqual(category['api_url'], reverse('api-part-category-list')) self.assertEqual(category['api_url'], reverse('api-part-category-list'))
self.assertEqual(category['help_text'], 'Part category') self.assertEqual(category['help_text'], 'Part category')
def test_part_label_translation(self):
"""Test that 'label' values are correctly translated."""
response = self.options(reverse('api-part-list'))
labels = {
'IPN': 'IPN',
'category': 'Category',
'assembly': 'Assembly',
'ordering': 'On Order',
'stock_item_count': 'Stock Items',
}
help_text = {
'IPN': 'Internal Part Number',
'active': 'Is this part active?',
'barcode_hash': 'Unique hash of barcode data',
'category': 'Part category',
}
# Check basic return values
for field, value in labels.items():
self.assertEqual(response.data['actions']['POST'][field]['label'], value)
for field, value in help_text.items():
self.assertEqual(
response.data['actions']['POST'][field]['help_text'], value
)
# Check again, with a different locale
response = self.options(
reverse('api-part-list'), headers={'Accept-Language': 'de'}
)
translated = {
'IPN': 'IPN (Interne Produktnummer)',
'category': 'Kategorie',
'assembly': 'Baugruppe',
'ordering': 'Bestellt',
'stock_item_count': 'Lagerartikel',
}
for field, value in translated.items():
label = response.data['actions']['POST'][field]['label']
self.assertEqual(label, value)
def test_category(self): def test_category(self):
"""Test the PartCategory API OPTIONS endpoint.""" """Test the PartCategory API OPTIONS endpoint."""
actions = self.getActions(reverse('api-part-category-list')) actions = self.getActions(reverse('api-part-category-list'))