Commit 0f3e63a0 authored by Serge S. Koval's avatar Serge S. Koval

Merge pull request #695 from singingwolfboy/sqla-respect-required-validator-on-relations

sqla: respect required validator on relations
parents 2323b5e0 230ea83e
...@@ -110,10 +110,13 @@ class AdminModelConverter(ModelConverterBase): ...@@ -110,10 +110,13 @@ class AdminModelConverter(ModelConverterBase):
kwargs['label'] = self._get_label(prop.key, kwargs) kwargs['label'] = self._get_label(prop.key, kwargs)
kwargs['description'] = self._get_description(prop.key, kwargs) kwargs['description'] = self._get_description(prop.key, kwargs)
if column.nullable or prop.direction.name != 'MANYTOONE': # determine optional/required, or respect existing
kwargs['validators'].append(validators.Optional()) requirement_options = (validators.Optional, validators.InputRequired)
else: if not any(isinstance(v, requirement_options) for v in kwargs['validators']):
kwargs['validators'].append(validators.InputRequired()) if column.nullable or prop.direction.name != 'MANYTOONE':
kwargs['validators'].append(validators.Optional())
else:
kwargs['validators'].append(validators.InputRequired())
# Contribute model-related parameters # Contribute model-related parameters
if 'allow_blank' not in kwargs: if 'allow_blank' not in kwargs:
...@@ -604,6 +607,11 @@ class InlineModelConverter(InlineModelConverterBase): ...@@ -604,6 +607,11 @@ class InlineModelConverter(InlineModelConverterBase):
if label: if label:
kwargs['label'] = label kwargs['label'] = label
view_info = self.get_info(self.view)
if view_info.form_args:
field_args = view_info.form_args.get(forward_prop.key, {})
kwargs.update(**field_args)
# Contribute field # Contribute field
setattr(form_class, setattr(form_class,
forward_prop.key, forward_prop.key,
......
from sqlalchemy.orm.exc import NoResultFound from sqlalchemy.orm.exc import NoResultFound
from wtforms import ValidationError from wtforms import ValidationError
try:
from wtforms.validators import InputRequired
except ImportError:
from wtforms.validators import Required as InputRequired
class Unique(object): class Unique(object):
...@@ -39,3 +43,26 @@ class Unique(object): ...@@ -39,3 +43,26 @@ class Unique(object):
raise ValidationError(self.message) raise ValidationError(self.message)
except NoResultFound: except NoResultFound:
pass pass
class ItemsRequired(InputRequired):
"""
A version of the ``InputRequired`` validator that works with relations,
to require a minimum number of related items.
"""
def __init__(self, min=1, message=None):
super(ItemsRequired, self).__init__(message=message)
self.min = min
def __call__(self, form, field):
if len(field.data) < self.min:
if self.message is None:
message = field.ngettext(
u"At least %d item is required",
u"At least %d items are required",
self.min
) % (self.min,)
else:
message = self.message
raise ValidationError(message)
...@@ -5,6 +5,7 @@ from wtforms import fields ...@@ -5,6 +5,7 @@ from wtforms import fields
from flask.ext.admin.contrib.sqla import ModelView from flask.ext.admin.contrib.sqla import ModelView
from flask.ext.admin.contrib.sqla.fields import InlineModelFormList from flask.ext.admin.contrib.sqla.fields import InlineModelFormList
from flask.ext.admin.contrib.sqla.validators import ItemsRequired
from . import setup from . import setup
...@@ -95,6 +96,54 @@ def test_inline_form(): ...@@ -95,6 +96,54 @@ def test_inline_form():
eq_(UserInfo.query.count(), 0) eq_(UserInfo.query.count(), 0)
def test_inline_form_required():
app, db, admin = setup()
client = app.test_client()
# Set up models and database
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, unique=True)
def __init__(self, name=None):
self.name = name
class UserEmail(db.Model):
__tablename__ = 'user_info'
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String, nullable=False, unique=True)
verified_at = db.Column(db.DateTime)
user_id = db.Column(db.Integer, db.ForeignKey(User.id))
user = db.relationship(User, backref=db.backref('emails', cascade="all, delete-orphan", single_parent=True))
db.create_all()
# Set up Admin
class UserModelView(ModelView):
inline_models = (UserEmail,)
form_args = {
"emails": {"validators": [ItemsRequired()]}
}
view = UserModelView(User, db.session)
admin.add_view(view)
# Create
rv = client.post('/admin/user/new/', data=dict(name=u'no-email'))
eq_(rv.status_code, 200)
eq_(User.query.count(), 0)
data = {
'name': 'hasEmail',
'emails-0-email': 'foo@bar.com',
}
rv = client.post('/admin/user/new/', data=data)
eq_(rv.status_code, 302)
eq_(User.query.count(), 1)
eq_(UserEmail.query.count(), 1)
def test_inline_form_ajax_fk(): def test_inline_form_ajax_fk():
app, db, admin = setup() app, db, admin = setup()
...@@ -150,6 +199,7 @@ def test_inline_form_ajax_fk(): ...@@ -150,6 +199,7 @@ def test_inline_form_ajax_fk():
ok_('userinfo-tag' in view._form_ajax_refs) ok_('userinfo-tag' in view._form_ajax_refs)
def test_inline_form_self(): def test_inline_form_self():
app, db, admin = setup() app, db, admin = setup()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment