## 12.2 模式2: 在模型表单中使用自定义表单字段验证器
在我们项目的甜品(dessert)apps中,如果我们想确定每一次使用title字段的时候,都以以‘Tasty’单词起始,要怎么做呢?
这是一个字符串验证问题,我们可以使用一个简单的自定义字段验证器来解决它。
在此模式中,我们会覆盖到这些:如何创建自定义单一字段验证器和证明如何添加它们到抽象模型和表单。
想象一下这个例子的目的我们我们拥有一个有着两个不同甜品模型的项目:一个基于冰淇淋风味的风味(Flavor)模型和一个基于不同种类的奶昔的奶昔模型(Milkshake)。假设我们的例子模型拥有title字段。
为了验证所有模型的可编辑的titles,我们通过创建一个validators.py模块开始:
EXAMPLE 12.2
# core/validators.py
from django.core.exceptions import ValidationError
def validate_tasty(value):
"""Raise a ValidationError if the value
doesn't start with the word 'Tasty'.
"""
"""
如果没有以‘Tasty’开始,产生一个验证错误。
"""
if not value.startswith(u"Tasty"):
msg = u"Must start with Tasty"
raise ValidationError(msg)
在Django中,一个自定义字段验证器只是一个函数,如果提交的参数没有通过测试就会产生一个错误。
当然,虽然我们的validate_tasty验证器函数因为这个例子的缘故仅仅做了一个简单的字符串检查,但是要牢记在实践中表单字段验证器能变得相当复杂。
提示:仔细测试你的验证器
因为验证器对于保证损坏数据原理Django项目数据库是极其重要的,我们要对它们编写详细的测试。 对于你的验证器的自定义逻辑的每一个条件来说,这些测试应该包括考虑周全的边缘情况测试。
为了在我们不同的甜品模型中使用我们的validate_tasty()验证器函数,首先我们将要添加一个抽象模型,叫做TastyTitleAbstractModel,我们在我们的项目中使用它。
假设我们的Flavor和Milkshake在不同的app中,把我们的验证器放在一个app中或者其他地方是没有意义的。相反,我们创建一个core/models.py模块并且把TastyTitleAbstractModel放在这里。
EXAMPLE 12.3
# core/models.py
from django.db import models
from .validators import validate_tasty
class TastyTitleAbstractModel(models.Model):
title = models.CharField(max_length=255, validators=[validate_tasty])
class Meta:
abstract = True
上面代码最后两行我们让TastyTitleAbstractModel成为一个抽象模型,这正是我们想要的。
让我们改变原来的flavors/models.py Flavor模型来使用TastyTitleAbstractModel作为父类:
EXAMPLE 12.4
# flavors/models.py
from django.core.urlresolvers import reverse
from django.db import models
from core.models import TastyTitleAbstractModel
.
class Flavor(TastyTitleAbstractModel):
slug = models.SlugField()
scoops_remaining = models.IntegerField(default=0)
def get_absolute_url(self):
return reverse("flavors:detail", kwargs={"slug": self.slug})
它将会和Flavor模型一同协作,并且他将会与其他任何的tasty食物模型一同协作,例如一个WaffleCone或者Cake模型。如果任何人试图保存一个没有以‘Tasty’起始的title的模型,那么继承自TastyTitleAbstractModel的任何模型将会抛出一个验证错误。
现在,让我们探索一对可能在你思想中形成的问题:
》》如果我们想仅在表单中使用validate_tasty(),要怎么做呢? 》》如果我们想把它应用到除了title的其他字段,要怎么做呢?
为了支持这些行为,我们需要利用我们的自定义验证器创建一个自定义FlavorForm:
EXAMPLE 12.5
# flavors/forms.py
from django import forms
from core.validators import validate_tasty
from .models import Flavor
class FlavorForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(FlavorForm, self).__init__(*args, **kwargs)
self.fields["title"].validators.append(validate_tasty)
self.fields["slug"].validators.append(validate_tasty)
class Meta:
model = Flavor
一个很好的事情是,在此模式中,关于这两个例子的验证器用法上,我们完全没有更改validate_tasty()的代码。相反,我们仅仅在新的地方导入和使用它。
我们的下一步是连接我们的自定义表单到我们的视图上。Django基于模型编辑视图的默认行为是在基于视图的模型属性上自动生成模型表单。我们将要重写默认行为并且传入到我们的自定义FlavorForm。在flavors/views.py模块编写它们,我们像下面证明的那样更改create和update表单:
EXAMPLE 12.6
# flavors/views.py
from django.contrib import messages
from django.views.generic import CreateView, UpdateView, DetailView
from braces.views import LoginRequiredMixin
from .models import Flavor
from .forms import FlavorForm
class FlavorActionMixin(object):
model = Flavor
fields = ('title', 'slug', 'scoops_remaining')
@property
def success_msg(self):
return NotImplemented
def form_valid(self, form):
messages.info(self.request, self.success_msg)
return super(FlavorActionMixin, self).form_valid(form)
class FlavorCreateView(LoginRequiredMixin, FlavorActionMixin,
CreateView):
success_msg = "created"
# Explicitly attach the FlavorForm class
# 显式附加FlavorForm类
form_class = FlavorForm
class FlavorUpdateView(LoginRequiredMixin, FlavorActionMixin,
UpdateView):
success_msg = "updated"
# Explicitly attach the FlavorForm class
# 显式附加FlavorForm类
form_class = FlavorForm
class FlavorDetailView(DetailView):
model = Flavor
FlavorCreateView和FlavorUpdateView现在使用新的FlavorForm来验证传入数据。
注意这些修改,Flavor模型可以跟本章开头的一样,也可以和更改后继承自TastyTitleAbstractModel的一样。