Django बाकी रूपरेखा नेस्टेड-रेफ़रेंशियल ऑब्जेक्ट्स


88

मेरे पास मॉडल है जो इस तरह दिखता है:

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)

मैं धारावाहिक के साथ सभी श्रेणियों के फ्लैट जोंस प्रतिनिधित्व प्राप्त करने में कामयाब रहा:

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

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

अब मैं जो करना चाहता हूं वह उपश्रेणियों की सूची में उनकी आईडी के बजाय उपश्रेणियों के इनलाइन जोंस प्रतिनिधित्व के लिए है। मैं django-rest-Framework के साथ ऐसा कैसे करूंगा? मैंने इसे प्रलेखन में खोजने की कोशिश की, लेकिन यह अधूरा लगता है।

जवाबों:


70

ManyRelatedField का उपयोग करने के बजाय, अपने क्षेत्र के रूप में नेस्टेड serializer का उपयोग करें:

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

यदि आप मनमाने ढंग से नेस्टेड फ़ील्ड से निपटना चाहते हैं, तो आपको डॉक्स के डिफ़ॉल्ट फ़ील्ड्स को कस्टमाइज़ करने पर एक नज़र डालनी चाहिए। आप वर्तमान में सीधे तौर पर अपने लिए एक क्षेत्र के रूप में एक धारावाहिक को घोषित नहीं कर सकते हैं, लेकिन आप इन विधियों का उपयोग करके डिफ़ॉल्ट रूप से फ़ील्ड का उपयोग कर सकते हैं।

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

वास्तव में, जैसा कि आपने ऊपर उल्लेख किया है कि यह काफी सही नहीं है। यह एक हैक का एक सा है, लेकिन हो सकता है कि धारावाहिक के पहले ही घोषित होने के बाद आप इस क्षेत्र को जोड़ने का प्रयास करें।

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

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

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

पुनरावर्ती रिश्तों को घोषित करने का एक तंत्र ऐसा है जिसे जोड़ा जाना चाहिए।


संपादित करें : ध्यान दें कि अब एक तृतीय-पक्ष पैकेज उपलब्ध है जो विशेष रूप से इस तरह के उपयोग-मामले से संबंधित है। Djangorestframework- पुनरावर्ती देखें ।


3
ठीक है, यह गहराई = 1 के लिए काम करता है। क्या होगा यदि मेरे पास ऑब्जेक्ट ट्री में अधिक स्तर हैं - श्रेणी में उपश्रेणी है जो उपश्रेणी है? मैं इनलाइन ऑब्जेक्ट्स के साथ मनमानी गहराई के पूरे पेड़ का प्रतिनिधित्व करना चाहता हूं। आपके दृष्टिकोण का उपयोग करते हुए, मैं SubCategorySerializer में उपश्रेणी क्षेत्र को परिभाषित नहीं कर सकता।
जेस्क चामेलेव्स्की

सेल्फ-रेफ़रेंशियल सीरियलों पर अधिक जानकारी के साथ संपादित
टॉम क्रिस्टी

अब मैं मिल गया KeyError at /api/category/ 'subcategories'। Btw अपने सुपर फास्ट जवाब के लिए धन्यवाद :)
Jacek Chmielewski

4
इस प्रश्न को देखने वाले किसी भी व्यक्ति के लिए, मैंने पाया कि प्रत्येक अतिरिक्त पुनरावर्ती स्तर के लिए, मुझे दूसरे संपादन में अंतिम पंक्ति को दोहराना था। अजीब बात है, लेकिन काम करने लगता है।
जेरेमी ब्लालॉक

19
मैं केवल यह बताना चाहूंगा कि "base_fields" अब काम नहीं करता है। DRF 3.1.0 के साथ "_declared_fields" जहां जादू है।
ट्रैविस स्वाइनटेक

50

