mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-02-03 21:24:33 +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