WTForms 中带有 SQLAlchemy 模型的独特验证器

问题描述 投票:0回答:6

我在使用 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 文档,我找到了一种使用简单的类来完成此操作的方法:

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/ 但是当用户尝试更新现有类别(不更改名称属性)时,它将不起作用。

当您想要更新现有类别时:您将使用要编辑的类别属性实例化表单:

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

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

有办法吗?谢谢。

sqlalchemy unique validation wtforms
6个回答
12
投票

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

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

然后,在唯一验证器中,将 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):

8
投票

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

在遇到这个问题的情况下,我将唯一性视为“乐观”的情况,允许它通过表单提交并在数据库约束上失败。然后我捕获失败并将错误添加到表单中。 优点有很多。首先,它极大地简化了您的

WTForms

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

INSERT
    


3
投票

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。


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={})



1
投票
ModelForm

轻松实现,它是为了处理与模型(您案例中的类别模型)强耦合的表单而构建的。 使用方法:

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

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


0
投票
column_name_in_db

中具有唯一的名称,否则它将无法工作。

class SomeForm(FlaskForm):
    id = IntegerField(widget=HiddenInput())
    fieldname = StringField('Field name', validators=[DataRequired()])
    ...
    
    def validate_fieldname(self, fieldname):
        names_in_db = dict(Model.query.with_entities(Model.id, 
        Model.column_name_in_db).filter_by(some_filters_if_needed).all())
        if fieldname.data in names_in_db.values() and names_in_db[int(self.id)] != fieldname.data:
            raise ValidationError('Name must be unique')

	
© www.soinside.com 2019 - 2024. All rights reserved.