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 ...@@ -7,6 +7,7 @@ Next release
* Fix display of inline x-editable boolean fields on list view * Fix display of inline x-editable boolean fields on list view
* Add support for several SQLAlchemy-Utils data types * Add support for several SQLAlchemy-Utils data types
* Support searching on SQLAlchemy hybrid properties * 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 * Add enum34 dependency when running on legacy Python version
* Update Mapbox API v1 URL format * Update Mapbox API v1 URL format
* Update jQuery and moment dependencies in templates * 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 ...@@ -405,6 +405,10 @@ Now, to make your view classes use this template, set the appropriate class prop
edit_template = 'microblog_edit.html' edit_template = 'microblog_edit.html'
# create_template = 'microblog_create.html' # create_template = 'microblog_create.html'
# list_template = 'microblog_list.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 If you want to use your own base template, then pass the name of the template to
the admin constructor during initialization:: the admin constructor during initialization::
......
...@@ -58,13 +58,16 @@ class QueryAjaxModelLoader(AjaxModelLoader): ...@@ -58,13 +58,16 @@ class QueryAjaxModelLoader(AjaxModelLoader):
return getattr(model, self.pk), as_unicode(model) return getattr(model, self.pk), as_unicode(model)
def get_query(self):
return self.session.query(self.model)
def get_one(self, pk): def get_one(self, pk):
# prevent autoflush from occuring during populate_obj # prevent autoflush from occuring during populate_obj
with self.session.no_autoflush: 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): 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) filters = (cast(field, String).ilike(u'%%%s%%' % term) for field in self._cached_fields)
query = query.filter(or_(*filters)) query = query.filter(or_(*filters))
......
...@@ -1701,29 +1701,39 @@ class BaseModelView(BaseView, ActionsMixin): ...@@ -1701,29 +1701,39 @@ class BaseModelView(BaseView, ActionsMixin):
def get_empty_list_message(self): def get_empty_list_message(self):
return gettext('There are no items in the table.') 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 # URL generation helpers
def _get_list_filter_args(self): def _get_list_filter_args(self):
if self._filters: if self._filters:
filters = [] filters = []
for n in request.args: for arg in request.args:
if not n.startswith('flt'): if not arg.startswith('flt'):
continue continue
if '_' not in n: if '_' not in arg:
continue continue
pos, key = n[3:].split('_', 1) pos, key = arg[3:].split('_', 1)
if key in self._filter_args: if key in self._filter_args:
idx, flt = self._filter_args[key] idx, flt = self._filter_args[key]
value = request.args[n] value = request.args[arg]
if flt.validate(value): 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: else:
flash(gettext('Invalid Filter Value: %(value)s', value=value), 'error') flash(self.get_invalid_value_msg(value, flt), 'error')
# Sort filters # Sort filters
return [v[1] for v in sorted(filters, key=lambda n: n[0])] return [v[1] for v in sorted(filters, key=lambda n: n[0])]
...@@ -2054,6 +2064,9 @@ class BaseModelView(BaseView, ActionsMixin): ...@@ -2054,6 +2064,9 @@ class BaseModelView(BaseView, ActionsMixin):
get_pk_value=self.get_pk_value, get_pk_value=self.get_pk_value,
get_value=self.get_list_value, get_value=self.get_list_value,
return_url=self._get_list_url(view_args), return_url=self._get_list_url(view_args),
# Extras
extra_args=view_args.extra_args,
) )
@expose('/new/', methods=('GET', 'POST')) @expose('/new/', methods=('GET', 'POST'))
......
...@@ -34,6 +34,9 @@ ...@@ -34,6 +34,9 @@
{% macro filter_form() %} {% macro filter_form() %}
<form id="filter_form" method="GET" action="{{ return_url }}"> <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 %} {% if sort_column is not none %}
<input type="hidden" name="sort" value="{{ sort_column }}"> <input type="hidden" name="sort" value="{{ sort_column }}">
{% endif %} {% endif %}
...@@ -63,6 +66,9 @@ ...@@ -63,6 +66,9 @@
{% for flt_name, flt_value in filter_args.items() %} {% for flt_name, flt_value in filter_args.items() %}
<input type="hidden" name="{{ flt_name }}" value="{{ flt_value }}"> <input type="hidden" name="{{ flt_name }}" value="{{ flt_value }}">
{% endfor %} {% 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 %} {% if page_size != default_page_size %}
<input type="hidden" name="page_size" value="{{ page_size }}"> <input type="hidden" name="page_size" value="{{ page_size }}">
{% endif %} {% endif %}
......
...@@ -34,6 +34,9 @@ ...@@ -34,6 +34,9 @@
{% macro filter_form() %} {% macro filter_form() %}
<form id="filter_form" method="GET" action="{{ return_url }}"> <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 %} {% if sort_column is not none %}
<input type="hidden" name="sort" value="{{ sort_column }}"> <input type="hidden" name="sort" value="{{ sort_column }}">
{% endif %} {% endif %}
...@@ -63,6 +66,9 @@ ...@@ -63,6 +66,9 @@
{% for flt_name, flt_value in filter_args.items() %} {% for flt_name, flt_value in filter_args.items() %}
<input type="hidden" name="{{ flt_name }}" value="{{ flt_value }}"> <input type="hidden" name="{{ flt_name }}" value="{{ flt_value }}">
{% endfor %} {% 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 %} {% if page_size != default_page_size %}
<input type="hidden" name="page_size" value="{{ page_size }}"> <input type="hidden" name="page_size" value="{{ page_size }}">
{% endif %} {% endif %}
......
...@@ -436,6 +436,47 @@ def test_column_searchable_list(): ...@@ -436,6 +436,47 @@ def test_column_searchable_list():
ok_('model2-test' in data) 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(): def test_complex_searchable_list():
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