Làm cách nào để lọc một bộ tuần tự lồng nhau trong Django Rest Framework?


81

Trong Django Rest Framework, làm cách nào để lọc một bộ tuần tự khi nó được lồng trong một bộ nối tiếp khác?

Các bộ lọc của tôi được áp dụng trong các bộ xem DRF, nhưng khi bạn gọi một bộ nối tiếp từ bên trong một bộ nối tiếp khác, bộ xem của bộ nối tiếp lồng nhau sẽ không bao giờ được gọi, vì vậy các kết quả lồng nhau sẽ không được lọc.

Tôi đã thử thêm một bộ lọc trên tập hợp chế độ xem gốc, nhưng dường như nó không lọc được các kết quả lồng nhau vì các kết quả lồng nhau được gọi là một truy vấn được viết sẵn riêng biệt. (Bộ nối tiếp lồng nhau là một tra cứu ngược, bạn thấy đấy.)

Có thể thêm ghi đè get_queryset () trong chính bộ tuần tự lồng nhau (di chuyển nó ra khỏi chế độ xem), để thêm bộ lọc vào đó không? Tôi cũng đã thử mà không gặp may.

Đây là những gì tôi đã thử, nhưng nó thậm chí không được gọi là:

class QuestionnaireSerializer(serializers.ModelSerializer):
    edition = EditionSerializer(read_only=True)
    company = serializers.StringRelatedField(read_only=True)

    class Meta:
        model = Questionnaire

    def get_queryset(self):
        query = super(QuestionnaireSerializer, self).get_queryset(instance)
        if not self.request.user.is_staff:
            query = query.filter(user=self.request.user, edition__hide=False)
        return query

6
get_querysetlà một lớp trên ModelViewSet, không phải trên Serializer, đó là lý do tại sao nó không nhận được gọi là
NotSimon

Câu trả lời:


96

Bạn có thể phân lớp ListSerializer và ghi đè to_representationphương thức.

Theo mặc định, to_representationphương thức gọi data.all()trên bộ truy vấn lồng nhau. Vì vậy, bạn cần phải thực hiện data = data.filter(**your_filters)trước khi phương thức được gọi. Sau đó, bạn cần thêm ListSerializer phân lớp của mình dưới dạng list_serializer_class trên meta của trình tuần tự lồng nhau.

  1. lớp con ListSerializer, ghi đè to_representationvà sau đó gọi super
  2. thêm ListSerializer phân lớp dưới dạng meta list_serializer_classtrên Serializer lồng nhau

Đây là mã liên quan cho mẫu của bạn.

class FilteredListSerializer(serializers.ListSerializer):

    def to_representation(self, data):
        data = data.filter(user=self.context['request'].user, edition__hide=False)
        return super(FilteredListSerializer, self).to_representation(data)


class EditionSerializer(serializers.ModelSerializer):

    class Meta:
        list_serializer_class = FilteredListSerializer
        model = Edition


class QuestionnaireSerializer(serializers.ModelSerializer):
    edition = EditionSerializer(read_only=True)
    company = serializers.StringRelatedField(read_only=True)

    class Meta:
        model = Questionnaire

1
Đó là mẹo! Mặc dù cuối cùng, tôi quyết định rằng các bộ tuần tự của mình trở nên quá phức tạp và tôi đã cấu trúc lại tất cả, buộc khách hàng phải chạy thêm một vài lệnh gọi api nhưng đơn giản hóa rất nhiều ứng dụng của tôi.
John

3
Cố gắng sử dụng điều này làm cơ sở cho giải pháp cho một vấn đề tương tự; không chắc liệu nó có thực sự xứng đáng với câu hỏi của chính nó hay không. Làm cách nào để chuyển một var từ QuestionnaireSerializervào ListSerializer? Để gần đúng, tôi cần lọc theo ID của Phiên bản cũng như ID của Bảng câu hỏi.
Brendan

3
Điều này phải có trong tài liệu DRF. Siêu hữu ích cảm ơn bạn!
Daniel van Flymen

7
Trong quá trình triển khai của tôi, tôi đang nhận được Có 'FilteredListSerializer' object has no attribute 'request'ai khác nhận được điều tương tự không?
Dominooch

11
Để trả lời câu @Dominooch bạn cần phải sử dụng self.context [ 'yêu cầu'] thay vì self.request
rojoca

24

Đã thử nghiệm nhiều giải pháp từ SO và những nơi khác.

Chỉ tìm thấy một giải pháp hoạt động cho Django 2.0 + DRF 3.7.7.

Định nghĩa một phương thức trong mô hình có lớp lồng nhau. Tạo một bộ lọc phù hợp với nhu cầu của bạn.

class Channel(models.Model):
    name = models.CharField(max_length=40)
    number = models.IntegerField(unique=True)
    active = models.BooleanField(default=True)

    def current_epg(self):
        return Epg.objects.filter(channel=self, end__gt=datetime.now()).order_by("end")[:6]


class Epg(models.Model):
    start = models.DateTimeField()
    end = models.DateTimeField(db_index=True)
    title = models.CharField(max_length=300)
    description = models.CharField(max_length=800)
    channel = models.ForeignKey(Channel, related_name='onair', on_delete=models.CASCADE)

.

class EpgSerializer(serializers.ModelSerializer):
    class Meta:
        model = Epg
        fields = ('channel', 'start', 'end', 'title', 'description',)


class ChannelSerializer(serializers.ModelSerializer):
    onair = EpgSerializer(many=True, read_only=True, source="current_epg")

    class Meta:
        model = Channel
        fields = ('number', 'name', 'onair',)

