Django dinlenme çerçevesi, aynı ModelViewSet'te farklı serileştiriciler kullanın


197

İki farklı serileştirici sağlamak ve henüz tüm olanaklarından yararlanabilmek istiyorum ModelViewSet:

  • Nesnelerin bir listesini görüntülerken, her nesnenin ayrıntılarına yönlendiren bir url'ye sahip olmasını ve diğer her ilişkinin __unicode __hedef modeli kullanarak görünmesini isterim ;

misal:

{
  "url": "http://127.0.0.1:8000/database/gruppi/2/",
  "nome": "universitari",
  "descrizione": "unitn!",
  "creatore": "emilio",
  "accesso": "CHI",
  "membri": [
    "emilio",
    "michele",
    "luisa",
    "ivan",
    "saverio"
  ]
}
  • Bir nesnenin ayrıntılarını görüntülerken, varsayılanı kullanmak istiyorum HyperlinkedModelSerializer

misal:

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

Tüm bu çalışmaları dilediğim gibi yapmayı başardım:

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.

Temel olarak, kullanıcının bir liste görünümü veya ayrıntılı bir görünüm istediğini ve serializer_classihtiyaçlarıma uygun olarak değiştirdiğini tespit ederim. Gerçi bu koddan gerçekten memnun değilim, kirli bir kesmek gibi görünüyor ve en önemlisi, iki kullanıcı aynı anda bir liste ve detay isterse ne olur?

Bunu kullanarak başarmanın daha iyi bir yolu var mı yoksa kullanarak ModelViewSetsgeri çekilmem gerekiyor GenericAPIViewmu?

EDIT:
Özel bir taban kullanarak nasıl yapılacağı aşağıda açıklanmıştır 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.
    }

son olarak nasıl uyguladın? User2734679 tarafından önerilen şekli mi kullanıyorsunuz yoksa GenericAPIView mi kullanıyorsunuz?
andilabs

User2734679 tarafından önerildiği gibi; Her eylem için serileştiriciyi ve belirtilmediğinde varsayılan bir serileştiriciyi belirtmek için bir sözlük ekleyerek genel bir ViewSet oluşturdum
BlackBear

Benzer bir sorunum var ( stackoverflow.com/questions/24809737/… ) ve şimdilik bununla sona erdi ( gist.github.com/andilab/a23a6370bd118bf5e858 ), ancak bundan çok memnun değilim.
andilabs

1
Bunun için bu küçük paketi oluşturdu. github.com/Darwesh27/drf-custom-viewsets
Adil Malik

1
Geçersiz kılma alma yöntemi TAMAM.
gzerone

Yanıtlar:


289

get_serializer_classYönteminizi geçersiz kılın . Bu yöntem, model karışımlarınızda uygun Serializer sınıfını almak için kullanılır.

Ayrıca , doğru Serileştiricinin get_serializerbir örneğini döndüren bir yöntem olduğunu unutmayın.

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
Harika, teşekkürler! Olsa bile get_serializer_class geçersiz kılınmış
BlackBear

15
UYARI: django rest swagger bir self.action parametresi yerleştirmez, bu nedenle bu işlev bir istisna atar. Gonz'un cevabını kullanabilirsiniz ya da kullanabilirsinizif hasattr(self, 'action') and self.action == 'list'
Tom Leys

Bunun için küçük bir pypi paketi oluşturun. github.com/Darwesh27/drf-custom-viewsets
Adil Malik

pkİşlem yapılıyorsa, nesnenin istenenini nasıl alabiliriz retrieve?
Pranjal Mittal

Benim özlemim Yok. Birisi bana nedenini söyleyebilir mi?
Kakaji

86

Bu karışımı yararlı bulabilirsiniz, get_serializer_class yöntemini geçersiz kılar ve eylem ile serileştirici sınıfını veya olağan davranışa geri dönüşü eşleyen bir dikte bildirmenizi sağlar.

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

Bunun için bu küçük paketi oluşturdu. github.com/Darwesh27/drf-custom-viewsets
Adil Malik

15

Bu cevap kabul edilen cevapla aynıdır, ancak bu şekilde yapmayı tercih ederim.

Genel görünümler

get_serializer_class(self):

Serileştirici için kullanılması gereken sınıfı döndürür. Varsayılan değeriserializer_classÖzniteliği .

Okuma ve yazma işlemleri için farklı serileştiriciler kullanma veya farklı kullanıcı türlerine farklı serileştiriciler sağlama gibi dinamik davranış sağlamak için geçersiz kılınabilir. serializer_class özniteliği.

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)

Kullanılamıyor çünkü bana göre "eylem" özelliği yok. ProductIndex (generics.ListCreateAPIView) gibi görünüyor. Bu, kesinlikle görünüm kümelerini bağımsız değişken olarak geçirmeniz gerektiği anlamına mı geliyor yoksa generics API görünümlerini kullanarak bunu yapmanın bir yolu var mı?
Seb

