Ядро 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? Я пытался найти его в документации, но он кажется неполным.
Ответы
Ответ 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')