3

因此,我正在开发一个已在其注册过程中实施安全问题的 Web 应用程序。由于我的模型设置方式以及我尝试使用 Django 的基于类的视图 (CBV) 的事实,我在将这一切完全集成时遇到了一些问题。这是我的模型的样子:

模型.py

class AcctSecurityQuestions(models.Model):
    class Meta:
        db_table = 'security_questions'
    id = models.AutoField(primary_key=True)
    question = models.CharField(max_length = 250, null=False)

    def __unicode__(self):
        return u'%s' % self.question


class AcctUser(AbstractBaseUser, PermissionsMixin):
    ...
    user_questions = models.ManyToManyField(AcctSecurityQuestions, through='SecurityQuestionsInter')
    ...


class SecurityQuestionsInter(models.Model):
    class Meta:
        db_table = 'security_questions_inter'

    acct_user = models.ForeignKey(AcctUser)
    security_questions = models.ForeignKey(AcctSecurityQuestions, verbose_name="Security Question")
    answer = models.CharField(max_length=128, null=False)

这是我当前的视图:

视图.py

class AcctRegistration(CreateView):
    template_name = 'registration/registration_form.html'
    disallowed_url_name = 'registration_disallowed'
    model = AcctUser
    backend_path = 'registration.backends.default.DefaultBackend'
    form_class = AcctRegistrationForm
    success_url = 'registration_complete'

def form_valid(self, form):
    context = self.get_context_data()
    securityquestion_form = context['formset']
    if securityquestion_form.is_valid():
        self.object = form.save()
        securityquestion_form.instance = self.object
        securityquestion_form.save()
        return HttpResponseRedirect(self.get_success_url())
    else:
        return self.render_to_response(self.get_context_data(form=form))

    def get_context_data(self, **kwargs):
        ctx = super(AcctRegistration, self).get_context_data(**kwargs)
        if self.request.POST:
            ctx['formset'] = SecurityQuestionsInLineFormSet(self.request.POST, instance=self.object)
            ctx['formset'].full_clean()
        else:
            ctx['formset'] = SecurityQuestionsInLineFormSet(instance=self.object)
        return ctx

为了咯咯笑和完整性,我的表格如下所示:

表格.py

class AcctRegistrationForm(ModelForm):
    password1 = CharField(widget=PasswordInput(attrs=attrs_dict, render_value=False),
                          label="Password")
    password2 = CharField(widget=PasswordInput(attrs=attrs_dict, render_value=False),
                          label="Password (again)")

    class Meta:
        model = AcctUser

    ...

    def clean(self):
        if 'password1' in self.cleaned_data and 'password2' in self.cleaned_data:
            if self.cleaned_data['password1'] != self.cleaned_data['password2']:
                raise ValidationError(_("The two password fields didn't match."))
        return self.cleaned_data


SecurityQuestionsInLineFormSet = inlineformset_factory(AcctUser,
                                                       SecurityQuestionsInter,
                                                       extra=2,
                                                       max_num=2,
                                                       can_delete=False
                                                       )

这篇文章对我帮助很大,但是在所选答案的最新评论中,它提到应该将表单集数据集成到覆盖的 get 和 post 方法中的表单中:

具有内联模型表单或表单集的基于 django 类的视图

如果我要覆盖,getpost将如何从我的表单集中添加我的数据?我会调用什么来遍历表单集数据?

4

1 回答 1

12

当您已经在数据库中拥有用户对象时,内联表单集很方便。然后,当您初始化时,它会自动预加载正确的安全问题等。但是对于创建,一个普通的模型表单集可能是最好的,并且不包括与用户相关联的直通表上的字段。然后您可以创建用户并在创建的直通表上手动设置用户字段。

以下是我将如何使用仅模型表单集来执行此操作:

forms.py:

SecurityQuestionsFormSet = modelformset_factory(SecurityQuestionsInter,
                                                fields=('security_questions', 'answer'),
                                                extra=2,
                                                max_num=2,
                                                can_delete=False,
                                               )

views.py:

