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')
......
...@@ -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 }});
......
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