Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to serialize an 'object list' in Django REST Framework

I need a serializer for something like this:

{
    "items": {
        12: {
            "name": "item 1"
        },
        66: {
            "name": "item 2"
        }
    }
}

How should I declare my serializers to get something like this? Is that even a valid JSON or should it look like this:

{
    "items": [
        {
            "name": "item 1",
            "id": 12
        }, {
            "name": "item 2"
            "id": 66
        }
    ]
}

? (12, 66 are primary keys of those 'items') Using Django REST Framework 3.

like image 573
McAbra Avatar asked Jan 16 '15 12:01

McAbra


People also ask

What is serialization in Django REST framework?

Serializers in Django REST Framework are responsible for converting objects into data types understandable by javascript and front-end frameworks. Serializers also provide deserialization, allowing parsed data to be converted back into complex types, after first validating the incoming data.

How does serialization work in Django?

Django's serialization framework provides a mechanism for “translating” Django models into other formats. Usually these other formats will be text-based and used for sending Django data over a wire, but it's possible for a serializer to handle any format (text-based or not).

What is the difference between ModelSerializer and HyperlinkedModelSerializer?

The HyperlinkedModelSerializer class is similar to the ModelSerializer class except that it uses hyperlinks to represent relationships, rather than primary keys. By default the serializer will include a url field instead of a primary key field.

Is serialization necessary in Django?

It is not necessary to use a serializer. You can do what you would like to achieve in a view. However, serializers help you a lot. If you don't want to use serializer, you can inherit APIView at a function-based-view.


2 Answers

There is a ListField in django rest framework 3, you can check documentation here http://www.django-rest-framework.org/api-guide/fields/#listfield

For your example you can do something like this:

class ItemSerializer(serializers.Serializer):
    id = serializers.IntegerField()
    name = serializers.CharField()

class ItemsSerializer(serializers.Serializer):
    items = serializers.ListField(child=ItemSerializer())

The later serializer can be also:

class ItemsSerializer(serializers.Serializer):
    items = ItemSerializer(many=True)
like image 104
Gabriel Muj Avatar answered Oct 28 '22 03:10

Gabriel Muj


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)

like image 2
Felipe Buccioni Avatar answered Oct 28 '22 05:10

Felipe Buccioni