2014-10-06 39 views
7

Tôi đã cố gắng để có được một ModelMultipleChoiceFilter để làm việc trong nhiều giờ và đã đọc cả tài liệu Bộ lọc DRF và Django.Làm thế nào để sử dụng ModelMultipleChoiceFilter?

Tôi muốn có thể lọc một tập hợp các trang web dựa trên các thẻ đã được gán cho chúng thông qua ManyToManyField. Ví dụ: tôi muốn có thể nhận danh sách các trang web đã được gắn thẻ "Nấu ăn" hoặc "Nuôi ong".

Dưới đây là đoạn mã có liên quan của models.py hiện tại của tôi:

class SiteTag(models.Model): 
    """Site Categories""" 
    name = models.CharField(max_length=63) 

    def __str__(self): 
     return self.name 

class Website(models.Model): 
    """A website""" 
    domain = models.CharField(max_length=255, unique=True) 
    description = models.CharField(max_length=2047) 
    rating = models.IntegerField(default=1, choices=RATING_CHOICES) 
    tags = models.ManyToManyField(SiteTag) 
    added = models.DateTimeField(default=timezone.now()) 
    updated = models.DateTimeField(default=timezone.now()) 

    def __str__(self): 
     return self.domain 

Và đoạn views.py hiện tại của tôi:

class WebsiteFilter(filters.FilterSet): 
    # With a simple CharFilter I can chain together a list of tags using &tag=foo&tag=bar - but only returns site for bar (sites for both foo and bar exist). 
    tag = django_filters.CharFilter(name='tags__name') 

    # THE PROBLEM: 
    tags = django_filters.ModelMultipleChoiceFilter(name='name', queryset=SiteTag.objects.all(), lookup_type="eq") 

    rating_min = django_filters.NumberFilter(name="rating", lookup_type="gte") 
    rating_max = django_filters.NumberFilter(name="rating", lookup_type="lte") 

    class Meta: 
     model = Website 
     fields = ('id', 'domain', 'rating', 'rating_min', 'rating_max', 'tag', 'tags') 

class WebsiteViewSet(viewsets.ModelViewSet): 
    """API endpoint for sites""" 
    queryset = Website.objects.all() 
    serializer_class = WebsiteSerializer 
    filter_class = WebsiteFilter 
    filter_backends = (filters.DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter,) 
    search_fields = ('domain',) 
ordering_fields = ('id', 'domain', 'rating',) 

Tôi vừa được thử nghiệm với các chuỗi truy vấn [/path/to/sites]?tags=News và tôi Đảm bảo 100% rằng các bản ghi thích hợp tồn tại khi chúng hoạt động (như được mô tả) với truy vấn ?tag (thiếu truy vấn s).

Một ví dụ về những điều khác tôi đã cố gắng là một cái gì đó như:

tags = django_filters.ModelMultipleChoiceFilter(name='tags__name', queryset=Website.objects.all(), lookup_type="in") 

Làm thế nào tôi có thể trả lại bất kỳ trang web mà có một SiteTag thỏa mãn name == A OR name == B OR name == C?

+0

