Подтвердить что ты не робот

Ядро Django rest вложенные самореферентные объекты

У меня есть модель, которая выглядит так:

class Category(models.Model):
    parentCategory = models.ForeignKey('self', blank=True, null=True, related_name='subcategories')
    name = models.CharField(max_length=200)
    description = models.CharField(max_length=500)

Мне удалось получить плоское json-представление всех категорий с помощью сериализатора:

class CategorySerializer(serializers.HyperlinkedModelSerializer):
    parentCategory = serializers.PrimaryKeyRelatedField()
    subcategories = serializers.ManyRelatedField()

    class Meta:
        model = Category
        fields = ('parentCategory', 'name', 'description', 'subcategories')

Теперь я хочу, чтобы список подкатегорий имел встроенное json-представление подкатегорий вместо их идентификаторов. Как мне это сделать с django-rest-framework? Я пытался найти его в документации, но он кажется неполным.

4b9b3361

Ответ 1

Вместо использования ManyRelatedField в качестве поля используйте вложенный сериализатор:

class SubCategorySerializer(serializers.ModelSerializer):
    class Meta:
        model = Category
        fields = ('name', 'description')

class CategorySerializer(serializers.ModelSerializer):
    parentCategory = serializers.PrimaryKeyRelatedField()
    subcategories = serializers.SubCategorySerializer()

    class Meta:
        model = Category
        fields = ('parentCategory', 'name', 'description', 'subcategories')

Если вы хотите иметь дело с произвольно вложенными полями, вы должны взглянуть на настройку полей по умолчанию в документах. В настоящее время вы не можете прямо объявлять сериализатор как поле, но вы можете использовать эти методы, чтобы переопределить, какие поля используются по умолчанию.

class CategorySerializer(serializers.ModelSerializer):
    parentCategory = serializers.PrimaryKeyRelatedField()

    class Meta:
        model = Category
        fields = ('parentCategory', 'name', 'description', 'subcategories')

        def get_related_field(self, model_field):
            # Handles initializing the `subcategories` field
            return CategorySerializer()

Собственно, как вы уже отметили выше, это не совсем правильно. Это немного взломать, но вы можете попробовать добавить это поле после того, как сериализатор уже объявлен.

class CategorySerializer(serializers.ModelSerializer):
    parentCategory = serializers.PrimaryKeyRelatedField()

    class Meta:
        model = Category
        fields = ('parentCategory', 'name', 'description', 'subcategories')

CategorySerializer.base_fields['subcategories'] = CategorySerializer()

Механизм объявления рекурсивных отношений - это то, что нужно добавить.


Изменить. Обратите внимание: теперь есть доступный сторонний пакет, который специально относится к этому виду использования. См. djangorestframework-recursive.

Ответ 2

Решение @wjin работало отлично для меня, пока я не обновился до Django REST framework 3.0.0, который не признает to_native. Здесь мое решение DRF 3.0, которое является небольшой модификацией.

Предположим, что у вас есть модель с самореференциальным полем, например, с комментариями к потоку в свойстве "ответы". У вас есть древовидное представление этого потока комментариев, и вы хотите сериализовать дерево

Сначала определите свой многократный класс RecursiveField

class RecursiveField(serializers.Serializer):
    def to_representation(self, value):
        serializer = self.parent.parent.__class__(value, context=self.context)
        return serializer.data

Затем для вашего сериализатора используйте рекурсивный фильтр для сериализации значения "ответов"

