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 pourrais-je faire cela avec Django-rest-framework? J'ai essayé de le trouver dans la documentation, mais il semble incomplet.
Au lieu d'utiliser ManyRelatedField, utilisez un sérialiseur imbriqué comme champ:
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 vous souhaitez traiter des champs imbriqués arbitrairement, vous devriez jeter un œil à la partie personnalisation des champs par défaut des documents. Vous ne pouvez pas actuellement déclarer directement un sérialiseur en tant que champ sur lui-même, mais vous pouvez utiliser ces méthodes pour remplacer les champs utilisés par défaut.
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 fait, comme vous l'avez noté, ce qui précède n'est pas tout à fait correct. C'est un peu un hack, mais vous pouvez essayer d'ajouter le champ après que le sérialiseur est déjà déclaré.
class CategorySerializer(serializers.ModelSerializer):
parentCategory = serializers.PrimaryKeyRelatedField()
class Meta:
model = Category
fields = ('parentCategory', 'name', 'description', 'subcategories')
CategorySerializer.base_fields['subcategories'] = CategorySerializer()
Un mécanisme de déclaration des relations récursives doit être ajouté.
Edit : Notez qu'il existe maintenant un package tiers disponible qui traite spécifiquement de ce type de cas d'utilisation. Voir djangorestframework-recursive .
La solution de @ wjin fonctionnait très bien pour moi jusqu'à ce que je passe à Django REST framework 3.0.0, qui déprécie to_native. Voici mon 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 des "réponses"
class CommentSerializer(serializers.Serializer):
replies = RecursiveField(many=True)
class Meta:
model = Comment
fields = ('replies, ....)
Peasy 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 disons un graphique acyclique dirigé (FANCY!), Alors vous pouvez essayer le paquet de @ wjin - voir sa solution. Mais je n'ai eu aucun problème avec cette solution pour les arbres basés sur MPTTModel.
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/
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
Une autre option serait de reprendre 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)
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 toute situation, cependant ...
J'ai pu obtenir ce résultat en utilisant un serializers.SerializerMethodField
. Je ne sais pas si c'est la meilleure façon, 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
Il s'agit d'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 en 6ème ligne est appelé avec l'objet et l'attribut many = True.
Je pensais participer au plaisir!
Via wjin et Mark Chackerian j'ai créé une solution plus générale, qui fonctionne pour les modèles arborescents 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écursion infinie, au niveau le plus profond, les enfants sont représentés comme des 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'])
Avec Django REST framework 3.3.1, j'avais besoin du code suivant pour obtenir des sous-catégories ajoutées 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')