Django बाकी ढांचे, एक ही ModelViewSet में विभिन्न धारावाहिक का उपयोग करें


196

मैं दो अलग-अलग धारावाहिक प्रदान करना चाहूंगा और फिर भी सभी सुविधाओं का लाभ उठा सकता हूं ModelViewSet:

  • वस्तुओं की सूची को देखते समय, मैं चाहूंगा कि प्रत्येक वस्तु एक url हो जो उसके विवरण को पुनर्निर्देशित करे और हर दूसरा संबंध __unicode __लक्ष्य मॉडल का उपयोग करते हुए दिखाई दे ;

उदाहरण:

{
  "url": "http://127.0.0.1:8000/database/gruppi/2/",
  "nome": "universitari",
  "descrizione": "unitn!",
  "creatore": "emilio",
  "accesso": "CHI",
  "membri": [
    "emilio",
    "michele",
    "luisa",
    "ivan",
    "saverio"
  ]
}
  • किसी वस्तु का विवरण देखने के बाद, मैं डिफ़ॉल्ट का उपयोग करना चाहूंगा HyperlinkedModelSerializer

उदाहरण:

{
  "url": "http://127.0.0.1:8000/database/gruppi/2/",
  "nome": "universitari",
  "descrizione": "unitn!",
  "creatore": "http://127.0.0.1:8000/database/utenti/3/",
  "accesso": "CHI",
  "membri": [
    "http://127.0.0.1:8000/database/utenti/3/",
    "http://127.0.0.1:8000/database/utenti/4/",
    "http://127.0.0.1:8000/database/utenti/5/",
    "http://127.0.0.1:8000/database/utenti/6/",
    "http://127.0.0.1:8000/database/utenti/7/"
  ]
}

मैं निम्नलिखित तरीके से अपनी इच्छानुसार यह सब काम करने में कामयाब रहा:

serializers.py

# serializer to use when showing a list
class ListaGruppi(serializers.HyperlinkedModelSerializer):
    membri = serializers.RelatedField(many = True)
    creatore = serializers.RelatedField(many = False)

    class Meta:
        model = models.Gruppi

