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)
Me las arreglé para obtener una representación json plana de todas las categorías con 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 haría eso con django-rest-framework? Intenté encontrarlo en la documentación, pero parece incompleto.
fuente
KeyError at /api/category/ 'subcategories'
. Por cierto, gracias por sus respuestas súper rápidas :)La solución de @ wjin funcionó muy bien para mí 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 enhebrados en una propiedad llamada "respuestas". Tiene una representación de árbol de este hilo de comentarios y desea 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, ....)
Fácil, y solo necesita 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 - vea su solución. Pero no he tenido ningún problema con esta solución para árboles basados en MPTTModel.
fuente
print self.parent.parent.__class__
yprint self.parent.parent
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
fuente
parent.parent.__class__
cosas. Me gusta más.Tarde para el juego aquí, pero aquí está mi solución. Digamos que estoy serializando un Blah, con varios hijos también del 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 empaqueté para pip https://pypi.python.org/pypi/djangorestframework-recursive/
fuente
Blah
y tiene un campo llamadochild_blahs
que consiste en una lista deBlah
objetos.queryset=Class.objects.filter(level=0)
. Maneja el resto de las cosas por sí mismo.Pude lograr este resultado usando un
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
fuente
Otra opción sería recurrir a la vista que serializa tu modelo. He aquí 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)
fuente
Recientemente tuve el mismo problema y se me ocurrió una solución que parece funcionar hasta ahora, incluso para una 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 de que pueda funcionar de manera confiable en cualquier situación ...
fuente
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 sexta línea se llama con el objeto y el atributo many = True.
fuente
if 'branches'
debería cambiarse aif 'subcategories'
¡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 en forma de árbol y estructuras de árbol que tienen un modelo transversal . 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 recursividad infinita, en el nivel más profundo, los niños se representan como URLS (esa es la cláusula else final si prefiere que no sea 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'])
fuente
else
cláusula hace ciertas suposiciones sobre la vista. Tuve que reemplazar el mío conreturn value.pk
para que devolviera las claves primarias en lugar de intentar revertir la búsqueda de la vista.Con el marco Django REST 3.3.1, necesitaba el siguiente código para agregar subcategorías a las categorías:
modelos.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')
fuente
Esta solución es casi similar a las otras soluciones publicadas aquí, pero tiene una ligera diferencia en términos del problema de repetición de niños en el nivel raíz (si cree que es un problema). Para un ejemplo
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' )
y si tienes esta vista
class CategoryListAPIView(ListAPIView): queryset = Category.objects.all() serializer_class = CategoryListSerializer
Esto producirá el siguiente resultado,
[ { "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": [] } ]
Aquí
parent category
tiene unachild category
y la representación json es exactamente lo que queremos que represente.pero puede ver que hay una repetición del
child category
en el nivel raíz.Como algunas personas preguntan en las secciones de comentarios de las respuestas publicadas anteriormente, ¿cómo podemos detener esta repetición secundaria en el nivel raíz? Simplemente filtre su conjunto de consultas con
parent=None
, como el siguienteclass CategoryListAPIView(ListAPIView): queryset = Category.objects.filter(parent=None) serializer_class = CategoryListSerializer
resolverá el problema.
NOTA: Es posible que esta respuesta no esté directamente relacionada con la pregunta, pero el problema está relacionado de alguna manera. Además, este enfoque de uso
RecursiveSerializer
es caro. Mejor si usa otras opciones que sean propensas al rendimiento.fuente