Tôi đã giải quyết được vấn đề của mình ngay bây giờ bằng cách làm theo hướng dẫn của [Có thể thực hiện 'in'' lookup_type' thông qua bộ phân tích cú pháp URL bộ lọc django?] (Https://stackoverflow.com/questions/24041639/possible -to-do-an-trong-tra cứu-loại-qua-the-django-bộ lọc-url-phân tích cú pháp? rq = 1) và tạo Bộ lọc tùy chỉnh. Tôi vẫn quan tâm đến việc xem giải pháp cho vấn đề của mình vì tôi chắc chắn rằng nó sẽ giúp người khác - và mã mà tôi không sử dụng sẽ không có lỗi :) –

Trả lời

11

Tôi tình cờ gặp câu hỏi này trong khi cố gắng giải quyết một vấn đề gần như giống hệt với bản thân, và trong khi tôi có thể viết một bộ lọc tùy chỉnh, câu hỏi của bạn khiến tôi tò mò và phải đào sâu hơn!

Nó chỉ ra rằng một ModelMultipleChoiceFilter chỉ làm cho con người thay đổi theo một bình thường Filter, như đã thấy trong đoạn code django_filters nguồn dưới đây:

class ModelChoiceFilter(Filter): 
    field_class = forms.ModelChoiceField 

class ModelMultipleChoiceFilter(MultipleChoiceFilter): 
    field_class = forms.ModelMultipleChoiceField 

Nghĩa là, nó thay đổi field_class đến một ModelMultipleChoiceField từ Django được xây dựng theo các hình thức .

Hãy xem mã nguồn cho ModelMultipleChoiceField, một trong các đối số bắt buộc cho __init__()queryset, vì vậy bạn đã đi đúng hướng ở đó.

Phần khác của câu đố xuất phát từ phương pháp ModelMultipleChoiceField.clean(), với một dòng: key = self.to_field_name or 'pk'. Điều này có nghĩa là theo mặc định nó sẽ lấy bất cứ giá trị nào bạn chuyển cho nó (ví dụ, "cooking") và cố gắng tra cứu Tag.objects.filter(pk="cooking"), khi hiển nhiên chúng ta muốn nó nhìn vào tên, và như chúng ta có thể thấy trong dòng đó, trường mà nó so sánh được điều khiển bởi self.to_field_name.

May mắn thay, phương pháp Filter.field() của bao gồm những điều sau đây khi khởi tạo trường thực tế.

self._field = self.field_class(required=self.required, 
    label=self.label, widget=self.widget, **self.extra) 

Đáng chú ý đặc biệt là **self.extra, mà xuất phát từ Filter.__init__(): self.extra = kwargs, vì vậy tất cả chúng ta cần phải làm là vượt qua một thêm to_field_name kwarg đến ModelMultipleChoiceFilter và nó sẽ được trao thông qua các nền tảng ModelMultipleChoiceField.

Vì vậy, (bỏ qua đây để xem giải pháp thực tế!), mã thực tế bạn muốn là

tags = django_filters.ModelMultipleChoiceFilter(
    name='sitetags__name', 
    to_field_name='name', 
    lookup_type='in', 
    queryset=SiteTag.objects.all() 
) 

Vì vậy, bạn đã thực sự gần gũi với mã bạn đã đăng ở trên! Tôi không biết liệu giải pháp này có phù hợp với bạn nữa không, nhưng hy vọng nó có thể giúp người khác trong tương lai!

+0

Dự án đã bị hoãn vì nó quá gây tranh cãi - nhưng đây là lần thứ hai tôi viết loại chức năng đó. Tôi có thể sử dụng bộ lọc Django một lần nữa vì vậy tôi sẽ rất hạnh phúc khi lần thứ ba đá lên! Tôi nghĩ rằng nó sẽ là giá trị làm việc giải pháp của bạn vào tài liệu chính thức. Hãy cho tôi biết nếu bạn không thể làm phiền nó (và nhận được các đường phố cred mình) - Tôi sẽ cố gắng để tìm thời gian. –

+0

Tôi đã sử dụng Bộ lọc Django cho dự án hiện tại của mình và đã gặp sự cố này một lần nữa. Cảm ơn câu trả lời! –

+0

Điều này có vẻ hữu ích, nhưng khi nhiều giá trị được cung cấp qua hai hoặc nhiều tham số GET, nó không hoạt động không may. – mlissner

1

Giải pháp phù hợp với tôi là sử dụng MultipleChoiceFilter. Trong trường hợp của tôi, tôi có các thẩm phán có chủng tộc, và tôi muốn API của tôi cho phép mọi người truy vấn, hoặc là các thẩm phán đen hoặc trắng.

Bộ lọc kết thúc hạnh phúc:

race = filters.MultipleChoiceFilter(
    choices=Race.RACES, 
    action=lambda queryset, value: 
     queryset.filter(race__race__in=value) 
) 

Race là nhiều đối với nhiều lĩnh vực tắt của Judge:

class Race(models.Model): 
    RACES = (
     ('w', 'White'), 
     ('b', 'Black or African American'), 
     ('i', 'American Indian or Alaska Native'), 
     ('a', 'Asian'), 
     ('p', 'Native Hawaiian or Other Pacific Islander'), 
     ('h', 'Hispanic/Latino'), 
    ) 
    race = models.CharField(
     choices=RACES, 
     max_length=5, 
    ) 

Tôi không phải là một fan hâm mộ lớn của lambda chức năng thông thường, nhưng nó làm cho cảm giác ở đây vì nó là một chức năng nhỏ như vậy. Về cơ bản, điều này thiết lập một MultipleChoiceFilter chuyển giá trị từ tham số GET đến trường race của mô hình Race. Chúng được truyền vào như một danh sách, vì vậy đó là lý do tại sao tham số in hoạt động.

Vì vậy, người dùng của tôi có thể làm:

/api/judges/?race=w&race=b 

Và họ sẽ liên hệ lại ban giám khảo đã xác định là một trong hai màu đen hoặc trắng.

PS: Có, tôi nhận ra rằng đây không phải là toàn bộ các chủng tộc có thể. Nhưng nó điều tra dân số Hoa Kỳ thu thập!

Các vấn đề liên quan