我在使用 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)
主要问题是我不知道如何访问验证器中现有的类别对象,这可以让我从查询中排除已编辑的对象。
有办法吗?谢谢。
在验证阶段,您将可以访问所有字段。所以这里的技巧是将主键传递到您的编辑表单中,例如
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):
虽然这不是一个直接的答案,但我添加它是因为这个问题正在成为一个 XY 问题。 WTForms 主要工作是验证表单提交的内容。虽然可以认为验证字段的唯一性可以被视为表单验证器的责任,但更好的情况可以认为这是存储引擎的责任。
在遇到这个问题的情况下,我将唯一性视为“乐观”的情况,允许它通过表单提交并在数据库约束上失败。然后我捕获失败并将错误添加到表单中。 优点有很多。首先,它极大地简化了您的
WTForms 代码,因为您不必编写复杂的验证方案。其次,它可以提高应用程序的性能。 这是因为在尝试 SELECT
有效地使数据库流量加倍之前,您不必调度
INSERT
。 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。
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={})
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')