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

后端 未结 3 744
一个人的身影
一个人的身影 2021-02-13 20:59

I need a serializer for something like this:

{
    \"items\": {
        12: {
            \"name\": \"item 1\"
        },
        66: {
            \"name\": \"i         


        
相关标签:
3条回答
  • 2021-02-13 21:18

    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)
    
    0 讨论(0)
  • 2021-02-13 21:18

    Serializer is not needed. You can do simpler with Class-base View:

    from rest_framework import views
    from rest_framework.response import Response
    from django.http import JsonResponse
    
    class ListItems(views.APIView):
    
        def get(self, request, format=None):
            items = Item.objects.all().values()
            return return JsonResponse(list(items), safe=False)
    
    0 讨论(0)
  • 2021-02-13 21:34

    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)
    
    
    0 讨论(0)
提交回复
热议问题