## 12.4 Hacking表单字段(2 CBVs, 2 Forms, 1 Model)


从这里开始我们要发挥想象力了。我们将要涵盖一个两个views/forms对应一个model的情形。我们将会解密Django的表单来制作一个自定义行为的表单。

用户创建一些包括了将要添加数据的空字段的记录并不少见。一个可能的情况可能是一个商店的列表,我们想要每个商店尽可能快的进入系统,但是想要添加一些数据,例如电话和描述。这是我们的IceCreamStoremodel:

  1. EXAMPLE 12.9
  2. # stores/models.py
  3. from django.core.urlresolvers import reverse
  4. from django.db import models
  5. class IceCreamStore(models.Model):
  6. title = models.CharField(max_length=100)
  7. block_address = models.TextField() .
  8. phone = models.CharField(max_length=20, blank=True)
  9. description = models.TextField(blank=True)
  10. def get_absolute_url(self):
  11. return reverse("store_detail", kwargs={"pk": self.pk})

默认的ModelForm强制用户填写titleblock_address字段,但是允许跳过phonedescription字段。这对初始数据输入很好,但正如前面提到,我们想要未来的数据更新需要电话和描述字段。

在过去我们实现的方法是我们开始研究构造来重写电话和描述字段。这导致了大量重复的代码,就像这样:

BAD EXAMPLE 12.1

这个表单看起来很熟悉,为什么呢?

是的,我们几乎复制了IceCreamStore模型(model)。

这仅仅是一个简单的例子,但当在一个model里处理大量字段时,这种复制变得尤其难以维护。事实上,这里发生了模型到表单的代码复制,一个严重违反Don’t Repeat Yourself(不重复代码)的规则。

想要了解为什么严重?使用上述方法,如果我们想要在description字段添加一个简单的help_text属性,它将不会显现在模板(template)直到我们同样在表单中修改了description字段定义。如果这听起来很令人困惑,因为本身就是这样。

一个好的方法是依赖一个实用的小技巧,这会很好去理解Django的表单:实例化表单对象来存储字段(fields)在一个类似字典(dict)属性里,叫做fields

这次不是从模型字段定义中复制代码了,我们可以简单的应用新的属性到每个字段在ModelForm的init方法中 :

  1. EXAMPLE 12.10
  2. # stores/forms.py
  3. # Call phone and description from the self.fields dict-like object
  4. from django import forms
  5. from .models import IceCreamStore
  6. class IceCreamStoreUpdateForm(forms.ModelForm):
  7. class Meta:
  8. model = IceCreamStore
  9. def __init__(self, *args, **kwargs):
  10. # Call the original __init__ method before assigning
  11. # field overloads
  12. super(IceCreamStoreUpdateForm, self).__init__(*args,
  13. **kwargs)
  14. self.fields["phone"].required = True
  15. self.fields["description"].required = True

这种改进的方法允许我们不复制代码反而集中精力在字段特殊设定上。

这种情况下我们要明白的一点是,Django的表单仅仅是python的类(class)。它们被实例化为对象,它们可以继承其他类,它们也可以成为父类(superclasses)。

因此,我们可以依赖继承来修改我们的冰淇淋商店表单:

  1. EXAMPLE 12.11
  2. # stores/forms.py
  3. from django import forms
  4. from .models import IceCreamStore
  5. class IceCreamStoreCreateForm(forms.ModelForm):
  6. class Meta:
  7. model = IceCreamStore
  8. fields = ("title", "block_address", )
  9. class IceCreamStoreUpdateForm(IceCreamStoreCreateForm):
  10. def __init__(self, *args, **kwargs):
  11. super(IceCreamStoreUpdateForm,
  12. self).__init__(*args, **kwargs)
  13. self.fields["phone"].required = True
  14. self.fields["description"].required = True
  15. class Meta(IceCreamStoreCreateForm.Meta):
  16. # show all the fields!
  17. fields = ("title", "block_address", "phone",
  18. "description", )

警告:使用Meta.fields,不要使用Meta.exclude

我们使用Meta.fields而不是Meta.exclude,所以我们会知道我们显现了哪些字段。参见第26章,(安全性最佳实践)Security Best Practices,26.13部分,“不要使用ModelForms.Meta.exclude”(Don’t use ModelForms.Meta.exclude)。

最后,我们需要定义相应的CBVs,我们已经有了表单类,让我们在IceCreamStore中使用它们创建,更新视图:

  1. EXAMPLE 12.12
  2. # stores/views
  3. from django.views.generic import CreateView, UpdateView
  4. from .forms import IceCreamStoreCreateForm
  5. from .forms import IceCreamStoreUpdateForm
  6. from .models import IceCreamStore
  7. class IceCreamCreateView(CreateView):
  8. model = IceCreamStore
  9. form_class = IceCreamStoreCreateForm
  10. class IceCreamUpdateView(UpdateView):
  11. model = IceCreamStore
  12. form_class = IceCreamStoreUpdateForm

现在我们有了两个视图,两个表单,它们与一个模型共同协作。