how - django rest framework vs django
Django rest framework anidados objetos autorreferenciales (10)
Tengo un modelo que se ve así:
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)
Logré obtener una representación json plana de todas las categorías con el serializador:
class CategorySerializer(serializers.HyperlinkedModelSerializer):
parentCategory = serializers.PrimaryKeyRelatedField()
subcategories = serializers.ManyRelatedField()
class Meta:
model = Category
fields = (''parentCategory'', ''name'', ''description'', ''subcategories'')
Ahora lo que quiero hacer es que la lista de subcategorías tenga una representación json en línea de las subcategorías en lugar de sus identificadores. ¿Cómo podría hacer eso con django-rest-framework? Traté de encontrarlo en la documentación, pero parece incompleto.
¡Pensé en unirme a la diversión!
A través de wjin y Mark Chackerian creé una solución más general, que funciona para modelos directos de árbol y estructuras de árbol que tienen un modelo directo. No estoy seguro de si esto pertenece a su propia respuesta, pero pensé que podría ponerlo en alguna parte. Incluí una opción max_depth que evitará la recursión infinita, en el nivel más profundo los niños se representan como URLS (esa es la última cláusula else si prefieres que no fuera una 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''])
Con Django REST framework 3.3.1, necesitaba el siguiente código para obtener subcategorías agregadas a categorías:
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'')
En lugar de utilizar ManyRelatedField, use un serializador anidado como su campo:
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'')
Si desea tratar con campos anidados arbitrariamente, debe echar un vistazo a la personalización de la parte de los campos predeterminada de los documentos. Actualmente, no puede declarar directamente un serializador como un campo en sí mismo, pero puede usar estos métodos para anular qué campos se usan por defecto.
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()
En realidad, como has notado, lo anterior no está del todo bien. Esto es un truco, pero puede intentar agregar el campo después de que el serializador ya haya sido declarado.
class CategorySerializer(serializers.ModelSerializer):
parentCategory = serializers.PrimaryKeyRelatedField()
class Meta:
model = Category
fields = (''parentCategory'', ''name'', ''description'', ''subcategories'')
CategorySerializer.base_fields[''subcategories''] = CategorySerializer()
Un mecanismo para declarar relaciones recursivas es algo que debe agregarse.
Editar : tenga en cuenta que ahora hay un paquete de terceros disponible que trata específicamente este tipo de caso de uso. Ver djangorestframework-recursive .
Esta es una adaptación de la solución caipirginka que funciona en drf 3.0.5 y 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'')
Tenga en cuenta que el CategorySerializer en la 6ª línea se llama con el objeto y el atributo many = True.
La solución de @wjin me funcionaba muy bien hasta que actualicé a Django REST framework 3.0.0, que desaprueba to_native . Aquí está mi solución DRF 3.0, que es una pequeña modificación.
Supongamos que tiene un modelo con un campo autorreferencial, por ejemplo, comentarios en una propiedad llamada "respuestas". Tienes una representación en árbol de este hilo de comentarios, y quieres serializar el árbol
Primero, defina su clase RecursiveField reutilizable
class RecursiveField(serializers.Serializer):
def to_representation(self, value):
serializer = self.parent.parent.__class__(value, context=self.context)
return serializer.data
Luego, para su serializador, use el RecursiveField para serializar el valor de "respuestas"
class CommentSerializer(serializers.Serializer):
replies = RecursiveField(many=True)
class Meta:
model = Comment
fields = (''replies, ....)
Easy peasy, y solo necesitas 4 líneas de código para una solución reutilizable.
NOTA: Si su estructura de datos es más complicada que un árbol, como por ejemplo, un gráfico acíclico dirigido (¡FANCY!), Entonces podría probar el paquete de @ wjin: ver su solución. Pero no he tenido ningún problema con esta solución para árboles basados en MPTTModel.
Otra opción que funciona con 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
Otra opción sería recurrir en la vista que serializa su modelo. Aquí hay un ejemplo:
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)
Pude lograr este resultado utilizando serializers.SerializerMethodField
. No estoy seguro de si esta es la mejor manera, pero funcionó para mí:
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
Recientemente tuve el mismo problema y se me ocurrió una solución que parece funcionar hasta el momento, incluso para profundidad arbitraria. La solución es una pequeña modificación de la de 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()
Sin embargo, no estoy seguro si puede funcionar de manera confiable en cualquier situación ...
Tarde en el juego aquí, pero esta es mi solución. Digamos que estoy serializando un Blah, con varios hijos también de tipo Blah.
class RecursiveField(serializers.Serializer):
def to_native(self, value):
return self.parent.to_native(value)
Usando este campo puedo serializar mis objetos definidos recursivamente que tienen muchos objetos secundarios
class BlahSerializer(serializers.Serializer):
name = serializers.Field()
child_blahs = RecursiveField(many=True)
Escribí un campo recursivo para DRF3.0 y lo empaqué para pip https://pypi.python.org/pypi/djangorestframework-recursive/