mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-28 11:36:44 +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:
parent
797a0c10df
commit
ea6bdd3ca6
@ -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
|
||||||
|
@ -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]
|
||||||
|
@ -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(
|
||||||
|
@ -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'))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user