mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-31 13:15:43 +00:00 
			
		
		
		
	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:
		| @@ -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 | ||||
|   | ||||
| @@ -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: | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user