Commit ce5b36ef authored by Serge S. Koval's avatar Serge S. Koval

New data model for form_ajax_refs

parent f2107232
......@@ -122,8 +122,12 @@ class PostAdmin(sqla.ModelView):
)
form_ajax_refs = {
'user': (User.username, User.email),
'tags': (Tag.name,)
'user': {
'fields': (User.username, User.email)
},
'tags': {
'fields': (Tag.name,)
}
}
def __init__(self, session):
......
......@@ -5,18 +5,23 @@ from flask.ext.admin.model.ajax import AjaxModelLoader, DEFAULT_PAGE_SIZE
class QueryAjaxModelLoader(AjaxModelLoader):
def __init__(self, name, session, model, fields):
def __init__(self, name, session, model, options):
"""
Constructor.
:param fields:
Fields to run query against
"""
super(QueryAjaxModelLoader, self).__init__(name)
super(QueryAjaxModelLoader, self).__init__(name, options)
self.session = session
self.model = model
self.fields = fields
self.fields = options.get('fields')
if not self.fields:
raise ValueError('AJAX loading requires `fields` to be specified for %s.%s' % (model, self.name))
self._cached_fields = self._process_fields()
primary_keys = model._sa_class_manager.mapper.primary_key
if len(primary_keys) > 1:
......@@ -24,6 +29,23 @@ class QueryAjaxModelLoader(AjaxModelLoader):
self.pk = primary_keys[0].name
def _process_fields(self):
remote_fields = []
for field in self.fields:
if isinstance(field, string_types):
attr = getattr(self.model, field, None)
if not attr:
raise ValueError('%s.%s does not exist.' % (self.model, field))
remote_fields.append(attr)
else:
# TODO: Figure out if it is valid SQLAlchemy property?
remote_fields.append(field)
return remote_fields
def format(self, model):
if not model:
return None
......@@ -36,34 +58,20 @@ class QueryAjaxModelLoader(AjaxModelLoader):
def get_list(self, term, offset=0, limit=DEFAULT_PAGE_SIZE):
query = self.session.query(self.model)
filters = (field.like(u'%%%s%%' % term) for field in self.fields)
filters = (field.like(u'%%%s%%' % term) for field in self._cached_fields)
query = query.filter(or_(*filters))
return query.offset(offset).limit(limit).all()
def create_ajax_loader(model, session, name, field_name, fields):
attr = getattr(model, field_name, None)
if attr is None:
raise ValueError('Model %s does not have field %s.' % (model, field_name))
if not hasattr(attr, 'property') or not hasattr(attr.property, 'direction'):
raise ValueError('%s.%s is not a relation.' % (model, field_name))
remote_model = attr.prop.mapper.class_
remote_fields = []
for field in fields:
if isinstance(field, string_types):
attr = getattr(remote_model, field, None)
def create_ajax_loader(model, session, name, field_name, options):
attr = getattr(model, field_name, None)
if not attr:
raise ValueError('%s.%s does not exist.' % (remote_model, field))
if attr is None:
raise ValueError('Model %s does not have field %s.' % (model, field_name))
remote_fields.append(attr)
else:
# TODO: Figure out if it is valid SQLAlchemy property?
remote_fields.append(field)
if not hasattr(attr, 'property') or not hasattr(attr.property, 'direction'):
raise ValueError('%s.%s is not a relation.' % (model, field_name))
return QueryAjaxModelLoader(name, session, remote_model, remote_fields)
remote_model = attr.prop.mapper.class_
return QueryAjaxModelLoader(name, session, remote_model, options)
......@@ -498,7 +498,7 @@ class InlineModelConverter(InlineModelConverterBase):
new_name = '%s-%s' % (info.model.__name__.lower(), name)
loader = None
if isinstance(opts, (list, tuple)):
if isinstance(opts, dict):
loader = create_ajax_loader(info.model, self.session, new_name, name, opts)
else:
loader = opts
......
......@@ -586,8 +586,8 @@ class ModelView(BaseModelView):
return joined
# AJAX foreignkey support
def _create_ajax_loader(self, name, fields):
return create_ajax_loader(self.model, self.session, name, name, fields)
def _create_ajax_loader(self, name, options):
return create_ajax_loader(self.model, self.session, name, name, options)
# Database-related API
def get_query(self):
......
......@@ -5,7 +5,7 @@ class AjaxModelLoader(object):
"""
Ajax related model loader. Override this to implement custom loading behavior.
"""
def __init__(self, name):
def __init__(self, name, options):
"""
Constructor.
......@@ -13,6 +13,7 @@ class AjaxModelLoader(object):
Field name
"""
self.name = name
self.options = options
def format(self, model):
"""
......
......@@ -367,15 +367,28 @@ class BaseModelView(BaseView, ActionsMixin):
"""
Use AJAX for foreign key model loading.
Should contain dictionary, where key is field name and value is enumerable with list to fields
to check against (in remote model).
Should contain dictionary, where key is field name and value is either a dictionary which
configures AJAX lookups or backend-specific `AjaxModelLoader` class instance.
For example, it can look like::
class MyModelView(BaseModelView):
form_ajax_refs = {
'user': ('first_name', 'last_name', 'email')
'user': {
'fields': ('first_name', 'last_name', 'email')
'page_size': 10
}
}
Or with SQLAlchemy backend like this::
class MyModelView(BaseModelView):
form_ajax_refs = {
'user': QueryAjaxModelLoader('user', db.session, User, page_size=10)
}
If you need custom loading functionality, you can implement your custom loading behavior
in your `AjaxModelLoader` class.
"""
# Actions
......@@ -998,17 +1011,17 @@ class BaseModelView(BaseView, ActionsMixin):
result = {}
if self.form_ajax_refs:
for name, value in iteritems(self.form_ajax_refs):
if isinstance(value, AjaxModelLoader):
result[name] = value
elif isinstance(value, (list, tuple)):
result[name] = self._create_ajax_loader(name, value)
for name, options in iteritems(self.form_ajax_refs):
if isinstance(options, dict):
result[name] = self._create_ajax_loader(name, options)
elif isinstance(options, AjaxModelLoader):
result[name] = options
else:
raise ValueError('%s.form_ajax_refs can not handle %s types' % (self, type(value)))
raise ValueError('%s.form_ajax_refs can not handle %s types' % (self, type(options)))
return result
def _create_ajax_loader(self, name, fields):
def _create_ajax_loader(self, name, options):
"""
Model backend will override this to implement AJAX model loading.
"""
......
......@@ -690,7 +690,9 @@ def test_ajax_fk():
Model2, db.session,
url='view',
form_ajax_refs={
'model1': ('test1', 'test2')
'model1': {
'fields': ('test1', 'test2')
}
}
)
admin.add_view(view)
......@@ -774,7 +776,9 @@ def test_ajax_fk_multi():
Model2, db.session,
url='view',
form_ajax_refs={
'model1': ('name',)
'model1': {
'fields': ['name']
}
}
)
admin.add_view(view)
......
......@@ -129,7 +129,15 @@ def test_inline_form_ajax_fk():
# Set up Admin
class UserModelView(ModelView):
inline_models = [(UserInfo, {'form_ajax_refs': {'tag': ('name',)}})]
opts = {
'form_ajax_refs': {
'tag': {
'fields': ['name']
}
}
}
inline_models = [(UserInfo, opts)]
view = UserModelView(User, db.session)
admin.add_view(view)
......
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