# serializer to use when showing the details
class DettaglioGruppi(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = models.Gruppi

views.py

class DualSerializerViewSet(viewsets.ModelViewSet):
    """
    ViewSet providing different serializers for list and detail views.

    Use list_serializer and detail_serializer to provide them
    """
    def list(self, *args, **kwargs):
        self.serializer_class = self.list_serializer
        return viewsets.ModelViewSet.list(self, *args, **kwargs)

    def retrieve(self, *args, **kwargs):
        self.serializer_class = self.detail_serializer
        return viewsets.ModelViewSet.retrieve(self, *args, **kwargs)

class GruppiViewSet(DualSerializerViewSet):
    model = models.Gruppi
    list_serializer = serializers.ListaGruppi
    detail_serializer = serializers.DettaglioGruppi

    # etc.

मूल रूप से मैं पता लगाता हूं कि जब उपयोगकर्ता एक सूची दृश्य या एक विस्तृत दृश्य और serializer_classमेरी आवश्यकताओं के अनुरूप परिवर्तन का अनुरोध कर रहा है। मैं वास्तव में इस कोड से संतुष्ट नहीं हूं, हालांकि, यह एक गंदे हैक की तरह दिखता है और सबसे महत्वपूर्ण बात यह है कि अगर दो उपयोगकर्ता एक ही समय में एक सूची और एक विवरण का अनुरोध करते हैं?

क्या इसका उपयोग करने का एक बेहतर तरीका है ModelViewSetsया क्या मुझे वापस उपयोग करना पड़ता है GenericAPIView?

संपादित करें:
यहां बताया गया है कि कस्टम बेस का उपयोग करके इसे कैसे करें ModelViewSet:

class MultiSerializerViewSet(viewsets.ModelViewSet):
    serializers = { 
        'default': None,
    }

    def get_serializer_class(self):
            return self.serializers.get(self.action,
                        self.serializers['default'])

class GruppiViewSet(MultiSerializerViewSet):
    model = models.Gruppi

    serializers = {
        'list':    serializers.ListaGruppi,
        'detail':  serializers.DettaglioGruppi,
        # etc.
    }

आपने इसे अंतिम रूप से कैसे लागू किया? User2734679 द्वारा प्रस्तावित तरीका या GenericAPIView का उपयोग करके?
औरइलाब

जैसा कि user2734679 ने सुझाव दिया है; मैंने प्रत्येक कार्रवाई के लिए धारावाहिक को निर्दिष्ट करने के लिए एक डिक्शनरी जोड़ने के लिए एक सामान्य व्यूसेट बनाया और निर्दिष्ट नहीं किए जाने पर एक डिफ़ॉल्ट धारावाहिक
ब्लैकबियर

मेरे पास समान मुद्दा है ( stackoverflow.com/questions/24809737/… ) और अब इसके साथ समाप्त हो गया है ( gist.github.com/andilab/a23a6370bd118bf5e858 ), लेकिन मैं इससे बहुत संतुष्ट नहीं हूं।
andilabs

1
इसके लिए यह छोटा पैकेज बनाया। github.com/Darwesh27/drf-custom-viewsets
आदिल मलिक

1
ओवरराइड पुनर्प्राप्त विधि ठीक है।
गेजरोन

जवाबों:


288

अपनी get_serializer_classविधि को ओवरराइड करें । इस विधि का उपयोग आपके मॉडल मिश्रणों में उचित Serializer वर्ग को पुनः प्राप्त करने के लिए किया जाता है।

ध्यान दें कि एक get_serializerविधि भी है जो सही Serializer का एक उदाहरण देता है

class DualSerializerViewSet(viewsets.ModelViewSet):
    def get_serializer_class(self):
        if self.action == 'list':
            return serializers.ListaGruppi
        if self.action == 'retrieve':
            return serializers.DettaglioGruppi
        return serializers.Default # I dont' know what you want for create/destroy/update.                

1
यह बहुत अच्छा है, धन्यवाद! मैंने get_serializer_class को ओवरराइड कर दिया है
BlackBear

15
चेतावनी: django बाकी स्वैगर में स्व-निष्क्रियता पैरामीटर नहीं होता है, इसलिए यह फ़ंक्शन अपवाद को फेंक देगा। आप गॉन्ज़ के उत्तर का उपयोग कर सकते हैं या आप का उपयोग कर सकते हैंif hasattr(self, 'action') and self.action == 'list'
टॉम

इसके लिए एक छोटा पिपी पैकेज बनाएं। github.com/Darwesh27/drf-custom-viewsets
आदिल मलिक

हमें pkअनुरोध की गई वस्तु कैसे मिलती है , अगर कार्रवाई होती है retrieve?
प्रांजल मित्तल

मेरा सेल्फएक्शन कोई नहीं है। कोई मुझे बता सकता है क्यों?
काकाजी

86

आपको यह मिक्सिन उपयोगी लग सकता है, यह get_serializer_class पद्धति को ओवरराइड करता है और आपको एक ऐसा तानाशाही घोषित करने की अनुमति देता है जो कार्रवाई और सीरियलाइज़र वर्ग या सामान्य व्यवहार को कम करता है।

class MultiSerializerViewSetMixin(object):
    def get_serializer_class(self):
        """
        Look for serializer class in self.serializer_action_classes, which
        should be a dict mapping action name (key) to serializer class (value),
        i.e.:

        class MyViewSet(MultiSerializerViewSetMixin, ViewSet):
            serializer_class = MyDefaultSerializer
            serializer_action_classes = {
               'list': MyListSerializer,
               'my_action': MyActionSerializer,
            }

            @action
            def my_action:
                ...

        If there's no entry for that action then just fallback to the regular
        get_serializer_class lookup: self.serializer_class, DefaultSerializer.

        """
        try:
            return self.serializer_action_classes[self.action]
        except (KeyError, AttributeError):
            return super(MultiSerializerViewSetMixin, self).get_serializer_class()

इसके लिए यह छोटा पैकेज बनाया। github.com/Darwesh27/drf-custom-viewsets
आदिल मलिक

15

यह उत्तर स्वीकृत उत्तर के समान है लेकिन मैं इस तरह से करना पसंद करता हूं।

सामान्य विचार

get_serializer_class(self):

वह वर्ग लौटाता है जिसका उपयोग धारावाहिक के लिए किया जाना चाहिए। serializer_classविशेषता को वापस करने के लिए चूक ।

गतिशील व्यवहार प्रदान करने के लिए ओवरराइड किया जा सकता है, जैसे कि पढ़ने और लिखने के संचालन के लिए विभिन्न धारावाहिकों का उपयोग करना या विभिन्न प्रकार के उपयोगकर्ताओं को विभिन्न धारावाहिक प्रदान करना। serializer_class विशेषता।

class DualSerializerViewSet(viewsets.ModelViewSet):
    # mapping serializer into the action
    serializer_classes = {
        'list': serializers.ListaGruppi,
        'retrieve': serializers.DettaglioGruppi,
        # ... other actions
    }
    default_serializer_class = DefaultSerializer # Your default serializer

    def get_serializer_class(self):
        return self.serializer_classes.get(self.action, self.default_serializer_class)

इसका उपयोग नहीं किया जा सकता क्योंकि यह मुझे बताता है कि मेरे विचार में "कार्रवाई" की कोई विशेषता नहीं है। यह ProductIndex (generics.ListCreateAPIView) जैसा दिखता है। क्या इसका मतलब है कि आपको व्यूसेट्स को तर्क के रूप में पारित करने की आवश्यकता है या जेनेरिक एपीआई विचारों का उपयोग करने का एक तरीका है?
सेब

1
@ टिप्पणी का देर से जवाब - शायद कोई व्यक्ति इससे लाभान्वित हो सकता है :) उदाहरण व्यूसेट्स का उपयोग करता है, न कि दृश्यों :)
फैनी