@ wjin समाधान मेरे लिए बहुत अच्छा काम कर रहा था जब तक कि मैंने Django REST फ्रेमवर्क 3.0.0 में अपग्रेड नहीं किया, जो कि in_native को दर्शाता है । यहां मेरा डीआरएफ 3.0 समाधान है, जो थोड़ा संशोधन है।

मान लें कि आपके पास स्वयं-संदर्भ क्षेत्र के साथ एक मॉडल है, उदाहरण के लिए "जवाब" नामक संपत्ति में थ्रेडेड टिप्पणियां। आपके पास इस टिप्पणी धागे का एक पेड़ प्रतिनिधित्व है, और आप पेड़ को क्रमबद्ध करना चाहते हैं

सबसे पहले, अपने पुन: प्रयोज्य RecursiveField वर्ग को परिभाषित करें

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

फिर, अपने धारावाहिक के लिए, "जवाब" के मूल्य को क्रमबद्ध करने के लिए RecursiveField का उपयोग करें

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

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

आसान मटर, और आपको केवल पुन: प्रयोज्य समाधान के लिए कोड की 4 लाइनों की आवश्यकता है।

नोट: यदि आपकी डेटा संरचना किसी पेड़ से अधिक जटिल है, जैसे कि एक निर्देशित एसाइक्लिक ग्राफ (FANCY!) कहें, तो आप @ wjin के पैकेज को आज़मा सकते हैं - उसका समाधान देखें। लेकिन मुझे MPTTModel आधारित पेड़ों के समाधान के साथ कोई समस्या नहीं है।


1
लाइन सीरियलाइज़र = self.parent.parent .__ वर्ग __ (मान, संदर्भ = self.context) क्या करता है। क्या यह_प्रदर्शन () विधि है?
मौरिसियो

यह पंक्ति सबसे महत्वपूर्ण हिस्सा है - यह सही धारावाहिक को संदर्भित करने के लिए क्षेत्र के प्रतिनिधित्व की अनुमति देता है। इस उदाहरण में, मेरा मानना ​​है कि यह टिप्पणी करने वाला होगा।
मार्क चकेरियन

1
मुझे माफ कर दो। मुझे समझ नहीं आया कि यह कोड क्या कर रहा है। मैंने इसे चलाया और यह काम करता है। लेकिन मुझे नहीं पता कि यह वास्तव में कैसे काम करता है।
मौरिसियो

कुछ प्रिंट स्टेटमेंट जैसे print self.parent.parent.__class__औरprint self.parent.parent
मार्क चेकरियन

समाधान काम करता है लेकिन मेरे धारावाहिक का काउंट आउटपुट गलत है। यह केवल रूट नोड्स को गिनता है। कोई विचार? यह djangorestframework-recursive के साथ समान है।
लुकास वेगा 13

37

एक अन्य विकल्प जो Django REST फ्रेमवर्क 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

6
यह स्वीकृत उत्तर क्यों नहीं है? अच्छी तरह से काम।
कार्तिक RP

5
यह बहुत सरलता से काम करता है, मेरे पास इस पोस्ट को अन्य समाधानों की तुलना में बहुत आसान समय है।
निक बीएल

इस समाधान के लिए अतिरिक्त कक्षाओं की आवश्यकता नहीं है और parent.parent.__class__सामान की तुलना में समझना आसान है । मुझे यह सबसे ज्यादा पसंद है।
SergiyKolesnikov

27

यहाँ खेल के लिए देर हो चुकी है, लेकिन यहाँ मेरा समाधान है। मान लीजिए कि मैं एक ब्लाह को क्रमबद्ध कर रहा हूं, जिसमें कई बच्चे भी ब्लाह के हैं।

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

इस क्षेत्र का उपयोग करके मैं अपनी पुनरावृत्ति-परिभाषित वस्तुओं को क्रमबद्ध कर सकता हूं जिनमें कई बाल-ऑब्जेक्ट हैं

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

