From f67147587a2b70f44d4be93d39178a04c296992d Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 26 Jun 2024 23:11:54 +1000 Subject: [PATCH] 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 --- .../InvenTree/InvenTree/api_version.py | 5 ++- src/backend/InvenTree/InvenTree/metadata.py | 32 +++++++++++++++---- .../InvenTree/InvenTree/serializers.py | 21 ++++++++++++ src/backend/InvenTree/users/test_api.py | 23 +++++++++++++ 4 files changed, 74 insertions(+), 7 deletions(-) diff --git a/src/backend/InvenTree/InvenTree/api_version.py b/src/backend/InvenTree/InvenTree/api_version.py index 0c050f86cfe..c0af7f8b98a 100644 --- a/src/backend/InvenTree/InvenTree/api_version.py +++ b/src/backend/InvenTree/InvenTree/api_version.py @@ -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 diff --git a/src/backend/InvenTree/InvenTree/metadata.py b/src/backend/InvenTree/InvenTree/metadata.py index 19404f46b27..d01303a0e9b 100644 --- a/src/backend/InvenTree/InvenTree/metadata.py +++ b/src/backend/InvenTree/InvenTree/metadata.py @@ -115,9 +115,14 @@ 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. @@ -125,6 +130,12 @@ def override_value(self, field_name, field_value, model_value): - 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 @@ def override_value(self, field_name, field_value, 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 @@ -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 @@ -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: diff --git a/src/backend/InvenTree/InvenTree/serializers.py b/src/backend/InvenTree/InvenTree/serializers.py index dd906f42c91..829216089f0 100644 --- a/src/backend/InvenTree/InvenTree/serializers.py +++ b/src/backend/InvenTree/InvenTree/serializers.py @@ -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.""" @@ -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 diff --git a/src/backend/InvenTree/users/test_api.py b/src/backend/InvenTree/users/test_api.py index 6e9805c6018..ea271f7e19c 100644 --- a/src/backend/InvenTree/users/test_api.py +++ b/src/backend/InvenTree/users/test_api.py @@ -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)