class CommentSerializer(serializers.Serializer):
    replies = RecursiveField(many=True)

    class Meta:
        model = Comment
        fields = ('replies, ....)

Легкий peasy, и вам нужно всего 4 строки кода для повторно используемого решения.

ПРИМЕЧАНИЕ. Если ваша структура данных более сложна, чем дерево, например, как ориентированный ациклический граф (FANCY!), вы можете попробовать пакет @wjin - см. его решение. Но у меня не было никаких проблем с этим решением для деревьев на основе MPTTModel.

Ответ 3

Поздно к игре здесь, но вот мое решение. Скажем, я сериализую Бла, с несколькими детьми также типа Бла.

    class RecursiveField(serializers.Serializer):
        def to_native(self, value):
            return self.parent.to_native(value)

Используя это поле, я могу сериализовать рекурсивно определенные объекты, у которых есть много дочерних объектов

    class BlahSerializer(serializers.Serializer):
        name = serializers.Field()
        child_blahs = RecursiveField(many=True)

Я написал рекурсивное поле для DRF3.0 и упаковал его для pip https://pypi.python.org/pypi/djangorestframework-recursive/

Ответ 4

Недавно у меня была такая же проблема, и я придумал решение, которое, похоже, работает до сих пор даже для произвольной глубины. Решение представляет собой небольшую модификацию от Tom Christie:

class CategorySerializer(serializers.ModelSerializer):
    parentCategory = serializers.PrimaryKeyRelatedField()

    def convert_object(self, obj):
        #Add any self-referencing fields here (if not already done)
        if not self.fields.has_key('subcategories'):
            self.fields['subcategories'] = CategorySerializer()      
        return super(CategorySerializer,self).convert_object(obj) 

    class Meta:
        model = Category
        #do NOT include self-referencing fields here
        #fields = ('parentCategory', 'name', 'description', 'subcategories')
        fields = ('parentCategory', 'name', 'description')
#This is not needed
#CategorySerializer.base_fields['subcategories'] = CategorySerializer()

Я не уверен, что он может надежно работать в ситуации любой, хотя...

Ответ 5

Другой вариант - это перезаписать в представлении, которое сериализует вашу модель. Вот пример:

class DepartmentSerializer(ModelSerializer):
    class Meta:
        model = models.Department


class DepartmentViewSet(ModelViewSet):
    model = models.Department
    serializer_class = DepartmentSerializer

    def serialize_tree(self, queryset):
        for obj in queryset:
            data = self.get_serializer(obj).data
            data['children'] = self.serialize_tree(obj.children.all())
            yield data

    def list(self, request):
        queryset = self.get_queryset().filter(level=0)
        data = self.serialize_tree(queryset)
        return Response(data)

    def retrieve(self, request, pk=None):
        self.object = self.get_object()
        data = self.serialize_tree([self.object])
        return Response(data)

Ответ 6

Это адаптация из решения caipirginka, которое работает на drf 3.0.5 и django 2.7.4:

class CategorySerializer(serializers.ModelSerializer):

    def to_representation(self, obj):
        #Add any self-referencing fields here (if not already done)
        if 'branches' not in self.fields:
            self.fields['subcategories'] = CategorySerializer(obj, many=True)      
        return super(CategorySerializer, self).to_representation(obj) 

    class Meta:
        model = Category
        fields = ('id', 'description', 'parentCategory')

Обратите внимание, что CategorySerializer в 6-й строке вызывается с объектом и атрибутом many = True.

Ответ 7

Другой вариант, который работает с Django REST Framework 3.3.2:

class CategorySerializer(serializers.ModelSerializer):
    class Meta:
        model = Category
        fields = ('id', 'name', 'parentid', 'subcategories')

    def get_fields(self):
        fields = super(CategorySerializer, self).get_fields()
        fields['subcategories'] = CategorySerializer(many=True)
        return fields

Ответ 8

Я смог достичь этого результата с помощью serializers.SerializerMethodField. Я не уверен, что это лучший способ, но работал на меня:

    class CategorySerializer(serializers.ModelSerializer):
    subcategories = serializers.SerializerMethodField(
        read_only=True, method_name="get_child_categories")

    class Meta:
        model = Category
        fields = [
            'name',
            'category_id',
            'subcategories',
        ]

    def get_child_categories(self, obj):
        """ self referral field """
        serializer = CategorySerializer(
            instance=obj.subcategories_set.all(),
            many=True
        )
        return serializer.data

Ответ 9

Я думал, что присоединяюсь к веселью!

Через wjin и Mark Chackerian Я создал более общее решение, которое работает для прямого древовидного модели и древовидные структуры, которые имеют сквозную модель. Я не уверен, что это принадлежит собственному ответу, но я подумал, что могу как-то положить его куда-нибудь. Я включил параметр max_depth, который предотвратит бесконечную рекурсию, на самом глубоком уровне дети представляются как URLS (это последнее предложение else, если вы предпочли бы, чтобы это не был URL-адрес).

from rest_framework.reverse import reverse
from rest_framework import serializers

class RecursiveField(serializers.Serializer):
    """
    Can be used as a field within another serializer,
    to produce nested-recursive relationships. Works with
    through models, and limited and/or arbitrarily deep trees.
    """
    def __init__(self, **kwargs):
        self._recurse_through = kwargs.pop('through_serializer', None)
        self._recurse_max = kwargs.pop('max_depth', None)
        self._recurse_view = kwargs.pop('reverse_name', None)
        self._recurse_attr = kwargs.pop('reverse_attr', None)
        self._recurse_many = kwargs.pop('many', False)

        super(RecursiveField, self).__init__(**kwargs)

    def to_representation(self, value):
        parent = self.parent
        if isinstance(parent, serializers.ListSerializer):
            parent = parent.parent

        lvl = getattr(parent, '_recurse_lvl', 1)
        max_lvl = self._recurse_max or getattr(parent, '_recurse_max', None)

        # Defined within RecursiveField(through_serializer=A)
        serializer_class = self._recurse_through
        is_through = has_through = True

        # Informed by previous serializer (for through m2m)
        if not serializer_class:
            is_through = False
            serializer_class = getattr(parent, '_recurse_next', None)

        # Introspected for cases without through models.
        if not serializer_class:
            has_through = False
            serializer_class = parent.__class__

        if is_through or not max_lvl or lvl <= max_lvl: 
            serializer = serializer_class(
                value, many=self._recurse_many, context=self.context)

            # Propagate hereditary attributes.
            serializer._recurse_lvl = lvl + is_through or not has_through
            serializer._recurse_max = max_lvl

            if is_through:
                # Delay using parent serializer till next lvl.
                serializer._recurse_next = parent.__class__

            return serializer.data
        else:
            view = self._recurse_view or self.context['request'].resolver_match.url_name
            attr = self._recurse_attr or 'id'
            return reverse(view, args=[getattr(value, attr)],
                           request=self.context['request'])

Ответ 10

С Django REST framework 3.3.1 мне нужен следующий код для получения подкатегорий, добавленных в категории:

models.py

class Category(models.Model):

    id = models.AutoField(
        primary_key=True
    )

    name = models.CharField(
        max_length=45, 
        blank=False, 
        null=False
    )

    parentid = models.ForeignKey(
        'self',
        related_name='subcategories',
        blank=True,
        null=True
    )

    class Meta:
        db_table = 'Categories'

serializers.py

class SubcategorySerializer(serializers.ModelSerializer):

    class Meta:
        model = Category
        fields = ('id', 'name', 'parentid')


class CategorySerializer(serializers.ModelSerializer):
    subcategories = SubcategorySerializer(many=True, read_only=True)

    class Meta:
        model = Category
        fields = ('id', 'name', 'parentid', 'subcategories')