Уникальный валидатор в WTForms с моделями SQLAlchemy - PullRequest
13 голосов
/ 16 апреля 2011

Я определил некоторые формы WTForms в приложении, которое использует SQLALchemy для управления операциями с базой данных.

Например, форма для управления категориями:

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)

Основная проблема в том, что я не знаю, как получить доступ к существующему объекту категории в валидаторе, что позволило бы мне исключить отредактированный объект из запроса.

Есть ли способ сделать это? Спасибо.

Ответы [ 5 ]

10 голосов
/ 02 ноября 2011

На этапе проверки у вас будет доступ ко всем полям. Таким образом, хитрость заключается в том, чтобы передать первичный ключ в форму редактирования, например,

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):
5 голосов
/ 31 мая 2014

Хотя это не прямой ответ, я добавляю его, потому что этот вопрос заигрывает с проблемой XY . WTForms Основная задача заключается в проверке содержимого отправленной формы.Несмотря на то, что можно привести убедительные аргументы в пользу того, что проверка уникальности поля может считаться обязанностью валидатора формы, можно привести более убедительный аргумент в пользу того, что это является обязанностью механизма хранения.

В тех случаях, когда мне представили эту проблему, я рассматривал уникальность как оптимистичный случай, позволял ей передавать отправку формы и не выполнять ограничение базы данных.Затем я улавливаю ошибку и добавляю ошибку в форму.

Преимуществ несколько.Во-первых, это значительно упрощает ваш код WTForms , поскольку вам не нужно писать сложные схемы проверки.Во-вторых, это может улучшить производительность вашего приложения.Это связано с тем, что вам не нужно отправлять SELECT, прежде чем попытаться INSERT эффективно удвоить трафик вашей базы данных.

2 голосов
/ 20 мая 2016

Декларация

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={})
2 голосов
/ 15 марта 2014

Уникальный валидатор должен сначала использовать новые и старые данные для сравнения, прежде чем проверять, являются ли данные уникальными.

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.

1 голос
/ 08 февраля 2018

Похоже, то, что вы ищете, может быть легко достигнуто с помощью ModelForm , который предназначен для работы с формами, которые тесно связаны с моделями (модель категории в вашем случае).

Дляиспользуйте его:

...
from wtforms_components import Unique
from wtforms_alchemy import ModelForm

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

Он будет проверять уникальные значения при рассмотрении текущего значения в модели.Вы можете использовать оригинальный уникальный валидатор с ним.

...