Commit c974d407 authored by Serge S. Koval's avatar Serge S. Koval

Merge pull request #703 from pawl/daterange_filters

Add date/datetime/time range filters for SQLAlchemy, combine active/new filter generation logic
parents a0649029 9606f209
......@@ -5,7 +5,7 @@ import datetime
from flask.ext.admin.babel import lazy_gettext
from flask.ext.admin.model import filters
from flask.ext.admin.contrib.sqla import tools
from sqlalchemy.sql import not_
class BaseSQLAFilter(filters.BaseFilter):
"""
......@@ -90,43 +90,156 @@ class BooleanNotEqualFilter(FilterNotEqual, filters.BaseBooleanFilter):
class DateEqualFilter(FilterEqual, filters.BaseDateFilter):
pass
class DateNotEqualFilter(FilterNotEqual, filters.BaseDateFilter):
pass
class DateGreaterFilter(FilterGreater, filters.BaseDateFilter):
pass
class DateSmallerFilter(FilterSmaller, filters.BaseDateFilter):
pass
class DateBetweenFilter(BaseSQLAFilter):
def clean(self, value):
return datetime.datetime.strptime(value, '%Y-%m-%d').date()
return [datetime.datetime.strptime(range, '%Y-%m-%d') for range in value.split(' to ')]
def apply(self, query, value):
start, end = value
return query.filter(self.column.between(start, end))
def operation(self):
return lazy_gettext('between')
def validate(self, value):
try:
self.clean(value)
return True
value = [datetime.datetime.strptime(range, '%Y-%m-%d') for range in value.split(' to ')]
# if " to " is missing, fail validation
# sqlalchemy's .between() will not work if end date is before start date
if (len(value) == 2) and (value[0] <= value[1]):
return True
else:
return False
except ValueError:
return False
return False
class DateNotBetweenFilter(DateBetweenFilter):
def apply(self, query, value):
start, end = value
# ~between() isn't possible until sqlalchemy 1.0.0
return query.filter(not_(self.column.between(start, end)))
def operation(self):
return lazy_gettext('not between')
class DateTimeEqualFilter(FilterEqual, filters.BaseDateTimeFilter):
def clean(self, value):
return datetime.datetime.strptime(value, '%Y-%m-%d %H:%M:%S')
pass
class DateTimeNotEqualFilter(FilterNotEqual, filters.BaseDateTimeFilter):
pass
class DateTimeGreaterFilter(FilterGreater, filters.BaseDateTimeFilter):
pass
class DateTimeSmallerFilter(FilterSmaller, filters.BaseDateTimeFilter):
pass
class DateTimeBetweenFilter(BaseSQLAFilter):
def clean(self, value):
return [datetime.datetime.strptime(range, '%Y-%m-%d %H:%M:%S') for range in value.split(' to ')]
def apply(self, query, value):
start, end = value
return query.filter(self.column.between(start, end))
def operation(self):
return lazy_gettext('between')
def validate(self, value):
try:
self.clean(value)
return True
value = [datetime.datetime.strptime(range, '%Y-%m-%d %H:%M:%S') for range in value.split(' to ')]
if (len(value) == 2) and (value[0] <= value[1]):
return True
else:
return False
except ValueError:
return False
class DateTimeNotBetweenFilter(DateTimeBetweenFilter):
def apply(self, query, value):
start, end = value
return query.filter(not_(self.column.between(start, end)))
def operation(self):
return lazy_gettext('not between')
class TimeEqualFilter(FilterEqual, filters.BaseTimeFilter):
pass
class TimeNotEqualFilter(FilterNotEqual, filters.BaseTimeFilter):
pass
class TimeGreaterFilter(FilterGreater, filters.BaseTimeFilter):
pass
class TimeSmallerFilter(FilterSmaller, filters.BaseTimeFilter):
pass
class TimeBetweenFilter(BaseSQLAFilter):
def clean(self, value):
timetuple = time.strptime(value, '%H:%M:%S')
return datetime.time(timetuple.tm_hour,
timetuple.tm_min,
timetuple.tm_sec)
timetuples = [time.strptime(range, '%H:%M:%S')
for range in value.split(' to ')]
return [datetime.time(timetuple.tm_hour,
timetuple.tm_min,
timetuple.tm_sec)
for timetuple in timetuples]
def apply(self, query, value):
start, end = value
return query.filter(self.column.between(start, end))
def operation(self):
return lazy_gettext('between')
def validate(self, value):
try:
self.clean(value)
return True
timetuples = [time.strptime(range, '%H:%M:%S')
for range in value.split(' to ')]
if (len(timetuples) == 2) and (timetuples[0] <= timetuples[1]):
return True
else:
return False
except ValueError:
raise
return False
class TimeNotBetweenFilter(TimeBetweenFilter):
def apply(self, query, value):
start, end = value
return query.filter(not_(self.column.between(start, end)))
def operation(self):
return lazy_gettext('not between')
# Base SQLA filter field converter
class FilterConverter(filters.BaseFilterConverter):
strings = (FilterEqual, FilterNotEqual, FilterLike, FilterNotLike)
......@@ -137,7 +250,6 @@ class FilterConverter(filters.BaseFilterConverter):
def convert(self, type_name, column, name, **kwargs):
if type_name.lower() in self.converters:
return self.converters[type_name.lower()](column, name, **kwargs)
return None
@filters.convert('string', 'unicode', 'text', 'unicodetext', 'varchar')
......@@ -155,23 +267,29 @@ class FilterConverter(filters.BaseFilterConverter):
@filters.convert('date')
def conv_date(self, column, name, **kwargs):
return [DateEqualFilter(column, name),
FilterNotEqual(column, name, data_type='datepicker', **kwargs),
FilterGreater(column, name, data_type='datepicker', **kwargs),
FilterSmaller(column, name, data_type='datepicker', **kwargs)]
DateNotEqualFilter(column, name),
DateGreaterFilter(column, name),
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):
return [DateTimeEqualFilter(column, name),
FilterNotEqual(column, name, data_type='datetimepicker', **kwargs),
FilterGreater(column, name, data_type='datetimepicker', **kwargs),
FilterSmaller(column, name, data_type='datetimepicker', **kwargs)]
DateTimeNotEqualFilter(column, name),
DateTimeGreaterFilter(column, name),
DateTimeSmallerFilter(column, name),
DateTimeBetweenFilter(column, name, data_type='datetimerangepicker'),
DateTimeNotBetweenFilter(column, name, data_type='datetimerangepicker')]
@filters.convert('time')
def conv_time(self, column, name, **kwargs):
return [TimeEqualFilter(column, name),
FilterNotEqual(column, name, data_type='timepicker', **kwargs),
FilterGreater(column, name, data_type='timepicker', **kwargs),
FilterSmaller(column, name, data_type='timepicker', **kwargs)]
TimeNotEqualFilter(column, name),
TimeGreaterFilter(column, name),
TimeSmallerFilter(column, name),
TimeBetweenFilter(column, name, data_type='timerangepicker'),
TimeNotBetweenFilter(column, name, data_type='timerangepicker')]
@filters.convert('enum')
def conv_enum(self, column, name, options=None, **kwargs):
......
......@@ -768,7 +768,7 @@ class ModelView(BaseModelView):
# Apply filters
if filters and self._filters:
for idx, value in filters:
for idx, flt_name, value in filters:
flt = self._filters[idx]
# Figure out joins
......@@ -782,9 +782,9 @@ class ModelView(BaseModelView):
count_query = count_query.join(table)
joins.add(table.name)
# Apply filter
query = flt.apply(query, value)
count_query = flt.apply(count_query, value)
# turn into python format with .clean() and apply filter
query = flt.apply(query, flt.clean(value))
count_query = flt.apply(count_query, flt.clean(value))
# Calculate number of rows
count = count_query.scalar()
......
......@@ -1109,11 +1109,11 @@ class BaseModelView(BaseView, ActionsMixin):
if key in self._filter_args:
idx, flt = self._filter_args[key]
value = request.args[n]
if flt.validate(value):
filters.append((pos, (idx, flt.clean(value))))
filters.append((pos, (idx, flt.name, value)))
else:
flash(gettext('Invalid Filter Value: %(value)s', value=value))
......@@ -1151,7 +1151,7 @@ class BaseModelView(BaseView, ActionsMixin):
if view_args.filters:
for i, pair in enumerate(view_args.filters):
idx, value = pair
idx, flt_name, value = pair
key = 'flt%d_%s' % (i, self.get_filter_arg(idx, self._filters[idx]))
kwargs[key] = value
......@@ -1419,4 +1419,4 @@ class BaseModelView(BaseView, ActionsMixin):
abort(404)
data = [loader.format(m) for m in loader.get_list(query, offset, limit)]
return Response(json.dumps(data), mimetype='application/json')
return Response(json.dumps(data), mimetype='application/json')
\ No newline at end of file
import time
import datetime
from flask.ext.admin._compat import text_type
from flask.ext.admin.babel import lazy_gettext
......@@ -45,15 +48,20 @@ class BaseFilter(object):
Validate value.
If value is valid, returns `True` and `False` otherwise.
:param value:
Value to validate
"""
return True
# useful for filters with date conversions, see if conversion in clean() raises ValueError
try:
self.clean(value)
return True
except ValueError:
return False
def clean(self, value):
"""
Parse value into python format.
Parse value into python format. Occurs before .apply()
:param value:
Value to parse
......@@ -105,9 +113,8 @@ class BaseDateFilter(BaseFilter):
options,
data_type='datepicker')
def validate(self, value):
# TODO: Validation
return True
def clean(self, value):
return datetime.datetime.strptime(value, '%Y-%m-%d').date()
class BaseDateTimeFilter(BaseFilter):
......@@ -118,10 +125,10 @@ class BaseDateTimeFilter(BaseFilter):
super(BaseDateTimeFilter, self).__init__(name,
options,
data_type='datetimepicker')
def validate(self, value):
# TODO: Validation
return True
def clean(self, value):
# datetime filters will not work in SQLite + SQLAlchemy if value not converted to datetime
return datetime.datetime.strptime(value, '%Y-%m-%d %H:%M:%S')
class BaseTimeFilter(BaseFilter):
......@@ -133,10 +140,13 @@ class BaseTimeFilter(BaseFilter):
options,
data_type='timepicker')
def validate(self, value):
# TODO: Validation
return True
def clean(self, value):
# time filters will not work in SQLite + SQLAlchemy if value not converted to time
timetuple = time.strptime(value, '%H:%M:%S')
return datetime.time(timetuple.tm_hour,
timetuple.tm_min,
timetuple.tm_sec)
def convert(*args):
"""
......
var AdminFilters = function(element, filtersElement, filterGroups) {
var AdminFilters = function(element, filtersElement, filterGroups, activeFilters) {
var $root = $(element);
var $container = $('.filters', $root);
var lastCount = 0;
......@@ -40,7 +40,7 @@ var AdminFilters = function(element, filtersElement, filterGroups) {
return false;
}
function addFilter(name, subfilters) {
function addFilter(name, subfilters, selected, filterValue) {
var $el = $('<tr />').appendTo($container);
// Filter list
......@@ -58,19 +58,45 @@ var AdminFilters = function(element, filtersElement, filterGroups) {
var $select = $('<select class="filter-op" />')
.change(changeOperation);
$(subfilters).each(function() {
$select.append($('<option/>').attr('value', this.arg).text(this.operation));
// if one of the subfilters are selected, use that subfilter to create the input field
var filter_selection = 0;
$.each(subfilters, function( subfilterIndex, subfilter ) {
if (this.arg == selected) {
$select.append($('<option/>').attr('value', subfilter.arg).attr('selected', true).text(subfilter.operation));
filter_selection = subfilterIndex;
} else {
$select.append($('<option/>').attr('value', subfilter.arg).text(subfilter.operation));
}
});
$el.append(
$('<td/>').append($select)
);
$select.select2({width: 'resolve'});
// Input
var filter = subfilters[0];
// 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
filter = subfilters[filter_selection];
var $field;
if (filter.options) {
......@@ -78,8 +104,14 @@ var AdminFilters = function(element, filtersElement, filterGroups) {
.attr('name', makeName(filter.arg));
$(filter.options).each(function() {
$field.append($('<option/>')
.val(this[0]).text(this[1]));
// 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));
......@@ -90,31 +122,36 @@ var AdminFilters = function(element, filtersElement, filterGroups) {
.attr('name', makeName(filter.arg));
$el.append($('<td/>').append($field));
}
if (filter.type) {
$field.attr('data-role', filter.type);
if (filter.type == "datepicker") {
$field.attr('data-date-format', "YYYY-MM-DD");
}
else if (filter.type == "datetimepicker") {
$field.attr('data-date-format', "YYYY-MM-DD HH:mm:ss");
}
else if (filter.type == "timepicker") {
$field.attr('data-date-format', "HH:mm:ss");
}
faForm.applyStyle($field, filter.type);
}
styleFilterInput(filter, $field);
return $field;
}
$('a.filter', filtersElement).click(function() {
var name = ($(this).text().trim !== undefined ? $(this).text().trim() : $(this).text().replace(/^\s+|\s+$/g,''));
addFilter(name, filterGroups[name]);
addFilter(name, filterGroups[name], false, false);
$('button', $root).show();
//return false;
});
if(activeFilters.length > 0){
$('button', $root).show();
}
// add active filters on page load
$.each(activeFilters, function( activeIndex, activeFilter ) {
var idx = activeFilter[0],
name = activeFilter[1],
filterValue = activeFilter[2];
$field = addFilter(name, filterGroups[name], idx, filterValue);
// set value of newly created field
$field.val(filterValue);
});
$('.filter-op', $root).change(changeOperation);
$('.filter-val', $root).change(function() {
......@@ -122,7 +159,7 @@ var AdminFilters = function(element, filtersElement, filterGroups) {
});
$('.remove-filter', $root).click(removeFilter);
$('.filter-val', $root).each(function() {
$('.filter-val', $root).not('.select2-container').each(function() {
var count = getCount($(this).attr('name'));
if (count > lastCount)
lastCount = count;
......
......@@ -107,49 +107,95 @@
return true;
case 'datepicker':
$el.daterangepicker({
// TODO: Have separate converters for bs2 and bs3
// Bootstrap 2 option
// Bootstrap 3 option
timePicker: false,
showDropdowns: true,
singleDatePicker: true,
format: $el.attr('data-date-format')
showDropdowns: true,
singleDatePicker: true,
format: $el.attr('data-date-format')
},
function(start, end) {
$('.filter-val').trigger("change");
});
function(start, end) {
$('.filter-val').trigger("change");
});
return true;
case 'daterangepicker':
$el.daterangepicker({
timePicker: false,
showDropdowns: true,
separator: ' to ',
format: $el.attr('data-date-format')
},
function(start, end) {
$('.filter-val').trigger("change");
});
return true;
case 'datetimepicker':
$el.daterangepicker({
timePicker: true,
showDropdowns: true,
singleDatePicker: true,
timePickerIncrement: 1,
timePicker12Hour: false,
format: $el.attr('data-date-format')
},
function(start, end) {
$('.filter-val').trigger("change");
});
timePicker: true,
showDropdowns: true,
singleDatePicker: true,
timePickerIncrement: 1,
timePicker12Hour: false,
format: $el.attr('data-date-format')
},
function(start, end) {
$('.filter-val').trigger("change");
});
return true;
case 'datetimerangepicker':
$el.daterangepicker({
timePicker: true,
showDropdowns: true,
timePickerIncrement: 1,
timePicker12Hour: false,
separator: ' to ',
format: $el.attr('data-date-format')
},
function(start, end) {
$('.filter-val').trigger("change");
});
return true;
case 'timepicker':
$el.daterangepicker({
// Bootstrap 2 option
timePicker: true,
showDropdowns: true,
format: $el.attr('data-date-format'),
timePicker12Hour: false,
timePickerIncrement: 1,
singleDatePicker: true
timePicker: true,
showDropdowns: true,
format: $el.attr('data-date-format'),
timePicker12Hour: false,
timePickerIncrement: 1,
singleDatePicker: true
},
function(start, end) {
$('.filter-val').trigger("change");
});
// hack to hide calendar to create a time-only picker
$el.data('daterangepicker').container.find('.calendar-date').hide();
$el.on('showCalendar.daterangepicker', function (event, data) {
var $container = data.container;
$container.find('.calendar-date').remove();
});
return true;
case 'timerangepicker':
$el.daterangepicker({
// Bootstrap 2 option
timePicker: true,
showDropdowns: true,
format: $el.attr('data-date-format'),
timePicker12Hour: false,
separator: ' to ',
timePickerIncrement: 1
},
function(start, end) {
$('.filter-val').trigger("change");
});
$el.data('daterangepicker').container.find('.calendar-date').hide();
$el.on('showCalendar.daterangepicker', function (event, data) {
var $container = data.container;
$container.find('.calendar-date').remove();
});
function(start, end) {
$('.filter-val').trigger("change");
});
// hack - hide calendar + range inputs
$el.data('daterangepicker').container.find('.calendar-date').hide();
$el.data('daterangepicker').container.find('.daterangepicker_start_input').hide();
$el.data('daterangepicker').container.find('.daterangepicker_end_input').hide();
// hack - add TO between time inputs
$el.data('daterangepicker').container.find('.left').before($('<div style="float: right; margin-top: 20px; padding-left: 5px; padding-right: 5px;"> to </span>'));
$el.on('showCalendar.daterangepicker', function (event, data) {
var $container = data.container;
$container.find('.calendar-date').remove();
});
return true;
}
};
......@@ -183,7 +229,7 @@
prefix = id + '-' + idx;
}
// Get tempalate
// Get template
var $template = $($el.find('> .inline-field-template').text());
// Set form ID
......
......@@ -20,38 +20,7 @@
{% endif %}
</div>
<table class="filters">
{%- for n, values in enumerate(active_filters) -%}
<tr>
{% set idx, value = values %}
{% set filter = filters[idx] %}
{% set filter_arg = admin_view.get_filter_arg(idx, filter) %}
<td>
<a href="javascript:void(0)" class="btn remove-filter" title="{{ _gettext('Remove Filter') }}">
<span class="close-icon">&times;</span>&nbsp;{{ filter.name }}
</a>
</td>
<td>
<select class="filter-op" data-role="select2">
{% for op in filter_groups[filter.name] %}
<option value="{{ op['arg'] }}"{% if idx == op['index'] %} selected="selected"{% endif %}>{{ op['operation'] }}</option>
{% endfor %}
</select>
</td>
<td>
{%- if filter.options -%}
<select name="flt{{n}}_{{ filter_arg }}" class="filter-val" data-role="select2">
{%- for d in filter.options %}
<option value="{{ d[0] }}"{% if value == d[0] %} selected{% endif %}>{{ d[1] }}</option>
{%- endfor %}
</select>
{%- else -%}
<input name="flt{{n}}_{{ filter_arg }}" type="text" value="{{ value or '' }}" class="filter-val"{% if filter.data_type %} data-role="{{ filter.data_type }}"{% endif %} {% if filter.data_type == "datepicker" %}data-date-format="YYYY-MM-DD"{% elif filter.data_type == "datetimepicker" %}data-date-format="YYYY-MM-DD HH:mm:ss"{% elif filter.data_type == "timepicker" %}data-date-format="HH:mm:ss"{% endif %}></input>
{%- endif -%}
</td>
</tr>
{%- endfor -%}
</table>
<table class="filters"></table>
</form>
<div class="clearfix"></div>
{% endmacro %}
......@@ -59,21 +28,21 @@
{% macro search_form(input_class="span2") %}
<form method="GET" action="{{ return_url }}" class="search-form">
{% if sort_column is not none %}
<input type="hidden" name="sort" value="{{ sort_column }}"></input>
<input type="hidden" name="sort" value="{{ sort_column }}">
{% endif %}
{% if sort_desc %}
<input type="hidden" name="desc" value="{{ sort_desc }}"></input>
<input type="hidden" name="desc" value="{{ sort_desc }}">
{% endif %}
{% if search %}
<div class="input-append">
<input type="text" name="search" value="{{ search }}" class="{{ input_class }}" placeholder="{{ _gettext('Search') }}"></input>
<input type="text" name="search" value="{{ search }}" class="{{ input_class }}" placeholder="{{ _gettext('Search') }}">
<a href="{{ clear_search_url }}" class="clear add-on">
<i class="icon-remove"></i>
</a>
</div>
{% else %}
<div>
<input type="text" name="search" value="" class="{{ input_class }}" placeholder="{{ _gettext('Search') }}"></input>
<input type="text" name="search" value="" class="{{ input_class }}" placeholder="{{ _gettext('Search') }}">
</div>
{% endif %}
</form>
......
......@@ -160,8 +160,9 @@
{% if filter_groups %}
var filter = new AdminFilters(
'#filter_form', '.field-filters',
{{ filter_groups|tojson|safe }}
);
{{ filter_groups|tojson|safe }},
{{ active_filters|tojson|safe }}
);
{% endif %}
})(jQuery);
</script>
......
......@@ -20,38 +20,7 @@
{% endif %}
</div>
<table class="filters">
{%- for n, values in enumerate(active_filters) -%}
<tr>
{% set idx, value = values %}
{% set filter = filters[idx] %}
{% set filter_arg = admin_view.get_filter_arg(idx, filter) %}
<td>
<a href="javascript:void(0)" class="btn btn-filter remove-filter" title="{{ _gettext('Remove Filter') }}">
<span class="close-icon">&times;</span>&nbsp;{{ filter.name }}
</a>
</td>
<td>
<select class="filter-op" data-role="select2">
{% for op in filter_groups[filter.name] %}
<option value="{{ op['arg'] }}"{% if idx == op['index'] %} selected="selected"{% endif %}>{{ op['operation'] }}</option>
{% endfor %}
</select>
</td>
<td>
{%- if filter.options -%}
<select name="flt{{n}}_{{ filter_arg }}" class="filter-val" data-role="select2">
{%- for d in filter.options %}
<option value="{{ d[0] }}"{% if value == d[0] %} selected{% endif %}>{{ d[1] }}</option>
{%- endfor %}
</select>
{%- else -%}
<input name="flt{{n}}_{{ filter_arg }}" type="text" value="{{ value or '' }}" class="filter-val form-control"{% if filter.data_type %} data-role="{{ filter.data_type }}"{% endif %} {% if filter.data_type == "datepicker" %}data-date-format="YYYY-MM-DD"{% elif filter.data_type == "datetimepicker" %}data-date-format="YYYY-MM-DD HH:mm:ss"{% elif filter.data_type == "timepicker" %}data-date-format="HH:mm:ss"{% endif %}></input>
{%- endif -%}
</td>
</tr>
{%- endfor -%}
</table>
<table class="filters"></table>
</form>
<div class="clearfix"></div>
{% endmacro %}
......@@ -59,21 +28,21 @@
{% macro search_form(input_class="span2") %}
<form method="GET" action="{{ return_url }}" class="navbar-form navbar-left" role="search">
{% if sort_column is not none %}
<input type="hidden" name="sort" value="{{ sort_column }}"></input>
<input type="hidden" name="sort" value="{{ sort_column }}">
{% endif %}
{% if sort_desc %}
<input type="hidden" name="desc" value="{{ sort_desc }}"></input>
<input type="hidden" name="desc" value="{{ sort_desc }}">
{% endif %}
{% if search %}
<div class="input-append form-group">
<input type="text" name="search" value="{{ search }}" class="{{ input_class }} form-control" placeholder="{{ _gettext('Search') }}"></input>
<input type="text" name="search" value="{{ search }}" class="{{ input_class }} form-control" placeholder="{{ _gettext('Search') }}">
<a href="{{ clear_search_url }}" class="clear add-on">
<span class="glyphicon glyphicon-remove"></span>
</a>
</div>
{% else %}
<div class="form-group">
<input type="text" name="search" value="" class="{{ input_class }} form-control" placeholder="{{ _gettext('Search') }}"></input>
<input type="text" name="search" value="" class="{{ input_class }} form-control" placeholder="{{ _gettext('Search') }}">
</div>
{% endif %}
</form>
......
......@@ -160,7 +160,8 @@
{% if filter_groups %}
var filter = new AdminFilters(
'#filter_form', '.field-filters',
{{ filter_groups|tojson|safe }}
{{ filter_groups|tojson|safe }},
{{ active_filters|tojson|safe }}
);
{% endif %}
})(jQuery);
......
......@@ -454,23 +454,29 @@ def test_column_filters():
(0, 'equals'),
(1, 'not equal'),
(2, 'greater than'),
(3, 'smaller than')
(3, 'smaller than'),
(4, 'between'),
(5, 'not between')
])
eq_([(f['index'], f['operation']) for f in view._filter_groups[u'Datetime Field']],
[
(4, 'equals'),
(5, 'not equal'),
(6, 'greater than'),
(7, 'smaller than')
(6, 'equals'),
(7, 'not equal'),
(8, 'greater than'),
(9, 'smaller than'),
(10, 'between'),
(11, 'not between')
])
eq_([(f['index'], f['operation']) for f in view._filter_groups[u'Time Field']],
[
(8, 'equals'),
(9, 'not equal'),
(10, 'greater than'),
(11, 'smaller than')
(12, 'equals'),
(13, 'not equal'),
(14, 'greater than'),
(15, 'smaller than'),
(16, 'between'),
(17, 'not between')
])
# date - equals
......@@ -481,19 +487,124 @@ def test_column_filters():
ok_('date_obj2' not in data)
# datetime - equals
rv = client.get('/admin/_datetime/?flt0_4=2014-04-03+01%3A09%3A00')
rv = client.get('/admin/_datetime/?flt0_6=2014-04-03+01%3A09%3A00')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('datetime_obj1' in data)
ok_('datetime_obj2' not in data)
# time - equals
rv = client.get('/admin/_datetime/?flt0_8=11%3A10%3A09')
rv = client.get('/admin/_datetime/?flt0_12=11%3A10%3A09')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('time_obj1' in data)
ok_('time_obj2' not in data)
# date - not equal
rv = client.get('/admin/_datetime/?flt0_1=2014-11-17')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('date_obj1' not in data)
ok_('date_obj2' in data)
# datetime - not equal
rv = client.get('/admin/_datetime/?flt0_7=2014-04-03+01%3A09%3A00')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('datetime_obj1' not in data)
ok_('datetime_obj2' in data)
# time - not equal
rv = client.get('/admin/_datetime/?flt0_13=11%3A10%3A09')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('time_obj1' not in data)
ok_('time_obj2' in data)
# date - greater
rv = client.get('/admin/_datetime/?flt0_2=2014-11-16')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('date_obj1' in data)
ok_('date_obj2' not in data)
# datetime - greater
rv = client.get('/admin/_datetime/?flt0_8=2014-04-03+01%3A08%3A00')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('datetime_obj1' in data)
ok_('datetime_obj2' not in data)
# time - greater
rv = client.get('/admin/_datetime/?flt0_14=11%3A09%3A09')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('time_obj1' in data)
ok_('time_obj2' not in data)
# date - smaller
rv = client.get('/admin/_datetime/?flt0_3=2014-11-16')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('date_obj1' not in data)
ok_('date_obj2' in data)
# datetime - smaller
rv = client.get('/admin/_datetime/?flt0_9=2014-04-03+01%3A08%3A00')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('datetime_obj1' not in data)
ok_('datetime_obj2' in data)
# time - smaller
rv = client.get('/admin/_datetime/?flt0_15=11%3A09%3A09')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('time_obj1' not in data)
ok_('time_obj2' in data)
# date - between
rv = client.get('/admin/_datetime/?flt0_4=2014-11-13+to+2014-11-20')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('date_obj1' in data)
ok_('date_obj2' not in data)
# datetime - between
rv = client.get('/admin/_datetime/?flt0_10=2014-04-02+00%3A00%3A00+to+2014-11-20+23%3A59%3A59')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('datetime_obj1' in data)
ok_('datetime_obj2' not in data)
# time - between
rv = client.get('/admin/_datetime/?flt0_16=10%3A40%3A00+to+11%3A50%3A59')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('time_obj1' in data)
ok_('time_obj2' not in data)
# date - not between
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)
# datetime - not between
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)
data = rv.data.decode('utf-8')
ok_('datetime_obj1' not in data)
ok_('datetime_obj2' in data)
# time - not between
rv = client.get('/admin/_datetime/?flt0_17=10%3A40%3A00+to+11%3A50%3A59')
eq_(rv.status_code, 200)
data = rv.data.decode('utf-8')
ok_('time_obj1' not in data)
ok_('time_obj2' in data)
def test_url_args():
app, db, admin = setup()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment