I need a serializer for something like this:
{
\"items\": {
12: {
\"name\": \"item 1\"
},
66: {
\"name\": \"i
This do that kind of lists, in documents are dicts
, but in db are lists
, in your example you use integer as key, you need to use strings to comply the JSON standard.
from collections import OrderedDict
from django.core.exceptions import FieldDoesNotExist
from django.db import models as django_models
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from rest_framework.fields import SkipField
from rest_framework.settings import api_settings
from rest_framework.utils import html
class ObjectListSerializer(serializers.ListSerializer):
child = None
many = True
default_error_messages = {
'not_a_dict': _('Expected a dict of items but got type "{input_type}".'),
'empty': _('This dict may not be empty.')
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if not hasattr(self, 'index_field') or not self.index_field:
if 'index_field' in kwargs:
index_field = kwargs.pop('index_field')
if index_field in self.child.fields:
self.index_field = index_field
else:
raise FieldDoesNotExist(
_("Field {field_name} does not exists in {serializer_name}.").format(
field_name=index_field,
serializer_name=self.child.__class__.__name__
)
)
else:
index_field = None
serializer_model = self.child.get_model()
if serializer_model:
try:
index_field = serializer_model._meta.get_field('pk')
except FieldDoesNotExist:
try:
index_field = serializer_model._meta.get_field('id')
except FieldDoesNotExist:
pass
if index_field:
self.index_field = index_field.name
else:
raise FieldDoesNotExist(
_(
"Cannot find primary key in {serializer_name}, "
"try the argument 'index_field' in {my_name}."
).format(
my_name=self.__class__.__name__,
serializer_name=self.child.__class__.__name__
)
)
if not hasattr(self, 'index_field') or not self.index_field:
raise FieldDoesNotExist(
_("Provide the 'index_field' argument for {serializer_name},").format(
serializer_name=self.__class__.__name__
)
)
def get_initial(self):
if hasattr(self, 'initial_data'):
return self.to_representation(self.initial_data)
return {}
def to_internal_value(self, data):
"""
List of dicts of native values <- List of dicts of primitive datatypes.
"""
if html.is_html_input(data):
data = html.parse_html_list(data, default=[])
if not isinstance(data, dict):
message = self.error_messages['not_a_dict'].format(
input_type=type(data).__name__
)
raise ValidationError({
api_settings.NON_FIELD_ERRORS_KEY: [message]
}, code='not_a_list')
if not self.allow_empty and len(data) == 0:
if self.parent and self.partial:
raise SkipField()
message = self.error_messages['empty']
raise ValidationError({
api_settings.NON_FIELD_ERRORS_KEY: [message]
}, code='empty')
ret = []
errors = []
for index_value, item in data.items():
item[self.index_field] = index_value
try:
validated = self.child.run_validation(item)
except ValidationError as exc:
errors.append(exc.detail)
else:
ret.append(validated)
errors.append({})
if any(errors):
raise ValidationError(errors)
return ret
def to_representation(self, data):
"""
List of object instances -> List of dicts of primitive datatypes.
"""
# Dealing with nested relationships, data can be a Manager,
# so, first get a queryset from the Manager if needed
iterable = data.all() if isinstance(data, django_models.Manager) else data
ret = OrderedDict()
for item in iterable:
dict_doc = self.child.to_representation(item)
ret[dict_doc.pop(self.index_field)] = dict_doc
return ret
You can use this serializer using the argument index_field
at the init of the class
class ItemSerializer(serializers.Serializer):
name = serializers.CharField(max_length=64)
description = serializers.CharField()
class BucketSerializer(Serializer):
items = ObjectListSerializer(
child=ItemSerializer,
index_field='name',
allow_empty=True
)
Or by extending the class with index_field
predefined class value if you want to use as list_serializer_class
class ItemsListSerializer(ObjectListSerializer):
index_field = 'name'
allow_empty = True
class ItemSerializer(serializers.Serializer):
name = serializers.CharField(max_length=64)
description = serializers.CharField()
class Meta:
list_serializer_class = ItemListSerializer
class BucketSerializer(serializers.Serializer):
items = ItemSerializer(many=True, required=False)