7

我正在尝试构建一个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 视图中时...

在此处输入图像描述

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

有没有办法解决这个问题?也许通过一些创造性的使用 a 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 的评论,我尝试使用链接问题中的建议。这最终会像

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每个的表示DatedResource,而不是DatedResource.date字段和 b) 它们不是唯一的(例如,如果我有两个DatedResource相同的对象date,它们的str表示都出现在列表。这也是不可持续的,因为我有 200k+ DatedResources,并且在尝试将它们全部加载时页面挂起(与values_list过滤器相比,它能够在几秒钟内拉出所有不同的日期。

4

2 回答 2

8

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

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-filterwith DRF

现在,我通过 Django 管理控制台填充了一些数据。之后,相册 api 如下所示, 我得到了as 然后,我通过 Django admin 添加了新条目——(屏幕截图)并刷新了 DRF API 端点,可能的选择如下,
专辑列表 API 结果
release_date
日期选择之前


FilterList 中的新选择

于 2019-04-11T07:57:32.750 回答
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 函数)传递给该字段。

于 2019-04-10T14:25:49.537 回答