तो इस पोस्ट के साथ संयुक्त stackoverflow.com/questions/32589087/… , ViewSets विभिन्न विचारों पर अधिक नियंत्रण के लिए जाने के लिए और स्वचालित रूप से एक संगत एपीआई है url उत्पन्न करने के लिए रास्ता लगता है? मूल रूप से सोचा था कि generics.ListeCreateAPIView सबसे कुशल था, लेकिन बहुत बुनियादी अधिकार था?
एसईबी

10

अलग-अलग धारावाहिक उपलब्ध कराने के बारे में, HTTP पद्धति की जांच करने वाले दृष्टिकोण के लिए कोई क्यों नहीं जा रहा है? यह स्पष्ट IMO है और इसके लिए अतिरिक्त जांच की आवश्यकता नहीं है।

def get_serializer_class(self):
    if self.request.method == 'POST':
        return NewRackItemSerializer
    return RackItemSerializer

क्रेडिट / स्रोत: https://github.com/encode/django-rest-framework/issues/1563#issuecomment-42357718


12
प्रश्न में मामले के लिए, जो क्रियाओं listऔर retrieveकार्यों के लिए एक अलग धारावाहिक का उपयोग करने के बारे में है , आपको समस्या है कि दोनों GETविधि का उपयोग करते हैं। यही कारण है कि django बाकी ढांचा ViewSets कार्यों की अवधारणा का उपयोग करता है , जो समान हैं, लेकिन संबंधित http विधियों की तुलना में थोड़ा अलग है।
होकेन लिड

8

@Gonz और @ user2734679 उत्तरों के आधार पर मैंने यह छोटा अजगर पैकेज बनाया है जो इस कार्यक्षमता को ModelViewset के बाल वर्ग के रूप में प्रस्तुत करता है। यहाँ दिया गया है कि यह कैसे काम करता है।

from drf_custom_viewsets.viewsets.CustomSerializerViewSet
from myapp.serializers import DefaltSerializer, CustomSerializer1, CustomSerializer2

class MyViewSet(CustomSerializerViewSet):
    serializer_class = DefaultSerializer
    custom_serializer_classes = {
        'create':  CustomSerializer1,
        'update': CustomSerializer2,
    }

6
यह बेहतर उपयोग मिश्रण है जो बहुत सामान्य है।
iamsk

1

हालाँकि, कई अन्य धारावाहिकों को पूर्व-परिभाषित या किसी अन्य तरीके से परिभाषित करना सबसे स्पष्ट रूप से प्रलेखित तरीका लगता है, लेकिन FWIW एक वैकल्पिक दृष्टिकोण है जो अन्य प्रलेखित कोड पर आकर्षित होता है और जो धारावाहिक के लिए तर्क को पारित करने में सक्षम बनाता है क्योंकि यह त्वरित है। मुझे लगता है कि यह संभवत: अधिक सार्थक होगा यदि आपको विभिन्न कारकों के आधार पर तर्क उत्पन्न करने की आवश्यकता होती है, जैसे उपयोगकर्ता व्यवस्थापक स्तर, कार्रवाई कहा जा रहा है, शायद उदाहरण के गुण भी।

पहेली का पहला टुकड़ा तात्कालिकता के बिंदु पर एक धारावाहिक को गतिशील रूप से संशोधित करने पर प्रलेखन है । यह दस्तावेज़ीकरण यह नहीं बताता है कि इस कोड को किसी व्यूसेट से कैसे कॉल किया जाए या खेतों की स्थिति के बाद उन्हें कैसे संशोधित किया जाए - लेकिन यह बहुत कठिन नहीं है।

दूसरा टुकड़ा - get_serializer विधि भी प्रलेखित है - ('अन्य विधियों' के तहत get_serializer_class के पृष्ठ से थोड़ा आगे) तो इस पर भरोसा करना सुरक्षित होना चाहिए (और स्रोत बहुत सरल है, जो उम्मीद है कि अनपेक्षित की संभावना कम है संशोधन से होने वाले दुष्प्रभाव)। GenericAPIView (ModelViewSet -) के तहत स्रोत की जाँच करें और ऐसा लगता है कि दर्शक वर्ग में निर्मित - GenericAPIView से विरासत में मिला, जो get_serializer को परिभाषित करता है।