class AcctRegistration(CreateView):

    # class data like form name as usual

    def form_valid(self):
        # override the ModelFormMixin definition so you don't save twice
        return HttpResponseRedirect(self.get_success_url())

    def form_invalid(self, form, formset):
        return self.render_to_response(self.get_context_data(form=form, formset=formset))

    def get(self, request, *args, **kwargs):
        self.object = None
        form_class = self.get_form_class()
        form = self.get_form(form_class)
        formset = SecurityQuestionsFormSet(queryset=SecurityQuestionsInter.objects.none())
        return self.render_to_response(self.get_context_data(form=form, formset=formset))

    def post(self, request, *args, **kwargs):
        self.object = None
        form_class = self.get_form_class()
        form = self.get_form(form_class)
        formset = SecurityQuestionsFormSet(request.POST)
        form_valid = form.is_valid()
        formset_valid = formset.is_valid()
        if form_valid and formset_valid:
            self.object = form.save()
            security_questions = formset.save(commit=False)
            for security_question in security_questions:
                security_question.acct_user = self.object
                security_question.save()
            formset.save_m2m()
            return self.form_valid()
        else:
            return self.form_invalid(form, formset)

关于评论中关于为什么会这样工作的一些问题:

我不太明白为什么我们需要查询集

查询集定义了表单集对象的初始可编辑范围。它是要绑定到查询集中每个表单的一组实例,类似于instance单个表单的参数。然后,如果查询集的大小不超过max_num参数,它将添加extra未绑定的表单,最多max_num或指定数量的附加项。指定空查询集意味着我们已经说过我们不想编辑任何模型实例,我们只想创建新数据。

如果您检查未提交表单的 HTML 以查找使用默认查询集的版本,您将看到隐藏的输入提供中间行的 ID - 此外,您将看到所选的问题和答案显示在非隐藏输入中。

可以说,表单默认为未绑定(除非您指定实例)而表单集默认为绑定到整个表(除非您另有指定),这可能会令人困惑。正如评论所显示的那样,它确实让我失望了一段时间。但是表单集本质上是复数形式,而单一形式不是,所以就是这样。

限制查询集是内联表单集所做的事情之一。

或者在我们为表单集设置 acct_user 之前,表单集如何知道它是相关的。为什么我们不使用实例参数

表单集实际上永远不知道它是相关的。最终SecurityQuestionsInter,一旦我们设置了模型字段,对象就会这样做。

基本上,HTML 表单在 POST 数据中传递其所有字段的值 - 两个密码,加上两个安全问题选择的 ID 和用户的答案,以及可能与此问题无关的任何其他内容。我们创建的每个 Python 对象 (formformset) 都可以根据字段 id 和表单集前缀(默认值在这里工作正常,一个页面中有多个表单集会变得更加复杂)来判断 POST 数据的哪些部分是它的职责。 form处理密码,但对安全问题一无所知。 formset处理两个安全问题,但对密码(或暗示用户)一无所知。在内部,formset创建两个表单,每个表单处理一个问题/答案对 - 同样,它们依赖于 id 中的编号来告诉它们处理 POST 数据的哪些部分。

正是这种观点将两者联系在一起。没有一个表单知道它们之间的关系,但视图知道。

内联表单集具有用于跟踪这种关系的各种特殊行为,经过更多代码审查后,我认为有一种方法可以在这里使用它们,而无需在验证安全 Q/A 对之前保存用户——它们确实构建了一个内部查询集过滤到实例,但看起来他们实际上不需要评估该查询集以进行验证。主要部分让我不再说你可以使用它们,而只是传入一个未提交的用户对象(即 的返回值form.save(commit=False))作为instance参数,或者None如果用户表单无效是我不是 100%确定它会在第二种情况下做正确的事情。如果您发现该方法更清晰,则可能值得测试 - 设置您最初拥有的内联表单集,初始化表单集get没有参数,然后将最终的保存行为留form_valid在毕竟:

def form_valid(self, form, formset):
    # commit the uncommitted version set in post
    self.object.save()
    form.save_m2m()
    formset.save()
    return HttpResponseRedirect(self.get_success_url())

def post(self, request, *args, **kwargs):
    self.object = None
    form_class = self.get_form_class()
    form = self.get_form(form_class)
    if form.is_valid():
        self.object = form.save(commit=False)
    # passing in None as the instance if the user form is not valid
    formset = SecurityQuestionsInLineFormSet(request.POST, instance=self.object)
    if form.is_valid() and formset.is_valid():
        return self.form_valid(form, formset)
    else:
        return self.form_invalid(form, formset)

如果在表单无效时可以正常工作,我可能已经说服自己该版本会更好。在幕后,它只是在做非内联版本所做的事情,但更多的处理是隐藏的。它首先也更接近于各种通用 mixin 的实现——尽管您也可以将保存行为移到form_valid非内联版本中。

于 2013-06-06T00:15:20.833 回答