Hãy chú ý source="current_epg"và bạn sẽ nhận được điểm.


Đúng! Nhận xét này thúc đẩy khả năng của nguồn trở thành một hàm mà bạn xác định trên mô hình, sau đó bạn có thể tận dụng khả năng lọc trong đó! Mát mẻ!
chó có túi

có thể truyền một chuỗi cho hàm dưới lớp không?
AlexW

Tôi chỉ cần đặt hàng của nhiều lĩnh vực liên quan. Cũng đã thử nhiều giải pháp khác nhau (dự định chơi chữ). Nhưng đây là giải pháp duy nhất phù hợp với tôi! Cảm ơn!
gabn88

Có vẻ như đây là giải pháp đúng về triết lý mã của django hơn là câu trả lời được chấp nhận. Django đề xuất phương pháp tiếp cận ActiveModel ("mô hình chất béo"), do đó, việc lọc nên được thực hiện ở cấp mô hình (hoặc cấp độ khung nhìn) và tuần tự hóa không nên biết gì về logic nghiệp vụ.
oxfn

10

Mặc dù tất cả các câu trả lời trên đều hoạt động, nhưng tôi thấy việc sử dụng Prefetchđối tượng của Django là cách dễ nhất.

Giả sử một đối tượng Restaurantcó rất nhiều MenuItems, trong đó có một số is_remove == Truevà bạn chỉ muốn những đối tượng không bị xóa.

Trong RestaurantViewSet, làm một cái gì đó như

from django.db.models import Prefetch

queryset = Restaurant.objects.prefetch_related(
    Prefetch('menu_items', queryset=MenuItem.objects.filter(is_removed=False), to_attr='filtered_menu_items')
)

Trong RestaurantSerializer, làm một cái gì đó như

class RestaurantSerializer(serializers.ModelSerializer):
    menu_items = MenuItemSerializer(source='filtered_menu_items', many=True, read_only=True)


Giải pháp tuyệt vời, tôi đồng ý đây là cách tốt nhất để giải quyết nó.
Jordan

Điều này nên ở trên cùng. Giải pháp hàng đầu hiện tại lọc dữ liệu bằng to_representation sau khi nó đã được lấy từ cơ sở dữ liệu. Giải pháp này lọc dữ liệu trên truy vấn và tìm nạp nó theo yêu cầu hàng loạt. Cách tốt hơn cho hầu hết các trường hợp.
Alex

7

Khi một trình tuần tự được khởi tạo và nhiều = True được chuyển, một thể hiện ListSerializer sẽ được tạo. Sau đó, lớp serializer trở thành con của ListSerializer cha

Phương thức này lấy đích của trường làm đối số giá trị và sẽ trả về biểu diễn cần được sử dụng để tuần tự hóa đích. Đối số giá trị thường sẽ là một cá thể mô hình.

Dưới đây là ví dụ về bộ nối tiếp lồng nhau

class UserSerializer(serializers.ModelSerializer):
    """ Here many=True is passed, So a ListSerializer instance will be 
     created"""
    system = SystemSerializer(many=True, read_only=True)

    class Meta:
        model = UserProfile
        fields = ('system', 'name')

class FilteredListSerializer(serializers.ListSerializer):
    
    """Serializer to filter the active system, which is a boolen field in 
       System Model. The value argument to to_representation() method is 
      the model instance"""
    
    def to_representation(self, data):
        data = data.filter(system_active=True)
        return super(FilteredListSerializer, self).to_representation(data)

class SystemSerializer(serializers.ModelSerializer):
    mac_id = serializers.CharField(source='id')
    system_name = serializers.CharField(source='name')
    serial_number = serializers.CharField(source='serial')

    class Meta:
        model = System
        list_serializer_class = FilteredListSerializer
        fields = (
            'mac_id', 'serial_number', 'system_name', 'system_active', 
        )

Theo quan điểm:

class SystemView(viewsets.GenericViewSet, viewsets.ViewSet):
    def retrieve(self, request, email=None):
        data = get_object_or_404(UserProfile.objects.all(), email=email)
        serializer = UserSerializer(data)
        return Response(serializer.data)

4

Tôi thấy việc sử dụng một SerializerMethodFieldtrên trường serializer mà bạn muốn lọc sẽ dễ dàng hơn và dễ dàng hơn .

Vì vậy, bạn sẽ làm một cái gì đó như thế này.

class CarTypesSerializer(serializers.ModelSerializer):

    class Meta:
        model = CarType
        fields = '__all__'


class CarSerializer(serializers.ModelSerializer):

    car_types = serializers.SerializerMethodField()

    class Meta:
        model = Car
        fields = '__all__'

    def get_car_types(self, instance):
        # Filter using the Car model instance and the CarType's related_name
        # (which in this case defaults to car_types_set)
        car_types_instances = instance.car_types_set.filter(brand="Toyota")
        return CarTypesSerializer(car_types_instances, many=True).data

Điều này giúp bạn không phải tạo nhiều ghi đè serializers.ListSerializernếu bạn cần các tiêu chí lọc khác nhau cho các bộ tuần tự khác nhau.

Nó cũng có thêm lợi ích là thấy chính xác những gì bộ lọc làm trong bộ tuần tự hóa thay vì đi sâu vào định nghĩa lớp con.

Tất nhiên, nhược điểm là nếu bạn có một bộ nối tiếp với nhiều đối tượng lồng vào nhau mà tất cả đều cần được lọc theo một cách nào đó. Nó có thể làm cho mã serializer tăng lên rất nhiều. Bạn muốn lọc như thế nào tùy thuộc vào bạn.

Hi vọng điêu nay co ich!

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.