Commit cd3a3117 authored by Bryan Hoyt's avatar Bryan Hoyt

Better factoring of named URL filters, to allow user-customisable filter names

parent 8a9f3c6b
...@@ -31,4 +31,4 @@ ...@@ -31,4 +31,4 @@
{% endblock %} {% endblock %}
{% block model_menu_bar %} {% block model_menu_bar %}
{% endblock %} {% endblock %}
\ No newline at end of file
...@@ -251,6 +251,15 @@ class BaseModelView(BaseView, ActionsMixin): ...@@ -251,6 +251,15 @@ class BaseModelView(BaseView, ActionsMixin):
class MyModelView(BaseModelView): class MyModelView(BaseModelView):
column_filters = ('user', 'email') column_filters = ('user', 'email')
""" """
named_filter_urls = False
"""
Set to True to use human-readable names for filters in URL parameters.
False by default so as to be robust across translations.
If you override unique_filter_label(), this has no effect.
"""
column_display_pk = ObsoleteAttr('column_display_pk', column_display_pk = ObsoleteAttr('column_display_pk',
'list_display_pk', 'list_display_pk',
...@@ -544,25 +553,19 @@ class BaseModelView(BaseView, ActionsMixin): ...@@ -544,25 +553,19 @@ class BaseModelView(BaseView, ActionsMixin):
self.column_descriptions = dict() self.column_descriptions = dict()
if self._filters: if self._filters:
self._filter_groups = [] self._flattened_filters_by_group = {}
self._filter_dict = dict()
for flt in self._filters:
for i, n in enumerate(self._filters): if flt.name not in self._flattened_filters_by_group:
if n.name not in self._filter_dict: self._flattened_filters_by_group[flt.name] = []
group = [] group = self._flattened_filters_by_group[flt.name]
self._filter_dict[n.name] = group group.append({'name': flt.name,
self._filter_groups.append((n.name, group)) 'label': self.unique_filter_label(flt),
else: 'operation': flt.operation(),
group = self._filter_dict[n.name] 'options': flt.get_options(self) or None,
'data_type': flt.data_type})
group.append((i, n.operation()))
self._filter_types = dict((i, f.data_type)
for i, f in enumerate(self._filters)
if f.data_type)
else: else:
self._filter_groups = None self._flattened_filters_by_group = None
self._filter_types = None
# Form rendering rules # Form rendering rules
if self.form_create_rules: if self.form_create_rules:
...@@ -948,6 +951,24 @@ class BaseModelView(BaseView, ActionsMixin): ...@@ -948,6 +951,24 @@ 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 unique_filter_label(self, flt):
"""
Given a filter `flt`, return a unique name for that filter in
this view.
By default, returns a numeric index or a human-readable filter name
Does not include the `flt[n]_` portion of the filter name.
To use custom names, override this function, eg
`def unique_filter_label(self, flt): return flt.__class__.__name__`
"""
if self.named_filter_urls:
return u'{name}_{operation}'.format(name=flt.name, operation=flt.operation()).lower().replace(' ', '_')
else:
return unicode(self._filters.index(flt))
def get_filter_args(self): def get_filter_args(self):
""" """
Retrieve and parse filter parameters from the request URL. Retrieve and parse filter parameters from the request URL.
...@@ -960,7 +981,7 @@ class BaseModelView(BaseView, ActionsMixin): ...@@ -960,7 +981,7 @@ class BaseModelView(BaseView, ActionsMixin):
if not self._filters: if not self._filters:
return None return None
filter_idx_by_label = dict((flt.query_label(), i) for i, flt in enumerate(self._filters)) filter_idx_by_label = dict((self.unique_filter_label(flt), i) for i, flt in enumerate(self._filters))
sfilters = [] sfilters = []
...@@ -1033,7 +1054,7 @@ class BaseModelView(BaseView, ActionsMixin): ...@@ -1033,7 +1054,7 @@ class BaseModelView(BaseView, ActionsMixin):
if filters: if filters:
for flt in filters: for flt in filters:
key = 'flt_%s' % self._filters[flt[0]].query_label() key = 'flt_%s' % self.unique_filter_label(self._filters[flt[0]])
kwargs[key] = flt[1] kwargs[key] = flt[1]
return url_for(view, **kwargs) return url_for(view, **kwargs)
...@@ -1054,11 +1075,11 @@ class BaseModelView(BaseView, ActionsMixin): ...@@ -1054,11 +1075,11 @@ class BaseModelView(BaseView, ActionsMixin):
""" """
return rec_getattr(model, name) return rec_getattr(model, name)
def _get_filter_dict(self): def filters_by_label(self):
""" """
Return flattened filter dictionary which can be JSON-serialized. Flattened dict of all filters, indexed by their label.
""" """
return dict((as_unicode(k), v) for k, v in iteritems(self._filter_dict)) return dict((self.unique_filter_label(flt), flt) for flt in self._filters)
@contextfunction @contextfunction
def get_list_value(self, context, model, name): def get_list_value(self, context, model, name):
...@@ -1137,18 +1158,6 @@ class BaseModelView(BaseView, ActionsMixin): ...@@ -1137,18 +1158,6 @@ class BaseModelView(BaseView, ActionsMixin):
if count % self.page_size != 0: if count % self.page_size != 0:
num_pages += 1 num_pages += 1
# Pregenerate filters
if self._filters:
filters_data = dict()
for idx, f in enumerate(self._filters):
flt_data = f.get_options(self)
if flt_data:
filters_data[idx] = flt_data
else:
filters_data = None
# Various URL generation helpers # Various URL generation helpers
def pager_url(p): def pager_url(p):
# Do not add page number if it is first page # Do not add page number if it is first page
...@@ -1203,9 +1212,7 @@ class BaseModelView(BaseView, ActionsMixin): ...@@ -1203,9 +1212,7 @@ class BaseModelView(BaseView, ActionsMixin):
search=search, search=search,
# Filters # Filters
filters=self._filters, filters=self._filters,
filter_groups=self._filter_groups, filter_groups=self._flattened_filters_by_group,
filter_types=self._filter_types,
filter_data=filters_data,
active_filters=filters, active_filters=filters,
# Actions # Actions
......
...@@ -72,14 +72,6 @@ class BaseFilter(object): ...@@ -72,14 +72,6 @@ class BaseFilter(object):
""" """
raise NotImplemented() raise NotImplemented()
def query_label(self):
"""
Return a string that can be used in a url to identify this filter.
For example u'username_equals'
"""
return '{name}_{operation}'.format(name=self.name, operation=self.operation()).lower().replace(' ', '_')
def __unicode__(self): def __unicode__(self):
return self.name return self.name
......
var AdminFilters = function(element, filters_element, operations, options, types) { var AdminFilters = function(element, filters_element, filters_by_group) {
var $root = $(element); var $root = $(element);
var $container = $('.filters', $root); var $container = $('.filters', $root);
var lastCount = 0; var lastCount = 0;
...@@ -23,7 +23,7 @@ var AdminFilters = function(element, filters_element, operations, options, types ...@@ -23,7 +23,7 @@ var AdminFilters = function(element, filters_element, operations, options, types
return false; return false;
} }
function addFilter(name, op) { function addFilter(name, subfilters) {
var $el = $('<tr />').appendTo($container); var $el = $('<tr />').appendTo($container);
// Filter list // Filter list
...@@ -41,9 +41,9 @@ var AdminFilters = function(element, filters_element, operations, options, types ...@@ -41,9 +41,9 @@ var AdminFilters = function(element, filters_element, operations, options, types
var $select = $('<select class="filter-op" />') var $select = $('<select class="filter-op" />')
.change(changeOperation); .change(changeOperation);
$(op).each(function() { $(subfilters).each(function() {
var filter_label = (name + '_' + this[1]).toLowerCase().replace(/ /g, '_'); console.log(this);
$select.append($('<option/>').attr('value', filter_label).text(this[1])); $select.append($('<option/>').attr('value', this.label).text(this.operation));
}); });
$el.append( $el.append(
...@@ -52,17 +52,14 @@ var AdminFilters = function(element, filters_element, operations, options, types ...@@ -52,17 +52,14 @@ var AdminFilters = function(element, filters_element, operations, options, types
$select.select2({width: 'resolve'}); $select.select2({width: 'resolve'});
// Input
var optId = op[0][0];
var filter_label = (name + '_' + op[0][1]).toLowerCase().replace(/ /g, '_');
var $field; var $field;
if (optId in options) { var firstFilter = subfilters[0];
if (firstFilter.options) {
$field = $('<select class="filter-val" />') $field = $('<select class="filter-val" />')
.attr('name', 'flt' + lastCount + '_' + filter_label); .attr('name', 'flt' + lastCount + '_' + firstFilter.label);
$(options[optId]).each(function() { $(firstFilter.options).each(function() {
$field.append($('<option/>') $field.append($('<option/>')
.val(this[0]).text(this[1])); .val(this[0]).text(this[1]));
}); });
...@@ -72,13 +69,13 @@ var AdminFilters = function(element, filters_element, operations, options, types ...@@ -72,13 +69,13 @@ var AdminFilters = function(element, filters_element, operations, options, types
} else } else
{ {
$field = $('<input type="text" class="filter-val" />') $field = $('<input type="text" class="filter-val" />')
.attr('name', 'flt' + lastCount + '_' + filter_label); .attr('name', 'flt' + lastCount + '_' + firstFilter.label);
$el.append($('<td/>').append($field)); $el.append($('<td/>').append($field));
} }
if (optId in types) { if (firstFilter.data_type) {
$field.attr('data-role', types[optId]); $field.attr('data-role', firstFilter.data_type);
faForm.applyStyle($field, types[optId]); faForm.applyStyle($field, firstFilter.data_type);
} }
lastCount += 1; lastCount += 1;
...@@ -86,7 +83,7 @@ var AdminFilters = function(element, filters_element, operations, options, types ...@@ -86,7 +83,7 @@ var AdminFilters = function(element, filters_element, operations, options, types
$('a.filter', filters_element).click(function() { $('a.filter', filters_element).click(function() {
var name = $(this).text().trim(); var name = $(this).text().trim();
addFilter(name, operations[name]); addFilter(name, filters_by_group[name]);
$('button', $root).show(); $('button', $root).show();
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
<ul class="dropdown-menu field-filters"> <ul class="dropdown-menu field-filters">
{% for k in filter_groups %} {% for k in filter_groups %}
<li> <li>
<a href="javascript:void(0)" class="filter" onclick="return false;">{{ k[0] }}</a> <a href="javascript:void(0)" class="filter" onclick="return false;">{{ k }}</a>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
...@@ -23,31 +23,29 @@ ...@@ -23,31 +23,29 @@
<table class="filters"> <table class="filters">
{%- for i, flt in enumerate(active_filters) -%} {%- for i, flt in enumerate(active_filters) -%}
<tr> <tr>
{% set filter = admin_view._filters[flt[0]] %} {% set filter = filters[flt[0]] %}
{%- set filter_label = admin_view.unique_filter_label(filter) -%}
<td> <td>
<a href="javascript:void(0)" class="btn remove-filter" title="{{ _gettext('Remove Filter') }}"> <a href="javascript:void(0)" class="btn remove-filter" title="{{ _gettext('Remove Filter') }}">
<span class="close-icon">&times;</span>&nbsp;{{ filters[flt[0]] }} <span class="close-icon">&times;</span>&nbsp;{{ filter.name }}
</a> </a>
</td> </td>
<td> <td>
<select class="filter-op" data-role="select2"> <select class="filter-op" data-role="select2">
{% for op in admin_view._filter_dict[filter.name] %} {% for subfilter in filter_groups[filter.name] %}
{{ op }} <option value="{{ subfilter.label }}"{% if subfilter.label == filter_label %} selected="selected"{% endif %}>{{ subfilter.operation }}</option>
{%- set filter_label = filter.name.lower() + '_' + op[1].replace(' ', '_') -%}
<option value="{{ filter_label }}"{% if flt[0] == op[0] %} selected="selected"{% endif %}>{{ op[1] }}</option>
{% endfor %} {% endfor %}
</select> </select>
</td> </td>
<td> <td>
{%- set data = filter_data.get(flt[0]) -%} {%- if filter.options -%}
{%- if data -%} <select name="flt{{ i }}_{{ filter_label }}" class="filter-val" data-role="select2">
<select name="flt{{ i }}_{{ filter.query_label() }}" class="filter-val" data-role="select2"> {%- for o in filter.options %}
{%- for d in data %} <option value="{{ o[0] }}"{% if flt[1] == o[0] %} selected{% endif %}>{{ o[1] }}</option>
<option value="{{ d[0] }}"{% if flt[1] == d[0] %} selected{% endif %}>{{ d[1] }}</option>
{%- endfor %} {%- endfor %}
</select> </select>
{%- else -%} {%- else -%}
<input name="flt{{ i }}_{{ filter.query_label() }}" type="text" value="{{ flt[1] or '' }}" class="filter-val"{% if flt[0] in filter_types %} data-role="{{ filter_types[flt[0]] }}"{% endif %}></input> <input name="flt{{ i }}_{{ filter_label }}" type="text" value="{{ flt[1] or '' }}" class="filter-val"{% if filter.data_type %} data-role="{{ filter.data_type }}"{% endif %}></input>
{%- endif -%} {%- endif -%}
</td> </td>
</tr> </tr>
......
...@@ -157,12 +157,10 @@ ...@@ -157,12 +157,10 @@
html: true, html: true,
placement: 'bottom' placement: 'bottom'
}); });
{% if filter_groups is not none and filter_data is not none %} {% if filter_groups is not none %}
var filter = new AdminFilters( var filter = new AdminFilters(
'#filter_form', '.field-filters', '#filter_form', '.field-filters',
{{ admin_view._get_filter_dict()|tojson|safe }}, {{ filter_groups|tojson|safe }}
{{ filter_data|tojson|safe }},
{{ filter_types|tojson|safe }}
); );
{% endif %} {% endif %}
})(jQuery); })(jQuery);
......
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