【问题标题】:Django Form Validation Error When Filtering by Count按计数过滤时的 Django 表单验证错误
【发布时间】: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


【解决方案1】:
    else: # form is not valid
         #todo
         print(form.errors.as_data())

在这种情况下,您应该return form.errors.as_data() 它指出表单无效,您没有返回任何内容,因此导致错误。

【讨论】:

  • 问题仍然存在,基于查询集的更改,为什么表单无效...
猜你喜欢
  • 2016-03-20
  • 2011-02-21
  • 1970-01-01
  • 1970-01-01
  • 2019-06-27
  • 2015-01-03
  • 2011-01-03
  • 1970-01-01
  • 2013-04-01
相关资源
最近更新 更多