-
-
Notifications
You must be signed in to change notification settings - Fork 7k
Add ListSerializer.get_child hook #5847
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Want to give an example of usage here? Then we can figure out if we'd also need to make this public API, and where we'd update the docs if so. |
Sure - just to expand on my particular case, I have a polymorphic serializer that can represent multiple models that inherit a common parent. As a contrived example, take a bunch of physical media types that we want to catalog. class Media(models.Model):
collection = models.ForeignKey(...)
class Article(Media):
author = models.ForeignKey(...)
class Book(Media)
authors = models.ManytoManyField(...)
class Photograph(Media):
photographer = models.ForeignKey(...) I've created a polymorphic serializer class that can represent all of the sub types as part of the same collection. Example setup: class MediaSerializer(PolymorphicModelSerializer):
class Meta:
model = models.Media
types = OrderedDict([
(models.Article, ArticleSerializer),
(models.Book, BookSerializer),
(models.Photo, PhotoSerializer),
])
list_serializer_class = PolymorphicListSerializer >>> collection = Collection.objects.create(...)
>>> article = Article.objects.create(collection=collection, author=...)
>>> book = Book.objects.create(collection=collection, authors=[...])
>>> photograph = Photograph.objects.create(collection=collection, photographer=...)
>>> serializer = MediaSerializer(instance=collection.media.all(), many=True)
>>> serializer.data The resulting json would look something like: [
{
"type": "article",
"id": 1,
"collection": 1,
"author": "..."
}, {
"type": "book",
"id": 2,
"collection": 1,
"author": ["..."]
}, {
"type": "photograph",
"id": 3,
"collection": 1,
"photographer": "..."
}
] This also supports creating/updating multiple objects. Anyway, this all works right now, but as explained above, it's necessary to duplicate methods like |
Seems reasonable. What does the implementation of PolynorphicListSerializer look like assuming we pulled this in? |
I wasn't looking to merge the polymorphic serializer (at least not any time soon), but I could try and whip up a PR at some point. Right now the code is somewhat specific to the project and makes a few assumptions, which should probably be cleaned up:
|
I know - I was more looking for an example of using the new hook. |
Oh, sure. Code is included in the collapsed details below. Expandpolymorphic serializer: from django.db import models
from rest_framework import serializers
def verbose_name(model):
opts = model._meta
# if model._deferred:
# return verbose_name(opts.proxy_for_model)
return opts.verbose_name.lower()
class CheckedTypeField(serializers.Field):
def __init__(self, **kwargs):
# The `source` needs to be set to '*', since there is no corresponding
# 'type' field on the model. The type is instead derived via the model's
# meta class. This is relevant to serialization of model instances.
kwargs['source'] = '*'
super(CheckedTypeField, self).__init__(**kwargs)
def run_validation(self, data):
self.validate_type(data)
return super(CheckedTypeField, self).run_validation(data)
def validate_type(self, data):
expected = verbose_name(self.parent.Meta.model)
if data != expected:
raise serializers.ValidationError(
'Invalid type. Expected \'{}\', but got \'{}\'.'.format(
expected, data,
)
)
def to_internal_value(self, data):
return {}
def to_representation(self, value):
return verbose_name(value)
class CheckedPolymorphicTypeField(CheckedTypeField):
def validate_type(self, data):
expected = [verbose_name(model) for model in self.parent.Meta.types]
if data not in expected:
raise serializers.ValidationError(
'Invalid type. Expected one of {}, but got \'{}\'.'.format(
expected, data,
)
)
class PolymorphicModelSerializer(serializers.ModelSerializer):
type = CheckedPolymorphicTypeField()
def __new__(cls, instance=None, data=serializers.empty, **kwargs):
assert hasattr(cls.Meta, 'types')
# get the serializer subclass for provided data
if isinstance(data, dict):
model = {
verbose_name(model): model for model in cls.Meta.types
}.get(data.get('type'))
if model is not None:
cls = cls.Meta.types[model]
return cls(instance=instance, data=data, **kwargs)
# get the serializer subclass for an instance
if hasattr(instance, '_meta'):
model = instance._meta.model
if model in cls.Meta.types:
cls = cls.Meta.types[model]
return cls(instance=instance, data=data, **kwargs)
return super(PolymorphicModelSerializer, cls) \
.__new__(cls, instance=instance, data=data, **kwargs)
class PolymorphicListSerializer(serializers.ListSerializer):
def __init__(self, instance=None, data=serializers.empty, **kwargs):
self.child_class = type(kwargs['child'])
self.child_kwargs = kwargs.copy()
self.child_kwargs.pop('child')
super().__init__(instance=instance, data=data, **kwargs)
def get_child(self, instance=None, data=serializers.empty):
return self.child_class(instance=instance, data=data,
**self.child_kwargs)
def get_initial(self):
if hasattr(self, 'initial_data'):
return [
self.get_child(data=item).get_initial()
for item in self.initial_data
]
return []
def create(self, validated_data):
return [
self.get_child(data=data).create(attrs)
for data, attrs in zip(self.get_initial(), validated_data)
] example usage: class ArticleSerializer(serializers.ModelSerializer):
type = CheckedTypeField()
class Meta:
model = models.Article
fields = ['id', 'type', 'author', 'title', 'content']
class MediaSerializer(PolymorphicModelSerializer):
class Meta:
model = models.Media
types = OrderedDict([
(models.Article, ArticleSerializer),
(models.Book, BookSerializer),
(models.Photo, PhotoSerializer),
])
fields = ['type']
list_serializer_class = PolymorphicListSerializer |
I'd like to close this off. Agree that we don't do a great job of handling polymorphic cases, but I don't really want us to make the serializers any more complicated than they already are. |
This should resolve #5537 and similar issues by allowing users to customize/override the
ListSerializer
child on a per-instance/per-item basis. Currently, thechild
is created once during initialization, and customizing the child requires duplicating a few methods, such asto_internal_value
.As a practical example, I have a polymorphic serializer class that needs to uses different child instances to validate the various types.