Commit ba750147 authored by Paul Brown's avatar Paul Brown

add 'in list' and 'empty' filters, allow changing to filters with opts, add...

add 'in list' and 'empty' filters, allow changing to filters with opts, add versions to js filenames

fix between filters
parent ec0f1054
...@@ -5,7 +5,7 @@ import datetime ...@@ -5,7 +5,7 @@ import datetime
from flask.ext.admin.babel import lazy_gettext from flask.ext.admin.babel import lazy_gettext
from flask.ext.admin.model import filters from flask.ext.admin.model import filters
from flask.ext.admin.contrib.sqla import tools from flask.ext.admin.contrib.sqla import tools
from sqlalchemy.sql import not_ from sqlalchemy.sql import not_, or_
class BaseSQLAFilter(filters.BaseFilter): class BaseSQLAFilter(filters.BaseFilter):
""" """
...@@ -80,6 +80,40 @@ class FilterSmaller(BaseSQLAFilter): ...@@ -80,6 +80,40 @@ class FilterSmaller(BaseSQLAFilter):
return lazy_gettext('smaller than') return lazy_gettext('smaller than')
class FilterEmpty(BaseSQLAFilter, filters.BaseBooleanFilter):
def apply(self, query, value):
if value == '1':
return query.filter(self.column == None)
else:
return query.filter(self.column != None)
def operation(self):
return lazy_gettext('empty')
class FilterInList(BaseSQLAFilter):
def __init__(self, column, name, options=None, data_type=None):
super(FilterInList, self).__init__(column, name, options, data_type='select2-tags')
def clean(self, value):
return [v.strip() for v in value.split(',') if v.strip()]
def apply(self, query, value):
return query.filter(self.column.in_(value))
def operation(self):
return lazy_gettext('in list')
class FilterNotInList(FilterInList):
def apply(self, query, value):
# NOT IN can exclude NULL values, so "or_ == None" needed to be added
return query.filter(or_(~self.column.in_(value), self.column == None))
def operation(self):
return lazy_gettext('not in list')
# Customized type filters # Customized type filters
class BooleanEqualFilter(FilterEqual, filters.BaseBooleanFilter): class BooleanEqualFilter(FilterEqual, filters.BaseBooleanFilter):
pass pass
...@@ -88,7 +122,7 @@ class BooleanEqualFilter(FilterEqual, filters.BaseBooleanFilter): ...@@ -88,7 +122,7 @@ class BooleanEqualFilter(FilterEqual, filters.BaseBooleanFilter):
class BooleanNotEqualFilter(FilterNotEqual, filters.BaseBooleanFilter): class BooleanNotEqualFilter(FilterNotEqual, filters.BaseBooleanFilter):
pass pass
class DateEqualFilter(FilterEqual, filters.BaseDateFilter): class DateEqualFilter(FilterEqual, filters.BaseDateFilter):
pass pass
...@@ -106,6 +140,9 @@ class DateSmallerFilter(FilterSmaller, filters.BaseDateFilter): ...@@ -106,6 +140,9 @@ class DateSmallerFilter(FilterSmaller, filters.BaseDateFilter):
class DateBetweenFilter(BaseSQLAFilter): class DateBetweenFilter(BaseSQLAFilter):
def __init__(self, column, name, options=None, data_type=None):
super(DateBetweenFilter, self).__init__(column, name, options, data_type='daterangepicker')
def clean(self, value): def clean(self, value):
return [datetime.datetime.strptime(range, '%Y-%m-%d') for range in value.split(' to ')] return [datetime.datetime.strptime(range, '%Y-%m-%d') for range in value.split(' to ')]
...@@ -156,6 +193,9 @@ class DateTimeSmallerFilter(FilterSmaller, filters.BaseDateTimeFilter): ...@@ -156,6 +193,9 @@ class DateTimeSmallerFilter(FilterSmaller, filters.BaseDateTimeFilter):
class DateTimeBetweenFilter(BaseSQLAFilter): class DateTimeBetweenFilter(BaseSQLAFilter):
def __init__(self, column, name, options=None, data_type=None):
super(DateTimeBetweenFilter, self).__init__(column, name, options, data_type='datetimerangepicker')
def clean(self, value): def clean(self, value):
return [datetime.datetime.strptime(range, '%Y-%m-%d %H:%M:%S') for range in value.split(' to ')] return [datetime.datetime.strptime(range, '%Y-%m-%d %H:%M:%S') for range in value.split(' to ')]
...@@ -175,7 +215,7 @@ class DateTimeBetweenFilter(BaseSQLAFilter): ...@@ -175,7 +215,7 @@ class DateTimeBetweenFilter(BaseSQLAFilter):
return False return False
except ValueError: except ValueError:
return False return False
class DateTimeNotBetweenFilter(DateTimeBetweenFilter): class DateTimeNotBetweenFilter(DateTimeBetweenFilter):
def apply(self, query, value): def apply(self, query, value):
...@@ -188,21 +228,24 @@ class DateTimeNotBetweenFilter(DateTimeBetweenFilter): ...@@ -188,21 +228,24 @@ class DateTimeNotBetweenFilter(DateTimeBetweenFilter):
class TimeEqualFilter(FilterEqual, filters.BaseTimeFilter): class TimeEqualFilter(FilterEqual, filters.BaseTimeFilter):
pass pass
class TimeNotEqualFilter(FilterNotEqual, filters.BaseTimeFilter): class TimeNotEqualFilter(FilterNotEqual, filters.BaseTimeFilter):
pass pass
class TimeGreaterFilter(FilterGreater, filters.BaseTimeFilter): class TimeGreaterFilter(FilterGreater, filters.BaseTimeFilter):
pass pass
class TimeSmallerFilter(FilterSmaller, filters.BaseTimeFilter): class TimeSmallerFilter(FilterSmaller, filters.BaseTimeFilter):
pass pass
class TimeBetweenFilter(BaseSQLAFilter): class TimeBetweenFilter(BaseSQLAFilter):
def __init__(self, column, name, options=None, data_type=None):
super(TimeBetweenFilter, self).__init__(column, name, options, data_type='timerangepicker')
def clean(self, value): def clean(self, value):
timetuples = [time.strptime(range, '%H:%M:%S') timetuples = [time.strptime(range, '%H:%M:%S')
for range in value.split(' to ')] for range in value.split(' to ')]
...@@ -210,7 +253,7 @@ class TimeBetweenFilter(BaseSQLAFilter): ...@@ -210,7 +253,7 @@ class TimeBetweenFilter(BaseSQLAFilter):
timetuple.tm_min, timetuple.tm_min,
timetuple.tm_sec) timetuple.tm_sec)
for timetuple in timetuples] for timetuple in timetuples]
def apply(self, query, value): def apply(self, query, value):
start, end = value start, end = value
return query.filter(self.column.between(start, end)) return query.filter(self.column.between(start, end))
...@@ -242,57 +285,55 @@ class TimeNotBetweenFilter(TimeBetweenFilter): ...@@ -242,57 +285,55 @@ class TimeNotBetweenFilter(TimeBetweenFilter):
# Base SQLA filter field converter # Base SQLA filter field converter
class FilterConverter(filters.BaseFilterConverter): class FilterConverter(filters.BaseFilterConverter):
strings = (FilterEqual, FilterNotEqual, FilterLike, FilterNotLike) strings = (FilterEqual, FilterNotEqual, FilterLike, FilterNotLike, FilterEmpty, FilterInList, FilterNotInList)
numeric = (FilterEqual, FilterNotEqual, FilterGreater, FilterSmaller) numeric = (FilterEqual, FilterNotEqual, FilterGreater, FilterSmaller, FilterEmpty, FilterInList, FilterNotInList)
bool = (BooleanEqualFilter, BooleanNotEqualFilter) bool = (BooleanEqualFilter, BooleanNotEqualFilter)
enum = (FilterEqual, FilterNotEqual) enum = (FilterEqual, FilterNotEqual, FilterEmpty, FilterInList, FilterNotInList)
date_filters = (DateEqualFilter, DateNotEqualFilter, DateGreaterFilter, DateSmallerFilter,
DateBetweenFilter, DateNotBetweenFilter, FilterEmpty)
datetime_filters = (DateTimeEqualFilter, DateTimeNotEqualFilter, DateTimeGreaterFilter,
DateTimeSmallerFilter, DateTimeBetweenFilter, DateTimeNotBetweenFilter,
FilterEmpty)
time_filters = (TimeEqualFilter, TimeNotEqualFilter, TimeGreaterFilter, TimeSmallerFilter,
TimeBetweenFilter, TimeNotBetweenFilter, FilterEmpty)
def convert(self, type_name, column, name, **kwargs): def convert(self, type_name, column, name, **kwargs):
if type_name.lower() in self.converters: if type_name.lower() in self.converters:
return self.converters[type_name.lower()](column, name, **kwargs) return self.converters[type_name.lower()](column, name, **kwargs)
return None return None
@filters.convert('string', 'unicode', 'text', 'unicodetext', 'varchar') @filters.convert('string', 'char', 'unicode', 'varchar', 'tinytext',
'text', 'mediumtext', 'longtext', 'unicodetext',
'nchar', 'nvarchar', 'ntext')
def conv_string(self, column, name, **kwargs): def conv_string(self, column, name, **kwargs):
return [f(column, name, **kwargs) for f in self.strings] return [f(column, name, **kwargs) for f in self.strings]
@filters.convert('boolean', 'tinyint') @filters.convert('boolean', 'tinyint')
def conv_bool(self, column, name, **kwargs): def conv_bool(self, column, name, **kwargs):
return [f(column, name, **kwargs) for f in self.bool] return [f(column, name, **kwargs) for f in self.bool]
@filters.convert('integer', 'smallinteger', 'numeric', 'float', 'biginteger') @filters.convert('int', 'integer', 'smallinteger', 'smallint', 'numeric',
'float', 'real', 'biginteger', 'bigint', 'decimal',
'double_precision', 'double', 'mediumint')
def conv_int(self, column, name, **kwargs): def conv_int(self, column, name, **kwargs):
return [f(column, name, **kwargs) for f in self.numeric] return [f(column, name, **kwargs) for f in self.numeric]
@filters.convert('date') @filters.convert('date')
def conv_date(self, column, name, **kwargs): def conv_date(self, column, name, **kwargs):
return [DateEqualFilter(column, name), return [f(column, name, **kwargs) for f in self.date_filters]
DateNotEqualFilter(column, name),
DateGreaterFilter(column, name), @filters.convert('datetime', 'datetime2', 'timestamp', 'smalldatetime')
DateSmallerFilter(column, name),
DateBetweenFilter(column, name, data_type='daterangepicker'),
DateNotBetweenFilter(column, name, data_type='daterangepicker')]
@filters.convert('datetime')
def conv_datetime(self, column, name, **kwargs): def conv_datetime(self, column, name, **kwargs):
return [DateTimeEqualFilter(column, name), return [f(column, name, **kwargs) for f in self.datetime_filters]
DateTimeNotEqualFilter(column, name),
DateTimeGreaterFilter(column, name),
DateTimeSmallerFilter(column, name),
DateTimeBetweenFilter(column, name, data_type='datetimerangepicker'),
DateTimeNotBetweenFilter(column, name, data_type='datetimerangepicker')]
@filters.convert('time') @filters.convert('time')
def conv_time(self, column, name, **kwargs): def conv_time(self, column, name, **kwargs):
return [TimeEqualFilter(column, name), return [f(column, name, **kwargs) for f in self.time_filters]
TimeNotEqualFilter(column, name),
TimeGreaterFilter(column, name),
TimeSmallerFilter(column, name),
TimeBetweenFilter(column, name, data_type='timerangepicker'),
TimeNotBetweenFilter(column, name, data_type='timerangepicker')]
@filters.convert('enum') @filters.convert('enum')
def conv_enum(self, column, name, options=None, **kwargs): def conv_enum(self, column, name, options=None, **kwargs):
# set all operations to select2
kwargs['data_type'] = "select2"
if not options: if not options:
options = [ options = [
(v, v) (v, v)
......
...@@ -93,7 +93,7 @@ class Select2Field(fields.SelectField): ...@@ -93,7 +93,7 @@ class Select2Field(fields.SelectField):
""" """
`Select2 <https://github.com/ivaynberg/select2>`_ styled select widget. `Select2 <https://github.com/ivaynberg/select2>`_ styled select widget.
You must include select2.js, form.js and select2 stylesheet for it to You must include select2.js, form-x.x.x.js and select2 stylesheet for it to
work. work.
""" """
widget = admin_widgets.Select2Widget() widget = admin_widgets.Select2Widget()
...@@ -141,7 +141,7 @@ class Select2Field(fields.SelectField): ...@@ -141,7 +141,7 @@ class Select2Field(fields.SelectField):
class Select2TagsField(fields.StringField): class Select2TagsField(fields.StringField):
"""`Select2 <http://ivaynberg.github.com/select2/#tags>`_ styled text field. """`Select2 <http://ivaynberg.github.com/select2/#tags>`_ styled text field.
You must include select2.js, form.js and select2 stylesheet for it to work. You must include select2.js, form-x.x.x.js and select2 stylesheet for it to work.
""" """
widget = admin_widgets.Select2TagsWidget() widget = admin_widgets.Select2TagsWidget()
......
...@@ -15,7 +15,7 @@ class Select2Widget(widgets.Select): ...@@ -15,7 +15,7 @@ class Select2Widget(widgets.Select):
""" """
`Select2 <https://github.com/ivaynberg/select2>`_ styled select widget. `Select2 <https://github.com/ivaynberg/select2>`_ styled select widget.
You must include select2.js, form.js and select2 stylesheet for it to You must include select2.js, form-x.x.x.js and select2 stylesheet for it to
work. work.
""" """
def __call__(self, field, **kwargs): def __call__(self, field, **kwargs):
...@@ -30,7 +30,7 @@ class Select2Widget(widgets.Select): ...@@ -30,7 +30,7 @@ class Select2Widget(widgets.Select):
class Select2TagsWidget(widgets.TextInput): class Select2TagsWidget(widgets.TextInput):
"""`Select2 <http://ivaynberg.github.com/select2/#tags>`_ styled text widget. """`Select2 <http://ivaynberg.github.com/select2/#tags>`_ styled text widget.
You must include select2.js, form.js and select2 stylesheet for it to work. You must include select2.js, form-x.x.x.js and select2 stylesheet for it to work.
""" """
def __call__(self, field, **kwargs): def __call__(self, field, **kwargs):
kwargs.setdefault('data-role', u'select2') kwargs.setdefault('data-role', u'select2')
...@@ -43,7 +43,7 @@ class DatePickerWidget(widgets.TextInput): ...@@ -43,7 +43,7 @@ class DatePickerWidget(widgets.TextInput):
""" """
Date picker widget. Date picker widget.
You must include bootstrap-datepicker.js and form.js for styling to work. You must include bootstrap-datepicker.js and form-x.x.x.js for styling to work.
""" """
def __call__(self, field, **kwargs): def __call__(self, field, **kwargs):
kwargs.setdefault('data-role', u'datepicker') kwargs.setdefault('data-role', u'datepicker')
...@@ -57,7 +57,7 @@ class DateTimePickerWidget(widgets.TextInput): ...@@ -57,7 +57,7 @@ class DateTimePickerWidget(widgets.TextInput):
""" """
Datetime picker widget. Datetime picker widget.
You must include bootstrap-datepicker.js and form.js for styling to work. You must include bootstrap-datepicker.js and form-x.x.x.js for styling to work.
""" """
def __call__(self, field, **kwargs): def __call__(self, field, **kwargs):
kwargs.setdefault('data-role', u'datetimepicker') kwargs.setdefault('data-role', u'datetimepicker')
...@@ -69,7 +69,7 @@ class TimePickerWidget(widgets.TextInput): ...@@ -69,7 +69,7 @@ class TimePickerWidget(widgets.TextInput):
""" """
Date picker widget. Date picker widget.
You must include bootstrap-datepicker.js and form.js for styling to work. You must include bootstrap-datepicker.js and form-x.x.x.js for styling to work.
""" """
def __call__(self, field, **kwargs): def __call__(self, field, **kwargs):
kwargs.setdefault('data-role', u'timepicker') kwargs.setdefault('data-role', u'timepicker')
......
...@@ -98,7 +98,7 @@ class BaseBooleanFilter(BaseFilter): ...@@ -98,7 +98,7 @@ class BaseBooleanFilter(BaseFilter):
super(BaseBooleanFilter, self).__init__(name, super(BaseBooleanFilter, self).__init__(name,
(('1', lazy_gettext(u'Yes')), (('1', lazy_gettext(u'Yes')),
('0', lazy_gettext(u'No'))), ('0', lazy_gettext(u'No'))),
data_type) data_type="select2")
def validate(self, value): def validate(self, value):
return value in ('0', '1') return value in ('0', '1')
...@@ -112,7 +112,7 @@ class BaseDateFilter(BaseFilter): ...@@ -112,7 +112,7 @@ class BaseDateFilter(BaseFilter):
super(BaseDateFilter, self).__init__(name, super(BaseDateFilter, self).__init__(name,
options, options,
data_type='datepicker') data_type='datepicker')
def clean(self, value): def clean(self, value):
return datetime.datetime.strptime(value, '%Y-%m-%d').date() return datetime.datetime.strptime(value, '%Y-%m-%d').date()
...@@ -139,7 +139,7 @@ class BaseTimeFilter(BaseFilter): ...@@ -139,7 +139,7 @@ class BaseTimeFilter(BaseFilter):
super(BaseTimeFilter, self).__init__(name, super(BaseTimeFilter, self).__init__(name,
options, options,
data_type='timepicker') data_type='timepicker')
def clean(self, value): def clean(self, value):
# time filters will not work in SQLite + SQLAlchemy if value not converted to time # time filters will not work in SQLite + SQLAlchemy if value not converted to time
timetuple = time.strptime(value, '%H:%M:%S') timetuple = time.strptime(value, '%H:%M:%S')
......
...@@ -2,31 +2,23 @@ var AdminFilters = function(element, filtersElement, filterGroups, activeFilters ...@@ -2,31 +2,23 @@ var AdminFilters = function(element, filtersElement, filterGroups, activeFilters
var $root = $(element); var $root = $(element);
var $container = $('.filters', $root); var $container = $('.filters', $root);
var lastCount = 0; var lastCount = 0;
function getCount(name) { function getCount(name) {
var idx = name.indexOf('_'); var idx = name.indexOf('_');
if (idx === -1) { if (idx === -1) {
return 0; return 0;
} }
return parseInt(name.substr(3, idx - 3), 10); return parseInt(name.substr(3, idx - 3), 10);
} }
function makeName(name) { function makeName(name) {
var result = 'flt' + lastCount + '_' + name; var result = 'flt' + lastCount + '_' + name;
lastCount += 1; lastCount += 1;
return result; return result;
} }
function changeOperation() {
var $row = $(this).closest('tr');
var $el = $('.filter-val:input', $row);
var count = getCount($el.attr('name'));
$el.attr('name', 'flt' + count + '_' + $(this).val());
$('button', $root).show();
}
function removeFilter() { function removeFilter() {
$(this).closest('tr').remove(); $(this).closest('tr').remove();
if($('.filters tr').length == 0) { if($('.filters tr').length == 0) {
...@@ -39,10 +31,76 @@ var AdminFilters = function(element, filtersElement, filterGroups, activeFilters ...@@ -39,10 +31,76 @@ var AdminFilters = function(element, filtersElement, filterGroups, activeFilters
return false; return false;
} }
function addFilter(name, subfilters, selected, filterValue) { // triggered when the filter operation (equals, not equals, etc) is changed
function changeOperation(subfilters, $el, filter, $select) {
// get the filter_group subfilter based on the index of the selected option
var selectedFilter = subfilters[$select.select2('data').element[0].index];
var $inputContainer = $el.find('td').last();
// recreate and style the input field (turn into date range or select2 if necessary)
var $field = createFilterInput($inputContainer, null, selectedFilter);
styleFilterInput(selectedFilter, $field);
$('button', $root).show();
}
// generate HTML for filter input - allows changing filter input type to one with options or tags
function createFilterInput(inputContainer, filterValue, filter) {
if (filter.type == "select2") {
var $field = $('<select class="filter-val" />').attr('name', makeName(filter.arg));
$(filter.options).each(function() {
// for active filter inputs with options, add "selected" if there is a matching active filter
if (filterValue && (filterValue == this[0])) {
$field.append($('<option/>')
.val(this[0]).text(this[1]).attr('selected', true));
} else {
$field.append($('<option/>')
.val(this[0]).text(this[1]));
}
});
} else if (filter.type == "select2-tags") {
var $field = $('<input type="hidden" class="filter-val form-control" />').attr('name', makeName(filter.arg));
$field.val(filterValue);
} else {
var $field = $('<input type="text" class="filter-val form-control" />').attr('name', makeName(filter.arg));
$field.val(filterValue);
}
inputContainer.replaceWith($('<td/>').append($field));
return $field;
}
// add styling to input field, accommodates filters that change the input field's HTML
function styleFilterInput(filter, field) {
if (filter.type) {
if ((filter.type == "datepicker") || (filter.type == "daterangepicker")) {
field.attr('data-date-format', "YYYY-MM-DD");
} else if ((filter.type == "datetimepicker") || (filter.type == "datetimerangepicker")) {
field.attr('data-date-format', "YYYY-MM-DD HH:mm:ss");
} else if ((filter.type == "timepicker") || (filter.type == "timerangepicker")) {
field.attr('data-date-format', "HH:mm:ss");
} else if (filter.type == "select2-tags") {
var options = [];
if (filter.options) {
filter.options.forEach(function(option) {
options.push({id:option[0], text:option[1]});
});
// save tag options as json on data attribute
field.attr('data-tags', JSON.stringify(options));
}
}
faForm.applyStyle(field, filter.type);
}
return field;
}
function addFilter(name, subfilters, selectedIndex, filterValue) {
var $el = $('<tr />').appendTo($container); var $el = $('<tr />').appendTo($container);
// Filter list // Filter list
$el.append( $el.append(
$('<td/>').append( $('<td/>').append(
...@@ -53,117 +111,69 @@ var AdminFilters = function(element, filtersElement, filterGroups, activeFilters ...@@ -53,117 +111,69 @@ var AdminFilters = function(element, filtersElement, filterGroups, activeFilters
.click(removeFilter) .click(removeFilter)
) )
); );
// Filter type // Filter operation <select> (equal, not equal, etc)
var $select = $('<select class="filter-op" />') var $select = $('<select class="filter-op" />');
.change(changeOperation);
// if one of the subfilters are selected, use that subfilter to create the input field // if one of the subfilters are selected, use that subfilter to create the input field
var filter_selection = 0; var filterSelection = 0;
$.each(subfilters, function( subfilterIndex, subfilter ) { $.each(subfilters, function( subfilterIndex, subfilter ) {
if (this.arg == selected) { if (this.arg == selectedIndex) {
$select.append($('<option/>').attr('value', subfilter.arg).attr('selected', true).text(subfilter.operation)); $select.append($('<option/>').attr('value', subfilter.arg).attr('selected', true).text(subfilter.operation));
filter_selection = subfilterIndex; filterSelection = subfilterIndex;
} else { } else {
$select.append($('<option/>').attr('value', subfilter.arg).text(subfilter.operation)); $select.append($('<option/>').attr('value', subfilter.arg).text(subfilter.operation));
} }
}); });
$el.append( $el.append(
$('<td/>').append($select) $('<td/>').append($select)
); );
// on change, get the subfilter based on the index of the added element, then modify the input field (turn into date range if necessary)
$select.select2({width: 'resolve'}).on("change", function(e) {
styleFilterInput(subfilters[e.added.element[0].index], $el.find('input').last());
});
// add styling to input field, accommodates filters that change the type of the field
function styleFilterInput(filter, field) {
if (filter.type) {
field.attr('data-role', filter.type);
if ((filter.type == "datepicker") || (filter.type == "daterangepicker")) {
field.attr('data-date-format', "YYYY-MM-DD");
}
else if ((filter.type == "datetimepicker") || (filter.type == "datetimerangepicker")) {
field.attr('data-date-format', "YYYY-MM-DD HH:mm:ss");
}
else if ((filter.type == "timepicker") || (filter.type == "timerangepicker")) {
field.attr('data-date-format', "HH:mm:ss");
}
faForm.applyStyle(field, filter.type);
}
}
// initial filter creation // select2 for filter-op (equal, not equal, etc)
filter = subfilters[filter_selection]; $select.select2({width: 'resolve'}).on("change", function(e) {
var $field; changeOperation(subfilters, $el, filter, $select);
});
if (filter.options) {
$field = $('<select class="filter-val" />')
.attr('name', makeName(filter.arg));
$(filter.options).each(function() {
// for active fields, add "selected" to matching value
if (filterValue && (filterValue == this[0])) {
$field.append($('<option/>')
.val(this[0]).text(this[1]).attr('selected', true));
} else {
$field.append($('<option/>')
.val(this[0]).text(this[1]));
}
});
$el.append($('<td/>').append($field));
$field.select2({width: 'resolve'});
} else
{
$field = $('<input type="text" class="filter-val form-control" />')
.attr('name', makeName(filter.arg));
$el.append($('<td/>').append($field));
}
styleFilterInput(filter, $field); // get filter option from filter_group, only for new filter creation
var filter = subfilters[filterSelection];
var $inputContainer = $('<td/>').appendTo($el);
var $newFilterField = createFilterInput($inputContainer, filterValue, filter);
var $styledFilterField = styleFilterInput(filter, $newFilterField);
return $field; return $styledFilterField;
} }
// Add Filter Button, new filter
$('a.filter', filtersElement).click(function() { $('a.filter', filtersElement).click(function() {
var name = ($(this).text().trim !== undefined ? $(this).text().trim() : $(this).text().replace(/^\s+|\s+$/g,'')); var name = ($(this).text().trim !== undefined ? $(this).text().trim() : $(this).text().replace(/^\s+|\s+$/g,''));
addFilter(name, filterGroups[name], false, false); addFilter(name, filterGroups[name], false, null);
$('button', $root).show(); $('button', $root).show();
//return false;
}); });
if(activeFilters.length > 0){ // on page load - add active filters
$('button', $root).show();
}
// add active filters on page load
$.each(activeFilters, function( activeIndex, activeFilter ) { $.each(activeFilters, function( activeIndex, activeFilter ) {
var idx = activeFilter[0], var idx = activeFilter[0],
name = activeFilter[1], name = activeFilter[1],
filterValue = activeFilter[2]; filterValue = activeFilter[2];
$field = addFilter(name, filterGroups[name], idx, filterValue); var $activeField = addFilter(name, filterGroups[name], idx, filterValue);
// set value of newly created field
$field.val(filterValue);
}); });
$('.filter-op', $root).change(changeOperation); // show "Apply Filter" button when filter input is changed
$('.filter-val', $root).change(function() { $('.filter-val', $root).on('input change', function() {
$('button', $root).show(); $('button', $root).show();
}); });
$('.remove-filter', $root).click(removeFilter); $('.remove-filter', $root).click(removeFilter);
$('.filter-val', $root).not('.select2-container').each(function() { $('.filter-val', $root).not('.select2-container').each(function() {
var count = getCount($(this).attr('name')); var count = getCount($(this).attr('name'));
if (count > lastCount) if (count > lastCount)
lastCount = count; lastCount = count;
}); });
lastCount += 1; lastCount += 1;
}; };
\ No newline at end of file
...@@ -259,6 +259,39 @@ ...@@ -259,6 +259,39 @@
$el.select2(opts); $el.select2(opts);
return true; return true;
case 'select2-tags':
// get tags from element
if ($el.attr('data-tags')) {
var tags = JSON.parse($el.attr('data-tags'));
} else {
var tags = [];
}
// default to a comma for separating list items
// allows using spaces as a token separator
if ($el.attr('data-token-separators')) {
var tokenSeparators = JSON.parse($el.attr('data-tags'));
} else {
var tokenSeparators = [','];
}
var opts = {
width: 'resolve',
tags: tags,
tokenSeparators: tokenSeparators,
formatNoMatches: function() {
return 'Enter comma separated values';
}
};
$el.select2(opts);
// submit on ENTER
$el.parent().find('input.select2-input').on('keyup', function(e) {
if(e.keyCode === 13)
$(this).closest('form').submit();
});
return true;
case 'select2-ajax': case 'select2-ajax':
processAjaxWidget($el, name); processAjaxWidget($el, name);
return true; return true;
......
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
{% macro script(message, actions, actions_confirmation) %} {% macro script(message, actions, actions_confirmation) %}
{% if actions %} {% if actions %}
<script src="{{ admin_static.url(filename='admin/js/actions.js') }}"></script> <script src="{{ admin_static.url(filename='admin/js/actions-1.0.0.js') }}"></script>
<script language="javascript"> <script language="javascript">
var modelActions = new AdminModelActions({{ message|tojson|safe }}, {{ actions_confirmation|tojson|safe }}); var modelActions = new AdminModelActions({{ message|tojson|safe }}, {{ actions_confirmation|tojson|safe }});
</script> </script>
......
...@@ -189,5 +189,5 @@ ...@@ -189,5 +189,5 @@
<script src="{{ admin_static.url(filename='vendor/leaflet/leaflet.draw.js') }}"></script> <script src="{{ admin_static.url(filename='vendor/leaflet/leaflet.draw.js') }}"></script>
{% endif %} {% endif %}
<script src="{{ admin_static.url(filename='vendor/bootstrap-daterangepicker/daterangepicker.js') }}"></script> <script src="{{ admin_static.url(filename='vendor/bootstrap-daterangepicker/daterangepicker.js') }}"></script>
<script src="{{ admin_static.url(filename='admin/js/form.js') }}"></script> <script src="{{ admin_static.url(filename='admin/js/form-1.0.0.js') }}"></script>
{% endmacro %} {% endmacro %}
...@@ -145,7 +145,7 @@ ...@@ -145,7 +145,7 @@
{% block tail %} {% block tail %}
{{ super() }} {{ super() }}
{{ lib.form_js() }} {{ lib.form_js() }}
<script src="{{ admin_static.url(filename='admin/js/filters.js') }}"></script> <script src="{{ admin_static.url(filename='admin/js/filters-1.0.0.js') }}"></script>
{{ actionlib.script(_gettext('Please select at least one record.'), {{ actionlib.script(_gettext('Please select at least one record.'),
actions, actions,
......
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
{% block tail %} {% block tail %}
{{ super() }} {{ super() }}
<script src="{{ admin_static.url(filename='admin/js/rediscli.js') }}"></script> <script src="{{ admin_static.url(filename='admin/js/rediscli-1.0.0.js') }}"></script>
<script language="javascript"> <script language="javascript">
$(function() { $(function() {
var redisCli = new RedisCli({{ get_url('.execute_view')|tojson }}); var redisCli = new RedisCli({{ get_url('.execute_view')|tojson }});
......
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
{% macro script(message, actions, actions_confirmation) %} {% macro script(message, actions, actions_confirmation) %}
{% if actions %} {% if actions %}
<script src="{{ admin_static.url(filename='admin/js/actions.js') }}"></script> <script src="{{ admin_static.url(filename='admin/js/actions-1.0.0.js') }}"></script>
<script language="javascript"> <script language="javascript">
var modelActions = new AdminModelActions({{ message|tojson|safe }}, {{ actions_confirmation|tojson|safe }}); var modelActions = new AdminModelActions({{ message|tojson|safe }}, {{ actions_confirmation|tojson|safe }});
</script> </script>
......
...@@ -182,5 +182,5 @@ ...@@ -182,5 +182,5 @@
<script src="{{ admin_static.url(filename='vendor/leaflet/leaflet.draw.js') }}"></script> <script src="{{ admin_static.url(filename='vendor/leaflet/leaflet.draw.js') }}"></script>
{% endif %} {% endif %}
<script src="{{ admin_static.url(filename='vendor/bootstrap-daterangepicker/daterangepicker.js') }}"></script> <script src="{{ admin_static.url(filename='vendor/bootstrap-daterangepicker/daterangepicker.js') }}"></script>
<script src="{{ admin_static.url(filename='admin/js/form.js') }}"></script> <script src="{{ admin_static.url(filename='admin/js/form-1.0.0.js') }}"></script>
{% endmacro %} {% endmacro %}
...@@ -144,7 +144,7 @@ ...@@ -144,7 +144,7 @@
{% block tail %} {% block tail %}
{{ super() }} {{ super() }}
<script src="{{ admin_static.url(filename='admin/js/filters.js') }}"></script> <script src="{{ admin_static.url(filename='admin/js/filters-1.0.0.js') }}"></script>
{{ lib.form_js() }} {{ lib.form_js() }}
{{ actionlib.script(_gettext('Please select at least one record.'), {{ actionlib.script(_gettext('Please select at least one record.'),
......
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
{% block tail %} {% block tail %}
{{ super() }} {{ super() }}
<script src="{{ admin_static.url(filename='admin/js/rediscli.js') }}"></script> <script src="{{ admin_static.url(filename='admin/js/rediscli-1.0.0.js') }}"></script>
<script language="javascript"> <script language="javascript">
$(function() { $(function() {
var redisCli = new RedisCli({{ admin_view.get_url('.execute_view')|tojson }}); var redisCli = new RedisCli({{ admin_view.get_url('.execute_view')|tojson }});
......
...@@ -26,7 +26,8 @@ class CustomModelView(ModelView): ...@@ -26,7 +26,8 @@ class CustomModelView(ModelView):
def create_models(db): def create_models(db):
class Model1(db.Model): class Model1(db.Model):
def __init__(self, test1=None, test2=None, test3=None, test4=None, def __init__(self, test1=None, test2=None, test3=None, test4=None,
bool_field=False, date_field=None, time_field=None, datetime_field=None): bool_field=False, date_field=None, time_field=None,
datetime_field=None, enum_field=None):
self.test1 = test1 self.test1 = test1
self.test2 = test2 self.test2 = test2
self.test3 = test3 self.test3 = test3
...@@ -35,6 +36,7 @@ def create_models(db): ...@@ -35,6 +36,7 @@ def create_models(db):
self.date_field = date_field self.date_field = date_field
self.time_field = time_field self.time_field = time_field
self.datetime_field = datetime_field self.datetime_field = datetime_field
self.enum_field = enum_field
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
test1 = db.Column(db.String(20)) test1 = db.Column(db.String(20))
...@@ -42,7 +44,7 @@ def create_models(db): ...@@ -42,7 +44,7 @@ def create_models(db):
test3 = db.Column(db.Text) test3 = db.Column(db.Text)
test4 = db.Column(db.UnicodeText) test4 = db.Column(db.UnicodeText)
bool_field = db.Column(db.Boolean) bool_field = db.Column(db.Boolean)
enum_field = db.Column(db.Enum('model1_v1', 'model1_v1'), nullable=True) enum_field = db.Column(db.Enum('model1_v1', 'model1_v2'), nullable=True)
date_field = db.Column(db.Date) date_field = db.Column(db.Date)
time_field = db.Column(db.Time) time_field = db.Column(db.Time)
...@@ -287,14 +289,17 @@ def test_column_filters(): ...@@ -287,14 +289,17 @@ def test_column_filters():
) )
admin.add_view(view) admin.add_view(view)
eq_(len(view._filters), 4) eq_(len(view._filters), 7)
eq_([(f['index'], f['operation']) for f in view._filter_groups[u'Test1']], eq_([(f['index'], f['operation']) for f in view._filter_groups[u'Test1']],
[ [
(0, u'equals'), (0, u'equals'),
(1, u'not equal'), (1, u'not equal'),
(2, u'contains'), (2, u'contains'),
(3, u'not contains') (3, u'not contains'),
(4, u'empty'),
(5, u'in list'),
(6, u'not in list'),
]) ])
# Test filter that references property # Test filter that references property
...@@ -306,43 +311,58 @@ def test_column_filters(): ...@@ -306,43 +311,58 @@ def test_column_filters():
(0, u'equals'), (0, u'equals'),
(1, u'not equal'), (1, u'not equal'),
(2, u'contains'), (2, u'contains'),
(3, u'not contains') (3, u'not contains'),
(4, u'empty'),
(5, u'in list'),
(6, u'not in list'),
]) ])
eq_([(f['index'], f['operation']) for f in view._filter_groups[u'Model1 / Test2']], eq_([(f['index'], f['operation']) for f in view._filter_groups[u'Model1 / Test2']],
[ [
(4, 'equals'), (7, u'equals'),
(5, 'not equal'), (8, u'not equal'),
(6, 'contains'), (9, u'contains'),
(7, 'not contains') (10, u'not contains'),
(11, u'empty'),
(12, u'in list'),
(13, u'not in list'),
]) ])
eq_([(f['index'], f['operation']) for f in view._filter_groups[u'Model1 / Test3']], eq_([(f['index'], f['operation']) for f in view._filter_groups[u'Model1 / Test3']],
[ [
(8, u'equals'), (14, u'equals'),
(9, u'not equal'), (15, u'not equal'),
(10, u'contains'), (16, u'contains'),
(11, u'not contains') (17, u'not contains'),
(18, u'empty'),
(19, u'in list'),
(20, u'not in list'),
]) ])
eq_([(f['index'], f['operation']) for f in view._filter_groups[u'Model1 / Test4']], eq_([(f['index'], f['operation']) for f in view._filter_groups[u'Model1 / Test4']],
[ [
(12, u'equals'), (21, u'equals'),
(13, u'not equal'), (22, u'not equal'),
(14, u'contains'), (23, u'contains'),
(15, u'not contains') (24, u'not contains'),
(25, u'empty'),
(26, u'in list'),
(27, u'not in list'),
]) ])
eq_([(f['index'], f['operation']) for f in view._filter_groups[u'Model1 / Bool Field']], eq_([(f['index'], f['operation']) for f in view._filter_groups[u'Model1 / Bool Field']],
[ [
(16, u'equals'), (28, u'equals'),
(17, u'not equal'), (29, u'not equal'),
]) ])
eq_([(f['index'], f['operation']) for f in view._filter_groups[u'Model1 / Enum Field']], eq_([(f['index'], f['operation']) for f in view._filter_groups[u'Model1 / Enum Field']],
[ [
(18, u'equals'), (30, u'equals'),
(19, u'not equal'), (31, u'not equal'),
(32, u'empty'),
(33, u'in list'),
(34, u'not in list'),
]) ])
# Test filter with a dot # Test filter with a dot
...@@ -356,58 +376,211 @@ def test_column_filters(): ...@@ -356,58 +376,211 @@ def test_column_filters():
]) ])
# Fill DB # Fill DB
model1_obj1 = Model1('model1_obj1', bool_field=True) model1_obj1 = Model1('test1_val_1', 'test2_val_1', bool_field=True)
model1_obj2 = Model1('model1_obj2') model1_obj2 = Model1('test1_val_2', 'test2_val_2')
model1_obj3 = Model1('model1_obj3') model1_obj3 = Model1('test1_val_3', 'test2_val_3')
model1_obj4 = Model1('model1_obj4') model1_obj4 = Model1('test1_val_4', 'test2_val_4')
model2_obj1 = Model2('model2_obj1', model1=model1_obj1) model2_obj1 = Model2('test2_val_1', model1=model1_obj1)
model2_obj2 = Model2('model2_obj2', model1=model1_obj1) model2_obj2 = Model2('test2_val_2', model1=model1_obj1)
model2_obj3 = Model2('model2_obj3') model2_obj3 = Model2('test2_val_3', int_field=5000)
model2_obj4 = Model2('model2_obj4') model2_obj4 = Model2('test2_val_4', int_field=9000)
date_obj1 = Model1('date_obj1', date_field=date(2014,11,17)) date_obj1 = Model1('date_obj1', date_field=date(2014,11,17))
date_obj2 = Model1('date_obj2', date_field=date(2013,10,16)) date_obj2 = Model1('date_obj2', date_field=date(2013,10,16))
time_obj1 = Model1('time_obj1', time_field=time(11,10,9)) timeonly_obj1 = Model1('timeonly_obj1', time_field=time(11,10,9))
time_obj2 = Model1('time_obj2', time_field=time(10,9,8)) timeonly_obj2 = Model1('timeonly_obj2', time_field=time(10,9,8))
datetime_obj1 = Model1('datetime_obj1', datetime_field=datetime(2014,4,3,1,9,0)) datetime_obj1 = Model1('datetime_obj1', datetime_field=datetime(2014,4,3,1,9,0))
datetime_obj2 = Model1('datetime_obj2', datetime_field=datetime(2013,3,2,0,8,0)) datetime_obj2 = Model1('datetime_obj2', datetime_field=datetime(2013,3,2,0,8,0))
enum_obj1 = Model1('enum_obj1', enum_field="model1_v1")
enum_obj2 = Model1('enum_obj2', enum_field="model1_v2")
empty_obj = Model1(test2="empty_obj")
db.session.add_all([ db.session.add_all([
model1_obj1, model1_obj2, model1_obj3, model1_obj4, model1_obj1, model1_obj2, model1_obj3, model1_obj4,
model2_obj1, model2_obj2, model2_obj3, model2_obj4, model2_obj1, model2_obj2, model2_obj3, model2_obj4,
date_obj1, time_obj1, datetime_obj1, date_obj1, timeonly_obj1, datetime_obj1,
date_obj2, time_obj2, datetime_obj2 date_obj2, timeonly_obj2, datetime_obj2,
enum_obj1, enum_obj2, empty_obj
]) ])
db.session.commit() db.session.commit()
client = app.test_client() client = app.test_client()
rv = client.get('/admin/model1/?flt0_0=model1_obj1') rv = client.get('/admin/model1/?flt0_0=test1_val_1')
eq_(rv.status_code, 200) eq_(rv.status_code, 200)
data = rv.data.decode('utf-8') data = rv.data.decode('utf-8')
ok_('model1_obj1' in data) # the filter value is always in "data"
ok_('model1_obj2' not in data) # need to check a different column than test1 for the expected row
ok_('test2_val_1' in data)
ok_('test1_val_2' not in data)
rv = client.get('/admin/model1/?flt0_5=model1_obj1') rv = client.get('/admin/model1/?flt0_6=test1_val_1')
eq_(rv.status_code, 200) eq_(rv.status_code, 200)
data = rv.data.decode('utf-8') data = rv.data.decode('utf-8')
ok_('model1_obj1' in data) ok_('test2_val_1' not in data)
ok_('model1_obj2' in data) ok_('test1_val_2' in data)
# Test different filter types # Test string filter
view = CustomModelView(Model1, db.session,
column_filters=['test1'], endpoint='_strings')
admin.add_view(view)
eq_([(f['index'], f['operation']) for f in view._filter_groups[u'Test1']],
[
(0, 'equals'),
(1, 'not equal'),
(2, 'contains'),
(3, 'not contains'),
(4, 'empty'),
(5, 'in list'),
(6, 'not in list'),
])
# string - equals
rv = client.get('/admin/_strings/?flt0_0=test1_val_1')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('test2_val_1' in data)
ok_('test1_val_2' not in data)
# string - not equal
rv = client.get('/admin/_strings/?flt0_1=test1_val_1')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('test2_val_1' not in data)
ok_('test1_val_2' in data)
# string - contains
rv = client.get('/admin/_strings/?flt0_2=test1_val_1')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('test2_val_1' in data)
ok_('test1_val_2' not in data)
# string - not contains
rv = client.get('/admin/_strings/?flt0_3=test1_val_1')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('test2_val_1' not in data)
ok_('test1_val_2' in data)
# string - empty
rv = client.get('/admin/_strings/?flt0_4=1')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('empty_obj' in data)
ok_('test1_val_1' not in data)
ok_('test1_val_2' not in data)
# string - not empty
rv = client.get('/admin/_strings/?flt0_4=0')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('empty_obj' not in data)
ok_('test1_val_1' in data)
ok_('test1_val_2' in data)
# string - in list
rv = client.get('/admin/_strings/?flt0_5=test1_val_1%2Ctest1_val_2')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('test2_val_1' in data)
ok_('test2_val_2' in data)
ok_('test1_val_3' not in data)
ok_('test1_val_4' not in data)
# string - not in list
rv = client.get('/admin/_strings/?flt0_6=test1_val_1%2Ctest1_val_2')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('test2_val_1' not in data)
ok_('test2_val_2' not in data)
ok_('test1_val_3' in data)
ok_('test1_val_4' in data)
# Test numeric filter
view = CustomModelView(Model2, db.session, view = CustomModelView(Model2, db.session,
column_filters=['int_field']) column_filters=['int_field'])
admin.add_view(view) admin.add_view(view)
eq_([(f['index'], f['operation']) for f in view._filter_groups[u'Int Field']], eq_([(f['index'], f['operation']) for f in view._filter_groups[u'Int Field']],
[ [
(0, 'equals'), (0, 'equals'),
(1, 'not equal'), (1, 'not equal'),
(2, 'greater than'), (2, 'greater than'),
(3, 'smaller than') (3, 'smaller than'),
(4, 'empty'),
(5, 'in list'),
(6, 'not in list'),
]) ])
# integer - equals
rv = client.get('/admin/model2/?flt0_0=5000')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('test2_val_3' in data)
ok_('test2_val_4' not in data)
# integer - not equal
rv = client.get('/admin/model2/?flt0_1=5000')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('test2_val_3' not in data)
ok_('test2_val_4' in data)
# integer - greater
rv = client.get('/admin/model2/?flt0_2=6000')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('test2_val_3' not in data)
ok_('test2_val_4' in data)
# integer - smaller
rv = client.get('/admin/model2/?flt0_3=6000')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('test2_val_3' in data)
ok_('test2_val_4' not in data)
# integer - empty
rv = client.get('/admin/model2/?flt0_4=1')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('test2_val_1' in data)
ok_('test2_val_2' in data)
ok_('test2_val_3' not in data)
ok_('test2_val_4' not in data)
# integer - not empty
rv = client.get('/admin/model2/?flt0_4=0')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('test2_val_1' not in data)
ok_('test2_val_2' not in data)
ok_('test2_val_3' in data)
ok_('test2_val_4' in data)
# integer - in list
rv = client.get('/admin/model2/?flt0_5=5000%2C9000')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('test2_val_1' not in data)
ok_('test2_val_2' not in data)
ok_('test2_val_3' in data)
ok_('test2_val_4' in data)
# integer - not in list
rv = client.get('/admin/model2/?flt0_6=5000%2C9000')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('test2_val_1' in data)
ok_('test2_val_2' in data)
ok_('test2_val_3' not in data)
ok_('test2_val_4' not in data)
# Test filters to joined table field # Test filters to joined table field
view = CustomModelView( view = CustomModelView(
Model2, db.session, Model2, db.session,
...@@ -424,10 +597,10 @@ def test_column_filters(): ...@@ -424,10 +597,10 @@ def test_column_filters():
rv = client.get('/admin/_model2/?flt1_0=1') rv = client.get('/admin/_model2/?flt1_0=1')
eq_(rv.status_code, 200) eq_(rv.status_code, 200)
data = rv.data.decode('utf-8') data = rv.data.decode('utf-8')
ok_('model2_obj1' in data) ok_('test2_val_1' in data)
ok_('model2_obj2' in data) ok_('test2_val_2' in data)
ok_('model2_obj3' not in data) ok_('test2_val_3' not in data)
ok_('model2_obj4' not in data) ok_('test2_val_4' not in data)
# Test human readable URLs # Test human readable URLs
view = CustomModelView( view = CustomModelView(
...@@ -438,11 +611,11 @@ def test_column_filters(): ...@@ -438,11 +611,11 @@ def test_column_filters():
) )
admin.add_view(view) admin.add_view(view)
rv = client.get('/admin/_model3/?flt1_test1_equals=model1_obj1') rv = client.get('/admin/_model3/?flt1_test1_equals=test1_val_1')
eq_(rv.status_code, 200) eq_(rv.status_code, 200)
data = rv.data.decode('utf-8') data = rv.data.decode('utf-8')
ok_('model1_obj1' in data) ok_('test1_val_1' in data)
ok_('model1_obj2' not in data) ok_('test1_val_2' not in data)
# Test date, time, and datetime filters # Test date, time, and datetime filters
view = CustomModelView(Model1, db.session, view = CustomModelView(Model1, db.session,
...@@ -457,27 +630,30 @@ def test_column_filters(): ...@@ -457,27 +630,30 @@ def test_column_filters():
(2, 'greater than'), (2, 'greater than'),
(3, 'smaller than'), (3, 'smaller than'),
(4, 'between'), (4, 'between'),
(5, 'not between') (5, 'not between'),
(6, 'empty'),
]) ])
eq_([(f['index'], f['operation']) for f in view._filter_groups[u'Datetime Field']], eq_([(f['index'], f['operation']) for f in view._filter_groups[u'Datetime Field']],
[ [
(6, 'equals'), (7, 'equals'),
(7, 'not equal'), (8, 'not equal'),
(8, 'greater than'), (9, 'greater than'),
(9, 'smaller than'), (10, 'smaller than'),
(10, 'between'), (11, 'between'),
(11, 'not between') (12, 'not between'),
(13, 'empty'),
]) ])
eq_([(f['index'], f['operation']) for f in view._filter_groups[u'Time Field']], eq_([(f['index'], f['operation']) for f in view._filter_groups[u'Time Field']],
[ [
(12, 'equals'), (14, 'equals'),
(13, 'not equal'), (15, 'not equal'),
(14, 'greater than'), (16, 'greater than'),
(15, 'smaller than'), (17, 'smaller than'),
(16, 'between'), (18, 'between'),
(17, 'not between') (19, 'not between'),
(20, 'empty'),
]) ])
# date - equals # date - equals
...@@ -487,124 +663,224 @@ def test_column_filters(): ...@@ -487,124 +663,224 @@ def test_column_filters():
ok_('date_obj1' in data) ok_('date_obj1' in data)
ok_('date_obj2' not in data) ok_('date_obj2' not in data)
# datetime - equals # date - not equal
rv = client.get('/admin/_datetime/?flt0_6=2014-04-03+01%3A09%3A00') rv = client.get('/admin/_datetime/?flt0_1=2014-11-17')
eq_(rv.status_code, 200) eq_(rv.status_code, 200)
data = rv.data.decode('utf-8') data = rv.data.decode('utf-8')
ok_('datetime_obj1' in data) ok_('date_obj1' not in data)
ok_('datetime_obj2' not in data) ok_('date_obj2' in data)
# time - equals # date - greater
rv = client.get('/admin/_datetime/?flt0_12=11%3A10%3A09') rv = client.get('/admin/_datetime/?flt0_2=2014-11-16')
eq_(rv.status_code, 200) eq_(rv.status_code, 200)
data = rv.data.decode('utf-8') data = rv.data.decode('utf-8')
ok_('time_obj1' in data) ok_('date_obj1' in data)
ok_('time_obj2' not in data) ok_('date_obj2' not in data)
# date - not equal # date - smaller
rv = client.get('/admin/_datetime/?flt0_1=2014-11-17') rv = client.get('/admin/_datetime/?flt0_3=2014-11-16')
eq_(rv.status_code, 200) eq_(rv.status_code, 200)
data = rv.data.decode('utf-8') data = rv.data.decode('utf-8')
ok_('date_obj1' not in data) ok_('date_obj1' not in data)
ok_('date_obj2' in data) ok_('date_obj2' in data)
# datetime - not equal # date - between
rv = client.get('/admin/_datetime/?flt0_7=2014-04-03+01%3A09%3A00') rv = client.get('/admin/_datetime/?flt0_4=2014-11-13+to+2014-11-20')
eq_(rv.status_code, 200) eq_(rv.status_code, 200)
data = rv.data.decode('utf-8') data = rv.data.decode('utf-8')
ok_('datetime_obj1' not in data) ok_('date_obj1' in data)
ok_('datetime_obj2' in data) ok_('date_obj2' not in data)
# time - not equal # date - not between
rv = client.get('/admin/_datetime/?flt0_13=11%3A10%3A09') rv = client.get('/admin/_datetime/?flt0_5=2014-11-13+to+2014-11-20')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('date_obj1' not in data)
ok_('date_obj2' in data)
# date - empty
rv = client.get('/admin/_datetime/?flt0_6=1')
eq_(rv.status_code, 200) eq_(rv.status_code, 200)
data = rv.data.decode('utf-8') data = rv.data.decode('utf-8')
ok_('time_obj1' not in data) ok_('test1_val_1' in data)
ok_('time_obj2' in data) ok_('date_obj1' not in data)
ok_('date_obj2' not in data)
# date - greater # date - empty
rv = client.get('/admin/_datetime/?flt0_2=2014-11-16') rv = client.get('/admin/_datetime/?flt0_6=0')
eq_(rv.status_code, 200) eq_(rv.status_code, 200)
data = rv.data.decode('utf-8') data = rv.data.decode('utf-8')
ok_('test1_val_1' not in data)
ok_('date_obj1' in data) ok_('date_obj1' in data)
ok_('date_obj2' not in data) ok_('date_obj2' in data)
# datetime - greater # datetime - equals
rv = client.get('/admin/_datetime/?flt0_8=2014-04-03+01%3A08%3A00') rv = client.get('/admin/_datetime/?flt0_7=2014-04-03+01%3A09%3A00')
eq_(rv.status_code, 200) eq_(rv.status_code, 200)
data = rv.data.decode('utf-8') data = rv.data.decode('utf-8')
ok_('datetime_obj1' in data) ok_('datetime_obj1' in data)
ok_('datetime_obj2' not in data) ok_('datetime_obj2' not in data)
# time - greater # datetime - not equal
rv = client.get('/admin/_datetime/?flt0_14=11%3A09%3A09') rv = client.get('/admin/_datetime/?flt0_8=2014-04-03+01%3A09%3A00')
eq_(rv.status_code, 200) eq_(rv.status_code, 200)
data = rv.data.decode('utf-8') data = rv.data.decode('utf-8')
ok_('time_obj1' in data) ok_('datetime_obj1' not in data)
ok_('time_obj2' not in data) ok_('datetime_obj2' in data)
# date - smaller # datetime - greater
rv = client.get('/admin/_datetime/?flt0_3=2014-11-16') rv = client.get('/admin/_datetime/?flt0_9=2014-04-03+01%3A08%3A00')
eq_(rv.status_code, 200) eq_(rv.status_code, 200)
data = rv.data.decode('utf-8') data = rv.data.decode('utf-8')
ok_('date_obj1' not in data) ok_('datetime_obj1' in data)
ok_('date_obj2' in data) ok_('datetime_obj2' not in data)
# datetime - smaller # datetime - smaller
rv = client.get('/admin/_datetime/?flt0_9=2014-04-03+01%3A08%3A00') rv = client.get('/admin/_datetime/?flt0_10=2014-04-03+01%3A08%3A00')
eq_(rv.status_code, 200) eq_(rv.status_code, 200)
data = rv.data.decode('utf-8') data = rv.data.decode('utf-8')
ok_('datetime_obj1' not in data) ok_('datetime_obj1' not in data)
ok_('datetime_obj2' in data) ok_('datetime_obj2' in data)
# time - smaller # datetime - between
rv = client.get('/admin/_datetime/?flt0_15=11%3A09%3A09') rv = client.get('/admin/_datetime/?flt0_11=2014-04-02+00%3A00%3A00+to+2014-11-20+23%3A59%3A59')
eq_(rv.status_code, 200) eq_(rv.status_code, 200)
data = rv.data.decode('utf-8') data = rv.data.decode('utf-8')
ok_('time_obj1' not in data) ok_('datetime_obj1' in data)
ok_('time_obj2' in data) ok_('datetime_obj2' not in data)
# date - between # datetime - not between
rv = client.get('/admin/_datetime/?flt0_4=2014-11-13+to+2014-11-20') rv = client.get('/admin/_datetime/?flt0_12=2014-04-02+00%3A00%3A00+to+2014-11-20+23%3A59%3A59')
eq_(rv.status_code, 200) eq_(rv.status_code, 200)
data = rv.data.decode('utf-8') data = rv.data.decode('utf-8')
ok_('date_obj1' in data) ok_('datetime_obj1' not in data)
ok_('date_obj2' not in data) ok_('datetime_obj2' in data)
# datetime - between # datetime - empty
rv = client.get('/admin/_datetime/?flt0_10=2014-04-02+00%3A00%3A00+to+2014-11-20+23%3A59%3A59') rv = client.get('/admin/_datetime/?flt0_13=1')
eq_(rv.status_code, 200) eq_(rv.status_code, 200)
data = rv.data.decode('utf-8') data = rv.data.decode('utf-8')
ok_('datetime_obj1' in data) ok_('test1_val_1' in data)
ok_('datetime_obj1' not in data)
ok_('datetime_obj2' not in data) ok_('datetime_obj2' not in data)
# time - between # datetime - not empty
rv = client.get('/admin/_datetime/?flt0_16=10%3A40%3A00+to+11%3A50%3A59') rv = client.get('/admin/_datetime/?flt0_13=0')
eq_(rv.status_code, 200) eq_(rv.status_code, 200)
data = rv.data.decode('utf-8') data = rv.data.decode('utf-8')
ok_('time_obj1' in data) ok_('test1_val_1' not in data)
ok_('time_obj2' not in data) ok_('datetime_obj1' in data)
ok_('datetime_obj2' in data)
# date - not between
rv = client.get('/admin/_datetime/?flt0_5=2014-11-13+to+2014-11-20') # time - equals
rv = client.get('/admin/_datetime/?flt0_14=11%3A10%3A09')
eq_(rv.status_code, 200) eq_(rv.status_code, 200)
data = rv.data.decode('utf-8') data = rv.data.decode('utf-8')
ok_('date_obj1' not in data) ok_('timeonly_obj1' in data)
ok_('date_obj2' in data) ok_('timeonly_obj2' not in data)
# datetime - not between # time - not equal
rv = client.get('/admin/_datetime/?flt0_11=2014-04-02+00%3A00%3A00+to+2014-11-20+23%3A59%3A59') rv = client.get('/admin/_datetime/?flt0_15=11%3A10%3A09')
eq_(rv.status_code, 200) eq_(rv.status_code, 200)
data = rv.data.decode('utf-8') data = rv.data.decode('utf-8')
ok_('datetime_obj1' not in data) ok_('timeonly_obj1' not in data)
ok_('datetime_obj2' in data) ok_('timeonly_obj2' in data)
# time - greater
rv = client.get('/admin/_datetime/?flt0_16=11%3A09%3A09')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('timeonly_obj1' in data)
ok_('timeonly_obj2' not in data)
# time - smaller
rv = client.get('/admin/_datetime/?flt0_17=11%3A09%3A09')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('timeonly_obj1' not in data)
ok_('timeonly_obj2' in data)
# time - between
rv = client.get('/admin/_datetime/?flt0_18=10%3A40%3A00+to+11%3A50%3A59')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('timeonly_obj1' in data)
ok_('timeonly_obj2' not in data)
# time - not between # time - not between
rv = client.get('/admin/_datetime/?flt0_17=10%3A40%3A00+to+11%3A50%3A59') rv = client.get('/admin/_datetime/?flt0_19=10%3A40%3A00+to+11%3A50%3A59')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('timeonly_obj1' not in data)
ok_('timeonly_obj2' in data)
# time - empty
rv = client.get('/admin/_datetime/?flt0_20=1')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('test1_val_1' in data)
ok_('timeonly_obj1' not in data)
ok_('timeonly_obj2' not in data)
# time - not empty
rv = client.get('/admin/_datetime/?flt0_20=0')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('test1_val_1' not in data)
ok_('timeonly_obj1' in data)
ok_('timeonly_obj2' in data)
# Test enum filter
view = CustomModelView(Model1, db.session,
column_filters=['enum_field'],
endpoint="_enumfield")
admin.add_view(view)
# enum - equals
rv = client.get('/admin/_enumfield/?flt0_0=model1_v1')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('enum_obj1' in data)
ok_('enum_obj2' not in data)
# enum - not equal
rv = client.get('/admin/_enumfield/?flt0_1=model1_v1')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('enum_obj1' not in data)
ok_('enum_obj2' in data)
# enum - empty
rv = client.get('/admin/_enumfield/?flt0_2=1')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('test1_val_1' in data)
ok_('enum_obj1' not in data)
ok_('enum_obj2' not in data)
# enum - not empty
rv = client.get('/admin/_enumfield/?flt0_2=0')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('test1_val_1' not in data)
ok_('enum_obj1' in data)
ok_('enum_obj2' in data)
# enum - in list
rv = client.get('/admin/_enumfield/?flt0_3=model1_v1%2Cmodel1_v2')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('test1_val_1' not in data)
ok_('enum_obj1' in data)
ok_('enum_obj2' in data)
# enum - not in list
rv = client.get('/admin/_enumfield/?flt0_4=model1_v1%2Cmodel1_v2')
eq_(rv.status_code, 200) eq_(rv.status_code, 200)
data = rv.data.decode('utf-8') data = rv.data.decode('utf-8')
ok_('time_obj1' not in data) ok_('test1_val_1' in data)
ok_('time_obj2' in data) ok_('enum_obj1' not in data)
ok_('enum_obj2' not in data)
def test_url_args(): def test_url_args():
app, db, admin = setup() app, db, admin = setup()
...@@ -937,27 +1213,31 @@ def test_extra_field_order(): ...@@ -937,27 +1213,31 @@ def test_extra_field_order():
def test_modelview_localization(): def test_modelview_localization():
def test_locale(locale): def test_locale(locale):
app, db, admin = setup() try:
app, db, admin = setup()
app.config['BABEL_DEFAULT_LOCALE'] = locale
babel = Babel(app) app.config['BABEL_DEFAULT_LOCALE'] = locale
babel = Babel(app)
Model1, _ = create_models(db)
Model1, _ = create_models(db)
view = CustomModelView(
Model1, db.session, view = CustomModelView(
column_filters=['test1', 'bool_field', 'date_field', 'datetime_field', 'time_field'] Model1, db.session,
) column_filters=['test1', 'bool_field', 'date_field', 'datetime_field', 'time_field']
)
admin.add_view(view)
admin.add_view(view)
client = app.test_client()
client = app.test_client()
rv = client.get('/admin/model1/')
eq_(rv.status_code, 200) rv = client.get('/admin/model1/')
eq_(rv.status_code, 200)
rv = client.get('/admin/model1/new/')
eq_(rv.status_code, 200) rv = client.get('/admin/model1/new/')
eq_(rv.status_code, 200)
except:
print("Error on the following locale:", locale)
raise
locales = ['en', 'cs', 'de', 'es', 'fa', 'fr', 'pt', 'ru', 'zh_CN', 'zh_TW'] locales = ['en', 'cs', 'de', 'es', 'fa', 'fr', 'pt', 'ru', 'zh_CN', 'zh_TW']
for locale in locales: for locale in locales:
......
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