Commit 87f8a188 authored by P.J. Janse van Rensburg's avatar P.J. Janse van Rensburg

Merge branch 'master' into bootstrap4

parents e9f97ba1 e9ef3bf6
......@@ -7,6 +7,7 @@ Next release
* Fix display of inline x-editable boolean fields on list view
* Add support for several SQLAlchemy-Utils data types
* Support searching on SQLAlchemy hybrid properties
* Extra URL paramaters are now propagated to the next page when searching / filtering
* Add enum34 dependency when running on legacy Python version
* Update Mapbox API v1 URL format
* Update jQuery and moment dependencies in templates
......
......@@ -405,6 +405,10 @@ Now, to make your view classes use this template, set the appropriate class prop
edit_template = 'microblog_edit.html'
# create_template = 'microblog_create.html'
# list_template = 'microblog_list.html'
# details_template = 'microblog_details.html'
# edit_modal_template = 'microblog_edit_modal.html'
# create_modal_template = 'microblog_create_modal.html'
# details_modal_template = 'microblog_details_modal.html'
If you want to use your own base template, then pass the name of the template to
the admin constructor during initialization::
......
......@@ -58,13 +58,16 @@ class QueryAjaxModelLoader(AjaxModelLoader):
return getattr(model, self.pk), as_unicode(model)
def get_query(self):
return self.session.query(self.model)
def get_one(self, pk):
# prevent autoflush from occuring during populate_obj
with self.session.no_autoflush:
return self.session.query(self.model).get(pk)
return self.get_query().get(pk)
def get_list(self, term, offset=0, limit=DEFAULT_PAGE_SIZE):
query = self.session.query(self.model)
query = self.get_query()
filters = (cast(field, String).ilike(u'%%%s%%' % term) for field in self._cached_fields)
query = query.filter(or_(*filters))
......
......@@ -1701,29 +1701,39 @@ class BaseModelView(BaseView, ActionsMixin):
def get_empty_list_message(self):
return gettext('There are no items in the table.')
def get_invalid_value_msg(self, value, filter):
"""
Returns message, which should be printed in case of failed validation.
:param value: Invalid value
:param filter: Filter
:return: string
"""
return gettext('Invalid Filter Value: %(value)s', value=value)
# URL generation helpers
def _get_list_filter_args(self):
if self._filters:
filters = []
for n in request.args:
if not n.startswith('flt'):
for arg in request.args:
if not arg.startswith('flt'):
continue
if '_' not in n:
if '_' not in arg:
continue
pos, key = n[3:].split('_', 1)
pos, key = arg[3:].split('_', 1)
if key in self._filter_args:
idx, flt = self._filter_args[key]
value = request.args[n]
value = request.args[arg]
if flt.validate(value):
filters.append((pos, (idx, as_unicode(flt.name), value)))
data = (pos, (idx, as_unicode(flt.name), value))
filters.append(data)
else:
flash(gettext('Invalid Filter Value: %(value)s', value=value), 'error')
flash(self.get_invalid_value_msg(value, flt), 'error')
# Sort filters
return [v[1] for v in sorted(filters, key=lambda n: n[0])]
......@@ -2054,6 +2064,9 @@ class BaseModelView(BaseView, ActionsMixin):
get_pk_value=self.get_pk_value,
get_value=self.get_list_value,
return_url=self._get_list_url(view_args),
# Extras
extra_args=view_args.extra_args,
)
@expose('/new/', methods=('GET', 'POST'))
......
......@@ -34,6 +34,9 @@
{% macro filter_form() %}
<form id="filter_form" method="GET" action="{{ return_url }}">
{% for arg_name, arg_value in extra_args.items() %}
<input type="hidden" name="{{ arg_name }}" value="{{ arg_value }}">
{% endfor %}
{% if sort_column is not none %}
<input type="hidden" name="sort" value="{{ sort_column }}">
{% endif %}
......@@ -63,6 +66,9 @@
{% for flt_name, flt_value in filter_args.items() %}
<input type="hidden" name="{{ flt_name }}" value="{{ flt_value }}">
{% endfor %}
{% for arg_name, arg_value in extra_args.items() %}
<input type="hidden" name="{{ arg_name }}" value="{{ arg_value }}">
{% endfor %}
{% if page_size != default_page_size %}
<input type="hidden" name="page_size" value="{{ page_size }}">
{% endif %}
......
......@@ -34,6 +34,9 @@
{% macro filter_form() %}
<form id="filter_form" method="GET" action="{{ return_url }}">
{% for arg_name, arg_value in extra_args.items() %}
<input type="hidden" name="{{ arg_name }}" value="{{ arg_value }}">
{% endfor %}
{% if sort_column is not none %}
<input type="hidden" name="sort" value="{{ sort_column }}">
{% endif %}
......@@ -63,6 +66,9 @@
{% for flt_name, flt_value in filter_args.items() %}
<input type="hidden" name="{{ flt_name }}" value="{{ flt_value }}">
{% endfor %}
{% for arg_name, arg_value in extra_args.items() %}
<input type="hidden" name="{{ arg_name }}" value="{{ arg_value }}">
{% endfor %}
{% if page_size != default_page_size %}
<input type="hidden" name="page_size" value="{{ page_size }}">
{% endif %}
......
......@@ -436,6 +436,47 @@ def test_column_searchable_list():
ok_('model2-test' in data)
def test_extra_args_search():
app, db, admin = setup()
Model1, Model2 = create_models(db)
view1 = CustomModelView(Model1, db.session,
column_searchable_list=['test1', ])
admin.add_view(view1)
db.session.add(Model2('model1-test', ))
db.session.commit()
client = app.test_client()
# check that extra args in the url are propagated as hidden fields in the search form
rv = client.get('/admin/model1/?search=model1&foo=bar')
data = rv.data.decode('utf-8')
ok_('<input type="hidden" name="foo" value="bar">' in data)
def test_extra_args_filter():
app, db, admin = setup()
Model1, Model2 = create_models(db)
view2 = CustomModelView(Model2, db.session,
column_filters=['int_field', ])
admin.add_view(view2)
db.session.add(Model2('model2-test', 5000))
db.session.commit()
client = app.test_client()
# check that extra args in the url are propagated as hidden fields in the form
rv = client.get('/admin/model2/?flt1_0=5000&foo=bar')
data = rv.data.decode('utf-8')
ok_('<input type="hidden" name="foo" value="bar">' in data)
def test_complex_searchable_list():
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