## 12.2 模式2: 在模型表单中使用自定义表单字段验证器


在我们项目的甜品(dessert)apps中,如果我们想确定每一次使用title字段的时候,都以以‘Tasty’单词起始,要怎么做呢?

Figure 12.1

这是一个字符串验证问题,我们可以使用一个简单的自定义字段验证器来解决它。

在此模式中,我们会覆盖到这些:如何创建自定义单一字段验证器和证明如何添加它们到抽象模型和表单。

想象一下这个例子的目的我们我们拥有一个有着两个不同甜品模型的项目:一个基于冰淇淋风味的风味(Flavor)模型和一个基于不同种类的奶昔的奶昔模型(Milkshake)。假设我们的例子模型拥有title字段。

为了验证所有模型的可编辑的titles,我们通过创建一个validators.py模块开始:

  1. EXAMPLE 12.2
  2. # core/validators.py
  3. from django.core.exceptions import ValidationError
  4. def validate_tasty(value):
  5. """Raise a ValidationError if the value
  6. doesn't start with the word 'Tasty'.
  7. """
  8. """
  9. 如果没有以‘Tasty’开始,产生一个验证错误。
  10. """
  11. if not value.startswith(u"Tasty"):
  12. msg = u"Must start with Tasty"
  13. raise ValidationError(msg)

在Django中,一个自定义字段验证器只是一个函数,如果提交的参数没有通过测试就会产生一个错误。

当然,虽然我们的validate_tasty验证器函数因为这个例子的缘故仅仅做了一个简单的字符串检查,但是要牢记在实践中表单字段验证器能变得相当复杂。

提示:仔细测试你的验证器

因为验证器对于保证损坏数据原理Django项目数据库是极其重要的,我们要对它们编写详细的测试。 对于你的验证器的自定义逻辑的每一个条件来说,这些测试应该包括考虑周全的边缘情况测试。

为了在我们不同的甜品模型中使用我们的validate_tasty()验证器函数,首先我们将要添加一个抽象模型,叫做TastyTitleAbstractModel,我们在我们的项目中使用它。

假设我们的FlavorMilkshake在不同的app中,把我们的验证器放在一个app中或者其他地方是没有意义的。相反,我们创建一个core/models.py模块并且把TastyTitleAbstractModel放在这里。

  1. EXAMPLE 12.3
  2. # core/models.py
  3. from django.db import models
  4. from .validators import validate_tasty
  5. class TastyTitleAbstractModel(models.Model):
  6. title = models.CharField(max_length=255, validators=[validate_tasty])
  7. class Meta:
  8. abstract = True

上面代码最后两行我们让TastyTitleAbstractModel成为一个抽象模型,这正是我们想要的。

让我们改变原来的flavors/models.py Flavor模型来使用TastyTitleAbstractModel作为父类:

  1. EXAMPLE 12.4
  2. # flavors/models.py
  3. from django.core.urlresolvers import reverse
  4. from django.db import models
  5. from core.models import TastyTitleAbstractModel
  6. .
  7. class Flavor(TastyTitleAbstractModel):
  8. slug = models.SlugField()
  9. scoops_remaining = models.IntegerField(default=0)
  10. def get_absolute_url(self):
  11. return reverse("flavors:detail", kwargs={"slug": self.slug})

它将会和Flavor模型一同协作,并且他将会与其他任何的tasty食物模型一同协作,例如一个WaffleCone或者Cake模型。如果任何人试图保存一个没有以‘Tasty’起始的title的模型,那么继承自TastyTitleAbstractModel的任何模型将会抛出一个验证错误。

现在,让我们探索一对可能在你思想中形成的问题:

》》如果我们想仅在表单中使用validate_tasty(),要怎么做呢? 》》如果我们想把它应用到除了title的其他字段,要怎么做呢?

为了支持这些行为,我们需要利用我们的自定义验证器创建一个自定义FlavorForm

  1. EXAMPLE 12.5
  2. # flavors/forms.py
  3. from django import forms
  4. from core.validators import validate_tasty
  5. from .models import Flavor
  6. class FlavorForm(forms.ModelForm):
  7. def __init__(self, *args, **kwargs):
  8. super(FlavorForm, self).__init__(*args, **kwargs)
  9. self.fields["title"].validators.append(validate_tasty)
  10. self.fields["slug"].validators.append(validate_tasty)
  11. class Meta:
  12. model = Flavor

一个很好的事情是,在此模式中,关于这两个例子的验证器用法上,我们完全没有更改validate_tasty()的代码。相反,我们仅仅在新的地方导入和使用它。

我们的下一步是连接我们的自定义表单到我们的视图上。Django基于模型编辑视图的默认行为是在基于视图的模型属性上自动生成模型表单。我们将要重写默认行为并且传入到我们的自定义FlavorForm。在flavors/views.py模块编写它们,我们像下面证明的那样更改create和update表单:

  1. EXAMPLE 12.6
  2. # flavors/views.py
  3. from django.contrib import messages
  4. from django.views.generic import CreateView, UpdateView, DetailView
  5. from braces.views import LoginRequiredMixin
  6. from .models import Flavor
  7. from .forms import FlavorForm
  8. class FlavorActionMixin(object):
  9. model = Flavor
  10. fields = ('title', 'slug', 'scoops_remaining')
  11. @property
  12. def success_msg(self):
  13. return NotImplemented
  14. def form_valid(self, form):
  15. messages.info(self.request, self.success_msg)
  16. return super(FlavorActionMixin, self).form_valid(form)
  17. class FlavorCreateView(LoginRequiredMixin, FlavorActionMixin,
  18. CreateView):
  19. success_msg = "created"
  20. # Explicitly attach the FlavorForm class
  21. # 显式附加FlavorForm类
  22. form_class = FlavorForm
  23. class FlavorUpdateView(LoginRequiredMixin, FlavorActionMixin,
  24. UpdateView):
  25. success_msg = "updated"
  26. # Explicitly attach the FlavorForm class
  27. # 显式附加FlavorForm类
  28. form_class = FlavorForm
  29. class FlavorDetailView(DetailView):
  30. model = Flavor

FlavorCreateViewFlavorUpdateView现在使用新的FlavorForm来验证传入数据。

注意这些修改,Flavor模型可以跟本章开头的一样,也可以和更改后继承自TastyTitleAbstractModel的一样。