दोनों को एक साथ रखकर आप कुछ ऐसा कर सकते हैं:

एक सीरियल फ़ाइल में (मेरे लिए base_serializers.py):

class DynamicFieldsModelSerializer(serializers.ModelSerializer):
"""
A ModelSerializer that takes an additional `fields` argument that
controls which fields should be displayed.
"""

def __init__(self, *args, **kwargs):
    # Don't pass the 'fields' arg up to the superclass
    fields = kwargs.pop('fields', None)

    # Adding this next line to the documented example
    read_only_fields = kwargs.pop('read_only_fields', None)

    # Instantiate the superclass normally
    super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)

    if fields is not None:
        # Drop any fields that are not specified in the `fields` argument.
        allowed = set(fields)
        existing = set(self.fields)
        for field_name in existing - allowed:
            self.fields.pop(field_name)

    # another bit we're adding to documented example, to take care of readonly fields 
    if read_only_fields is not None:
        for f in read_only_fields:
            try:
                self.fields[f].read_only = True
            exceptKeyError:
                #not in fields anyway
                pass

तब आपके विचार में आप ऐसा कुछ कर सकते हैं:

class MyViewSet(viewsets.ModelViewSet):
    # ...permissions and all that stuff

    def get_serializer(self, *args, **kwargs):

        # the next line is taken from the source
        kwargs['context'] = self.get_serializer_context()

        # ... then whatever logic you want for this class e.g:
        if self.action == "list":
            rofs = ('field_a', 'field_b')
            fs = ('field_a', 'field_c')
        if self.action == retrieve”:
            rofs = ('field_a', 'field_c’, ‘field_d’)
            fs = ('field_a', 'field_b’)
        #  add all your further elses, elifs, drawing on info re the actions, 
        # the user, the instance, anything passed to the method to define your read only fields and fields ...
        #  and finally instantiate the specific class you want (or you could just
        # use get_serializer_class if you've defined it).  
        # Either way the class you're instantiating should inherit from your DynamicFieldsModelSerializer
        kwargs['read_only_fields'] = rofs
        kwargs['fields'] = fs
        return MyDynamicSerializer(*args, **kwargs)

और यही होना चाहिए! MyViewSet का उपयोग करते हुए अब अपने MyDynamicSerializer को उन तर्कों के साथ त्वरित करना चाहिए जो आप चाहते हैं - और अपने सीरियलफ़ायर को अपने DynamicsFieldsModelSerializer से विरासत में लेते हुए, यह जानना चाहिए कि क्या करना है।

शायद यह ध्यान देने योग्य है कि यह विशेष समझ में आता है यदि आप धारावाहिक को कुछ अन्य तरीकों से अनुकूलित करना चाहते हैं ... जैसे कि एक read_only_exception सूची में लेने वाली चीज़ों को करना और इसे ब्लैकलिस्ट फ़ील्ड्स के बजाय श्वेतसूची में उपयोग करना (जो मैं करना चाहता हूं)। मैं भी यह उपयोगी है अगर इसकी पारित नहीं किया और फिर बस कोई नहीं के लिए चेक निकालने के एक खाली टपल करने के लिए फ़ील्ड सेट करने के लिए ... और मैं 'के लिए मेरी विरासत में serializers पर अपने क्षेत्रों परिभाषाओं सेट सब '। इस का मतलब है कोई फ़ील्ड है कि जब serializer instantiating पारित नहीं कर रहे हैं दुर्घटना से जीवित रहते हैं और मैं भी विरासत में serializer वर्ग परिभाषा को पता है कि शामिल किया गया है ... जैसे भीतर से serializer मंगलाचरण की तुलना करने की जरूरत नहीं है init DynamicFieldsModelSerializer की:

# ....
fields = kwargs.pop('fields', ())
# ...
allowed = set(fields)
existing = set(self.fields)
for field_name in existing - allowed:
self.fields.pop(field_name)
# ....

एनबी अगर मुझे सिर्फ दो या तीन वर्ग चाहिए थे जो अलग-अलग कार्यों के लिए मैप करते थे और / या मुझे कोई विशेष गतिशील धारावाहिक व्यवहार नहीं चाहिए था, तो मैं यहां दूसरों द्वारा बताए गए दृष्टिकोणों में से एक का उपयोग कर सकता हूं, लेकिन मुझे लगा कि यह विकल्प के रूप में प्रस्तुत करने लायक है। , विशेष रूप से इसके अन्य उपयोगों को देखते हुए।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.