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

Merge pull request #711 from pawl/inlist_filter3

add 'in list' and 'empty' filters, allow changing to filters with options, add js versions, fix bootstrap3+select
parents ec0f1054 5fb50407
Custom filter with SQLAlchemy backend example.
To run this example:
1. Clone the repository::
git clone https://github.com/mrjoes/flask-admin.git
cd flask-admin
2. Create and activate a virtual environment::
virtualenv env
source env/bin/activate
3. Install requirements::
pip install -r 'examples/custom-filter/requirements.txt'
4. Run the application::
python examples/custom-filter/app.py
The first time you run this example, a sample sqlite database gets populated automatically. To suppress this behaviour,
comment the following lines in app.py:::
if not os.path.exists(database_path):
build_sample_db()
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
from flask.ext.admin.contrib import sqla
from flask.ext.admin import expose, Admin
# required for creating custom filters
from flask.ext.admin.contrib.sqla.filters import BaseSQLAFilter, FilterEqual
# Create application
app = Flask(__name__)
# Create dummy secrey key so we can use sessions
app.config['SECRET_KEY'] = '123456790'
# Create in-memory database
app.config['DATABASE_FILE'] = 'sample_db.sqlite'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + app.config['DATABASE_FILE']
app.config['SQLALCHEMY_ECHO'] = True
db = SQLAlchemy(app)
# Create model
class User(db.Model):
def __init__(self, first_name, last_name, username, email):
self.first_name = first_name
self.last_name = last_name
self.username = username
self.email = email
id = db.Column(db.Integer, primary_key=True)
first_name = db.Column(db.String(100))
last_name = db.Column(db.String(100))
username = db.Column(db.String(80), unique=True)
email = db.Column(db.String(120), unique=True)
# Required for administrative interface. For python 3 please use __str__ instead.
def __unicode__(self):
return self.username
# Create custom filter class
class FilterLastNameBrown(BaseSQLAFilter):
def apply(self, query, value):
if value == '1':
return query.filter(self.column == "Brown")
else:
return query.filter(self.column != "Brown")
def operation(self):
return 'is Brown'
# Add custom filter and standard FilterEqual to ModelView
class UserAdmin(sqla.ModelView):
# each filter in the list is a filter operation (equals, not equals, etc)
# filters with the same name will appear as operations under the same filter
column_filters = [
FilterEqual(User.last_name, 'Last Name'),
FilterLastNameBrown(User.last_name, 'Last Name', options=(('1', 'Yes'),('0', 'No')))
]
admin = Admin(app, template_mode="bootstrap3")
admin.add_view(UserAdmin(User, db.session))
def build_sample_db():
db.drop_all()
db.create_all()
user_obj1 = User("Paul", "Brown", "pbrown", "paul@gmail.com")
user_obj2 = User("Luke", "Brown", "lbrown", "luke@gmail.com")
user_obj3 = User("Serge", "Koval", "skoval", "serge@gmail.com")
db.session.add_all([user_obj1, user_obj2, user_obj3])
db.session.commit()
if __name__ == '__main__':
build_sample_db()
app.run(port=5000)
\ No newline at end of file
Flask
Flask-Admin
Flask-SQLAlchemy
...@@ -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
...@@ -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 ')]
...@@ -203,6 +243,9 @@ class TimeSmallerFilter(FilterSmaller, filters.BaseTimeFilter): ...@@ -203,6 +243,9 @@ class TimeSmallerFilter(FilterSmaller, filters.BaseTimeFilter):
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 ')]
...@@ -242,17 +285,26 @@ class TimeNotBetweenFilter(TimeBetweenFilter): ...@@ -242,17 +285,26 @@ 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]
...@@ -260,36 +312,23 @@ class FilterConverter(filters.BaseFilterConverter): ...@@ -260,36 +312,23 @@ class FilterConverter(filters.BaseFilterConverter):
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):
......
...@@ -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')
......
...@@ -60,14 +60,6 @@ table.flters tr td:nth-child(2){ ...@@ -60,14 +60,6 @@ table.flters tr td:nth-child(2){
width: 60%; width: 60%;
} }
.filters input, .filters div.select2-container {
margin-bottom: 0px;
}
.filters div.select2-container {
width: 100% !important;
}
.filters a.remove-filter { .filters a.remove-filter {
margin-bottom: 0; margin-bottom: 0;
display: block; display: block;
...@@ -91,9 +83,13 @@ table.flters tr td:nth-child(2){ ...@@ -91,9 +83,13 @@ table.flters tr td:nth-child(2){
opacity: 0.4; opacity: 0.4;
} }
.filters .filter-op > a { /* filters */
height: 28px; .filters .filter-op {
line-height: 28px; width: 130px;
}
.filters .filter-val {
width: 220px;
} }
/* Inline forms */ /* Inline forms */
...@@ -139,26 +135,6 @@ td>span.glyphicon { ...@@ -139,26 +135,6 @@ td>span.glyphicon {
padding-left: 35%; padding-left: 35%;
} }
/* Patch Select2 */
.select2-results li {
min-height: 24px !important;
}
.select2-container.form-control {
height: auto !important;
padding: 0px;
border-width: 0px;
}
form .select2-choice {
height: 30px !important;
}
.select2-search-choice {
height: 24px !important;
margin-top: 4px !important;
}
/* link style */ /* link style */
a.btn-cancel { a.btn-cancel {
border-radius: 4px; border-radius: 4px;
......
...@@ -19,14 +19,6 @@ var AdminFilters = function(element, filtersElement, filterGroups, activeFilters ...@@ -19,14 +19,6 @@ var AdminFilters = function(element, filtersElement, filterGroups, activeFilters
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) {
...@@ -40,7 +32,75 @@ var AdminFilters = function(element, filtersElement, filterGroups, activeFilters ...@@ -40,7 +32,75 @@ 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-tags") {
var $field = $('<input type="hidden" class="filter-val form-control" />').attr('name', makeName(filter.arg));
$field.val(filterValue);
} else if (filter.options) {
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 {
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);
} else if (filter.options) {
filter.type = "select2";
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
...@@ -54,16 +114,15 @@ var AdminFilters = function(element, filtersElement, filterGroups, activeFilters ...@@ -54,16 +114,15 @@ var AdminFilters = function(element, filtersElement, filterGroups, activeFilters
) )
); );
// 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));
} }
...@@ -73,90 +132,43 @@ var AdminFilters = function(element, filtersElement, filterGroups, activeFilters ...@@ -73,90 +132,43 @@ var AdminFilters = function(element, filtersElement, filterGroups, activeFilters
$('<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) // select2 for filter-op (equal, not equal, etc)
$select.select2({width: 'resolve'}).on("change", function(e) { $select.select2({width: 'resolve'}).on("change", function(e) {
styleFilterInput(subfilters[e.added.element[0].index], $el.find('input').last()); changeOperation(subfilters, $el, filter, $select);
});
// 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) {
$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)); // get filter option from filter_group, only for new filter creation
$field.select2({width: 'resolve'}); var filter = subfilters[filterSelection];
} else var $inputContainer = $('<td/>').appendTo($el);
{
$field = $('<input type="text" class="filter-val form-control" />')
.attr('name', makeName(filter.arg));
$el.append($('<td/>').append($field));
}
styleFilterInput(filter, $field); 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() {
......
...@@ -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;
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
...@@ -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>
......
...@@ -166,6 +166,7 @@ ...@@ -166,6 +166,7 @@
{% macro form_css() %} {% macro form_css() %}
<link href="{{ admin_static.url(filename='vendor/select2/select2.css') }}" rel="stylesheet"> <link href="{{ admin_static.url(filename='vendor/select2/select2.css') }}" rel="stylesheet">
<link href="{{ admin_static.url(filename='vendor/select2/select2-bootstrap3.css') }}" rel="stylesheet">
<link href="{{ admin_static.url(filename='vendor/bootstrap-daterangepicker/daterangepicker-bs3.css') }}" rel="stylesheet"> <link href="{{ admin_static.url(filename='vendor/bootstrap-daterangepicker/daterangepicker-bs3.css') }}" rel="stylesheet">
{% if config.MAPBOX_MAP_ID %} {% if config.MAPBOX_MAP_ID %}
<link href="{{ admin_static.url(filename='vendor/leaflet/leaflet.css') }}" rel="stylesheet"> <link href="{{ admin_static.url(filename='vendor/leaflet/leaflet.css') }}" rel="stylesheet">
...@@ -182,5 +183,5 @@ ...@@ -182,5 +183,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