Commit 6c53c78b authored by Bryan Hoyt's avatar Bryan Hoyt

Revert "Merge branch 'named-filter-forms'"

This reverts commit 323405e6, reversing
changes made to 41a68dde.
parent 4ec6473a
import re
import warnings
from flask import request, url_for, redirect, flash, abort, json, Response
......@@ -19,25 +18,6 @@ from .helpers import prettify_name, get_mdict_item_or_list
from .ajax import AjaxModelLoader
try:
from collections import OrderedDict
except ImportError:
# Bare-bones OrderedDict implementation for Python2.6 compatibility
class OrderedDict(dict):
def __init__(self, *args, **kwargs):
dict.__init__(self, *args, **kwargs)
self.ordered_keys = []
def __setitem__(self, key, value):
self.ordered_keys.append(key)
dict.__setitem__(self, key, value)
def __iter__(self):
return (k for k in self.ordered_keys)
def iteritems(self):
return ((k, self[k]) for k in self.ordered_keys)
def items(self):
return list(self.iteritems())
class BaseModelView(BaseView, ActionsMixin):
"""
Base model view.
......@@ -272,18 +252,6 @@ class BaseModelView(BaseView, ActionsMixin):
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.
Changing this parameter will break any existing URLs.
Override unique_filter_label() if you want to change the default format
of filter urls. This parameter only controls the default method.
"""
column_display_pk = ObsoleteAttr('column_display_pk',
'list_display_pk',
False)
......@@ -576,19 +544,25 @@ class BaseModelView(BaseView, ActionsMixin):
self.column_descriptions = dict()
if self._filters:
self._flattened_filters_by_group = OrderedDict()
for flt in self._filters:
if flt.name not in self._flattened_filters_by_group:
self._flattened_filters_by_group[flt.name] = []
group = self._flattened_filters_by_group[flt.name]
group.append({'name': flt.name,
'label': self.unique_filter_label(flt),
'operation': flt.operation(),
'options': flt.get_options(self) or None,
'data_type': flt.data_type})
self._filter_groups = []
self._filter_dict = dict()
for i, n in enumerate(self._filters):
if n.name not in self._filter_dict:
group = []
self._filter_dict[n.name] = group
self._filter_groups.append((n.name, group))
else:
group = self._filter_dict[n.name]
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:
self._flattened_filters_by_group = None
self._filter_groups = None
self._filter_types = None
# Form rendering rules
if self.form_create_rules:
......@@ -974,82 +948,45 @@ class BaseModelView(BaseView, ActionsMixin):
def get_empty_list_message(self):
return gettext('There are no items in the table.')
def unique_filter_label(self, flt):
# URL generation helper
def _get_extra_args(self):
"""
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.name + flt.__class__.__name__
Be aware that if you override this method, the default URL format
will no longer work.
"""
if self.named_filter_urls:
return re.sub('\W', '_', u'{name}_{operation}'.format(name=flt.name, operation=flt.operation())).lower()
else:
return str(self._filters.index(flt))
def get_filter_args(self):
Return arguments from query string.
"""
Retrieve and parse filter parameters from the request URL.
Returns a list of 2-tuples in the format [(idx, value), ...],
where idx is the index into the list returned by get_filters().
Override this method to provide your own URL filter format.
"""
if not self._filters:
return None
filter_idx_by_label = dict((self.unique_filter_label(flt), i) for i, flt in enumerate(self._filters))
page = request.args.get('page', 0, type=int)
sort = request.args.get('sort', None, type=int)
sort_desc = request.args.get('desc', None, type=int)
search = request.args.get('search', None)
# Gather filters
if self._filters:
sfilters = []
for n in request.args:
if not n.startswith('flt'):
continue
if '_' not in n:
if n.startswith('flt'):
ofs = n.find('_')
if ofs == -1:
continue
pos, filter_label = n[3:].split('_', 1)
# If pos not specified, just add incrementally to the list.
pos = int(pos) if pos else len(sfilters)
try:
# See if filter is numeric
idx = int(filter_label)
pos = int(n[3:ofs])
idx = int(n[ofs + 1:])
except ValueError:
# If non-numeric, look filter up by name
try:
idx = filter_idx_by_label[filter_label]
except KeyError:
# No matching filter name
continue
if 0 <= idx < len(self._filters):
if idx >= 0 and idx < len(self._filters):
flt = self._filters[idx]
value = request.args[n]
if flt.validate(value):
sfilters.append((pos, (idx, flt.clean(value))))
return [v[1] for v in sorted(sfilters, key=lambda n: n[0])]
filters = [v[1] for v in sorted(sfilters, key=lambda n: n[0])]
else:
filters = None
def _get_listing_args(self):
"""
Return generic list view arguments from query string.
"""
page = request.args.get('page', 0, type=int)
sort = request.args.get('sort', None, type=int)
sort_desc = request.args.get('desc', None, type=int)
search = request.args.get('search', None)
return page, sort, sort_desc, search
return page, sort, sort_desc, search, filters
def _get_url(self, view=None, page=None, sort=None, sort_desc=None,
search=None, filters=None):
......@@ -1079,8 +1016,8 @@ class BaseModelView(BaseView, ActionsMixin):
kwargs = dict(page=page, sort=sort, desc=sort_desc, search=search)
if filters:
for flt in filters:
key = 'flt_%s' % self.unique_filter_label(self._filters[flt[0]])
for i, flt in enumerate(filters):
key = 'flt%d_%d' % (i, flt[0])
kwargs[key] = flt[1]
return url_for(view, **kwargs)
......@@ -1101,11 +1038,11 @@ class BaseModelView(BaseView, ActionsMixin):
"""
return rec_getattr(model, name)
def filters_by_label(self):
def _get_filter_dict(self):
"""
Flattened dict of all filters, indexed by their label.
Return flattened filter dictionary which can be JSON-serialized.
"""
return dict((self.unique_filter_label(flt), flt) for flt in self._filters)
return dict((as_unicode(k), v) for k, v in iteritems(self._filter_dict))
@contextfunction
def get_list_value(self, context, model, name):
......@@ -1167,8 +1104,7 @@ class BaseModelView(BaseView, ActionsMixin):
List view
"""
# Grab parameters from URL
page, sort_idx, sort_desc, search = self._get_listing_args()
filters = self.get_filter_args()
page, sort_idx, sort_desc, search, filters = self._get_extra_args()
# Map column index to column name
sort_column = self._get_column_by_idx(sort_idx)
......@@ -1184,6 +1120,18 @@ class BaseModelView(BaseView, ActionsMixin):
if count % self.page_size != 0:
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
def pager_url(p):
# Do not add page number if it is first page
......@@ -1238,7 +1186,9 @@ class BaseModelView(BaseView, ActionsMixin):
search=search,
# Filters
filters=self._filters,
filter_groups=self._flattened_filters_by_group,
filter_groups=self._filter_groups,
filter_types=self._filter_types,
filter_data=filters_data,
active_filters=filters,
# Actions
......
var AdminFilters = function(element, filters_element, filters_by_group) {
var AdminFilters = function(element, filters_element, operations, options, types) {
var $root = $(element);
var $container = $('.filters', $root);
var lastCount = 0;
......@@ -23,7 +23,7 @@ var AdminFilters = function(element, filters_element, filters_by_group) {
return false;
}
function addFilter(name, subfilters) {
function addFilter(name, op) {
var $el = $('<tr />').appendTo($container);
// Filter list
......@@ -41,8 +41,8 @@ var AdminFilters = function(element, filters_element, filters_by_group) {
var $select = $('<select class="filter-op" />')
.change(changeOperation);
$(subfilters).each(function() {
$select.append($('<option/>').attr('value', this.label).text(this.operation));
$(op).each(function() {
$select.append($('<option/>').attr('value', this[0]).text(this[1]));
});
$el.append(
......@@ -51,14 +51,16 @@ var AdminFilters = function(element, filters_element, filters_by_group) {
$select.select2({width: 'resolve'});
// Input
var optId = op[0][0];
var $field;
var firstFilter = subfilters[0];
if (firstFilter.options) {
if (optId in options) {
$field = $('<select class="filter-val" />')
.attr('name', 'flt' + lastCount + '_' + firstFilter.label);
.attr('name', 'flt' + lastCount + '_' + optId);
$(firstFilter.options).each(function() {
$(options[optId]).each(function() {
$field.append($('<option/>')
.val(this[0]).text(this[1]));
});
......@@ -68,13 +70,13 @@ var AdminFilters = function(element, filters_element, filters_by_group) {
} else
{
$field = $('<input type="text" class="filter-val" />')
.attr('name', 'flt' + lastCount + '_' + firstFilter.label);
.attr('name', 'flt' + lastCount + '_' + optId);
$el.append($('<td/>').append($field));
}
if (firstFilter.data_type) {
$field.attr('data-role', firstFilter.data_type);
faForm.applyStyle($field, firstFilter.data_type);
if (optId in types) {
$field.attr('data-role', types[optId]);
faForm.applyStyle($field, types[optId]);
}
lastCount += 1;
......@@ -82,7 +84,8 @@ var AdminFilters = function(element, filters_element, filters_by_group) {
$('a.filter', filters_element).click(function() {
var name = $(this).text().trim();
addFilter(name, filters_by_group[name]);
addFilter(name, operations[name]);
$('button', $root).show();
......
......@@ -5,7 +5,7 @@
<ul class="dropdown-menu field-filters">
{% for k in filter_groups %}
<li>
<a href="javascript:void(0)" class="filter" onclick="return false;">{{ k }}</a>
<a href="javascript:void(0)" class="filter" onclick="return false;">{{ k[0] }}</a>
</li>
{% endfor %}
</ul>
......@@ -23,29 +23,29 @@
<table class="filters">
{%- for i, flt in enumerate(active_filters) -%}
<tr>
{% set filter = filters[flt[0]] %}
{%- set filter_label = admin_view.unique_filter_label(filter) -%}
{% set filter = admin_view._filters[flt[0]] %}
<td>
<a href="javascript:void(0)" class="btn remove-filter" title="{{ _gettext('Remove Filter') }}">
<span class="close-icon">&times;</span>&nbsp;{{ filter.name }}
<span class="close-icon">&times;</span>&nbsp;{{ filters[flt[0]] }}
</a>
</td>
<td>
<select class="filter-op" data-role="select2">
{% for subfilter in filter_groups[filter.name] %}
<option value="{{ subfilter.label }}"{% if subfilter.label == filter_label %} selected="selected"{% endif %}>{{ subfilter.operation }}</option>
{% for op in admin_view._filter_dict[filter.name] %}
<option value="{{ op[0] }}"{% if flt[0] == op[0] %} selected="selected"{% endif %}>{{ op[1] }}</option>
{% endfor %}
</select>
</td>
<td>
{%- if filter.options -%}
<select name="flt{{ i }}_{{ filter_label }}" class="filter-val" data-role="select2">
{%- for o in filter.options %}
<option value="{{ o[0] }}"{% if flt[1] == o[0] %} selected{% endif %}>{{ o[1] }}</option>
{%- set data = filter_data.get(flt[0]) -%}
{%- if data -%}
<select name="flt{{ i }}_{{ flt[0] }}" class="filter-val" data-role="select2">
{%- for d in data %}
<option value="{{ d[0] }}"{% if flt[1] == d[0] %} selected{% endif %}>{{ d[1] }}</option>
{%- endfor %}
</select>
{%- else -%}
<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>
<input name="flt{{ i }}_{{ flt[0] }}" type="text" value="{{ flt[1] or '' }}" class="filter-val"{% if flt[0] in filter_types %} data-role="{{ filter_types[flt[0]] }}"{% endif %}></input>
{%- endif -%}
</td>
</tr>
......
......@@ -159,10 +159,12 @@
html: true,
placement: 'bottom'
});
{% if filter_groups is not none %}
{% if filter_groups is not none and filter_data is not none %}
var filter = new AdminFilters(
'#filter_form', '.field-filters',
{{ filter_groups|tojson|safe }}
{{ admin_view._get_filter_dict()|tojson|safe }},
{{ filter_data|tojson|safe }},
{{ filter_types|tojson|safe }}
);
{% endif %}
})(jQuery);
......
......@@ -233,52 +233,61 @@ def test_column_filters():
eq_(len(view._filters), 4)
eq_([ (f['label'], f['operation']) for f in view._flattened_filters_by_group['Test1'] ],
[('0', u'equals'),
('1', u'not equal'),
('2', u'contains'),
('3', u'not contains')])
eq_(view._filter_dict, {
u'Test1': [
(0, u'equals'),
(1, u'not equal'),
(2, u'contains'),
(3, u'not contains')
]})
# Test filter that references property
view = CustomModelView(Model2, db.session,
column_filters=['model1'])
eq_([ (f['label'], f['operation']) for f in view._flattened_filters_by_group['Model1 / Test1'] ],
[('0', u'equals'),
('1', u'not equal'),
('2', u'contains'),
('3', u'not contains')])
eq_([ (f['label'], f['operation']) for f in view._flattened_filters_by_group['Model1 / Test2'] ],
[('4', u'equals'),
('5', u'not equal'),
('6', u'contains'),
('7', u'not contains')])
eq_([ (f['label'], f['operation']) for f in view._flattened_filters_by_group['Model1 / Test3'] ],
[('8', u'equals'),
('9', u'not equal'),
('10', u'contains'),
('11', u'not contains')])
eq_([ (f['label'], f['operation']) for f in view._flattened_filters_by_group['Model1 / Test4'] ],
[('12', u'equals'),
('13', u'not equal'),
('14', u'contains'),
('15', u'not contains')])
eq_([ (f['label'], f['operation']) for f in view._flattened_filters_by_group['Model1 / Bool Field'] ],
[('16', u'equals'),
('17', u'not equal')])
eq_([ (f['label'], f['operation']) for f in view._flattened_filters_by_group['Model1 / Enum Field'] ],
[('18', u'equals'),
('19', u'not equal')])
eq_(view._filter_dict, {
u'Model1 / Test1': [
(0, u'equals'),
(1, u'not equal'),
(2, u'contains'),
(3, u'not contains')
],
'Model1 / Test2': [
(4, 'equals'),
(5, 'not equal'),
(6, 'contains'),
(7, 'not contains')
],
u'Model1 / Test3': [
(8, u'equals'),
(9, u'not equal'),
(10, u'contains'),
(11, u'not contains')
],
u'Model1 / Test4': [
(12, u'equals'),
(13, u'not equal'),
(14, u'contains'),
(15, u'not contains')
],
u'Model1 / Bool Field': [
(16, u'equals'),
(17, u'not equal'),
],
u'Model1 / Enum Field': [
(18, u'equals'),
(19, u'not equal'),
]})
# Test filter with a dot
view = CustomModelView(Model2, db.session,
column_filters=['model1.bool_field'])
eq_([ (f['label'], f['operation']) for f in view._flattened_filters_by_group['Model1 / Bool Field'] ],
[('0', u'equals'),
('1', u'not equal')])
eq_(view._filter_dict, {
'Model1 / Bool Field': [
(0, 'equals'),
(1, 'not equal'),
]})
# Fill DB
model1_obj1 = Model1('model1_obj1', bool_field=True)
......@@ -315,11 +324,9 @@ def test_column_filters():
column_filters=['int_field'])
admin.add_view(view)
eq_([ (f['label'], f['operation']) for f in view._flattened_filters_by_group['Int Field'] ],
[('0', u'equals'),
('1', u'not equal'),
('2', u'greater than'),
('3', u'smaller than')])
eq_(view._filter_dict, {'Int Field': [(0, 'equals'), (1, 'not equal'),
(2, 'greater than'), (3, 'smaller than')]})
#Test filters to joined table field
view = CustomModelView(
......
......@@ -302,8 +302,8 @@ def test_column_filters():
eq_(view._filters[0].name, 'col1')
eq_(view._filters[1].name, 'col2')
eq_([ (f['label'], f['operation']) for f in view._flattened_filters_by_group['col1'] ], [('0', 'test')])
eq_([ (f['label'], f['operation']) for f in view._flattened_filters_by_group['col2'] ], [('1', 'test')])
eq_(view._filter_dict, {'col1': [(0, 'test')],
'col2': [(1, 'test')]})
# TODO: Make calls with filters
......
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