使用SQLAlchemy模型的WTForms中的唯一验证器

时间:2011-04-16 10:15:24

标签: sqlalchemy unique validation wtforms

我在使用SQLALchemy管理数据库操作的应用程序中定义了一些WTForms表单。

例如,用于管理类别的表单:

class CategoryForm(Form):
    name = TextField(u'name', [validators.Required()])

这是相应的SQLAlchemy模型:

class Category(Base):
    __tablename__= 'category'
    id = Column(Integer, primary_key=True)
    name = Column(Unicode(255))

    def __repr__(self):
        return '<Category %i>'% self.id

    def __unicode__(self):
        return self.name

我想在表单验证上添加唯一约束(不在模型本身上)。

阅读WTForms documentation,我找到了一种方法,只需一个简单的类:

class Unique(object):
    """ validator that checks field uniqueness """
    def __init__(self, model, field, message=None):
        self.model = model
        self.field = field
        if not message:
            message = u'this element already exists'
        self.message = message

    def __call__(self, form, field):         
        check = self.model.query.filter(self.field == field.data).first()
        if check:
            raise ValidationError(self.message)

现在我可以将验证器添加到 CategoryForm ,如下所示:

name = TextField(u'name', [validators.Required(), Unique(Category, Category.name)])

当用户尝试添加已存在的类别时,此检查很有用\ o / 当用户尝试更新现有类别(不更改名称属性)时,它将无效。

如果要更新现有类别:您将使用要编辑的category属性实例化表单:

def category_update(category_id):
    """ update the given category """
    category = Category.query.get(category_id)
    form = CategoryForm(request.form, category)

主要问题是我不知道如何访问验证器中的现有类别对象,这样我就可以从查询中排除已编辑的对象。

有办法吗?感谢。

5 个答案:

答案 0 :(得分:10)

在验证阶段,您将可以访问所有字段。因此,这里的技巧是将主键传递到您的编辑表单,例如

class CategoryEditForm(CategoryForm):
    id = IntegerField(widget=HiddenInput())

然后,在Unique验证器中,将if条件更改为:

check = self.model.query.filter(self.field == field.data).first()
if 'id' in form:
    id = form.id.data
else:
    id = None
if check and (id is None or id != check.id):

答案 1 :(得分:5)

虽然这不是一个直接的答案我会添加它,因为这个问题正在与XY Problem调情。 WTForms 主要工作是验证表单提交的内容。虽然可以做出一个体面的案例来验证字段的唯一性可以被认为是表单验证器的责任,但是可以做出更好的情况,这是存储引擎的责任。

如果我遇到此问题,我将唯一性视为乐观情况,允许它传递表单提交并在数据库约束上失败。然后我抓住了失败并将错误添加到表单中。

优点有几个。首先,它极大地简化了您的 WTForms 代码,因为您不必编写复杂的验证方案。其次,它可以提高应用程序的性能。这是因为在尝试SELECT有效地使数据库流量加倍之前,您不必调度INSERT

答案 2 :(得分:2)

在检查数据是否唯一之前,唯一验证器需要首先使用新数据和旧数据进行比较。

class Unique(object):
...
def __call__(self, form, field):
    if field.object_data == field.data:
        return
    check = DBSession.query(model).filter(field == data).first()
    if check:
        raise ValidationError(self.message)

此外,您可能也想要压缩空值。取决于您是否真正唯一或唯一但允许空值。

我使用WTForms 1.0.5和SQLAlchemy 0.9.1。

答案 3 :(得分:2)

声明

from wtforms.validators import ValidationError

class Unique(object):

    def __init__(self, model=None, pk="id", get_session=None, message=None,ignoreif=None):
        self.pk = pk
        self.model = model
        self.message = message
        self.get_session = get_session
        self.ignoreif = ignoreif
        if not self.ignoreif:
            self.ignoreif = lambda field: not field.data

    @property
    def query(self):
        self._check_for_session(self.model)
        if self.get_session:
            return self.get_session().query(self.model)
        elif hasattr(self.model, 'query'):
            return getattr(self.model, 'query')
        else:
            raise Exception(
                'Validator requires either get_session or Flask-SQLAlchemy'
                ' styled query parameter'
            )

    def _check_for_session(self, model):
        if not hasattr(model, 'query') and not self.get_session:
            raise Exception('Could not obtain SQLAlchemy session.')

    def __call__(self, form, field):
        if self.ignoreif(field):
            return True

        query = self.query
        query = query.filter(getattr(self.model,field.id)== form[field.id].data)
        if form[self.pk].data:
            query = query.filter(getattr(self.model,self.pk)!=form[self.pk].data)
        obj = query.first()
        if obj:
            if self.message is None:
                self.message = field.gettext(u'Already exists.')
            raise ValidationError(self.message)

使用它

class ProductForm(Form):
    id = HiddenField()
    code = TextField("Code",validators=[DataRequired()],render_kw={"required": "required"})
    name = TextField("Name",validators=[DataRequired()],render_kw={"required": "required"})
    barcode = TextField("Barcode",
                        validators=[Unique(model= Product, get_session=lambda : db)],
                        render_kw={})

答案 4 :(得分:0)

使用ModelForm可以很容易地实现您所寻找的内容,{{3}}用于处理与模型强烈耦合的表单(在您的案例中为类别模型)。

使用它:

...
from wtforms_components import Unique
from wtforms_alchemy import ModelForm

class CategoryForm(ModelForm):
    name = TextField(u'name', [validators.Required(), Unique(Category, Category.name)])

在考虑模型中的当前值时,它将验证唯一值。您可以使用原始的唯一验证器。