Django 表单集 - form.is_valid() 为假导致表单集验证失败

1 投票
2 回答
6496 浏览
提问于 2025-04-15 15:42

我正在使用一个表单集,让用户可以订阅多个信息源。我需要做的是:a) 用户通过选择一个布尔字段来选择订阅,并且还需要给订阅打标签;b) 用户必须订阅指定数量的订阅。

目前,下面的代码能够确保用户给订阅打标签,但我的一些表单的is_valid()返回False,这导致我无法验证整个表单集。[编辑] 此外,相关的表单集错误信息也没有显示出来。

以下是代码:

from django import forms
from django.forms.formsets import BaseFormSet
from tagging.forms import TagField
from rss.feeder.models import Feed 


class FeedForm(forms.Form):
    subscribe = forms.BooleanField(required=False, initial=False)
    tags = TagField(required=False, initial='')

    def __init__(self, *args, **kwargs):
        feed = kwargs.pop("feed")
        super(FeedForm, self).__init__(*args, **kwargs)
        self.title = feed.title
        self.description = feed.description

    def clean(self):
        """apply our custom validation rules"""
        data = self.cleaned_data
        feed = data.get("subscribe")
        tags = data.get("tags")
        tag_len = len(tags.split())
        self._errors = {}
        if feed == True and tag_len < 1:
            raise forms.ValidationError("No tags specified for feed")
        return data



class FeedFormSet(BaseFormSet):

    def __init__(self, *args, **kwargs):
        self.feeds = list(kwargs.pop("feeds"))
        self.req_subs = 3    # TODO: convert to kwargs arguement
        self.extra = len(self.feeds)
        super(FeedFormSet, self).__init__(*args, **kwargs)

    # WARNING! Using  undocumented. see   for details...
    def _construct_form(self, i, **kwargs):
        kwargs["feed"] = self.feeds[i]
        return super(FeedFormSet, self)._construct_form(i, **kwargs)


    def clean(self):
        """Checks that only a required number of Feed subscriptions are present"""
        if any(self.errors):
            # Do nothing, don't bother doing anything unless all the FeedForms are valid
            return
        total_subs = 0
        for i in range(0, self.extra):
            form = self.forms[i]
            feed = form.cleaned_data
            subs = feed.get("subscribe")
            if subs == True:
                total_subs += 1
        if total_subs != self.req_subs:
            raise forms.ValidationError("More subscriptions...") # TODO more informative
        return form.cleaned_data

应要求,视图代码如下:

from django.forms import formsets
from django.http import Http404
from django.http import HttpResponseRedirect
from django.shortcuts import render_to_response

from rss.feeder.forms import FeedForm
from rss.feeder.forms import FeedFormSet
from rss.feeder.models import Feed

FeedSet = formsets.formset_factory(FeedForm, FeedFormSet)

def feeds(request):
    if request.method == "POST":
        formset = create_feed_formset(request.POST)
        if formset.is_valid():
            # submit the results
            return HttpResponseRedirect('/feeder/thanks/')
    else:
        formset = create_feed_formset() 
    return render_to_response('feeder/register_step_two.html', {'formset': formset})    


def create_feed_formset(data=None):
    """Create and populate a feed formset"""
    feeds = Feed.objects.order_by('id')
    if not feeds:
        # No feeds found, we should have created them
        raise Http404('Invalid Step')
    return FeedSet(data, feeds=feeds)        # return the instance of the formset

任何帮助都将不胜感激。

附注:为了完全透明,这段代码是基于 http://google.com/search?q=cache:rVtlfQ3QAjwJ:https://www.pointy-stick.com/blog/2009/01/23/advanced-formset-usage-django/+django+formset

[已解决] 见下面的解决方案。

2 个回答

0

我尝试绕过我的问题……这并不是一个好解决方案,实际上这很像是个小把戏。它允许用户在订阅了所需数量的内容源(下面的例子中是超过1个)时继续操作,但如果订阅的内容源少于要求的数量,就不会显示错误信息。

def clean(self):
    count = 0
    for i in range(0, self.extra):
        form = self.forms[i]
        try:
            if form.cleaned_data:
                count += 1
        except AttributeError:
            pass
    if count > 1:
        raise forms.ValidationError('not enough subscriptions')
    return form.cleaned_data

我在我的模板中确实使用了 {{ formset.management_form }},所以据我所知,错误信息应该会显示。下面是我的模板,以防我理解错了。

{% extends "base.html" %}
{% load i18n %}

{% block content %}
<form action="." method="post">
    {{ formset.management_form }}
    <ol> 
        {% for form in formset.forms %}
        {{ form.as_p }}
        </li>
        {% endfor %}
    </ol>
    <input type="submit">
</form>

{% endblock %}
3

解决了。下面是解决方案的简单介绍。

报告错误需要处理和格式化一个特殊的错误信息。在表单集的源代码中,我发现适用于整个表单的错误被称为 non_form_errors,并基于此生成了一个自定义错误。[注意:我没有找到任何权威的文档,所以可能有人知道更好的方法]。代码如下:

def append_non_form_error(self, message):
    errors = super(FeedFormSet, self).non_form_errors()
    errors.append(message)
    raise forms.ValidationError(errors)

表单集的清理方法也需要做一些调整。基本上,它会检查表单是否被绑定(空的表单没有绑定,所以在问题中 is_valid 是 false),如果是,就会检查它的订阅值。

def clean(self):
    """Checks that only a required number of Feed subscriptions are present"""
    count = 0
    for form in self.forms:
        if form.is_bound:
            if form['subscribe'].data:
                count += 1
    if count > 0 and count != self.required:
        self.append_non_form_error("not enough subs")

有些人可能会想,为什么我选择用 form['field_name'].data 的格式来访问值。这种方式可以让我们获取原始值,并始终统计订阅的数量,这样我就可以返回整个表单集的所有相关信息,也就是单个表单的具体问题和更高层次的问题(比如订阅的数量),这样用户就不需要一次又一次地重新提交表单来处理错误列表。

最后,我的模板缺少一个关键部分,就是 {{ formset.non_form_errors }} 标签。下面是更新后的模板:

{% extends "base.html" %}
{% load i18n %}

{% block content %}
<form action="." method="post">
 {{ formset.management_form }}
 {{ formset.non_form_errors }}
    <ol> 
        {% for form in formset.forms %}
        <li><p>{{ form.title }}</p>
   <p>{{ form.description }}</p>
        {{ form.as_p }}
        </li>
        {% endfor %}
    </ol>
    <input type="submit">
</form>

{% endblock %}

撰写回答