Skip to content

Commit 11fd3bf

Browse files
Ryan P Kilbytomchristie
authored andcommitted
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
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy