Skip to content

Commit

Permalink
Unique field validator for SQLAlchemy forms.
Browse files Browse the repository at this point in the history
Refactored code a bit.
  • Loading branch information
mrjoes committed Mar 26, 2012
1 parent 93d911c commit b82188f
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 29 deletions.
4 changes: 2 additions & 2 deletions flask_adminex/ext/fileadmin.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from flask.ext import wtf


class NameForm(wtf.Form):
class NameForm(form.BaseForm):
"""
Form with a filename input field.
Expand All @@ -31,7 +31,7 @@ def validate_name(self, field):
raise wtf.ValidationError('Invalid directory name')


class UploadForm(form.AdminForm):
class UploadForm(form.BaseForm):
"""
File upload form. Works with FileAdmin instance to check if it is allowed
to upload file with given extension.
Expand Down
93 changes: 68 additions & 25 deletions flask_adminex/ext/sqlamodel.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,47 @@
from sqlalchemy.orm.attributes import InstrumentedAttribute
from sqlalchemy.orm.exc import NoResultFound
from sqlalchemy.sql.expression import desc

from wtforms import fields
from wtforms import ValidationError, fields
from wtforms.ext.sqlalchemy.orm import model_form, converts, ModelConverter
from wtforms.ext.sqlalchemy.fields import QuerySelectField, QuerySelectMultipleField

from flask import flash

from flask.ext.adminex.model import BaseModelView
from flask.ext.adminex import form
from flask.ext.adminex import model, form


class Unique(object):
"""Checks field value unicity against specified table field.
:param get_session:
A function that return a SQAlchemy Session.
:param model:
The model to check unicity against.
:param column:
The unique column.
:param message:
The error message.
"""
field_flags = ('unique', )

def __init__(self, db_session, model, column, message=None):
self.db_session = db_session
self.model = model
self.column = column
self.message = message

def __call__(self, form, field):
try:
obj = (self.db_session.query(self.model)
.filter(self.column == field.data).one())

if not hasattr(form, '_obj') or not form._obj == obj:
if self.message is None:
self.message = field.gettext(u'Already exists.')
raise ValidationError(self.message)
except NoResultFound:
pass


class AdminModelConverter(ModelConverter):
Expand All @@ -30,37 +63,39 @@ def _get_label(self, name, field_args):
return None

def convert(self, model, mapper, prop, field_args):
if not field_args:
field_args = dict()
kwargs = {
'validators': [],
'filters': []
}

if field_args:
kwargs.update(field_args)

if hasattr(prop, 'direction'):
remote_model = prop.mapper.class_
local_column = prop.local_remote_pairs[0][0]

kwargs = {
'validators': [],
'filters': [],
kwargs.update({
'allow_blank': local_column.nullable,
'label': self._get_label(prop.key, field_args),
'query_factory': lambda: self.view.session.query(remote_model),
'default': None
}

if field_args:
kwargs.update(field_args)
'label': self._get_label(prop.key, kwargs),
'query_factory': lambda: self.view.session.query(remote_model)
})

if prop.direction.name == 'MANYTOONE':
return QuerySelectField(widget=form.ChosenSelectWidget(), **kwargs)
return QuerySelectField(widget=form.ChosenSelectWidget(),
**kwargs)
elif prop.direction.name == 'ONETOMANY':
# Skip backrefs
if not local_column.foreign_keys and self.view.hide_backrefs:
return None

return QuerySelectMultipleField(widget=form.ChosenSelectWidget(multiple=True),
**kwargs)
return QuerySelectMultipleField(
widget=form.ChosenSelectWidget(multiple=True),
**kwargs)
elif prop.direction.name == 'MANYTOMANY':
return QuerySelectMultipleField(widget=form.ChosenSelectWidget(multiple=True),
**kwargs)
return QuerySelectMultipleField(
widget=form.ChosenSelectWidget(multiple=True),
**kwargs)
else:
# Ignore pk/fk
if hasattr(prop, 'columns'):
Expand All @@ -69,12 +104,19 @@ def convert(self, model, mapper, prop, field_args):
if column.foreign_keys or column.primary_key:
return None

field_args['label'] = self._get_label(prop.key, field_args)
# If field is unique, validate it
if column.unique:
kwargs['validators'].append(Unique(self.view.session,
model,
column))

# Apply label
kwargs['label'] = self._get_label(prop.key, kwargs)

return super(AdminModelConverter, self).convert(model,
mapper,
prop,
field_args)
kwargs)

@converts('Date')
def convert_date(self, field_args, **extra):
Expand All @@ -91,7 +133,7 @@ def convert_time(self, field_args, **extra):
return form.TimeField(**field_args)


class ModelView(BaseModelView):
class ModelView(model.BaseModelView):
"""
SQLALchemy model view
Expand Down Expand Up @@ -166,7 +208,8 @@ def scaffold_sortable_columns(self):
# Sanity check
if len(p.columns) > 1:
raise Exception('Automatic form scaffolding is not supported' +
' for multi-column properties (%s.%s)' % (self.model.__name__, p.key))
' for multi-column properties (%s.%s)' % (
self.model.__name__, p.key))

column = p.columns[0]

Expand All @@ -183,7 +226,7 @@ def scaffold_form(self):
Create form from the model.
"""
return model_form(self.model,
form.AdminForm,
form.BaseForm,
self.form_columns,
field_args=self.form_args,
converter=AdminModelConverter(self))
Expand Down
6 changes: 5 additions & 1 deletion flask_adminex/form.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@
from wtforms import fields, widgets


class AdminForm(wtf.Form):
class BaseForm(wtf.Form):
"""
Customized form class.
"""
def __init__(self, formdata=None, obj=None, prefix='', **kwargs):
super(BaseForm, self).__init__(formdata, obj, prefix, **kwargs)

self._obj = obj

@property
def has_file_field(self):
Expand Down
2 changes: 1 addition & 1 deletion flask_adminex/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ def get_sortable_columns(self):

def scaffold_form(self):
"""
Create `form.AdminForm` class from the model. Must be implemented in
Create `form.BaseForm` inherited class from the model. Must be implemented in
the child class.
"""
raise NotImplemented('Please implement scaffold_form method')
Expand Down

0 comments on commit b82188f

Please sign in to comment.