Skip to content

Commit 11fd3bf

Browse files
Ryan P Kilbytomchristie
authored andcommitted
Add disabling of declared fields on serializer subclasses (encode#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
1 parent 8579683 commit 11fd3bf

File tree

3 files changed

+80
-11
lines changed

3 files changed

+80
-11
lines changed

docs/api-guide/serializers.md

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -578,16 +578,6 @@ Alternative representations include serializing using hyperlinks, serializing co
578578

579579
For full details see the [serializer relations][relations] documentation.
580580

581-
## Inheritance of the 'Meta' class
582-
583-
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:
584-
585-
class AccountSerializer(MyBaseSerializer):
586-
class Meta(MyBaseSerializer.Meta):
587-
model = Account
588-
589-
Typically we would recommend *not* using inheritance on inner Meta classes, but instead declaring all options explicitly.
590-
591581
## Customizing field mappings
592582

593583
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
10251015

10261016
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.
10271017

1018+
## Serializer Inheritance
1019+
1020+
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,
1021+
1022+
class MyBaseSerializer(Serializer):
1023+
my_field = serializers.CharField()
1024+
1025+
def validate_my_field(self):
1026+
...
1027+
1028+
class MySerializer(MyBaseSerializer):
1029+
...
1030+
1031+
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:
1032+
1033+
class AccountSerializer(MyBaseSerializer):
1034+
class Meta(MyBaseSerializer.Meta):
1035+
model = Account
1036+
1037+
Typically we would recommend *not* using inheritance on inner Meta classes, but instead declaring all options explicitly.
1038+
1039+
Additionally, the following caveats apply to serializer inheritance:
1040+
1041+
* 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.
1042+
* It’s possible to declaratively remove a `Field` inherited from a parent class by setting the name to be `None` on the subclass.
1043+
1044+
class MyBaseSerializer(ModelSerializer):
1045+
my_field = serializers.CharField()
1046+
1047+
class MySerializer(MyBaseSerializer):
1048+
my_field = None
1049+
1050+
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).
1051+
10281052
## Dynamically modifying fields
10291053

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

rest_framework/serializers.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,11 @@ def _get_declared_fields(cls, bases, attrs):
305305
# in order to maintain the correct order of fields.
306306
for base in reversed(bases):
307307
if hasattr(base, '_declared_fields'):
308-
fields = list(base._declared_fields.items()) + fields
308+
fields = [
309+
(field_name, obj) for field_name, obj
310+
in base._declared_fields.items()
311+
if field_name not in attrs
312+
] + fields
309313

310314
return OrderedDict(fields)
311315

tests/test_serializer.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
import pytest
99

10+
from django.db import models
11+
1012
from rest_framework import fields, relations, serializers
1113
from rest_framework.compat import unicode_repr
1214
from rest_framework.fields import Field
@@ -413,3 +415,42 @@ def test_4606_regression(self):
413415
serializer = self.Serializer(data=[{"name": "liz"}], many=True)
414416
with pytest.raises(serializers.ValidationError):
415417
serializer.is_valid(raise_exception=True)
418+
419+
420+
class TestDeclaredFieldInheritance:
421+
def test_declared_field_disabling(self):
422+
class Parent(serializers.Serializer):
423+
f1 = serializers.CharField()
424+
f2 = serializers.CharField()
425+
426+
class Child(Parent):
427+
f1 = None
428+
429+
class Grandchild(Child):
430+
pass
431+
432+
assert len(Parent._declared_fields) == 2
433+
assert len(Child._declared_fields) == 1
434+
assert len(Grandchild._declared_fields) == 1
435+
436+
def test_meta_field_disabling(self):
437+
# Declaratively setting a field on a child class will *not* prevent
438+
# the ModelSerializer from generating a default field.
439+
class MyModel(models.Model):
440+
f1 = models.CharField(max_length=10)
441+
f2 = models.CharField(max_length=10)
442+
443+
class Parent(serializers.ModelSerializer):
444+
class Meta:
445+
model = MyModel
446+
fields = ['f1', 'f2']
447+
448+
class Child(Parent):
449+
f1 = None
450+
451+
class Grandchild(Child):
452+
pass
453+
454+
assert len(Parent().get_fields()) == 2
455+
assert len(Child().get_fields()) == 2
456+
assert len(Grandchild().get_fields()) == 2

0 commit comments

Comments
 (0)