Tenho um modelo parecido com este:
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)
Consegui uma representação json plana de todas as categorias com o serializador:
class CategorySerializer(serializers.HyperlinkedModelSerializer):
parentCategory = serializers.PrimaryKeyRelatedField()
subcategories = serializers.ManyRelatedField()
class Meta:
model = Category
fields = ('parentCategory', 'name', 'description', 'subcategories')
Agora o que eu quero fazer é que a lista de subcategorias tenha uma representação json embutida das subcategorias em vez de seus ids. Como eu faria isso com o django-rest-framework? Tentei encontrar na documentação, mas parece incompleto.
fonte
A solução de @wjin estava funcionando muito bem para mim até que atualizei para o Django REST framework 3.0.0, que se tornou obsoleto para_native . Aqui está minha solução DRF 3.0, que é uma pequena modificação.
Digamos que você tenha um modelo com um campo autorreferencial, por exemplo, comentários encadeados em uma propriedade chamada "respostas". Você tem uma representação em árvore deste tópico de comentários e deseja serializar a árvore
Primeiro, defina sua classe reutilizável RecursiveField
class RecursiveField(serializers.Serializer): def to_representation(self, value): serializer = self.parent.parent.__class__(value, context=self.context) return serializer.data
Então, para o seu serializador, use o RecursiveField para serializar o valor de "respostas"
class CommentSerializer(serializers.Serializer): replies = RecursiveField(many=True) class Meta: model = Comment fields = ('replies, ....)
Fácil de usar, e você só precisa de 4 linhas de código para uma solução reutilizável.
NOTA: Se sua estrutura de dados é mais complicada do que uma árvore, como digamos um gráfico acíclico direcionado (FANCY!), Então você pode tentar o pacote de @wjin - veja a solução dele. Mas eu não tive nenhum problema com esta solução para árvores baseadas em MPTTModel.
fonte
print self.parent.parent.__class__
eprint self.parent.parent
Outra opção que funciona com 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
fonte
parent.parent.__class__
outras coisas. Eu gosto mais dissofields = super().get_fields()
Atrasado para o jogo aqui, mas aqui está minha solução. Digamos que eu esteja serializando um Blah, com vários filhos também do tipo Blah.
class RecursiveField(serializers.Serializer): def to_native(self, value): return self.parent.to_native(value)
Usando este campo, posso serializar meus objetos definidos recursivamente que têm muitos objetos-filho
class BlahSerializer(serializers.Serializer): name = serializers.Field() child_blahs = RecursiveField(many=True)
Eu escrevi um campo recursivo para DRF3.0 e empacotei para pip https://pypi.python.org/pypi/djangorestframework-recursive/
fonte
Blah
e ela tem um campo chamadochild_blahs
que consiste em uma lista deBlah
objetos.queryset=Class.objects.filter(level=0)
. Ele lida com o resto das coisas sozinho.Consegui atingir esse resultado usando um
serializers.SerializerMethodField
. Não tenho certeza se esta é a melhor maneira, mas funcionou para mim: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
fonte
Outra opção seria recursar na visualização que serializa seu modelo. Aqui está um exemplo:
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)
fonte
Recentemente, tive o mesmo problema e descobri uma solução que parece funcionar até agora, mesmo em profundidades arbitrárias. A solução é uma pequena modificação da 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()
Não tenho certeza se pode funcionar de forma confiável em qualquer situação, no entanto ...
fonte
Esta é uma adaptação da solução caipirginka que funciona no drf 3.0.5 e 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')
Observe que o CategorySerializer na 6ª linha é chamado com o objeto e o atributo many = True.
fonte
if 'branches'
deve ser alterado paraif 'subcategories'
Pensei em me juntar à diversão!
Via wjin e Mark Chackerian eu criei uma solução mais geral, que funciona para modelos diretos em forma de árvore e estruturas de árvore que têm um modelo direto. Não tenho certeza se isso pertence à sua própria resposta, mas achei melhor colocá-lo em algum lugar. Incluí uma opção max_depth que impedirá a recursão infinita, no nível mais profundo, os filhos são representados como URLS (essa é a cláusula else final, se você preferir que não seja um 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'])
fonte
else
cláusula faz certas suposições sobre a exibição. Tive que substituir o meu porreturn value.pk
para que ele retornasse as chaves primárias em vez de tentar reverter a visualização da visualização.Com o Django REST framework 3.3.1, eu precisava do seguinte código para obter subcategorias adicionadas às categorias:
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')
fonte
Esta solução é quase semelhante às outras soluções postadas aqui, mas tem uma pequena diferença em termos de problema de repetição de criança no nível raiz (se você acha que é um problema). Por exemplo
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' )
e se você tem essa visão
class CategoryListAPIView(ListAPIView): queryset = Category.objects.all() serializer_class = CategoryListSerializer
Isso produzirá o seguinte 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": [] } ]
Aqui, o
parent category
temchild category
ae a representação json é exatamente o que queremos que seja representado.mas você pode ver que há uma repetição do
child category
no nível raiz.Como algumas pessoas estão perguntando nas seções de comentários das respostas postadas acima, como podemos parar essa repetição infantil no nível raiz , basta filtrar seu queryset com
parent=None
, como a seguirclass CategoryListAPIView(ListAPIView): queryset = Category.objects.filter(parent=None) serializer_class = CategoryListSerializer
vai resolver o problema.
NOTA: Esta resposta pode não estar diretamente relacionada à pergunta, mas o problema está de alguma forma relacionado. Além disso, essa abordagem de uso
RecursiveSerializer
é cara. Melhor se você usar outras opções que são propensas ao desempenho.fonte