Phần còn lại của Django, sử dụng các bộ nối tiếp khác nhau trong cùng ModelViewset


195

Tôi muốn cung cấp hai bộ nối tiếp khác nhau và vẫn có thể hưởng lợi từ tất cả các phương tiện của ModelViewSet:

  • Khi xem danh sách các đối tượng, tôi muốn mỗi đối tượng có một url chuyển hướng đến các chi tiết của nó và mọi quan hệ khác xuất hiện bằng cách sử dụng __unicode __mô hình đích;

thí dụ:

{
  "url": "http://127.0.0.1:8000/database/gruppi/2/",
  "nome": "universitari",
  "descrizione": "unitn!",
  "creatore": "emilio",
  "accesso": "CHI",
  "membri": [
    "emilio",
    "michele",
    "luisa",
    "ivan",
    "saverio"
  ]
}
  • Khi xem chi tiết của một đối tượng, tôi muốn sử dụng mặc định HyperlinkedModelSerializer

thí dụ:

{
  "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ôi quản lý để làm cho tất cả công việc này như tôi muốn theo cách sau:

nối tiếp

# 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

lượt xem

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.

Về cơ bản tôi phát hiện khi người dùng yêu cầu chế độ xem danh sách hoặc chế độ xem chi tiết và thay đổi serializer_classcho phù hợp với nhu cầu của tôi. Tôi không thực sự hài lòng với mã này, nó trông giống như một bản hack bẩn và quan trọng nhất là, nếu hai người dùng yêu cầu một danh sách và một chi tiết cùng một lúc thì sao?

Có cách nào tốt hơn để đạt được điều này bằng cách sử dụng ModelViewSetshay tôi phải quay lại sử dụng GenericAPIView?

EDIT:
Đây là cách thực hiện bằng cơ sở tùy chỉnh 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.
    }

Làm thế nào bạn thực hiện nó cuối cùng? Sử dụng cách được đề xuất bởi user2734679 hoặc sử dụng GenericAPIView?
andilabs

Theo đề xuất của người dùng2734679; Tôi đã tạo một Viewset chung thêm từ điển để chỉ định serializer cho từng hành động và serializer mặc định khi không được chỉ định
BlackBear

Tôi có vấn đề tương tự ( stackoverflow.com/questions/24809737/ trên ) và hiện tại đã kết thúc với nó ( gist.github.com/andilab/a23a6370bd118bf5e858 ), nhưng tôi không hài lòng lắm.
andilabs

1
Tạo gói nhỏ này cho việc này. github.com/Darwesh27/drf-custom-viewets
Adil Malik

1
Ghi đè phương thức truy xuất là OK.
gzerone

Câu trả lời:


287

Ghi đè get_serializer_classphương thức của bạn . Phương thức này được sử dụng trong các mixins mô hình của bạn để lấy lớp serializer thích hợp.

Lưu ý rằng cũng có một get_serializerphương thức trả về một thể hiện của Trình tuần tự chính xác

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
Điều này là rất tốt, cảm ơn bạn! Mặc dù vậy, tôi đã ghi đè get_serializer_group
BlackBear

15
CẢNH BÁO: swagger phần còn lại của django không đặt tham số self.action, vì vậy hàm này sẽ đưa ra một ngoại lệ. Bạn có thể sử dụng câu trả lời của gonz hoặc bạn có thể sử dụngif hasattr(self, 'action') and self.action == 'list'
Tom Leys

Tạo một gói pypi nhỏ cho việc này. github.com/Darwesh27/drf-custom-viewets
Adil Malik

Làm thế nào để chúng ta có được pkđối tượng được yêu cầu, nếu hành động là retrieve?
Pranjal Găngal

Tự lực của tôi là Không có. Ai đó có thể cho tôi biết tại sao?
Kakaji

86

Bạn có thể thấy mixin này hữu ích, nó ghi đè phương thức get_serializer_group và cho phép bạn khai báo một lệnh đọc ánh xạ lớp hành động và tuần tự hóa hoặc dự phòng hành vi thông thường.

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

Tạo gói nhỏ này cho việc này. github.com/Darwesh27/drf-custom-viewets
Adil Malik

15

Câu trả lời này giống như câu trả lời được chấp nhận nhưng tôi thích làm theo cách này.

Chế độ xem chung

get_serializer_class(self):

Trả về lớp nên được sử dụng cho serializer. Mặc định trả về serializer_classthuộc tính.

Có thể bị ghi đè để cung cấp hành vi động, chẳng hạn như sử dụng các bộ nối tiếp khác nhau để đọc và ghi các hoạt động hoặc cung cấp các bộ nối tiếp khác nhau cho các loại người dùng khác nhau. thuộc tính serializer_group.

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)

Không thể sử dụng nó vì nó cho tôi biết rằng quan điểm của tôi không có "hành động" thuộc tính. Nó trông giống như Product Index (genericics.ListCreateAPIView). Điều đó có nghĩa là bạn hoàn toàn cần phải vượt qua các khung nhìn làm đối số hoặc có cách nào để làm điều đó bằng cách sử dụng các khung nhìn API tổng quát không?
Seb