मैंने DRF3.0 के लिए एक पुनरावर्ती क्षेत्र लिखा और इसे पाइप के लिए https://pypi.python.org/pypi/djangorestframework-recursive/ पर पैक किया।


1
MPTTModel को क्रमबद्ध करने के साथ काम करता है। अच्छा!
मार्क चकेरियन

2
आप अभी भी बच्चे को रूट थू में दोहराते हैं? मैं इसे कैसे रोक सकता हूँ?
प्रोमेथियस

क्षमा करें @Sputnik मुझे समझ नहीं आ रहा है कि आपका क्या मतलब है। मैंने यहां जो दिया है वह उस मामले के लिए काम करता है जहां आपके पास एक वर्ग है Blahऔर इसमें एक फ़ील्ड है जिसे ऑब्जेक्ट्स की child_blahsसूची में शामिल किया गया Blahहै।
14

4
जब तक मैंने DRF 3.0 में अपग्रेड नहीं किया, तब तक यह बहुत अच्छा काम कर रहा था, इसलिए मैंने 3.0 वेरिएशन पोस्ट किया।
मार्क चकेरियन

1
@ फाल्कन 1 आप क्वेरी को फ़िल्टर कर सकते हैं और केवल इस तरह से देख सकते हैं queryset=Class.objects.filter(level=0)। यह बाकी चीजों को खुद संभालता है।
छैनीताल

13

मैं एक का उपयोग कर इस परिणाम को प्राप्त करने में सक्षम था serializers.SerializerMethodField। मुझे यकीन नहीं है कि यह सबसे अच्छा तरीका है, लेकिन मेरे लिए काम किया:

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

1
मेरे लिए यह इस समाधान और yprez के समाधान के बीच एक विकल्प के लिए नीचे आया । वे पहले पोस्ट किए गए समाधानों की तुलना में स्पष्ट और सरल दोनों हैं। यहाँ समाधान जीत गया क्योंकि मैंने पाया कि यह ओपी द्वारा प्रस्तुत समस्या को हल करने का सबसे अच्छा तरीका है और साथ ही साथ इस समाधान का समर्थन गतिशील रूप से चयनित क्षेत्रों को क्रमबद्ध करने के लिए करता है । Yprez का समाधान एक अनंत पुनरावृत्ति का कारण बनता है या पुनरावृत्ति से बचने और ठीक से फ़ील्ड चुनने के लिए अतिरिक्त जटिलताओं की आवश्यकता होती है।
लुई

9

एक अन्य विकल्प यह देखने के लिए होगा कि आपके मॉडल को क्रमबद्ध किया जाए। यहाँ एक उदाहरण है:

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)

यह बहुत अच्छा है, मेरे पास मनमाने ढंग से गहरा पेड़ था जिसे मुझे क्रमबद्ध करने की आवश्यकता थी और यह एक आकर्षण की तरह काम करता था!
वीर ओर्री रेनिसन

अच्छा और बहुत उपयोगी जवाब। ModelSerializer पर बच्चे प्राप्त करते समय आप बाल तत्वों को प्राप्त करने के लिए कोई क्वेरी निर्दिष्ट नहीं कर सकते। इस मामले में आप ऐसा कर सकते हैं।
एफ्रिन

8

मुझे हाल ही में एक ही समस्या थी और एक समाधान के साथ आया था जो अब तक काम करने के लिए लगता है, यहां तक ​​कि मनमानी गहराई के लिए भी। समाधान टॉम क्रिस्टी से एक छोटा संशोधन है:

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

मुझे यकीन नहीं है कि यह मज़बूती से किसी भी स्थिति में काम कर सकता है, हालांकि ...


1
2.3.8 के अनुसार, कोई Convert_object विधि नहीं है। लेकिन एक ही चीज को to_native method को ओवरराइड करके किया जा सकता है।
अभाग

6

यह caipirginka समाधान से एक अनुकूलन है जो dr। 3.0.5 और 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')

