我正在尝试构建一个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
对象,则需要重新启动我的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
过滤器相比,该过滤器可以在几秒钟内提取所有不同的日期。
答案 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如下所示,
我得到 release_date
为
然后,I added new entry through Django admin -- (Screenshot)和我刷新DRF API端点,可能的选择如下所示,
答案 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函数)传递给该字段。