120 votes

Cadre de repos Django : objets autoréférentiels imbriqués

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 ids. Comment puis-je faire cela avec django-rest-framework ? J'ai essayé de le trouver dans la documentation, mais elle semble incomplète.

88voto

Tom Christie Points 8729

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 arbitrairement imbriqués, vous devriez jeter un coup d'œil à la fonction personnalisation des champs par défaut une partie des docs. Actuellement, vous ne pouvez pas déclarer directement un sérialiseur comme un 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 exact. C'est un peu un hack, mais vous pouvez essayer d'ajouter le champ après que le sérialiseur soit 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()

Il faut ajouter un mécanisme permettant de déclarer les relations récursives.


Modifier : Notez qu'il existe désormais un paquet tiers qui traite spécifiquement de ce type de cas d'utilisation. Voir djangorestframework-recursive .

77voto

Yuri Prezument Points 2745

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

75voto

Mark Chackerian Points 1562

La solution de @wjin fonctionnait très bien pour moi jusqu'à ce que j'effectue une mise à jour vers le cadre REST de Django 3.0.0, qui n'est plus adapté. vers_natif . Voici ma solution DRF 3.0, qui est une légère modification.

Disons que vous avez un modèle avec un champ autoréférentiel, par exemple des commentaires en fil de fer dans une propriété appelée "replies". Vous avez une représentation arborescente de ce fil de commentaires, et vous voulez sérialiser l'arbre

Tout d'abord, définissez votre classe réutilisable RecursiveField

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 "replies".

class CommentSerializer(serializers.Serializer):
    replies = RecursiveField(many=True)

    class Meta:
        model = Comment
        fields = ('replies, ....)

C'est facile, et vous n'avez besoin que de 4 lignes de code pour une solution réutilisable.

NOTE : Si votre structure de données est plus compliquée qu'un arbre, comme par exemple une graphe acyclique dirigé (FANCY !) alors vous pourriez essayer le paquet de @wjin -- voir sa solution. Mais je n'ai pas eu de problèmes avec cette solution pour les arbres basés sur le MPTTModel.

32voto

wjin Points 126

J'arrive tard, mais voici ma solution. Disons que je suis en train de sérialiser 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/

26voto

jarussi Points 520

J'ai pu obtenir ce résultat en utilisant un serializers.SerializerMethodField . Je ne suis pas sûr que ce soit la meilleure méthode, mais elle 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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X