mirror of
				https://github.com/encode/django-rest-framework.git
				synced 2025-10-31 16:07:38 +03:00 
			
		
		
		
	Add disabling of declared fields on serializer subclasses (#4764)
* Add test for disabling declared fields on child * Check that declared base field is not in attrs * Update meta inheritance docs to include serializer * Test that meta fields cannot be declared as None * Add docs example for declarative field disabling
This commit is contained in:
		
							parent
							
								
									8579683170
								
							
						
					
					
						commit
						11fd3bf108
					
				|  | @ -578,16 +578,6 @@ Alternative representations include serializing using hyperlinks, serializing co | ||||||
| 
 | 
 | ||||||
| For full details see the [serializer relations][relations] documentation. | For full details see the [serializer relations][relations] documentation. | ||||||
| 
 | 
 | ||||||
| ## Inheritance of the 'Meta' class |  | ||||||
| 
 |  | ||||||
| The inner `Meta` class on serializers is not inherited from parent classes by default. This is the same behavior as with Django's `Model` and `ModelForm` classes. If you want the `Meta` class to inherit from a parent class you must do so explicitly. For example: |  | ||||||
| 
 |  | ||||||
|     class AccountSerializer(MyBaseSerializer): |  | ||||||
|         class Meta(MyBaseSerializer.Meta): |  | ||||||
|             model = Account |  | ||||||
| 
 |  | ||||||
| Typically we would recommend *not* using inheritance on inner Meta classes, but instead declaring all options explicitly. |  | ||||||
| 
 |  | ||||||
| ## Customizing field mappings | ## Customizing field mappings | ||||||
| 
 | 
 | ||||||
| The ModelSerializer class also exposes an API that you can override in order to alter how serializer fields are automatically determined when instantiating the serializer. | The ModelSerializer class also exposes an API that you can override in order to alter how serializer fields are automatically determined when instantiating the serializer. | ||||||
|  | @ -1025,6 +1015,40 @@ If any of the validation fails, then the method should raise a `serializers.Vali | ||||||
| 
 | 
 | ||||||
| The `data` argument passed to this method will normally be the value of `request.data`, so the datatype it provides will depend on the parser classes you have configured for your API. | The `data` argument passed to this method will normally be the value of `request.data`, so the datatype it provides will depend on the parser classes you have configured for your API. | ||||||
| 
 | 
 | ||||||
|  | ## Serializer Inheritance | ||||||
|  | 
 | ||||||
|  | Similar to Django forms, you can extend and reuse serializers through inheritance. This allows you to declare a common set of fields or methods on a parent class that can then be used in a number of serializers. For example, | ||||||
|  | 
 | ||||||
|  |     class MyBaseSerializer(Serializer): | ||||||
|  |         my_field = serializers.CharField() | ||||||
|  | 
 | ||||||
|  |         def validate_my_field(self): | ||||||
|  |             ... | ||||||
|  | 
 | ||||||
|  |     class MySerializer(MyBaseSerializer): | ||||||
|  |         ... | ||||||
|  | 
 | ||||||
|  | Like Django's `Model` and `ModelForm` classes, the inner `Meta` class on serializers does not implicitly inherit from it's parents' inner `Meta` classes. If you want the `Meta` class to inherit from a parent class you must do so explicitly. For example: | ||||||
|  | 
 | ||||||
|  |     class AccountSerializer(MyBaseSerializer): | ||||||
|  |         class Meta(MyBaseSerializer.Meta): | ||||||
|  |             model = Account | ||||||
|  | 
 | ||||||
|  | Typically we would recommend *not* using inheritance on inner Meta classes, but instead declaring all options explicitly. | ||||||
|  | 
 | ||||||
|  | Additionally, the following caveats apply to serializer inheritance: | ||||||
|  | 
 | ||||||
|  | * Normal Python name resolution rules apply. If you have multiple base classes that declare a `Meta` inner class, only the first one will be used. This means the child’s `Meta`, if it exists, otherwise the `Meta` of the first parent, etc. | ||||||
|  | * It’s possible to declaratively remove a `Field` inherited from a parent class by setting the name to be `None` on the subclass. | ||||||
|  | 
 | ||||||
|  |         class MyBaseSerializer(ModelSerializer): | ||||||
|  |             my_field = serializers.CharField() | ||||||
|  | 
 | ||||||
|  |         class MySerializer(MyBaseSerializer): | ||||||
|  |             my_field = None | ||||||
|  | 
 | ||||||
|  |     However, you can only use this technique to opt out from a field defined declaratively by a parent class; it won’t prevent the `ModelSerializer` from generating a default field. To opt-out from default fields, see [Specifying which fields to include](#specifying-which-fields-to-include). | ||||||
|  | 
 | ||||||
| ## Dynamically modifying fields | ## Dynamically modifying fields | ||||||
| 
 | 
 | ||||||
| Once a serializer has been initialized, the dictionary of fields that are set on the serializer may be accessed using the `.fields` attribute.  Accessing and modifying this attribute allows you to dynamically modify the serializer. | Once a serializer has been initialized, the dictionary of fields that are set on the serializer may be accessed using the `.fields` attribute.  Accessing and modifying this attribute allows you to dynamically modify the serializer. | ||||||
|  |  | ||||||
|  | @ -305,7 +305,11 @@ class SerializerMetaclass(type): | ||||||
|         # in order to maintain the correct order of fields. |         # in order to maintain the correct order of fields. | ||||||
|         for base in reversed(bases): |         for base in reversed(bases): | ||||||
|             if hasattr(base, '_declared_fields'): |             if hasattr(base, '_declared_fields'): | ||||||
|                 fields = list(base._declared_fields.items()) + fields |                 fields = [ | ||||||
|  |                     (field_name, obj) for field_name, obj | ||||||
|  |                     in base._declared_fields.items() | ||||||
|  |                     if field_name not in attrs | ||||||
|  |                 ] + fields | ||||||
| 
 | 
 | ||||||
|         return OrderedDict(fields) |         return OrderedDict(fields) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -7,6 +7,8 @@ import re | ||||||
| 
 | 
 | ||||||
| import pytest | import pytest | ||||||
| 
 | 
 | ||||||
|  | from django.db import models | ||||||
|  | 
 | ||||||
| from rest_framework import fields, relations, serializers | from rest_framework import fields, relations, serializers | ||||||
| from rest_framework.compat import unicode_repr | from rest_framework.compat import unicode_repr | ||||||
| from rest_framework.fields import Field | from rest_framework.fields import Field | ||||||
|  | @ -413,3 +415,42 @@ class Test4606Regression: | ||||||
|         serializer = self.Serializer(data=[{"name": "liz"}], many=True) |         serializer = self.Serializer(data=[{"name": "liz"}], many=True) | ||||||
|         with pytest.raises(serializers.ValidationError): |         with pytest.raises(serializers.ValidationError): | ||||||
|             serializer.is_valid(raise_exception=True) |             serializer.is_valid(raise_exception=True) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class TestDeclaredFieldInheritance: | ||||||
|  |     def test_declared_field_disabling(self): | ||||||
|  |         class Parent(serializers.Serializer): | ||||||
|  |             f1 = serializers.CharField() | ||||||
|  |             f2 = serializers.CharField() | ||||||
|  | 
 | ||||||
|  |         class Child(Parent): | ||||||
|  |             f1 = None | ||||||
|  | 
 | ||||||
|  |         class Grandchild(Child): | ||||||
|  |             pass | ||||||
|  | 
 | ||||||
|  |         assert len(Parent._declared_fields) == 2 | ||||||
|  |         assert len(Child._declared_fields) == 1 | ||||||
|  |         assert len(Grandchild._declared_fields) == 1 | ||||||
|  | 
 | ||||||
|  |     def test_meta_field_disabling(self): | ||||||
|  |         # Declaratively setting a field on a child class will *not* prevent | ||||||
|  |         # the ModelSerializer from generating a default field. | ||||||
|  |         class MyModel(models.Model): | ||||||
|  |             f1 = models.CharField(max_length=10) | ||||||
|  |             f2 = models.CharField(max_length=10) | ||||||
|  | 
 | ||||||
|  |         class Parent(serializers.ModelSerializer): | ||||||
|  |             class Meta: | ||||||
|  |                 model = MyModel | ||||||
|  |                 fields = ['f1', 'f2'] | ||||||
|  | 
 | ||||||
|  |         class Child(Parent): | ||||||
|  |             f1 = None | ||||||
|  | 
 | ||||||
|  |         class Grandchild(Child): | ||||||
|  |             pass | ||||||
|  | 
 | ||||||
|  |         assert len(Parent().get_fields()) == 2 | ||||||
|  |         assert len(Child().get_fields()) == 2 | ||||||
|  |         assert len(Grandchild().get_fields()) == 2 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user