Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/logging/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
在基于Django类的视图(CBV)中保存inlineformset_Django_Django Class Based Views_Inline Formset - Fatal编程技术网

在基于Django类的视图(CBV)中保存inlineformset

在基于Django类的视图(CBV)中保存inlineformset,django,django-class-based-views,inline-formset,Django,Django Class Based Views,Inline Formset,因此,我正在开发一个web应用程序,该应用程序在注册过程中实现了安全问题。由于我的模型的设置方式,以及我试图使用Django的基于类的视图(CBV)的事实,我在将所有这些干净地集成起来时遇到了一些问题。以下是我的模型的外观: Model.py class AcctSecurityQuestions(models.Model): class Meta: db_table = 'security_questions' id = models.AutoField(pri

因此,我正在开发一个web应用程序,该应用程序在注册过程中实现了安全问题。由于我的模型的设置方式,以及我试图使用Django的基于类的视图(CBV)的事实,我在将所有这些干净地集成起来时遇到了一些问题。以下是我的模型的外观:

Model.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)
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
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
                                                       )
以下是我当前的视图:

View.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)
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
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
                                                       )
为了获得笑声和完整性,我的表格是这样的:

Forms.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)
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
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
                                                       )
这篇文章对我帮助很大,但是在所选答案的最新评论中,它提到了表单集数据应该以overidden get和post方法集成到表单中:


如果我过度隐藏了
get
post
我将如何从表单集中添加数据?我会调用什么来循环表单集数据呢?

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

下面是我将如何使用一个模型表单集来完成这项工作:

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)
关于评论中的一些问题,关于为什么这样做:

我不太明白为什么我们需要queryset

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

如果您检查未提交表单的HTML以获得使用默认queryset的版本,您将看到隐藏的输入,其中给出了中间行的ID,此外,您还将看到选择的问题和答案显示在非隐藏的输入中

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

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

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

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

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

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

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

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)

如果在表单无效的情况下,这样做是可行的,那么我可能会说服自己,让那个版本更好。在幕后,它只是在做非内联版本所做的事情,但更多的处理是隐藏的。首先,它还与各种通用混合的实现更为相似——尽管您也可以将保存行为移动到非内联版本的
form\u valid

因此我还是Django新手,所以我不确定是否需要内联表单集。这似乎是正确的做法,但我可能仍然不能完全理解两者之间的区别。我会尝试一下,让您知道,我是否还需要在
post
get
函数的开头添加一行:
self.object=None
,因为它们在
BaseCreate中