因此,我正在开发一个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)
以下是我目前的观点:
View.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
对于咯咯笑和完整性,我的表格是这样的:
Forms.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
)
这篇文章给了我很多帮助,但是在所选答案的最新评论中,它提到了formset数据应该在overover get和post方法中集成到表单中:
django class-based views with inline model-form or formset
如果我覆盖了get
和post
,我将如何从我的formset添加数据?我会调用什么来循环使用formset数据?
答案 0 :(得分:11)
当您已在数据库中拥有用户对象时,内联表单集非常方便。然后,当你初始化时,它会自动预加载正确的安全问题,等等。但是对于创建,正常的模型表单集可能是最好的,并且不包括连接表的字段。给用户。然后,您可以创建用户并在创建的直通表上手动设置用户字段。
以下是我如何使用一个模型表单集来完成此操作:
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定义formset的对象的初始可编辑范围。它是要绑定到查询集中每个表单的实例集,类似于单个表单的instance
参数。然后,如果查询集的大小不超过max_num
参数,则它会将extra
未绑定的表单添加到max_num
或指定数量的附加内容。指定空的查询集意味着我们已经说过我们不想编辑任何模型实例,我们只想创建新数据。
如果您检查使用默认查询集的版本的未提交表单的HTML,您将看到隐藏的输入,其中包含中间行的ID - 此外,您还会看到显示的所选问题和答案在非隐藏的输入中。
表格默认为未绑定(除非您指定实例),而formset默认绑定到整个表格(除非您另行指定),这可能会让人感到困惑。正如评论所显示的,它肯定会让我离开一段时间。但是,形式集本质上是复数形式,单个形式不是,所以就是这样。
限制查询集是内联表单集所做的事情之一。
或者在我们为formset设置acct_user之前,formset知道它是如何相关的。为什么我们没有使用实例参数
formset实际上从未知道它的相关性。一旦我们设置了该模型字段,最终SecurityQuestionsInter
个对象就会这样做。
基本上,HTML表单会传递POST数据中所有字段的值 - 两个密码,以及两个安全问题选择的ID和用户的答案,以及可能还有其他任何内容。与此问题相关。我们创建的每个Python对象(form
和formset
)都可以根据字段ID和formset前缀来判断(默认值在这里工作正常,在一个页面中有多个formset,它变得更复杂)部分POST数据是它的责任。 form
处理密码但对安全问题一无所知。 formset
处理两个安全问题,但对密码(或暗示,用户)一无所知。在内部,formset
创建了两个表单,每个表单处理一个问题/答案对 - 再次,他们依靠ID中的编号来告诉他们处理的POST数据的哪些部分。
将两者联系在一起的观点。没有一种形式知道它们之间的关系,但观点确实如此。
内联表单集具有跟踪此类关系的各种特殊行为,经过一些更多的代码审查后,我认为有一种方法可以在此处使用它们而无需在验证安全Q / A对之前保存用户 - 它们构建了一个过滤到实例的内部查询集,但看起来他们实际上不需要评估该查询集以进行验证。只是说你可以使用它们而只是传递一个未提交的用户对象(即form.save(commit=False)
的返回值)作为instance
参数,或{ {1}}如果用户表单无效,我不能100%确定它会在第二种情况下做正确的事情。如果您发现方法更清晰,可能值得测试 - 按照您最初的方式设置内联formset,在None
中初始化没有参数的formset,然后在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)
。