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
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_
from sqlalchemy.sql import not_, or_
class BaseSQLAFilter(filters.BaseFilter):
"""
......@@ -80,6 +80,40 @@ class FilterSmaller(BaseSQLAFilter):
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
class BooleanEqualFilter(FilterEqual, filters.BaseBooleanFilter):
pass
......@@ -88,7 +122,7 @@ class BooleanEqualFilter(FilterEqual, filters.BaseBooleanFilter):
class BooleanNotEqualFilter(FilterNotEqual, filters.BaseBooleanFilter):
pass
class DateEqualFilter(FilterEqual, filters.BaseDateFilter):
pass
......@@ -106,6 +140,9 @@ class DateSmallerFilter(FilterSmaller, filters.BaseDateFilter):
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):
return [datetime.datetime.strptime(range, '%Y-%m-%d') for range in value.split(' to ')]
......@@ -156,6 +193,9 @@ class DateTimeSmallerFilter(FilterSmaller, filters.BaseDateTimeFilter):
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):
return [datetime.datetime.strptime(range, '%Y-%m-%d %H:%M:%S') for range in value.split(' to ')]
......@@ -175,7 +215,7 @@ class DateTimeBetweenFilter(BaseSQLAFilter):
return False
except ValueError:
return False
class DateTimeNotBetweenFilter(DateTimeBetweenFilter):
def apply(self, query, value):
......@@ -188,21 +228,24 @@ class DateTimeNotBetweenFilter(DateTimeBetweenFilter):
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 __init__(self, column, name, options=None, data_type=None):
super(TimeBetweenFilter, self).__init__(column, name, options, data_type='timerangepicker')
def clean(self, value):
timetuples = [time.strptime(range, '%H:%M:%S')
for range in value.split(' to ')]
......@@ -210,7 +253,7 @@ class TimeBetweenFilter(BaseSQLAFilter):
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))
......@@ -242,57 +285,55 @@ class TimeNotBetweenFilter(TimeBetweenFilter):
# Base SQLA filter field converter
class FilterConverter(filters.BaseFilterConverter):
strings = (FilterEqual, FilterNotEqual, FilterLike, FilterNotLike)
numeric = (FilterEqual, FilterNotEqual, FilterGreater, FilterSmaller)
strings = (FilterEqual, FilterNotEqual, FilterLike, FilterNotLike, FilterEmpty, FilterInList, FilterNotInList)
numeric = (FilterEqual, FilterNotEqual, FilterGreater, FilterSmaller, FilterEmpty, FilterInList, FilterNotInList)
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):
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')
@filters.convert('string', 'char', 'unicode', 'varchar', 'tinytext',
'text', 'mediumtext', 'longtext', 'unicodetext',
'nchar', 'nvarchar', 'ntext')
def conv_string(self, column, name, **kwargs):
return [f(column, name, **kwargs) for f in self.strings]
@filters.convert('boolean', 'tinyint')
def conv_bool(self, column, name, **kwargs):
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):
return [f(column, name, **kwargs) for f in self.numeric]
@filters.convert('date')
def conv_date(self, column, name, **kwargs):
return [DateEqualFilter(column, name),
DateNotEqualFilter(column, name),
DateGreaterFilter(column, name),
DateSmallerFilter(column, name),
DateBetweenFilter(column, name, data_type='daterangepicker'),
DateNotBetweenFilter(column, name, data_type='daterangepicker')]
@filters.convert('datetime')
return [f(column, name, **kwargs) for f in self.date_filters]
@filters.convert('datetime', 'datetime2', 'timestamp', 'smalldatetime')
def conv_datetime(self, column, name, **kwargs):
return [DateTimeEqualFilter(column, name),
DateTimeNotEqualFilter(column, name),
DateTimeGreaterFilter(column, name),
DateTimeSmallerFilter(column, name),
DateTimeBetweenFilter(column, name, data_type='datetimerangepicker'),
DateTimeNotBetweenFilter(column, name, data_type='datetimerangepicker')]
return [f(column, name, **kwargs) for f in self.datetime_filters]
@filters.convert('time')
def conv_time(self, column, name, **kwargs):
return [TimeEqualFilter(column, name),
TimeNotEqualFilter(column, name),
TimeGreaterFilter(column, name),
TimeSmallerFilter(column, name),
TimeBetweenFilter(column, name, data_type='timerangepicker'),
TimeNotBetweenFilter(column, name, data_type='timerangepicker')]
return [f(column, name, **kwargs) for f in self.time_filters]
@filters.convert('enum')
def conv_enum(self, column, name, options=None, **kwargs):
# set all operations to select2
kwargs['data_type'] = "select2"
if not options:
options = [
(v, v)
......
......@@ -93,7 +93,7 @@ class Select2Field(fields.SelectField):
"""
`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.
"""
widget = admin_widgets.Select2Widget()
......@@ -141,7 +141,7 @@ class Select2Field(fields.SelectField):
class Select2TagsField(fields.StringField):
"""`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()
......
......@@ -15,7 +15,7 @@ class Select2Widget(widgets.Select):
"""
`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.
"""
def __call__(self, field, **kwargs):
......@@ -30,7 +30,7 @@ class Select2Widget(widgets.Select):
class Select2TagsWidget(widgets.TextInput):
"""`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):
kwargs.setdefault('data-role', u'select2')
......@@ -43,7 +43,7 @@ class DatePickerWidget(widgets.TextInput):
"""
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):
kwargs.setdefault('data-role', u'datepicker')
......@@ -57,7 +57,7 @@ class DateTimePickerWidget(widgets.TextInput):
"""
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):
kwargs.setdefault('data-role', u'datetimepicker')
......@@ -69,7 +69,7 @@ class TimePickerWidget(widgets.TextInput):
"""
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):
kwargs.setdefault('data-role', u'timepicker')
......
......@@ -98,7 +98,7 @@ class BaseBooleanFilter(BaseFilter):
super(BaseBooleanFilter, self).__init__(name,
(('1', lazy_gettext(u'Yes')),
('0', lazy_gettext(u'No'))),
data_type)
data_type="select2")
def validate(self, value):
return value in ('0', '1')
......@@ -112,7 +112,7 @@ class BaseDateFilter(BaseFilter):
super(BaseDateFilter, self).__init__(name,
options,
data_type='datepicker')
def clean(self, value):
return datetime.datetime.strptime(value, '%Y-%m-%d').date()
......@@ -139,7 +139,7 @@ class BaseTimeFilter(BaseFilter):
super(BaseTimeFilter, self).__init__(name,
options,
data_type='timepicker')
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')
......
......@@ -259,6 +259,39 @@
$el.select2(opts);
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':
processAjaxWidget($el, name);
return true;
......
......@@ -24,7 +24,7 @@
{% macro script(message, actions, actions_confirmation) %}
{% 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">
var modelActions = new AdminModelActions({{ message|tojson|safe }}, {{ actions_confirmation|tojson|safe }});
</script>
......
......@@ -189,5 +189,5 @@
<script src="{{ admin_static.url(filename='vendor/leaflet/leaflet.draw.js') }}"></script>
{% endif %}
<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 %}
......@@ -145,7 +145,7 @@
{% block tail %}
{{ super() }}
{{ 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.'),
actions,
......
......@@ -21,7 +21,7 @@
{% block tail %}
{{ 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">
$(function() {
var redisCli = new RedisCli({{ get_url('.execute_view')|tojson }});
......
......@@ -24,7 +24,7 @@
{% macro script(message, actions, actions_confirmation) %}
{% 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">
var modelActions = new AdminModelActions({{ message|tojson|safe }}, {{ actions_confirmation|tojson|safe }});
</script>
......
......@@ -182,5 +182,5 @@
<script src="{{ admin_static.url(filename='vendor/leaflet/leaflet.draw.js') }}"></script>
{% endif %}
<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 %}
......@@ -144,7 +144,7 @@
{% block tail %}
{{ 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() }}
{{ actionlib.script(_gettext('Please select at least one record.'),
......
......@@ -21,7 +21,7 @@
{% block tail %}
{{ 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">
$(function() {
var redisCli = new RedisCli({{ admin_view.get_url('.execute_view')|tojson }});
......
This diff is collapsed.
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