使用MultipleChoiceFilter时动态重新加载选择

时间:2019-04-03 13:56:30

标签: django django-filters multiplechoicefield modelmultiplechoicefield

我正在尝试构建一个MultipleChoiceFilter,其中选择是相关模型(DatedResource)上可能存在的日期的集合。

到目前为止,我正在与之合作...

resource_date = filters.MultipleChoiceFilter(
    field_name='dated_resource__date',
    choices=[
        (d, d.strftime('%Y-%m-%d')) for d in
        sorted(resource_models.DatedResource.objects.all().values_list('date', flat=True).distinct())
    ],
    label="Resource Date"
)

在html视图中显示时...

enter image description here

起初这很好用,但是如果我用新的不同的DatedResource值创建新的date对象,则需要重新启动我的Web服务器,以便将它们作为有效的选择。这个过滤器。我相信这是因为choices列表是在网络服务器启动时进行一次评估的,而不是在每次页面加载时进行评估的。

有什么办法可以解决这个问题?也许是通过创造性地使用ModelMultipleChoiceFilter来实现的?

谢谢!

编辑: 我尝试了一些简单的ModelMultipleChoice用法,但是遇到了一些问题。

resource_date = filters.ModelMultipleChoiceFilter(
    field_name='dated_resource__date',
    queryset=resource_models.DatedResource.objects.all().values_list('date', flat=True).order_by('date').distinct(),
    label="Resource Date"
)

HTML表单显示得很好,但是选择不是筛选器可接受的值。我假设有"2019-04-03" is not a valid value.个验证错误,因为此过滤器需要datetime.date个对象。我考虑过使用coerce参数,但是ModelMultipleChoice过滤器中不接受这些参数。

根据dirkgroten的评论,我尝试使用linked question中的建议。最终像是

resource_date = filters.ModelMultipleChoiceFilter(
    field_name='dated_resource__date',
    to_field_name='date',
    queryset=resource_models.DatedResource.objects.all(),
    label="Resource Date"
)

这也不是我想要的,因为HTML现在的形式是a)显示每个str的{​​{1}}表示形式,而不是DatedResource字段,b)它们不是唯一的(例如,如果我有两个DatedResource.date具有相同的DatedResource对象,则它们的两个date表示都会出现在列表中。由于我有200k + str,所以这也是不可持续的,并且尝试全部加载时页面会挂起(与DatedResources过滤器相比,该过滤器可以在几秒钟内提取所有不同的日期。

2 个答案:

答案 0 :(得分:7)

一种简单的解决方案是 覆盖filterset类的__init__()方法

from django_filters import filters, filterset


class FooFilter(filterset.FilterSet):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        try:
            self.filters['user'].extra['choices'] = [(d, d.strftime('%Y-%m-%d')) for d in sorted(
                resource_models.DatedResource.objects.all().values_list('date', flat=True).distinct())]
        except (KeyError, AttributeError):
            pass

    resource_date = filters.MultipleChoiceFilter(field_name='dated_resource__date', choices=[], label="Resource Date")

注意:在您的过滤器集类的字段定义中提供 choices=[]


结果

我已测试并验证了以下解决方案的依赖性
1. Python 3.6
2. Django 2.1
3. DRF 3.8.2
4. django-filter 2.0.0

我使用以下代码重现了行为

# models.py
from django.db import models


class Musician(models.Model):
    name = models.CharField(max_length=50)

    def __str__(self):
        return f'{self.name}'


class Album(models.Model):
    artist = models.ForeignKey(Musician, on_delete=models.CASCADE)
    name = models.CharField(max_length=100)
    release_date = models.DateField()

    def __str__(self):
        return f'{self.name} : {self.artist}'


# serializers.py
from rest_framework import serializers


class AlbumSerializer(serializers.ModelSerializer):
    artist = serializers.StringRelatedField()

    class Meta:
        fields = '__all__'
        model = Album


# filters.py
from django_filters import rest_framework as filters


class AlbumFilter(filters.FilterSet):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.filters['release_date'].extra['choices'] = self.get_album_filter_choices()

    def get_album_filter_choices(self):
        release_date_list = Album.objects.values_list('release_date', flat=True).distinct()
        return [(date, date) for date in release_date_list]

    release_date = filters.MultipleChoiceFilter(choices=[])

    class Meta:
        model = Album
        fields = ('release_date',)


# views.py
from rest_framework.viewsets import ModelViewSet
from django_filters import rest_framework as filters


class AlbumViewset(ModelViewSet):
    serializer_class = AlbumSerializer
    queryset = Album.objects.all()
    filter_backends = (filters.DjangoFilterBackend,)
    filter_class = AlbumFilter

在这里,我将 django-filter DRF 一起使用。

现在,我通过Django管理控制台填充了一些数据。之后,专辑api如下所示,
Album-List API Result
我得到 release_date
DateChoices Before

然后,I added new entry through Django admin -- (Screenshot)和我刷新DRF API端点,可能的选择如下所示,
New Choice in FilterList

答案 1 :(得分:2)

我调查了您的问题,并提出了以下建议

问题

您已正确解决问题。 MultipleChoiceFilter的选择是在每次运行服务器时静态计算的。这就是为什么每次在DatedResource中插入新实例时都不会动态更新它们的原因。

要使其正常工作,您必须为MultipleChoiceFilter动态提供选择。我搜索了文档,但没有找到任何有关此的内容。所以这是我的解决方案。

解决方案

您必须扩展MultipleChoiceFilter并创建自己的过滤器类。我创建了这个,就在这里。

from typing import Callable
from django_filters.conf import settings
import django_filters


class LazyMultipleChoiceFilter(django_filters.MultipleChoiceFilter):
    def get_field_choices(self):
        choices = self.extra.get('choices', [])
        if isinstance(choices, Callable):
            choices = choices()
        return choices

    @property
    def field(self):
        if not hasattr(self, '_field'):
            field_kwargs = self.extra.copy()

            if settings.DISABLE_HELP_TEXT:
                field_kwargs.pop('help_text', None)

            field_kwargs.update(choices=self.get_field_choices())

            self._field = self.field_class(label=self.label, **field_kwargs)
        return self._field

现在,您可以将此类用作替换类,并将选择作为此类的lambda函数传递。

resource_date = LazyMultipleChoiceFilter(
    field_name='dated_resource__date',
    choices=lambda: [
        (d, d.strftime('%Y-%m-%d')) for d in
        sorted(resource_models.DatedResource.objects.all().values_list('date', flat=True).distinct())
    ],
    label="Resource Date"
)

无论何时创建过滤器实例,选项都会动态更新。如果需要默认行为,也可以将选择静态(不带lambda函数)传递给该字段。