Skip to content

Commit

Permalink
Add field info to user serializer (#7518)
Browse files Browse the repository at this point in the history
* Add field info to user serializer

* Bump API version

* Adjust metadata translation lookup

* Improve field metadata introspection

* Add unit test
  • Loading branch information
SchrodingersGat authored Jun 26, 2024
1 parent c1b2cbe commit f671475
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 7 deletions.
5 changes: 4 additions & 1 deletion src/backend/InvenTree/InvenTree/api_version.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down
32 changes: 26 additions & 6 deletions src/backend/InvenTree/InvenTree/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,27 +115,43 @@ def determine_metadata(self, request, view):

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.
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)
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

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
Expand Down Expand Up @@ -197,10 +213,12 @@ def get_serializer_info(self, serializer):
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
Expand All @@ -220,10 +238,12 @@ def get_serializer_info(self, serializer):
)

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:
Expand Down
21 changes: 21 additions & 0 deletions src/backend/InvenTree/InvenTree/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,17 @@ class Meta:

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."""
Expand All @@ -424,6 +435,16 @@ class Meta(UserSerializer.Meta):

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
Expand Down
23 changes: 23 additions & 0 deletions src/backend/InvenTree/users/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,29 @@
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)
Expand Down

0 comments on commit f671475

Please sign in to comment.