1
trả lời muộn cho nhận xét @Seb - có thể ai đó có thể kiếm lợi từ đó :) Ví dụ sử dụng ViewSets chứ không phải Lượt xem :)
fanny

Vì vậy, kết hợp với bài đăng stackoverflow.com/questions/32589087/ này , ViewSets dường như là cách để có nhiều quyền kiểm soát hơn đối với các chế độ xem khác nhau và tự động tạo url để có API nhất quán? Ban đầu nghĩ rằng Generics.ListeCreateAPIView là hiệu quả nhất, nhưng quá cơ bản phải không?
Seb

10

Liên quan đến việc cung cấp các bộ nối tiếp khác nhau, tại sao không ai đi theo cách tiếp cận kiểm tra phương thức HTTP? IMO rõ ràng hơn và không yêu cầu kiểm tra thêm.

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

Tín dụng / nguồn: https://github.com/encode/django-rest-framework/issues/1563#issuecomment-42357718


12
Đối với trường hợp được đề cập, đó là về việc sử dụng một trình tuần tự hóa listretrievehành động khác nhau , bạn có vấn đề là cả hai đều sử dụng GETphương thức. Đây là lý do tại sao phần còn lại của django ViewSets sử dụng khái niệm hành động , tương tự nhau, nhưng hơi khác so với các phương thức http tương ứng.
Nắp Håken

8

Dựa trên các câu trả lời @gonz và @ user2734679 Tôi đã tạo gói python nhỏ này cung cấp chức năng này dưới dạng một lớp ModelViewset con. Đây là cách nó làm việc.

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
Nó tốt hơn nên sử dụng mixin mà nhiều chung chung.
iamsk

1

Mặc dù trước khi xác định nhiều serializers nhập hoặc cách này hay cách khác dường như là rõ ràng nhất ghi nhận bằng cách nào, FWIW có một cách tiếp cận khác mà rút ra trên mã tờ chi khác và cho phép truyền đối số cho các serializer khi nó được khởi tạo. Tôi nghĩ rằng nó có thể có xu hướng đáng giá hơn nếu bạn cần tạo logic dựa trên các yếu tố khác nhau, chẳng hạn như cấp quản trị người dùng, hành động được gọi, thậm chí có thể là các thuộc tính của thể hiện.

Phần đầu tiên của câu đố là tài liệu về việc tự động sửa đổi một bộ nối tiếp tại điểm khởi tạo . Tài liệu đó không giải thích cách gọi mã này từ chế độ xem hoặc cách sửa đổi trạng thái chỉ đọc của các trường sau khi chúng được khởi tạo - nhưng điều đó không khó lắm.

Phần thứ hai - phương thức get_serializer cũng được ghi lại - (chỉ một chút nữa từ trang get_serializer_ class dưới 'các phương thức khác') nên sẽ an toàn khi dựa vào (và nguồn này rất đơn giản, điều đó có nghĩa là ít có cơ hội ngoài ý muốn tác dụng phụ do sửa đổi). Kiểm tra nguồn trong GenericAPIView (ModelViewset - và tất cả các lớp viewset được xây dựng khác có vẻ như - kế thừa từ GenericAPIView, định nghĩa get_serializer.

Đặt hai thứ lại với nhau bạn có thể làm một cái gì đó như thế này:

Trong tệp tuần tự hóa (đối với tôi base_serialulators.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

Sau đó, trong chế độ xem của bạn, bạn có thể làm một cái gì đó như thế này:

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)

Và đó nên là nó! Bây giờ, sử dụng MyViewSet sẽ khởi tạo MyD bitSerializer của bạn với các đối số bạn muốn - và giả sử trình tuần tự hóa của bạn kế thừa từ DynamicFieldsModelSerializer, bạn chỉ cần biết phải làm gì.

Có lẽ điều đáng nói là nó có thể có ý nghĩa đặc biệt nếu bạn muốn điều chỉnh serializer theo một số cách khác, ví dụ như làm các việc như đưa vào danh sách read_only_exceptions và sử dụng nó để đưa vào danh sách trắng thay vì các trường trong danh sách đen (mà tôi có xu hướng làm). Tôi cũng thấy hữu ích khi đặt các trường thành một tuple trống nếu nó không được thông qua và sau đó chỉ xóa phần kiểm tra cho Không ... và tôi đặt các định nghĩa trường của mình trên Trình tuần tự kế thừa của mình thành ' tất cả '. Điều này có nghĩa là không có trường nào không được thông qua khi khởi tạo serializer tồn tại một cách tình cờ và tôi cũng không phải so sánh lời gọi serializer với định nghĩa lớp serializer kế thừa để biết những gì được đưa vào ... ví dụ như trong init của DynamicFieldsModelSerializer:

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

NB Nếu tôi chỉ muốn hai hoặc ba lớp ánh xạ tới các hành động riêng biệt và / hoặc tôi không muốn bất kỳ hành vi tuần tự động đặc biệt nào, tôi cũng có thể sử dụng một trong các cách tiếp cận được đề cập bởi những người khác ở đây, nhưng tôi nghĩ rằng đây là cách thay thế , đặc biệt cho các mục đích sử dụng khác của nó.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.