J'ai un modèle qui ressemble à ceci:
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)
J'ai réussi à obtenir une représentation json plate de toutes les catégories avec le sérialiseur:
class CategorySerializer(serializers.HyperlinkedModelSerializer):
parentCategory = serializers.PrimaryKeyRelatedField()
subcategories = serializers.ManyRelatedField()
class Meta:
model = Category
fields = ('parentCategory', 'name', 'description', 'subcategories')
Maintenant, ce que je veux faire, c'est que la liste des sous-catégories ait une représentation json en ligne des sous-catégories au lieu de leurs identifiants. Comment ferais-je cela avec django-rest-framework? J'ai essayé de le trouver dans la documentation, mais cela semble incomplet.
la source
KeyError at /api/category/ 'subcategories'
. Btw merci pour vos réponses ultra-rapides :)La solution de @ wjin fonctionnait très bien pour moi jusqu'à ce que je passe à Django REST Framework 3.0.0, qui désapprouve to_native . Voici ma solution DRF 3.0, qui est une légère modification.
Supposons que vous ayez un modèle avec un champ auto-référentiel, par exemple des commentaires filetés dans une propriété appelée «réponses». Vous avez une représentation arborescente de ce fil de commentaire et vous souhaitez sérialiser l'arborescence
Tout d'abord, définissez votre classe RecursiveField réutilisable
class RecursiveField(serializers.Serializer): def to_representation(self, value): serializer = self.parent.parent.__class__(value, context=self.context) return serializer.data
Ensuite, pour votre sérialiseur, utilisez le RecursiveField pour sérialiser la valeur de «réponses»
class CommentSerializer(serializers.Serializer): replies = RecursiveField(many=True) class Meta: model = Comment fields = ('replies, ....)
Facile, et vous n'avez besoin que de 4 lignes de code pour une solution réutilisable.
REMARQUE: Si votre structure de données est plus compliquée qu'un arbre, comme par exemple un graphe acyclique dirigé (FANCY!), Alors vous pouvez essayer le package de @ wjin - voir sa solution. Mais je n'ai eu aucun problème avec cette solution pour les arbres basés sur MPTTModel.
la source
print self.parent.parent.__class__
etprint self.parent.parent
Une autre option qui fonctionne avec 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
la source
parent.parent.__class__
choses. Je l'aime le plus.Tard dans le jeu ici, mais voici ma solution. Disons que je sérialise un Blah, avec plusieurs enfants également de type Blah.
class RecursiveField(serializers.Serializer): def to_native(self, value): return self.parent.to_native(value)
En utilisant ce champ, je peux sérialiser mes objets définis de manière récursive qui ont de nombreux objets enfants
class BlahSerializer(serializers.Serializer): name = serializers.Field() child_blahs = RecursiveField(many=True)
J'ai écrit un champ récursif pour DRF3.0 et l'ai empaqueté pour pip https://pypi.python.org/pypi/djangorestframework-recursive/
la source
Blah
et qu'elle a un champ appeléchild_blahs
qui consiste en une liste d'Blah
objets.queryset=Class.objects.filter(level=0)
. Il gère le reste des choses lui-même.J'ai pu obtenir ce résultat en utilisant un
serializers.SerializerMethodField
. Je ne sais pas si c'est le meilleur moyen, mais cela a fonctionné pour moi: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
la source
Une autre option serait de récurer dans la vue qui sérialise votre modèle. Voici un exemple:
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)
la source
J'ai récemment eu le même problème et j'ai trouvé une solution qui semble fonctionner jusqu'à présent, même pour une profondeur arbitraire. La solution est une petite modification de celle 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()
Je ne suis pas sûr que cela puisse fonctionner de manière fiable dans n'importe quelle situation, cependant ...
la source
Ceci est une adaptation de la solution caipirginka qui fonctionne sur drf 3.0.5 et 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')
Notez que le CategorySerializer de la 6ème ligne est appelé avec l'objet et l'attribut many = True.
la source
if 'branches'
devrait être changé enif 'subcategories'
J'ai pensé que je participerais à l'amusement!
Via wjin et Mark Chackerian, j'ai créé une solution plus générale, qui fonctionne pour les modèles d'arbres directs et les structures arborescentes qui ont un modèle traversant. Je ne sais pas si cela appartient à sa propre réponse, mais j'ai pensé que je pourrais aussi bien le mettre quelque part. J'ai inclus une option max_depth qui empêchera la récursivité infinie, au niveau le plus profond, les enfants sont représentés sous forme d'URL (c'est la dernière clause else si vous préférez que ce ne soit pas une 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'])
la source
else
clause fait certaines hypothèses sur la vue. J'ai dû remplacer le mien parreturn value.pk
donc il a renvoyé les clés primaires au lieu d'essayer d'inverser la vue.Avec le framework Django REST 3.3.1, j'avais besoin du code suivant pour ajouter des sous-catégories aux catégories:
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')
la source
Cette solution est presque similaire aux autres solutions publiées ici mais présente une légère différence en termes de problème de répétition des enfants au niveau de la racine (si vous pensez que c'est un problème). À titre d'exemple
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' )
et si vous avez cette vue
class CategoryListAPIView(ListAPIView): queryset = Category.objects.all() serializer_class = CategoryListSerializer
Cela produira le résultat suivant,
[ { "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": [] } ]
Ici, la représentation
parent category
achild category
et la représentation json est exactement ce que nous voulons qu'elle représente.mais vous pouvez voir qu'il y a une répétition du au
child category
niveau de la racine.Comme certaines personnes demandent dans les sections de commentaires des réponses publiées ci-dessus, comment pouvons-nous arrêter cette répétition enfant au niveau de la racine , il suffit de filtrer votre jeu de requête avec
parent=None
, comme suitclass CategoryListAPIView(ListAPIView): queryset = Category.objects.filter(parent=None) serializer_class = CategoryListSerializer
cela résoudra le problème.
REMARQUE: Cette réponse n'est peut-être pas directement liée à la question, mais le problème est en quelque sorte lié. Cette approche d'utilisation
RecursiveSerializer
est également coûteuse. Mieux si vous utilisez d'autres options qui sont sujettes aux performances.la source