ध्यान दें कि 6 वीं पंक्ति में कैटेसेरीलाइज़र को ऑब्जेक्ट और कई = सही विशेषता के साथ कहा जाता है।


कमाल है, यह मेरे लिए काम किया। हालाँकि, मुझे लगता है कि if 'branches'इसे बदल दिया जाना चाहिएif 'subcategories'
vabada

5

मुझे लगा कि मैं मज़े में शामिल हो जाऊंगा!

वाया वजिन और मार्क चेकरियन I ने एक अधिक सामान्य समाधान बनाया, जो सीधे पेड़ जैसे मॉडल और पेड़ संरचनाओं के लिए काम करता है जिनके पास एक मॉडल है। मुझे यकीन नहीं है कि अगर यह खुद के जवाब में है, लेकिन मुझे लगा कि मैं इसे कहीं रख सकता हूं। मैंने एक max_depth विकल्प शामिल किया है जो अनंत पुनरावृत्ति को रोक देगा, सबसे गहरे स्तर पर बच्चों को URLS के रूप में दर्शाया गया है (यदि आप एक यूआरएल नहीं था तो यह अंतिम रूप से खंड है)।

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

यह एक बहुत ही गहन समाधान है, हालाँकि, यह ध्यान देने योग्य है कि आपका elseक्लॉज़ दृश्य के बारे में कुछ धारणाएँ बनाता है। मुझे अपना स्थान बदलना पड़ा, return value.pkइसलिए इसने दृश्य को उलटने की कोशिश करने के बजाय प्राथमिक कुंजी वापस कर दी।
सोवियुत 25:16

4

Django बाकी ढांचे 3.3.1 के साथ, मुझे उप-श्रेणियों को श्रेणियों में जोड़ने के लिए निम्न कोड की आवश्यकता है:

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

1

यह समाधान यहां पोस्ट किए गए अन्य समाधानों के साथ लगभग समान है लेकिन मूल स्तर पर बाल पुनरावृत्ति की समस्या के संदर्भ में थोड़ा अंतर है (यदि आप इसे एक समस्या मानते हैं)। एक उदाहरण के लिए

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

और अगर आपके पास यह दृश्य है

class CategoryListAPIView(ListAPIView):
    queryset = Category.objects.all()
    serializer_class = CategoryListSerializer

यह निम्नलिखित परिणाम का उत्पादन करेगा,

[
{
    "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": []
}
]

यहाँ parent categoryएक child categoryऔर json प्रतिनिधित्व है, ठीक वैसा ही जैसा हम चाहते हैं कि वह प्रतिनिधित्व करे।

लेकिन आप देख सकते हैं child categoryकि जड़ स्तर पर एक पुनरावृत्ति है।

जैसा कि कुछ लोग उपरोक्त पोस्ट किए गए उत्तर के टिप्पणी अनुभागों में पूछ रहे हैं कि हम इस बच्चे की पुनरावृत्ति को जड़ स्तर पर कैसे रोक सकते हैं , बस अपनी क्वेरी parent=Noneको निम्न प्रकार से फ़िल्टर करें

class CategoryListAPIView(ListAPIView):
    queryset = Category.objects.filter(parent=None)
    serializer_class = CategoryListSerializer

यह समस्या को हल करेगा।

नोट: यह उत्तर सीधे प्रश्न से संबंधित नहीं हो सकता है, लेकिन समस्या किसी तरह से संबंधित है। इसके अलावा उपयोग करने का यह तरीका RecursiveSerializerमहंगा है। बेहतर है यदि आप अन्य विकल्पों का उपयोग करते हैं जो प्रदर्शन प्रवण हैं।


फ़िल्टर के साथ क्वेरी करने से मेरे लिए एक त्रुटि हुई। लेकिन इससे दोहराए गए क्षेत्र से छुटकारा पाने में मदद मिली। अनुक्रमिक वर्ग में ओवरराइड_प्रदर्शन विधि: stackoverflow.com/questions/37985581/…
हारून
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.