У меня есть модель, которая выглядит так:
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? Я пытался найти его в документации, но он кажется неполным.
источник
KeyError at /api/category/ 'subcategories'
. Кстати, спасибо за сверхбыстрые ответы :)Решение @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
Затем для сериализатора используйте RecursiveField, чтобы сериализовать значение «ответов».
class CommentSerializer(serializers.Serializer): replies = RecursiveField(many=True) class Meta: model = Comment fields = ('replies, ....)
Легко и просто, и вам нужно всего 4 строки кода для решения, которое можно использовать повторно.
ПРИМЕЧАНИЕ. Если ваша структура данных более сложна, чем дерево, например, ориентированный ациклический граф (ФАНТАЗИЯ!), Вы можете попробовать пакет @wjin - см. Его решение. Но у меня не было никаких проблем с этим решением для деревьев на основе MPTTModel.
источник
print self.parent.parent.__class__
print self.parent.parent
Другой вариант, который работает с 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
источник
parent.parent.__class__
материал. Мне это нравится больше всего.Поздно к игре, но вот мое решение. Скажем, я сериализирую Blah с несколькими дочерними элементами также типа Blah.
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/
источник
Blah
привел, работает для случая, когда у вас есть класс и у него есть поле с именем,child_blahs
которое состоит из спискаBlah
объектов.queryset=Class.objects.filter(level=0)
. Все остальное он берет на себя.Мне удалось добиться этого результата с помощью файла
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
источник
Другой вариант - выполнить рекурсию в представлении, которое сериализует вашу модель. Вот пример:
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)
источник
Недавно у меня была такая же проблема, и я пришел к решению, которое, кажется, работает до сих пор даже для произвольной глубины. Решение представляет собой небольшую модификацию решения Тома Кристи:
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()
Я не уверен, что он может надежно работать в любой ситуации ...
источник
Это адаптация решения 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.
источник
if 'branches'
следует изменить наif 'subcategories'
Я думал, что присоединюсь к веселью!
Через wjin и Mark Chackerian я создал более общее решение, которое работает для прямых древовидных моделей и древовидных структур, имеющих сквозную модель. Я не уверен, относится ли это к его собственному ответу, но я подумал, что мог бы его где-нибудь положить. Я включил параметр max_depth, который предотвратит бесконечную рекурсию, на самом глубоком уровне дочерние элементы представлены как URL-адреса (это последнее предложение 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'])
источник
else
предложение делает определенные предположения относительно представления. Мне пришлось заменить мой на,return value.pk
чтобы он возвращал первичные ключи вместо попытки обратного просмотра представления.В 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')
источник
Это решение почти аналогично другим решениям, размещенным здесь, но имеет небольшую разницу с точки зрения проблемы повторения дочерних элементов на корневом уровне (если вы думаете, что это проблема). Для примера
class RecursiveSerializer(serializers.Serializer): def to_representation(self, value): serializer = self.parent.parent.__class__(value, context=self.context) return serializer.data class CategoryListSerializer(ModelSerializer): sub_category = RecursiveSerializer(many=True, read_only=True) class Meta: model = Category fields = ( 'name', 'slug', 'parent', 'sub_category' )
и если у вас есть это мнение
class CategoryListAPIView(ListAPIView): queryset = Category.objects.all() serializer_class = CategoryListSerializer
Это даст следующий результат:
[ { "name": "parent category", "slug": "parent-category", "parent": null, "sub_category": [ { "name": "child category", "slug": "child-category", "parent": 20, "sub_category": [] } ] }, { "name": "child category", "slug": "child-category", "parent": 20, "sub_category": [] } ]
Здесь
parent category
есть a,child category
а представление json - это именно то, что мы хотим представить.но вы можете видеть, что
child category
на корневом уровне есть повторение .Поскольку некоторые люди спрашивают в разделах комментариев к опубликованным выше ответам, как мы можем остановить это дочернее повторение на корневом уровне , просто отфильтруйте свой набор запросов
parent=None
, как показано нижеclass CategoryListAPIView(ListAPIView): queryset = Category.objects.filter(parent=None) serializer_class = CategoryListSerializer
это решит проблему.
ПРИМЕЧАНИЕ. Этот ответ может не иметь прямого отношения к вопросу, но проблема каким-то образом связана. К тому же такой подход к использованию
RecursiveSerializer
стоит дорого. Лучше, если вы будете использовать другие варианты, которые зависят от производительности.источник