django 通用(类)视图中的多个表单类
Posted
技术标签:
【中文标题】django 通用(类)视图中的多个表单类【英文标题】:Multiple form classes in django generic (class) views 【发布时间】:2011-09-10 17:00:38 【问题描述】:我想将 django 1.3 的基于类的通用视图用于表单,但有时必须在一个表单中管理多个表单类。但是,看起来基于 FormMixin 的现有视图假定了一个表单类。
这对于通用视图是否可行,我该怎么做?
编辑:澄清一下,我有一种形式,但不止一种(基于 ModelForm 的)类。例如,在 django 文档中的 inline_formset 示例中,我想展示一个页面,其中作者和他的书可以以单一形式一次编辑:
author_form = AuthorForm(request.POST, instance = author)
books_formset = BookInlineFormSet(request.POST, request.FILES, instance=author)
【问题讨论】:
在不以某种可能不明显的方式解决字段名称冲突的情况下,Django 无法合并模型表单。此外,表单的接收者需要将表单拆分到多个模型中。ForeignKey
关系使事情进一步复杂化,就像在你的例子中一样,因为那时需要一些添加和删除东西的方法(你的例子中的书)。 (实际上,情况更糟,因为一个健壮的模型会意识到书籍与作者之间的关系实际上是ManyToManyField
,因为书籍可以有多个作者。)
【参考方案1】:
面对类似的问题,我得出的结论是不可能的。
虽然每页有多个表单本身被证明是一个设计错误,但会带来各种各样的麻烦。例如,用户填写了两张表格,点击其中一张提交,而另一张则丢失了数据。解决方法需要复杂的控制器,该控制器需要了解页面上所有表单的状态。 (有关相关问题的一些讨论,另请参见here。)
如果每页有多个表单不是您的确切要求,我建议您考虑其他解决方案。
例如,通常一次只能向用户显示一个可编辑表单。
就我而言,我切换到django-formwizard
(不是 django.contrib 的,它有点旧,目前似乎正在重新设计,但this one 更新: 从 Django 1.4 版开始,django-formwizard
应用程序将在 django.contrib
中可用,取代旧的窗体向导。它已经在主干中,see docs)。对于用户,我让它看起来页面上实际上有多个表单,但只有一个是可编辑的。用户必须按预定顺序填写表格。这使得处理多种表单变得更加容易。
否则,如果表单真的需要一次全部呈现,那么将它们组合成一个可能是有意义的。
更新(在您澄清之后):
不,您也不能使用通用 FormView
处理表单集。尽管您的示例似乎很容易实现:我认为它与表单集上的 Django 文档中的this example 非常相似。它处理两个表单集,您只需用表单替换一个(我认为您仍然需要指定前缀以避免元素的id
属性可能发生冲突)。
简而言之,在您的情况下,我将继承 django.views.generic.base.View
并覆盖 get()
和 post()
方法来处理类似于 Django 文档中的上述示例的表单和表单集。
在这种情况下,我认为呈现可编辑的表单和表单集是很好的——只需一个按钮即可同时提交它们。
另一个更新:
在 Django trac 中有一张有效的近期票证,#16256
More class based views: formsets derived generic views。如果一切顺利,新的通用视图将被添加到 Django:FormSetsView
、ModelFormSetsView
和 InlineFormSetsView
。特别是,最后一个“提供了一种使用内联表单集显示和处理模型的方法”。
【讨论】:
感谢您的警告。如果我理解正确,我的情况会有些不同,因为我只有一种形式,但它代表了多种模型。我已经编辑了我的问题以澄清它。 正是来自文档的正确示例,我将尝试对基本视图进行子类化。 值得注意的是,ModelForm 和 ModelFormset 实现了相似的接口:is_valid(), save(), clean(), errors
所以基本上如果 Django 是一致的,应该有只使用提到的接口的 SaveableView,并且可以在表单和表单集上工作。它甚至可以用于在一个通用视图中实现多个表单和表单集。
呃,一个页面可以同时包含搜索表单和“普通”表单。
当然,一个页面上可能需要多个表单。如果这是一个要求,我会在客户端处理它,使用 AJAX 保存数据。但只要我们使用 Django 的通用视图,并且我们还没有准备好实施措施来防止用户意外丢失输入的数据,我会将主页部分中可编辑表单的数量限制为一个。【参考方案2】:
在一个视图页面上显示来自两个模型的字段
您必须扩展 django.views.generic.View 类并覆盖 get(request) 和 post(request) 方法。
我就是这样做的。
我正在使用 Django 1.11。
这就是我的表单(由两个表单组成)的样子:
我的 View 类呈现我的两种形式:
from django.views.generic import View
class UserRegistrationView(View):
# Here I say which classes i'm gonna use
# (It's not mandatory, it's just that I find it easier)
user_form_class = UserForm
profile_form_class = ProfileForm
template_name = 'user/register.html'
def get(self, request):
if request.user.is_authenticated():
return render(request, 'user/already_logged_in.html')
# Here I make instances of my form classes and pass them None
# which tells them that there is no additional data to display (errors, for example)
user_form = self.user_form_class(None)
profile_form = self.profile_form_class(None)
# and then just pass them to my template
return render(request, self.template_name, 'user_form': user_form, 'profile_form': profile_form)
def post(self, request):
# Here I also make instances of my form classes but this time I fill
# them up with data from POST request
user_form = self.user_form_class(request.POST)
profile_form = self.profile_form_class(request.POST)
if user_form.is_valid() and profile_form.is_valid():
user = user_form.save(commit=False)
user_profile = profile_form.save(commit=False)
# form.cleaned_data is a dictionary which contains data validated
# by fields constraints (Say we have a field which is a number. The cleaning here would
# be to just convert a string which came from the browser to an integer.)
username = user_form.cleaned_data['username']
password = user_form.cleaned_data['password']
# This will be clarified later
# You can save each object individually if they're not connected, as mines are (see class UserProfile below)
user.set_password(password)
user.userprofile = user_profile
user.save()
user = authenticate(username=username, password=password)
if user is not None:
if user.is_active:
login(request, user)
return redirect('user:private_profile')
# else: # form not valid - each form will contain errors in form.errors
return render(request, self.template_name,
'user_form': user_form,
'profile_form': profile_form
)
我有一个User
和UserProfile
模型。
User
是django.contrib.auth.models.User
和UserProfile
如下:
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
friends = models.ManyToManyField('self', null=True, blank=True)
address = models.CharField(max_length=100, default='Some address 42')
def get_absolute_url(self):
return reverse('user:public_profile', kwargs='pk': self.pk)
def __str__(self):
return 'username: ' + self.user.username + '; address: ' + self.address
@receiver(post_save, sender=User) # see Clarification 1 below
def create_user_profile(sender, instance, created, **kwargs):
if created: # See Clarification 2 below
UserProfile.objects.create(user=instance, address=instance.userprofile.address)
@receiver(post_save, sender=User)
def update_user_profile(sender, instance, **kwargs):
instance.userprofile.save()
说明 1: @receiver(post_save, sender=User)
保存用户时(我在某处写了 user.save()(用户是 User 类的实例)) UserProfile 也将被保存。说明 2: 如果创建:(来自 View 类的说明)
如果正在创建用户,则创建用户配置文件 其中 user = 刚刚通过 UserForm 提交的用户实例
地址是从 ProfileForm 收集并添加到用户实例之前 调用 user.save()
我有两种形式:
用户表单:
class UserForm(forms.ModelForm):
password = forms.CharField(widget=forms.PasswordInput(render_value=True), required=True)
password_confirmation = forms.CharField(widget=forms.PasswordInput(render_value=True), required=True)
first_name = forms.CharField(required=True)
last_name = forms.CharField(required=True)
class Meta:
model = User
fields = ('email', 'username', 'first_name', 'last_name', 'password', 'password_confirmation')
def clean(self):
cleaned_data = super(UserForm, self).clean()
password = cleaned_data.get("password")
password_confirmation = cleaned_data.get("password_confirmation")
if password != password_confirmation:
self.fields['password'].widget = forms.PasswordInput()
self.fields['password_confirmation'].widget = forms.PasswordInput()
self.add_error('password', "Must match with Password confirmation")
self.add_error('password_confirmation', "Must match with Password")
raise forms.ValidationError(
"Password and Password confirmation do not match"
)
个人资料表格:
class ProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ('address',)
我希望我能很好地理解您的问题,这将对您(以及其他人)有所帮助。 :)
【讨论】:
我认为这现在应该是公认的答案。它很干净,利用了 Django 的类和内置功能,并且得到了很好的评论。感谢您发布。 唯一可能值得添加的是信号系统,以便用户配置文件在保存用户时接收来自用户的信号。 不客气!我现在完全忘记了 Django,因为这是 3 年前的事了...... :D 但是,请随意编辑答案。【参考方案3】:django 的一个原则是,您可以用几个较小的表单构建一个大表单,只需一个提交按钮。这就是为什么 <form>
-tags 不是由 django 自己生成的。
通用视图的问题,无论是否基于类,以及背景中的多种此类形式,当然是天空是极限。这些表格可能以某种方式相关:“母亲”表格和可选的额外数据,这些数据取决于母亲中的数据(例如onetoone)。然后是通过外键和/或中间表连接到其他几个模型的模型,您可以在其中使用表单+表单集。然后是所有表单集类型的页面,例如在管理员中,当您直接在列表视图中使某些字段可编辑时。每一种都是不同类型的多表单视图,我认为制作一个涵盖所有情况的通用视图不会有成效。
如果你有一个“母亲”模型,你可以使用标准的UpdateView
或CreateView
,并在处理的代码之后添加从get()
和post()
调用的额外表单的方法母亲模型。例如,在form_valid()
中,如果母表单有效,您可以处理其他表单。您将拥有母亲的 pk,然后您可以使用它来连接其他形式的数据。
【讨论】:
以上是关于django 通用(类)视图中的多个表单类的主要内容,如果未能解决你的问题,请参考以下文章
django:通用类视图 + POST = HTTP 405(不允许的方法)
django中的通用视图(类视图),如何获得和设置session?