webentwicklung-frage-antwort-db.com.de

Django Rest Framework verschachtelte selbstreferenzielle Objekte

Ich habe ein Modell, das so aussieht:

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)

Ich habe es geschafft, eine flache JSON-Darstellung aller Kategorien mit dem Serializer zu erhalten:

class CategorySerializer(serializers.HyperlinkedModelSerializer):
    parentCategory = serializers.PrimaryKeyRelatedField()
    subcategories = serializers.ManyRelatedField()

    class Meta:
        model = Category
        fields = ('parentCategory', 'name', 'description', 'subcategories')

Was ich jetzt tun möchte, ist, dass die Unterkategorieliste eine Inline-JSON-Darstellung der Unterkategorien anstelle ihrer IDs aufweist. Wie würde ich das mit Django-Rest-Framework machen? Ich habe versucht, es in der Dokumentation zu finden, aber es scheint unvollständig zu sein.

70

Verwenden Sie anstelle von ManyRelatedField einen verschachtelten Serializer als Feld:

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')

Wenn Sie mit willkürlich verschachtelten Feldern arbeiten möchten, sollten Sie sich den Abschnitt Anpassen der Standardfelder in den Dokumenten ansehen. Sie können einen Serialisierer derzeit nicht direkt als Feld deklarieren. Mit diesen Methoden können Sie jedoch überschreiben, welche Felder standardmäßig verwendet werden.

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()

Wie Sie bereits bemerkt haben, ist das oben Gesagte nicht ganz richtig. Dies ist ein kleiner Hack, aber Sie können versuchen, das Feld hinzuzufügen, nachdem der Serializer bereits deklariert wurde.

class CategorySerializer(serializers.ModelSerializer):
    parentCategory = serializers.PrimaryKeyRelatedField()

    class Meta:
        model = Category
        fields = ('parentCategory', 'name', 'description', 'subcategories')

CategorySerializer.base_fields['subcategories'] = CategorySerializer()

Ein Mechanismus zum Deklarieren rekursiver Beziehungen muss hinzugefügt werden.


Bearbeiten : Beachten Sie, dass jetzt ein Paket von Drittanbietern verfügbar ist, das sich speziell mit dieser Art von Anwendungsfall befasst. Siehe djangorestframework-recursive .

61
Tom Christie

Die Lösung von @ wjin hat für mich großartig funktioniert, bis ich auf Django REST framework 3.0.0, das to_native nicht mehr unterstützt DRF 3.0-Lösung, die eine leichte Modifikation ist.

Angenommen, Sie haben ein Modell mit einem selbstreferenzierenden Feld, z. B. Kommentare mit Thread in einer Eigenschaft namens "replys". Sie haben eine Baumdarstellung dieses Kommentarthreads und möchten den Baum serialisieren

Definieren Sie zunächst Ihre wiederverwendbare RecursiveField-Klasse

class RecursiveField(serializers.Serializer):
    def to_representation(self, value):
        serializer = self.parent.parent.__class__(value, context=self.context)
        return serializer.data

Verwenden Sie dann für Ihren Serializer das RecursiveField, um den Wert von "replies" zu serialisieren.

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

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

Einfach peasy, und Sie benötigen nur 4 Codezeilen für eine wiederverwendbare Lösung.

HINWEIS: Wenn Ihre Datenstruktur komplizierter ist als ein Baum, wie z. B. ein gerichteter azyklischer Graph (FANCY!), Können Sie das Paket von @ wjin ausprobieren - siehe seine Lösung. Ich hatte jedoch keine Probleme mit dieser Lösung für MPTTModel-basierte Bäume.

42
Mark Chackerian

Spät zum Spiel hier, aber hier ist meine Lösung. Nehmen wir an, ich serialisiere einen Blah mit mehreren Kindern, die ebenfalls vom Typ Blah sind.

    class RecursiveField(serializers.Serializer):
        def to_native(self, value):
            return self.parent.to_native(value)

Mit diesem Feld kann ich meine rekursiv definierten Objekte serialisieren, die viele untergeordnete Objekte haben

    class BlahSerializer(serializers.Serializer):
        name = serializers.Field()
        child_blahs = RecursiveField(many=True)

Ich habe ein rekursives Feld für DRF3.0 geschrieben und es für pip https://pypi.python.org/pypi/djangorestframework-recursive/ gepackt

24
wjin

Eine weitere Option, die mit Django REST Framework 3.3.2 funktioniert:

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
19
yprez

Eine andere Möglichkeit wäre, in der Ansicht, in der Ihr Modell serialisiert wird, erneut zu suchen. Hier ist ein Beispiel:

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)
9
Stefan Reinhard

Ich hatte vor kurzem das gleiche Problem und fand eine Lösung, die bisher auch für beliebige Tiefen zu funktionieren scheint. Die Lösung ist eine kleine Modifikation der von 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()

Ich bin nicht sicher, ob es in jeder Situation zuverlässig funktionieren kann ...

8
caipirginka

Dieses Ergebnis konnte ich mit einem serializers.SerializerMethodField. Ich bin mir nicht sicher, ob dies der beste Weg ist, habe aber für mich gearbeitet:

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
8
jarussi

Dies ist eine Anpassung aus der Caipirginka-Lösung, die auf DRF 3.0.5 und Django 2.7.4 funktioniert:

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')

Beachten Sie, dass der CategorySerializer in der 6. Zeile mit dem Objekt und dem Attribut many = True aufgerufen wird.

6

Ich dachte, ich würde mitmachen und Spaß haben!

Mit wjin und Mark Chackerian habe ich eine allgemeinere Lösung erstellt, die für direkte baumähnliche Modelle und Baumstrukturen mit einem Durchgangsmodell funktioniert. Ich bin mir nicht sicher, ob dies zu seiner eigenen Antwort gehört, aber ich dachte, ich könnte es genauso gut irgendwo hinstellen. Ich habe eine max_depth-Option hinzugefügt, die eine unendliche Rekursion verhindert. Auf der tiefsten Ebene werden Kinder als URLs dargestellt (das ist die letzte else-Klausel, wenn Sie lieber möchten, dass es keine URL ist).

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'])
5
Will S

Mit Django REST framework 3.3.1 benötigte ich den folgenden Code, um Unterkategorien zu Kategorien hinzuzufügen:

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')
4
AndraD