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
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."""
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
- Major refactor of the report template / report printing interface
- This is a *breaking change* to the report template API

View File

@ -115,6 +115,31 @@ class InvenTreeMetadata(SimpleMetadata):
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):
"""Override get_serializer_info so that we can add 'default' values to any fields whose Meta.model specifies a default value."""
self.serializer = serializer
@ -134,7 +159,12 @@ class InvenTreeMetadata(SimpleMetadata):
model_class = None
# 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:
model_class = serializer.Meta.model
@ -165,10 +195,12 @@ class InvenTreeMetadata(SimpleMetadata):
elif name in model_default_values:
serializer_info[name]['default'] = model_default_values[name]
for attr in extra_attributes:
if attr not in serializer_info[name]:
if hasattr(field, attr):
serializer_info[name][attr] = getattr(field, attr)
for field_key, model_key in extra_attributes.items():
field_value = serializer_info[name].get(field_key, None)
model_value = getattr(field, model_key, None)
if value := self.override_value(name, field_value, model_value):
serializer_info[name][field_key] = value
# Iterate through relations
for name, relation in model_fields.relations.items():
@ -186,13 +218,12 @@ class InvenTreeMetadata(SimpleMetadata):
relation.model_field.get_limit_choices_to()
)
for attr in extra_attributes:
if attr not in serializer_info[name] and hasattr(
relation.model_field, attr
):
serializer_info[name][attr] = getattr(
relation.model_field, attr
)
for field_key, model_key in extra_attributes.items():
field_value = serializer_info[name].get(field_key, None)
model_value = getattr(relation.model_field, model_key, None)
if value := self.override_value(name, field_value, model_value):
serializer_info[name][field_key] = value
if name in model_default_values:
serializer_info[name]['default'] = model_default_values[name]

View File

@ -851,7 +851,9 @@ class PartSerializer(
starred = serializers.SerializerMethodField()
# 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_min = InvenTree.serializers.InvenTreeMoneySerializer(

View File

@ -541,13 +541,58 @@ class PartOptionsAPITest(InvenTreeAPITestCase):
category = actions['category']
self.assertEqual(category['type'], 'related field')
self.assertTrue(category['required'])
self.assertFalse(category['required'])
self.assertFalse(category['read_only'])
self.assertEqual(category['label'], 'Category')
self.assertEqual(category['model'], 'partcategory')
self.assertEqual(category['api_url'], reverse('api-part-category-list'))
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):
"""Test the PartCategory API OPTIONS endpoint."""
actions = self.getActions(reverse('api-part-category-list'))