1
@Seb yorumuna geç bir cevap - belki birisi bundan kâr edebilir :) Örnek, ViewSets'i kullanır, Views değil :)
fanny

Yani bu yazı stackoverflow.com/questions/32589087/… ile birlikte , ViewSets farklı görünümler üzerinde daha fazla kontrol sahibi olmak ve tutarlı bir API için otomatik olarak url oluşturmak için gitmek için bir yol gibi görünüyor? Aslında generics.ListeCreateAPIView en verimli, ama çok temel olduğunu düşündüm değil mi?
Seb

11

Farklı serileştiriciler sağlama konusunda neden kimse HTTP yöntemini kontrol eden yaklaşıma gitmiyor? Daha net bir IMO'dur ve ekstra kontrol gerektirmez.

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

Kredi / kaynak: https://github.com/encode/django-rest-framework/issues/1563#issuecomment-42357718


12
Söz konusu davada, eylemler için farklı bir serileştirici kullanmakla ilgilidir listve retrieveher ikisinde de GETyöntem kullanma sorunu vardır . Bu nedenle ViewSets django dinlenme çerçevesi eylem kavramını kullanır benzer, ancak karşılık gelen http yöntemlerinden biraz farklı olan .
Håken Lid

8

@Gonz ve @ user2734679 yanıtlarına dayanarak, bu işlevselliği ModelViewset'in alt sınıfında veren bu küçük python paketini oluşturdum . İşte böyle çalışır.

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
Hangi daha genel mixin kullanmak daha iyidir.
iamsk

1

Her ne kadar çoklu Serileri önceden ya da böyle tanımlamak en açık şekilde belgelenmiş gibi gözükse de şekilde yol , FWIW, diğer belgelenmiş kodlardan yararlanan ve serileştiricinin somutlaştırıldığı gibi argümanlara geçilmesini sağlayan alternatif bir yaklaşım vardır. Kullanıcı yönetici düzeyleri, çağrılan eylem, hatta örneğin öznitelikleri gibi çeşitli faktörlere dayalı mantık oluşturmanız gerekiyorsa, muhtemelen daha değerli olmayacağını düşünüyorum.

Bulmacanın ilk parçası, serileştirme noktasında bir serileştiricinin dinamik olarak değiştirilmesine ilişkin belgelerdir. . Bu dokümantasyon, bu kodun bir görünüm kümesinden nasıl çağrılacağını veya alanların başlatıldıktan sonra salt okunur durumunun nasıl değiştirileceğini açıklamaz - ancak bu çok zor değildir.

İkinci parça - get_serializer yöntemi de belgelenmiştir - ('diğer yöntemler' altında get_serializer_class sayfasından biraz daha aşağıdadır), bu nedenle güvenmek güvenli olmalıdır (ve kaynak çok basittir, bu da umarım daha az istenmeyen şans anlamına gelir modifikasyondan kaynaklanan yan etkiler). GenericAPIView (ModelViewSet - ve göründüğü diğer tüm yerleşik set kümesi sınıfları) altındaki kaynağı kontrol edin, get_serializer'ı tanımlayan GenericAPIView'den devralın.

İkisini bir araya getirmek gibi bir şey yapabilirsiniz:

Bir serileştiriciler dosyasında (benim için 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

Sonra görünümünüzde böyle bir şey yapabilirsiniz:

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)

Ve bu olmalı! MyViewSet'i kullanmak artık MyDynamicSerializer'ınızı istediğiniz bağımsız değişkenlerle başlatmalı ve serileştiricinizin DynamicFieldsModelSerializer'ınızdan miras aldığını varsayarak, ne yapacağını bilmelidir.

Belki de, serileştiriciyi başka şekillerde uyarlamak istiyorsanız özel bir anlam ifade edebileceğini belirtmek gerekir… örneğin, bir read_only_exceptions listesinde almak ve bunu kara liste alanlarından ziyade beyaz listeye kullanmak gibi şeyler yapmak (bunu yapmak eğilimindeyim). Ben de onun geçti ve sonra sadece Yok çeki kaldırmaz eğer kullanışlı boş tuplea alanını belirleme bulmak ... ve 'benim miras serializers benim alanları tanımları set tüm '. Bu araçlar seri hale başlatmasını geçmedi hiçbir alanlar kazara hayatta ve ben de içinde mesela dahil oldu bilmek devralan serileştirici sınıf tanımının ... ile serileştirici çağırmayı karşılaştırmak gerekmez init DynamicFieldsModelSerializer ait:

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

Not: Sadece farklı eylemlerle eşleştirilmiş iki veya üç sınıf isteseydim ve / veya özel olarak dinamik bir serileştirici davranışı istemiyorsam, burada başkaları tarafından belirtilen yaklaşımlardan birini kullanabilirim, ancak alternatif olarak sunmaya değer olduğunu düşündüm , özellikle diğer kullanımları göz önüne alındığında.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.