Commit 1db59b00 authored by Serge S. Koval's avatar Serge S. Koval

Reworked filter UI (got idea from flask-peewee)

parent eb50adc8
- Core - Core
- Pregenerate URLs for menu - Pregenerate URLs for menu
- Calendar - add validation for time without seconds (automatically add seconds) - Calendar - add validation for time without seconds (automatically add seconds)
- View Site button?
- Model Admin - Model Admin
- Rework filter UI - Rework model UI
- Number of records
- Tabs
- Filters in drop down instead of add button
- Ability to sort by fields that are not visible? - Ability to sort by fields that are not visible?
- List display callables - List display callables
- Search - Search
- Rename init_search - Rename init_search
- Filters
- FK filters support
- Paginator class - Paginator class
- Custom CSS/JS in admin interface - Custom CSS/JS in admin interface
- Checkboxes and mass operations - Checkboxes and mass operations
......
...@@ -29,16 +29,16 @@ class FilterEqual(BaseSQLAFilter): ...@@ -29,16 +29,16 @@ class FilterEqual(BaseSQLAFilter):
def apply(self, query, value): def apply(self, query, value):
return query.filter(self.column == value) return query.filter(self.column == value)
def __unicode__(self): def operation(self):
return '%s equals' % self.name return 'equals'
class FilterNotEqual(BaseSQLAFilter): class FilterNotEqual(BaseSQLAFilter):
def apply(self, query, value): def apply(self, query, value):
return query.filter(self.column != value) return query.filter(self.column != value)
def __unicode__(self): def operation(self):
return '%s not equal' % self.name return 'not equal'
class FilterLike(BaseSQLAFilter): class FilterLike(BaseSQLAFilter):
...@@ -46,8 +46,8 @@ class FilterLike(BaseSQLAFilter): ...@@ -46,8 +46,8 @@ class FilterLike(BaseSQLAFilter):
stmt = tools.parse_like_term(value) stmt = tools.parse_like_term(value)
return query.filter(self.column.ilike(stmt)) return query.filter(self.column.ilike(stmt))
def __unicode__(self): def operation(self):
return '%s like' % self.name return 'like'
class FilterNotLike(BaseSQLAFilter): class FilterNotLike(BaseSQLAFilter):
...@@ -55,24 +55,24 @@ class FilterNotLike(BaseSQLAFilter): ...@@ -55,24 +55,24 @@ class FilterNotLike(BaseSQLAFilter):
stmt = tools.parse_like_term(value) stmt = tools.parse_like_term(value)
return query.filter(~self.column.ilike(stmt)) return query.filter(~self.column.ilike(stmt))
def __unicode__(self): def operation(self):
return '%s not like' % self.name return 'not like'
class FilterGreater(BaseSQLAFilter): class FilterGreater(BaseSQLAFilter):
def apply(self, query, value): def apply(self, query, value):
return query.filter(self.column > value) return query.filter(self.column > value)
def __unicode__(self): def operation(self):
return '%s greater than' % self.name return 'greater than'
class FilterSmaller(BaseSQLAFilter): class FilterSmaller(BaseSQLAFilter):
def apply(self, query, value): def apply(self, query, value):
return query.filter(self.column < value) return query.filter(self.column < value)
def __unicode__(self): def operation(self):
return '%s smaller than' % self.name return 'smaller than'
# Customized type filters # Customized type filters
......
from itertools import count
from flask import request, url_for, redirect, flash from flask import request, url_for, redirect, flash
from flask.ext.adminex.base import BaseView, expose from flask.ext.adminex.base import BaseView, expose
...@@ -222,12 +220,24 @@ class BaseModelView(BaseView): ...@@ -222,12 +220,24 @@ class BaseModelView(BaseView):
self._filters = self.get_filters() self._filters = self.get_filters()
if self._filters: if self._filters:
self._filter_names = [unicode(n) for n in self._filters] 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) self._filter_types = dict((i, f.data_type)
for i, f in enumerate(self._filters) for i, f in enumerate(self._filters)
if f.data_type) if f.data_type)
else: else:
self._filter_names = None self._filter_groups = None
self._filter_types = None self._filter_types = None
# Primary key # Primary key
...@@ -518,21 +528,29 @@ class BaseModelView(BaseView): ...@@ -518,21 +528,29 @@ class BaseModelView(BaseView):
# Gather filters # Gather filters
if self._filters: if self._filters:
filters = [] sfilters = []
for n in count(): for n in request.args:
param = 'flt%d' % n if n.startswith('flt'):
if param not in request.args: ofs = n.find('_')
break if ofs == -1:
continue
idx = request.args.get(param, None, type=int) try:
value = request.args.get(param + 'v', None) pos = int(n[3:ofs])
idx = int(n[ofs + 1:])
except ValueError:
continue
if idx >= 0 and idx < len(self._filters): if idx >= 0 and idx < len(self._filters):
flt = self._filters[idx] flt = self._filters[idx]
value = request.args[n]
if flt.validate(value): if flt.validate(value):
filters.append((idx, flt.clean(value))) sfilters.append((pos, (idx, flt.clean(value))))
filters = [v[1] for v in sorted(sfilters, key=lambda n: n[0])]
else: else:
filters = None filters = None
...@@ -567,10 +585,8 @@ class BaseModelView(BaseView): ...@@ -567,10 +585,8 @@ class BaseModelView(BaseView):
if filters: if filters:
for i, flt in enumerate(filters): for i, flt in enumerate(filters):
base = 'flt%d' % i key = 'flt%d_%d' % (i, flt[0])
kwargs[key] = flt[1]
kwargs[base] = flt[0]
kwargs[base + 'v'] = flt[1]
return url_for(view, **kwargs) return url_for(view, **kwargs)
...@@ -646,6 +662,7 @@ class BaseModelView(BaseView): ...@@ -646,6 +662,7 @@ class BaseModelView(BaseView):
search, search,
filters), filters),
# Pagination # Pagination
count=count,
pager_url=pager_url, pager_url=pager_url,
num_pages=num_pages, num_pages=num_pages,
page=page, page=page,
...@@ -661,7 +678,8 @@ class BaseModelView(BaseView): ...@@ -661,7 +678,8 @@ class BaseModelView(BaseView):
sort_desc), sort_desc),
search=search, search=search,
# Filters # Filters
filter_names=self._filter_names, filters=self._filters,
filter_groups=self._filter_groups,
filter_types=self._filter_types, filter_types=self._filter_types,
filter_data=filters_data, filter_data=filters_data,
active_filters=filters active_filters=filters
...@@ -672,10 +690,10 @@ class BaseModelView(BaseView): ...@@ -672,10 +690,10 @@ class BaseModelView(BaseView):
""" """
Create model view Create model view
""" """
return_url = request.args.get('url') return_url = request.args.get('url') or url_for('.index_view')
if not self.can_create: if not self.can_create:
return redirect(return_url or url_for('.index_view')) return redirect(return_url)
form = self.create_form(request.form) form = self.create_form(request.form)
...@@ -685,7 +703,7 @@ class BaseModelView(BaseView): ...@@ -685,7 +703,7 @@ class BaseModelView(BaseView):
flash('Model was successfully created.') flash('Model was successfully created.')
return redirect(url_for('.create_view', url=return_url)) return redirect(url_for('.create_view', url=return_url))
else: else:
return redirect(return_url or url_for('.index_view')) return redirect(return_url)
return self.render(self.create_template, return self.render(self.create_template,
form=form, form=form,
...@@ -696,40 +714,40 @@ class BaseModelView(BaseView): ...@@ -696,40 +714,40 @@ class BaseModelView(BaseView):
""" """
Edit model view Edit model view
""" """
return_url = request.args.get('url') return_url = request.args.get('url') or url_for('.index_view')
if not self.can_edit: if not self.can_edit:
return redirect(return_url or url_for('.index_view')) return redirect(return_url)
model = self.get_one(id) model = self.get_one(id)
if model is None: if model is None:
return redirect(return_url or url_for('.index_view')) return redirect(return_url)
form = self.edit_form(request.form, model) form = self.edit_form(request.form, model)
if form.validate_on_submit(): if form.validate_on_submit():
if self.update_model(form, model): if self.update_model(form, model):
return redirect(return_url or url_for('.index_view')) return redirect(return_url)
return self.render(self.edit_template, return self.render(self.edit_template,
form=form, form=form,
return_url=return_url or url_for('.index_view')) return_url=return_url)
@expose('/delete/<int:id>/', methods=('POST',)) @expose('/delete/<int:id>/', methods=('POST',))
def delete_view(self, id): def delete_view(self, id):
""" """
Delete model view. Only POST method is allowed. Delete model view. Only POST method is allowed.
""" """
return_url = request.args.get('url') return_url = request.args.get('url') or url_for('.index_view')
# TODO: Use post # TODO: Use post
if not self.can_delete: if not self.can_delete:
return redirect(return_url or url_for('.index_view')) return redirect(return_url)
model = self.get_one(id) model = self.get_one(id)
if model: if model:
self.delete_model(model) self.delete_model(model)
return redirect(return_url or url_for('.index_view')) return redirect(return_url)
...@@ -57,6 +57,14 @@ class BaseFilter(object): ...@@ -57,6 +57,14 @@ class BaseFilter(object):
""" """
raise NotImplemented() raise NotImplemented()
def operation(self):
"""
Return readable operation name.
For example: u'equals'
"""
raise NotImplemented()
def __unicode__(self): def __unicode__(self):
return self.name return self.name
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
display: inline-block; display: inline-block;
zoom: 1; zoom: 1;
*display: inline; *display: inline;
vertical-align: middle;
} }
.chzn-container .chzn-drop { .chzn-container .chzn-drop {
background: #fff; background: #fff;
......
...@@ -21,6 +21,15 @@ a.icon { ...@@ -21,6 +21,15 @@ a.icon {
text-decoration: none; text-decoration: none;
} }
/* Model search form */
form.search-form {
margin: 4px 0 0 0;
}
form.search-form a.clear i {
margin: 2px 0 0 0;
}
/* Filters */ /* Filters */
.filter-row { .filter-row {
margin: 4px; margin: 4px;
......
var Filters = function(element, operations, options, types) { var AdminFilters = function(element, filters_element, adminForm, operations, options, types) {
var $root = $(element) var $root = $(element)
var $container = $('#filters'); var $container = $('.filters', $root);
var count = $('#filters>div', $root).length; var lastCount = 0;
function appendValueControl(element, id, optionId) { function getCount(name) {
var field; var idx = name.indexOf('_');
return parseInt(name.substr(3, idx - 3));
// Conditionally generate select or textbox
if (optionId in options) {
field = $('<select class="filter-val" />').attr('name', 'flt' + id + 'v');
$(options[optionId]).each(function() {
field.append($('<option/>').val(this[0]).text(this[1]));
});
} else
{
field = $('<input type="text" class="filter-val" />').attr('name', 'flt' + id + 'v');
} }
$(element).append(field); function changeOperation() {
var $parent = $(this).parent();
if (optionId in options) var $el = $('.filter-val', $parent);
field.chosen(); var count = getCount($el.attr('name'));
$el.attr('name', 'flt' + count + '_' + $(this).val());
if (optionId in types) { $('button', $root).show();
field.attr('data-role', types[optionId]);
adminForm.applyStyle(field, types[optionId]);
} }
function removeFilter() {
$(this).parent().remove();
$('button', $root).show();
} }
function addFilter() { function addFilter(name, op) {
var node = $('<div class="filter-row" />').attr('id', 'fltdiv' + count).appendTo($container); var $el = $('<div class="filter-row" />').appendTo($container);
$('<a href="#" class="remove-filter" />') $('<a href="#" class="btn remove-filter" title="Remove Filter" />')
.append('<i class="icon-remove"/>') .text(name)
.click(removeFilter) .appendTo($el)
.appendTo(node); .click(removeFilter);
var operation = $('<select class="filter-op" />') var $select = $('<select class="filter-op" />')
.attr('name', 'flt' + count) .appendTo($el)
.change(changeOperation) .change(changeOperation);
.appendTo(node);
var index = 0; $(op).each(function() {
$(operations).each(function() { $select.append($('<option/>').attr('value', this[0]).text(this[1]));
operation.append($('<option/>').val(index).text(this.toString()));
index++;
}); });
operation.chosen();
appendValueControl(node, count, 0);
count += 1; $select.chosen();
$('button', $root).show(); var optId = op[0][0];
return false;
}
function removeFilter() { var $field;
var row = $(this).parent();
var idx = parseInt(row.attr('id').substr(6));
// Remove row if (optId in options) {
row.remove(); $field = $('<select class="filter-val" />')
.attr('name', 'flt' + lastCount + '_' + optId)
.appendTo($el);
// Renumber any rows that are after $(options[optId]).each(function() {
for (var i = idx + 1; i < count; ++i) { $field.append($('<option/>')
row = $('#fltdiv' + i); .val(this[0]).text(this[1]))
row.attr('id', 'fltdiv' + (i - 1)); .appendTo($el);
});
$('.filter-op', row).attr('name', 'flt' + (i - 1)); $field.chosen();
$('.filter-val', row).attr('name', 'flt' + (i - 1) + 'v'); } else
{
$field = $('<input type="text" class="filter-val" />')
.attr('name', 'flt' + lastCount + '_' + optId)
.appendTo($el);
} }
count -= 1; if (optId in types) {
$field.attr('data-role', types[optId]);
$('button', $root).show(); adminForm.applyStyle($field, types[optId]);
return false;
} }
function changeOperation() { lastCount += 1;
var row = $(this).parent(); }
var rowIdx = parseInt(row.attr('id').substr(6));
// Get old value field
var oldValue = $('.filter-val', row);
var oldValueId = oldValue.attr('id');
// Delete old value $('a.filter', filters_element).click(function() {
oldValue.remove(); var name = $(this).text().trim();
if (oldValueId != null)
$('div#' + oldValueId + '_chzn', row).remove();
var optId = $(this).val(); addFilter(name, operations[name]);
appendValueControl(row, rowIdx, optId);
$('button', $root).show(); $('button', $root).show();
}; });
$('#add_filter', $root).click(addFilter); $('.filter-op', $root).change(changeOperation);
$('.remove-filter', $root).click(removeFilter); $('.filter-val', $root).change(function() {
$('.filter-op').change(changeOperation);
$('.filter-val').change(function() {
$('button', $root).show(); $('button', $root).show();
}); });
$('.remove-filter', $root).click(removeFilter);
$('.filter-val', $root).each(function() {
var count = getCount($(this).attr('name'));
if (count > lastCount)
lastCount = count;
});
lastCount += 1;
}; };
var adminForm = new function() { var AdminForm = function() {
this.applyStyle = function(el, name) { this.applyStyle = function(el, name) {
switch (name) { switch (name) {
case 'chosen': case 'chosen':
...@@ -21,4 +21,4 @@ var adminForm = new function() { ...@@ -21,4 +21,4 @@ var adminForm = new function() {
$('[data-role=chosenblank]').chosen({allow_single_deselect: true}); $('[data-role=chosenblank]').chosen({allow_single_deselect: true});
$('[data-role=datepicker]').datepicker(); $('[data-role=datepicker]').datepicker();
$('[data-role=datetimepicker]').datepicker({displayTime: true}); $('[data-role=datetimepicker]').datepicker({displayTime: true});
} };
...@@ -11,6 +11,14 @@ ...@@ -11,6 +11,14 @@
<input name="_add_another" type="submit" class="btn btn-primary btn-large" value="Save and Add" /> <input name="_add_another" type="submit" class="btn btn-primary btn-large" value="Save and Add" />
{% endmacro %} {% endmacro %}
<ul class="nav nav-tabs">
<li>
<a href="{{ return_url }}">List</a>
</li>
<li class="active">
<a href="#">Create</a>
</li>
</ul>
{{ lib.render_form(form, return_url, extra()) }} {{ lib.render_form(form, return_url, extra()) }}
{% endblock %} {% endblock %}
......
...@@ -7,53 +7,83 @@ ...@@ -7,53 +7,83 @@
{% endblock %} {% endblock %}
{% block body %} {% block body %}
{% if search_supported %} <ul class="nav nav-tabs">
<form method="GET" action="{{ return_url }}" class="well form-search"> <li class="active">
{% if search %} <a href="#">List ({{ count }})</a>
<a href="{{ clear_search_url }}"> </li>
<i class="icon-remove"></i> {% if admin_view.can_create %}
<li>
<a href="{{ url_for('.create_view', url=return_url) }}">Create</a>
</li>
{% endif %}
{% if filter_groups %}
<li class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
Add Filter<b class="caret"></b>
</a> </a>
<ul class="dropdown-menu field-filters">
{% for k in filter_groups %}
<li>
<a href="#" class="filter">{{ k[0] }}</a>
</li>
{% endfor %}
</ul>
</li>
{% endif %} {% endif %}
{% if search_supported %}
<li>
<form method="GET" action="{{ return_url }}" class="search-form">
{% if sort_column is not none %} {% if sort_column is not none %}
<input type="hidden" name="sort" value="{{ sort_column }}"></input> <input type="hidden" name="sort" value="{{ sort_column }}"></input>
{% endif %} {% endif %}
{% if sort_desc %} {% if sort_desc %}
<input type="hidden" name="desc" value="{{ sort_desc }}"></input> <input type="hidden" name="desc" value="{{ sort_desc }}"></input>
{% endif %} {% endif %}
<input type="text" name="search" value="{{ search or '' }}" class="span10 search-query"></input> <input type="text" name="search" value="{{ search or '' }}" class="search-query span2" placeholder="Search"></input>
<button type="submit" class="btn">Search</button> {% if search %}
<a href="{{ clear_search_url }}" class="clear">
<i class="icon-remove"></i>
</a>
{% endif %}
</form> </form>
</li>
{% endif %} {% endif %}
</ul>
{% if filter_groups %}
<form id="filter_form" method="GET" action="{{ return_url }}">
<div class="pull-right">
<button type="submit" class="btn btn-primary" style="display: none">Apply</button>
{% if active_filters %}
<a href="{{ clear_search_url }}" class="btn">Reset Filters</a>
{% endif %}
</div>
{% if filter_names %} <div class="filters">
<form id="filter_form" method="GET" action="{{ return_url }}" class="well"> {%- for i, flt in enumerate(active_filters) -%}
<div id="filters"> <div class="filter-row">
{%- for idx, flt in enumerate(active_filters) -%} {% set filter = admin_view._filters[flt[0]] %}
<div id="fltdiv{{ idx }}" class="filter-row"> <a href="#" class="btn remove-filter" title="Remove Filter">
<a href="#" class="remove-filter"><i class="icon-remove"></i></a><select name="flt{{ idx }}" class="filter-op" data-role="chosen"> {{ filters[flt[0]] }}
{% for optidx, opt in enumerate(filter_names) -%} </a><select class="filter-op" data-role="chosen">
<option value="{{ optidx }}"{% if flt[0] == optidx %} selected="selected"{% endif %}>{{ opt }}</option> {% for op in admin_view._filter_dict[filter.name] %}
{%- endfor %} <option value="{{ op[0] }}"{% if flt[0] == op[0] %} selected="selected"{% endif %}>{{ op[1] }}</option>
{% endfor %}
</select> </select>
{%- set data = filter_data.get(flt[0]) -%} {%- set data = filter_data.get(flt[0]) -%}
{%- if data -%} {%- if data -%}
<select name="flt{{ idx }}v" class="filter-val" data-role="chosen"> <select name="flt{{ i }}_{{ flt[0] }}" class="filter-val" data-role="chosen">
{%- for opt in data %} {%- for d in data %}
<option value="{{ opt[0] }}"{% if flt[1] == opt[0] %} selected{% endif %}>{{ opt[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{{ idx }}v" 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 }}_{{ 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 -%} {%- endif -%}
</div> </div>
{%- endfor %} {% endfor %}
</div> </div>
{% if active_filters %}
<a href="{{ clear_search_url }}" class="btn">Reset Filters</a>
{% endif %}
<a id="add_filter" href="#" class="btn">Add Filter</a>
<button type="submit" class="btn" style="display: none">Apply</button>
</form> </form>
{% endif %} {% endif %}
...@@ -108,19 +138,17 @@ ...@@ -108,19 +138,17 @@
{% endfor %} {% endfor %}
</table> </table>
{{ lib.pager(page, num_pages, pager_url) }} {{ lib.pager(page, num_pages, pager_url) }}
{% if admin_view.can_create %}
<a class="btn btn-primary btn-large" href="{{ url_for('.create_view', url=return_url) }}">Create New</a>
{% endif %}
{% endblock %} {% endblock %}
{% block tail %} {% block tail %}
<script src="{{ url_for('admin.static', filename='js/bootstrap-datepicker.js') }}"></script> <script src="{{ url_for('admin.static', filename='js/bootstrap-datepicker.js') }}"></script>
<script src="{{ url_for('admin.static', filename='js/form.js') }}"></script> <script src="{{ url_for('admin.static', filename='js/form.js') }}"></script>
<script src="{{ url_for('admin.static', filename='js/filters.js') }}"></script> <script src="{{ url_for('admin.static', filename='js/filters.js') }}"></script>
{% if filter_names is not none and filter_data is not none %} {% if filter_groups is not none and filter_data is not none %}
<script language="javascript"> <script language="javascript">
var filter = new Filters('#filter_form', var form = new AdminForm();
{{ filter_names|tojson|safe }}, var filter = new AdminFilters('#filter_form', '.field-filters', form,
{{ admin_view._filter_dict|tojson|safe }},
{{ filter_data|tojson|safe }}, {{ filter_data|tojson|safe }},
{{ filter_types|tojson|safe }}); {{ filter_types|tojson|safe }});
</script> </script>
......
...@@ -28,6 +28,9 @@ class SimpleFilter(filters.BaseFilter): ...@@ -28,6 +28,9 @@ class SimpleFilter(filters.BaseFilter):
query._applied = True query._applied = True
return query return query
def operation(self):
return 'test'
class MockModelView(base.BaseModelView): class MockModelView(base.BaseModelView):
def __init__(self, model, name=None, category=None, endpoint=None, url=None, def __init__(self, model, name=None, category=None, endpoint=None, url=None,
...@@ -288,7 +291,8 @@ def test_column_filters(): ...@@ -288,7 +291,8 @@ def test_column_filters():
eq_(view._filters[0].name, 'col1') eq_(view._filters[0].name, 'col1')
eq_(view._filters[1].name, 'col2') eq_(view._filters[1].name, 'col2')
eq_(view._filter_names, ['col1', 'col2']) eq_(view._filter_dict, {'col1': [(0, 'test')],
'col2': [(1, 'test')]})
# TODO: Make calls with filters # TODO: Make calls with filters
......
...@@ -215,8 +215,10 @@ def test_column_filters(): ...@@ -215,8 +215,10 @@ def test_column_filters():
eq_(len(view._filters), 4) eq_(len(view._filters), 4)
eq_(view._filter_names, ['Test1 equals', 'Test1 not equal', eq_(view._filter_dict, {'Test1': [(0, 'equals'),
'Test1 like', 'Test1 not like']) (1, 'not equal'),
(2, 'like'),
(3, 'not like')]})
db.session.add(Model1('model1')) db.session.add(Model1('model1'))
db.session.add(Model1('model2')) db.session.add(Model1('model2'))
...@@ -226,12 +228,12 @@ def test_column_filters(): ...@@ -226,12 +228,12 @@ def test_column_filters():
client = app.test_client() client = app.test_client()
rv = client.get('/admin/model1view/?flt0=0&flt0v=model1') rv = client.get('/admin/model1view/?flt0_0=model1')
eq_(rv.status_code, 200) eq_(rv.status_code, 200)
ok_('model1' in rv.data) ok_('model1' in rv.data)
ok_('model2' not in rv.data) ok_('model2' not in rv.data)
rv = client.get('/admin/model1view/?flt0=5') rv = client.get('/admin/model1view/?flt0_5=model1')
eq_(rv.status_code, 200) eq_(rv.status_code, 200)
ok_('model1' in rv.data) ok_('model1' in rv.data)
ok_('model2' in rv.data) ok_('model2' in rv.data)
...@@ -241,10 +243,8 @@ def test_column_filters(): ...@@ -241,10 +243,8 @@ def test_column_filters():
column_filters=['int_field']) column_filters=['int_field'])
admin.add_view(view) admin.add_view(view)
eq_(view._filter_names, ['Int Field equals', eq_(view._filter_dict, {'Int Field': [(0, 'equals'), (1, 'not equal'),
'Int Field not equal', (2, 'greater than'), (3, 'smaller than')]})
'Int Field greater than',
'Int Field smaller than'])
def test_url_args(): def test_url_args():
...@@ -297,6 +297,7 @@ def test_url_args(): ...@@ -297,6 +297,7 @@ def test_url_args():
rv = client.get('/admin/model1view/?flt0=1&flt0v=data1') rv = client.get('/admin/model1view/?flt0=1&flt0v=data1')
ok_('data2' in rv.data) ok_('data2' in rv.data)
def test_form(): def test_form():
# TODO: form_columns # TODO: form_columns
# TODO: excluded_form_columns # TODO: excluded_form_columns
......
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