Add field info to user serializer (#7518)

* Add field info to user serializer

* Bump API version

* Adjust metadata translation lookup

* Improve field metadata introspection

* Add unit test
This commit is contained in:
Oliver 2024-06-26 23:11:54 +10:00 committed by GitHub
parent c1b2cbef5e
commit f67147587a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 74 additions and 7 deletions

View File

@ -1,11 +1,14 @@
"""InvenTree API version information."""
# InvenTree API version
INVENTREE_API_VERSION = 209
INVENTREE_API_VERSION = 210
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
INVENTREE_API_TEXT = """
v210 - 2024-06-26 : https://github.com/inventree/InvenTree/pull/7518
- Adds translateable text to User API fields
v209 - 2024-06-26 : https://github.com/inventree/InvenTree/pull/7514
- Add "top_level" filter to PartCategory API endpoint
- Add "top_level" filter to StockLocation API endpoint

View File

@ -115,9 +115,14 @@ class InvenTreeMetadata(SimpleMetadata):
return metadata
def override_value(self, field_name, field_value, model_value):
def override_value(self, field_name: str, field_key: str, field_value, model_value):
"""Override a value on the serializer with a matching value for the model.
Often, the serializer field will point to an underlying model field,
which contains extra information (which is translated already).
Rather than duplicating this information in the serializer, we can extract it from the model.
This is used to override the serializer values with model values,
if (and *only* if) the model value should take precedence.
@ -125,6 +130,12 @@ class InvenTreeMetadata(SimpleMetadata):
- 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)
Arguments:
- field_name: The name of the field
- field_key: The property key to override
- field_value: The value of the field (if available)
- model_value: The equivalent value of the model (if available)
"""
if model_value and not field_value:
return model_value
@ -132,10 +143,15 @@ class InvenTreeMetadata(SimpleMetadata):
if field_value and not model_value:
return field_value
# Callable values will be evaluated later
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:
if callable(field_value) and not callable(model_value):
return field_value
# Prioritize translated text over raw string values
if type(field_value) is str and type(model_value) is not str:
return model_value
return field_value
@ -197,10 +213,12 @@ class InvenTreeMetadata(SimpleMetadata):
serializer_info[name]['default'] = model_default_values[name]
for field_key, model_key in extra_attributes.items():
field_value = serializer_info[name].get(field_key, None)
field_value = getattr(serializer.fields[name], field_key, None)
model_value = getattr(field, model_key, None)
if value := self.override_value(name, field_value, model_value):
if value := self.override_value(
name, field_key, field_value, model_value
):
serializer_info[name][field_key] = value
# Iterate through relations
@ -220,10 +238,12 @@ class InvenTreeMetadata(SimpleMetadata):
)
for field_key, model_key in extra_attributes.items():
field_value = serializer_info[name].get(field_key, None)
field_value = getattr(serializer.fields[name], field_key, None)
model_value = getattr(relation.model_field, model_key, None)
if value := self.override_value(name, field_value, model_value):
if value := self.override_value(
name, field_key, field_value, model_value
):
serializer_info[name][field_key] = value
if name in model_default_values:

View File

@ -404,6 +404,17 @@ class UserSerializer(InvenTreeModelSerializer):
read_only_fields = ['username']
username = serializers.CharField(label=_('Username'), help_text=_('Username'))
first_name = serializers.CharField(
label=_('First Name'), help_text=_('First name of the user')
)
last_name = serializers.CharField(
label=_('Last Name'), help_text=_('Last name of the user')
)
email = serializers.EmailField(
label=_('Email'), help_text=_('Email address of the user')
)
class ExendedUserSerializer(UserSerializer):
"""Serializer for a User with a bit more info."""
@ -424,6 +435,16 @@ class ExendedUserSerializer(UserSerializer):
read_only_fields = UserSerializer.Meta.read_only_fields + ['groups']
is_staff = serializers.BooleanField(
label=_('Staff'), help_text=_('Does this user have staff permissions')
)
is_superuser = serializers.BooleanField(
label=_('Superuser'), help_text=_('Is this user a superuser')
)
is_active = serializers.BooleanField(
label=_('Active'), help_text=_('Is this user account active')
)
def validate(self, attrs):
"""Expanded validation for changing user role."""
# Check if is_staff or is_superuser is in attrs

View File

@ -12,6 +12,29 @@ from users.models import ApiToken
class UserAPITests(InvenTreeAPITestCase):
"""Tests for user API endpoints."""
def test_user_options(self):
"""Tests for the User OPTIONS request."""
self.assignRole('admin.add')
response = self.options(reverse('api-user-list'), expected_code=200)
fields = response.data['actions']['POST']
# Check some of the field values
self.assertEqual(fields['username']['label'], 'Username')
self.assertEqual(fields['email']['label'], 'Email')
self.assertEqual(fields['email']['help_text'], 'Email address of the user')
self.assertEqual(fields['is_active']['label'], 'Active')
self.assertEqual(
fields['is_active']['help_text'], 'Is this user account active'
)
self.assertEqual(fields['is_staff']['label'], 'Staff')
self.assertEqual(
fields['is_staff']['help_text'], 'Does this user have staff permissions'
)
def test_user_api(self):
"""Tests for User API endpoints."""
response = self.get(reverse('api-user-list'), expected_code=200)