【发布时间】:2020-11-18 13:10:40
【问题描述】:
我将 Django Forms 用于我的 Web 应用程序的服务器端过滤器功能,并且我进行了一些自定义,以便在模板中呈现复选框并使用自定义标签包含动态计数(代表学生论文提交的数量) ),如下:
[x] 有趣的道格 (5)
[ ] 斯基特瓦伦丁 (3)
[x] 帕蒂蛋黄酱 (1)
[ ] 罗杰·克洛茨 (0)
现在,该字段的查询集将返回尚未提交任何论文的学生的学生姓名(计数 = 0)。由于从页面加载/性能的角度来看这有点太贵了(有很多学生的名字,还有很多论文提交),我决定调整查询集以便 不 在列表中包含计数为 0 的学生姓名。为了进行此更改,我只是在查询中添加了一个新过滤器 (.filter(count__gt=0))。
students = Student.objects.annotate(
num_papers=Coalesce(
Subquery(
Paper.objects.filter(student=OuterRef('pk'))
.values('student').filter(pk__in=date_subset).filter(pk__in=keyword_subset)
.annotate(cnt=Count('pk'))
.values('cnt')
)
,0)
).filter(count__gt=0)
这成功地从过滤器列表中删除了尚未提交任何论文的学生。但是现在,一旦我在列表中选择任何其他名称进行过滤,我就会收到以下错误:
{'studentCheckbox': [ValidationError(['选择一个有效的选项。588 不是可用的选项之一。'])]}
似乎触发了我认为的 form.is_valid() 调用。但是为什么在计数上添加一个简单的过滤器会导致这个错误呢?我已经做了一些 Field 和 Widget 的子类化,所以也许我已经在某处引入了错误。也许与 MyModelMultipleChoiceField 实例化中定义的通用模型查询集与 init 类中的动态分配存在差异?我在下面包含了完整的 FilterForms.py 代码,并在底部定义了 FilterForms 类。
class MyModelMultipleChoiceField(forms.ModelMultipleChoiceField):
def label_from_instance(self, obj):
if hasattr(obj, 'count'):
self.widget.count = obj.count
return "%s (%s)" % (obj, obj.count)
class MyMultiSelectWidget(widgets.SelectMultiple):
def __init__(self, *args, **kwargs):
self.count = None
super().__init__(*args, **kwargs)
def create_option(self, name, value, label, selected, index, subindex=None, attrs=None):
index = str(index) if subindex is None else "%s_%s" % (index, subindex)
if attrs is None:
attrs = {}
option_attrs = self.build_attrs(self.attrs, attrs) if self.option_inherits_attrs else {}
if selected:
option_attrs.update(self.checked_attribute)
if 'id' in option_attrs:
option_attrs['id'] = self.id_for_label(option_attrs['id'], index)
return {
'name': name,
'count': str(self.count),
'value': value,
'label': label,
'selected': selected,
'index': index,
'attrs': option_attrs,
'type': self.input_type,
'template_name': self.option_template_name,
}
class FiltersForm(forms.Form):
...
studentCheckbox = MyModelMultipleChoiceField(widget=MyMultiSelectWidget, queryset=Student.objects.all(), required=False)
...
def __init__(self, *args, **kwargs):
date_subset = kwargs.pop('date_subset', [])
keyword_subset = kwargs.pop('keyword_subset', [])
super(FiltersForm, self).__init__(*args, **kwargs)
students = Student.objects.annotate(
num_papers=Coalesce(
Subquery(
Paper.objects.filter(student=OuterRef('pk'))
.values('student').filter(pk__in=date_subset).filter(pk__in=keyword_subset)
.annotate(cnt=Count('pk'))
.values('cnt')
)
,0)
).filter(count__gt=0)
self.fields['studentCheckbox'].queryset = students
堆栈跟踪:
[29/Jul/2020 02:51:10] "GET /webapp/?dateRadio=180&searchInput=&showBookmarks=None HTTP/1.1" 200 512113
{'studentCheckbox': [ValidationError(['Select a valid choice. 588 is not one of the available choices.'])]}
Internal Server Error: /webapp/
Traceback (most recent call last):
File "/home/user/.local/lib/python3.6/site-packages/django/core/handlers/exception.py", line 34, in inner
response = get_response(request)
File "/home/user/.local/lib/python3.6/site-packages/django/core/handlers/base.py", line 126, in _get_response
"returned None instead." % (callback.__module__, view_name)
ValueError: The view webapp.views.filter.filter didn't return an HttpResponse object. It returned None instead.
views.py:
def filter(request):
if request.method == 'GET':
form = FilterForm(request.GET)
if form.is_valid():
date = form.cleaned_data['dateRadio']
students = form.cleaned_data['studentCheckbox']
keywords = form.cleaned_data['keywordCheckbox']
df = Q()
if date:
start=datetime.today() - timedelta(int(date))
end = datetime.today()
df = Q(submission_date__range=(start, end))
sf = Q()
if students:
sub = Q()
for student in students:
sub |= Q(student=student)
sf = sub
kf = Q()
if keywords:
sub = Q()
for k in keywords:
sub |= Q(keywords=k)
kf = sub
df = Submission.objects.order_by('submission_date').filter(df)
sf = Submission.objects.order_by('submission_date').filter(sf)
kf = Submission.objects.order_by('submission_date').filter(kf)
form = FilterForm(request.GET, date_subset=df.values_list('pk', flat=True), student_subset=sf.values_list('pk', flat=True), keyword_subset=kf.values_list('pk', flat=True))
context = {
...
'filterForm' : form,
...
}
return render(request, 'index.html', context)
else: # form is not valid
#todo
print(form.errors.as_data())
【问题讨论】:
-
你用相关论文数量来注释学生的查询集似乎有点过于复杂,你能不能只做
students = Student.objects.annotate(num_papers=Count('paper_set'))? -
我最初尝试过(参见stackoverflow.com/questions/62317457/…),但我忘记在上面的代码中包含由视图传入的额外“pk__in”过滤器,这些过滤器允许我查询数据子集- 因此需要更复杂的查询。我已经继续并更新了上面的查询集代码。
-
您可以在标准
filter()中传递这些参数,特别是因为您只需要拥有论文的学生 -
当我尝试 students = Student.objects.annotate(count=Count('papers')).filter(papers__pk__in=date_subset).filter(papers__pk__in=keyword_subset) 时,结果计数在他们是非常大的数字。此外,我仍然收到上面的 ValidationError。
-
将过滤器放在注释之前,并将
distinct=True传递给计数
标签: django django-forms django-validation