From 69d05c40c6866e8a4d107543be547082674dbf89 Mon Sep 17 00:00:00 2001 From: Marc Sommerhalder Date: Thu, 31 Jan 2019 08:28:24 +0100 Subject: [PATCH] Adds a unique column value validator --- HISTORY.rst | 4 +++ onegov/form/tests/test_validators.py | 44 ++++++++++++++++++++++++++++ onegov/form/validators.py | 34 +++++++++++++++++++++ 3 files changed, 82 insertions(+) diff --git a/HISTORY.rst b/HISTORY.rst index 4497b06..2650a69 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,5 +1,9 @@ Changelog --------- + +- Adds a unique column value validator. + [msom] + 0.42.0 (2019-01-28) ~~~~~~~~~~~~~~~~~~~ diff --git a/onegov/form/tests/test_validators.py b/onegov/form/tests/test_validators.py index 7c8bd33..6a63ef8 100644 --- a/onegov/form/tests/test_validators.py +++ b/onegov/form/tests/test_validators.py @@ -1,8 +1,52 @@ +from onegov.core.orm import SessionManager +from onegov.form.validators import UniqueColumnValue from onegov.form.validators import ValidPhoneNumber from pytest import raises +from sqlalchemy import Column +from sqlalchemy import Text +from sqlalchemy.ext.declarative import declarative_base from wtforms.validators import ValidationError +def test_unique_column_value_validator(postgres_dsn): + Base = declarative_base() + + class Dummy(Base): + __tablename__ = 'dummies' + name = Column(Text, nullable=False, primary_key=True) + + class Field(object): + def __init__(self, name, data): + self.name = name + self.data = data + + class Request(object): + def __init__(self, session): + self.session = session + + class Form(object): + def __init__(self, session): + self.request = Request(session) + + mgr = SessionManager(postgres_dsn, Base) + mgr.bases.append(Base) + mgr.set_current_schema('foobar') + session = mgr.session() + session.add(Dummy(name='Alice')) + + validator = UniqueColumnValue(Dummy) + form = Form(session) + + with raises(RuntimeError): + validator(form, Field('id', 'Alice')) + with raises(ValidationError): + validator(form, Field('name', 'Alice')) + validator(form, Field('name', 'Bob')) + + form.model = session.query(Dummy).first() + validator(form, Field('name', 'Alice')) + + def test_phone_number_validator(): class Field(object): diff --git a/onegov/form/validators.py b/onegov/form/validators.py index fb7a83a..11af89c 100644 --- a/onegov/form/validators.py +++ b/onegov/form/validators.py @@ -219,3 +219,37 @@ def __call__(self, form, field): ) if not valid: raise ValidationError(self.message) + + +class UniqueColumnValue(object): + """ Test if the given table does not already have a value in the column + (identified by the field name). + + If the form provides a model with such an attribute, we allow this + value, too. + + Expects an :class:`wtforms.StringField` instance. + + Usage:: + + username = StringField(validators=[UniqueColumnValue(User)]) + + """ + + def __init__(self, table): + self.table = table + + def __call__(self, form, field): + if field.name not in self.table.__table__.columns: + raise RuntimeError("The field name must match a column!") + + if hasattr(form, 'model'): + if hasattr(form.model, field.name): + if getattr(form.model, field.name) == field.data: + return + + column = getattr(self.table, field.name) + query = form.request.session.query(column) + query = query.filter(column == field.data) + if query.first(): + raise ValidationError(_("This value already exists."))