initial commit
commit
94e57d4e8c
|
|
@ -0,0 +1,10 @@
|
|||
__pycache__/
|
||||
.python-version
|
||||
.coverage
|
||||
.coveralls.yml
|
||||
db.sqlite3*
|
||||
/django_admin_more_filters.egg-info/
|
||||
/build/
|
||||
/dist/
|
||||
/.tox/
|
||||
/.vscode/
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
Copyright (c) 2020, Thomas Leichtfuß.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the author nor the names of contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
include LICENSE
|
||||
include README.rst
|
||||
recursive-include more_filters/templates *
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class DjangoAdminSelectfilterConfig(AppConfig):
|
||||
name = 'django_admin_more_filters'
|
||||
|
|
@ -0,0 +1,423 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from django.contrib.admin.utils import prepare_lookup_value
|
||||
from django.contrib import admin
|
||||
from django.db.models import Q
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.contrib.admin.utils import reverse_field_path
|
||||
from django.contrib.admin.utils import get_model_from_relation
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.contrib.admin.options import IncorrectLookupParameters
|
||||
from django.contrib.admin.filters import AllValuesFieldListFilter
|
||||
from django.contrib.admin.filters import ChoicesFieldListFilter
|
||||
from django.contrib.admin.filters import RelatedFieldListFilter
|
||||
from django.contrib.admin.filters import RelatedOnlyFieldListFilter
|
||||
|
||||
|
||||
class SelectFilter(admin.SimpleListFilter):
|
||||
title = _('Selection')
|
||||
parameter_name = 'selected'
|
||||
parameter_inverse = 'inverse'
|
||||
template = 'selectfilter.html'
|
||||
|
||||
def __init__(self, request, params, model, model_admin):
|
||||
super(SelectFilter, self).__init__(request, params, model, model_admin)
|
||||
self.inverse = eval(params.pop(self.parameter_inverse, 'False'))
|
||||
|
||||
def has_output(self):
|
||||
return True
|
||||
|
||||
def lookups(self, request, model_admin):
|
||||
return ()
|
||||
|
||||
def queryset(self, request, queryset):
|
||||
if not self.value(): return
|
||||
if self.inverse:
|
||||
return queryset.exclude(id__in=self.value().split(','))
|
||||
else:
|
||||
return queryset.filter(id__in=self.value().split(','))
|
||||
|
||||
def choices(self, changelist):
|
||||
exclude = [self.parameter_name, self.parameter_inverse]
|
||||
yield {
|
||||
'selected': self.value() is None,
|
||||
'query_string': changelist.get_query_string({}, exclude),
|
||||
'display': _('All'),
|
||||
}
|
||||
yield {
|
||||
'selected': bool(self.value()),
|
||||
'query_string': changelist.get_query_string({}, exclude),
|
||||
'display': _('Select'),
|
||||
'id': 'selectfilter',
|
||||
}
|
||||
if self.value() and self.inverse:
|
||||
yield {
|
||||
'selected': False,
|
||||
'query_string': changelist.get_query_string({}, []),
|
||||
'display': _('* Remove'),
|
||||
'id': 'selectfilter_add'
|
||||
}
|
||||
exclude = [self.parameter_inverse]
|
||||
yield {
|
||||
'selected': False,
|
||||
'query_string': changelist.get_query_string({}, exclude),
|
||||
'display': _('* Undo inversion'),
|
||||
}
|
||||
elif self.value() and not self.inverse:
|
||||
yield {
|
||||
'selected': False,
|
||||
'query_string': changelist.get_query_string({}, []),
|
||||
'display': _('* Remove'),
|
||||
'id': 'selectfilter_remove'
|
||||
}
|
||||
include = {self.parameter_inverse: True}
|
||||
yield {
|
||||
'selected': False,
|
||||
'query_string': changelist.get_query_string(include, []),
|
||||
'display': _('* Invert'),
|
||||
}
|
||||
|
||||
|
||||
class MultiSelectMixin(object):
|
||||
def queryset(self, request, queryset):
|
||||
params = Q()
|
||||
for lookup_arg, value in self.used_parameters.items():
|
||||
params |= Q(**{lookup_arg:value})
|
||||
try:
|
||||
return queryset.filter(params)
|
||||
except (ValueError, ValidationError) as e:
|
||||
# Fields may raise a ValueError or ValidationError when converting
|
||||
# the parameters to the correct type.
|
||||
raise IncorrectLookupParameters(e)
|
||||
|
||||
def querystring_for_choices(self, val, changelist):
|
||||
lookup_vals = self.lookup_vals[:]
|
||||
if val in self.lookup_vals:
|
||||
lookup_vals.remove(val)
|
||||
else:
|
||||
lookup_vals.append(val)
|
||||
if lookup_vals:
|
||||
query_string = changelist.get_query_string({
|
||||
self.lookup_kwarg: ','.join(lookup_vals),
|
||||
}, [])
|
||||
else:
|
||||
query_string = changelist.get_query_string({},
|
||||
[self.lookup_kwarg])
|
||||
return query_string
|
||||
|
||||
def querystring_for_isnull(self, changelist):
|
||||
if self.lookup_val_isnull:
|
||||
query_string = changelist.get_query_string({},
|
||||
[self.lookup_kwarg_isnull])
|
||||
else:
|
||||
query_string = changelist.get_query_string({
|
||||
self.lookup_kwarg_isnull: 'True',
|
||||
}, [])
|
||||
return query_string
|
||||
|
||||
def has_output(self):
|
||||
return len(self.lookup_choices) > 1
|
||||
|
||||
|
||||
class MultiSelectFilter(MultiSelectMixin, admin.AllValuesFieldListFilter):
|
||||
def __init__(self, field, request, params, model, model_admin, field_path):
|
||||
self.lookup_kwarg = '%s__in' % field_path
|
||||
self.lookup_kwarg_isnull = '%s__isnull' % field_path
|
||||
lookup_vals = request.GET.get(self.lookup_kwarg)
|
||||
self.lookup_vals = lookup_vals.split(',') if lookup_vals else list()
|
||||
self.lookup_val_isnull = request.GET.get(self.lookup_kwarg_isnull)
|
||||
self.empty_value_display = model_admin.get_empty_value_display()
|
||||
parent_model, reverse_path = reverse_field_path(model, field_path)
|
||||
# Obey parent ModelAdmin queryset when deciding which options to show
|
||||
if model == parent_model:
|
||||
queryset = model_admin.get_queryset(request)
|
||||
else:
|
||||
queryset = parent_model._default_manager.all()
|
||||
self.lookup_choices = (queryset
|
||||
.distinct()
|
||||
.order_by(field.name)
|
||||
.values_list(field.name, flat=True))
|
||||
super(admin.AllValuesFieldListFilter, self).__init__(field, request, params, model, model_admin, field_path)
|
||||
self.used_parameters = self.prepare_used_parameters(self.used_parameters)
|
||||
|
||||
def prepare_querystring_value(self, value):
|
||||
# mask all commas or these values will be used
|
||||
# in a comma-seperated-list as get-parameter
|
||||
return str(value).replace(',', '%~')
|
||||
|
||||
def prepare_used_parameters(self, used_parameters):
|
||||
# remove comma-mask from list-values for __in-lookups
|
||||
for key, value in used_parameters.items():
|
||||
if not key.endswith('__in'): continue
|
||||
used_parameters[key] = [v.replace('%~', ',') for v in value]
|
||||
return used_parameters
|
||||
|
||||
def choices(self, changelist):
|
||||
yield {
|
||||
'selected': not self.lookup_vals and self.lookup_val_isnull is None,
|
||||
'query_string': changelist.get_query_string({}, [self.lookup_kwarg, self.lookup_kwarg_isnull]),
|
||||
'display': _('All'),
|
||||
}
|
||||
include_none = False
|
||||
for val in self.lookup_choices:
|
||||
if val is None:
|
||||
include_none = True
|
||||
continue
|
||||
val = str(val)
|
||||
qval = self.prepare_querystring_value(val)
|
||||
yield {
|
||||
'selected': qval in self.lookup_vals,
|
||||
'query_string': self.querystring_for_choices(qval, changelist),
|
||||
'display': val,
|
||||
}
|
||||
if include_none:
|
||||
yield {
|
||||
'selected': bool(self.lookup_val_isnull),
|
||||
'query_string': self.querystring_for_isnull(changelist),
|
||||
'display': self.empty_value_display,
|
||||
}
|
||||
|
||||
|
||||
class MultiSelectRelatedFilter(MultiSelectMixin, admin.RelatedFieldListFilter):
|
||||
def __init__(self, field, request, params, model, model_admin, field_path):
|
||||
other_model = get_model_from_relation(field)
|
||||
self.lookup_kwarg = '%s__%s__in' % (field_path, field.target_field.name)
|
||||
self.lookup_kwarg_isnull = '%s__isnull' % field_path
|
||||
lookup_vals = request.GET.get(self.lookup_kwarg)
|
||||
self.lookup_vals = lookup_vals.split(',') if lookup_vals else list()
|
||||
self.lookup_val_isnull = request.GET.get(self.lookup_kwarg_isnull)
|
||||
super(admin.RelatedFieldListFilter, self).__init__(field, request, params, model, model_admin, field_path)
|
||||
self.lookup_choices = self.field_choices(field, request, model_admin)
|
||||
if hasattr(field, 'verbose_name'):
|
||||
self.lookup_title = field.verbose_name
|
||||
else:
|
||||
self.lookup_title = other_model._meta.verbose_name
|
||||
self.title = self.lookup_title
|
||||
self.empty_value_display = model_admin.get_empty_value_display()
|
||||
|
||||
def choices(self, changelist):
|
||||
yield {
|
||||
'selected': not self.lookup_vals and not self.lookup_val_isnull,
|
||||
'query_string': changelist.get_query_string(
|
||||
{},
|
||||
[self.lookup_kwarg, self.lookup_kwarg_isnull]
|
||||
),
|
||||
'display': _('All'),
|
||||
}
|
||||
for pk_val, val in self.lookup_choices:
|
||||
pk_val = str(pk_val)
|
||||
yield {
|
||||
'selected': pk_val in self.lookup_vals,
|
||||
'query_string': self.querystring_for_choices(pk_val, changelist),
|
||||
'display': val,
|
||||
}
|
||||
if self.include_empty_choice:
|
||||
yield {
|
||||
'selected': bool(self.lookup_val_isnull),
|
||||
'query_string': self.querystring_for_isnull(changelist),
|
||||
'display': self.empty_value_display,
|
||||
}
|
||||
|
||||
|
||||
class MultiSelectDropdownFilter(MultiSelectFilter):
|
||||
template = 'dropdownmultiselectfilter.html'
|
||||
|
||||
def choices(self, changelist):
|
||||
query_string = changelist.get_query_string({}, [self.lookup_kwarg, self.lookup_kwarg_isnull])
|
||||
yield {
|
||||
'selected': not self.lookup_vals and self.lookup_val_isnull is None,
|
||||
'query_string': query_string,
|
||||
'display': _('All'),
|
||||
}
|
||||
include_none = False
|
||||
for val in self.lookup_choices:
|
||||
if val is None:
|
||||
include_none = True
|
||||
continue
|
||||
|
||||
val = str(val)
|
||||
qval = self.prepare_querystring_value(val)
|
||||
yield {
|
||||
'selected': qval in self.lookup_vals,
|
||||
'query_string': query_string,
|
||||
'display': val,
|
||||
'value': val,
|
||||
'key': self.lookup_kwarg,
|
||||
}
|
||||
if include_none:
|
||||
yield {
|
||||
'selected': bool(self.lookup_val_isnull),
|
||||
'query_string': query_string,
|
||||
'display': self.empty_value_display,
|
||||
'value': 'True',
|
||||
'key': self.lookup_kwarg_isnull,
|
||||
}
|
||||
|
||||
|
||||
class MultiSelectRelatedDropdownFilter(MultiSelectRelatedFilter):
|
||||
template = 'dropdownmultiselectfilter.html'
|
||||
|
||||
def choices(self, changelist):
|
||||
query_string = changelist.get_query_string({}, [self.lookup_kwarg, self.lookup_kwarg_isnull])
|
||||
yield {
|
||||
'selected': not self.lookup_vals and not self.lookup_val_isnull,
|
||||
'query_string': query_string,
|
||||
'display': _('All'),
|
||||
}
|
||||
for pk_val, val in self.lookup_choices:
|
||||
pk_val = str(pk_val)
|
||||
yield {
|
||||
'selected': pk_val in self.lookup_vals,
|
||||
'query_string': query_string,
|
||||
'display': val,
|
||||
'value': pk_val,
|
||||
'key': self.lookup_kwarg,
|
||||
}
|
||||
if self.include_empty_choice:
|
||||
yield {
|
||||
'selected': bool(self.lookup_val_isnull),
|
||||
'query_string': query_string,
|
||||
'display': self.empty_value_display,
|
||||
'value': 'True',
|
||||
'key': self.lookup_kwarg_isnull,
|
||||
}
|
||||
|
||||
|
||||
class DropdownFilter(AllValuesFieldListFilter):
|
||||
template = 'dropdownfilter.html'
|
||||
|
||||
|
||||
class ChoicesDropdownFilter(ChoicesFieldListFilter):
|
||||
template = 'dropdownfilter.html'
|
||||
|
||||
|
||||
class RelatedDropdownFilter(RelatedFieldListFilter):
|
||||
template = 'dropdownfilter.html'
|
||||
|
||||
|
||||
class RelatedOnlyDropdownFilter(RelatedOnlyFieldListFilter):
|
||||
template = 'dropdownfilter.html'
|
||||
|
||||
|
||||
class PlusMinusFilter(admin.AllValuesFieldListFilter):
|
||||
"""
|
||||
PlusMinusFilter
|
||||
"""
|
||||
|
||||
template = 'plusminusfilter.html'
|
||||
|
||||
def queryset(self, request, queryset):
|
||||
if self.lookup_val is None: return queryset
|
||||
sign = self.lookup_val[0]
|
||||
value = self.lookup_val[1:]
|
||||
if sign == '-':
|
||||
return queryset.exclude(**{self.field_path:value})
|
||||
elif sign == '+':
|
||||
return queryset.filter(**{self.field_path:value})
|
||||
|
||||
def choices(self, changelist):
|
||||
yield {
|
||||
'selected': self.lookup_val is None,
|
||||
'query_string': changelist.get_query_string({}, [self.lookup_kwarg]),
|
||||
'display': _('All'),
|
||||
}
|
||||
for lookup in self.lookup_choices:
|
||||
plus_lookup = '+' + lookup
|
||||
minus_lookup = '-' + lookup
|
||||
yield {
|
||||
'display': lookup,
|
||||
'selected': self.lookup_val and self.lookup_val[1:] == force_text(lookup),
|
||||
'plus': {
|
||||
'selected': self.lookup_val == force_text(plus_lookup),
|
||||
'query_string': changelist.get_query_string({self.lookup_kwarg: plus_lookup}, []),
|
||||
},
|
||||
'minus': {
|
||||
'selected': self.lookup_val == force_text(minus_lookup),
|
||||
'query_string': changelist.get_query_string({self.lookup_kwarg: minus_lookup}, []),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class AnnotationListFilter(admin.ListFilter):
|
||||
"""
|
||||
Baseclass for annotation-list-filters.
|
||||
|
||||
This is more or less a rewrite of admin.FieldListFilter. But we must not
|
||||
subclass it to not confuse django's filter-setup-routine.
|
||||
"""
|
||||
attribute_name = None
|
||||
nullable_attribute = None
|
||||
|
||||
@classmethod
|
||||
def init(cls, attribute_name, nullable=True):
|
||||
"""
|
||||
Since filters are listed as classes in ModelAdmin.list_filter we are
|
||||
not able to initialize the filter within the ModelAdmin.
|
||||
We use this classmethod to setup a filter-class for a specific annotated
|
||||
attribute::
|
||||
|
||||
MyModelAdmin(admin.ModelAdmin):
|
||||
list_filter = [
|
||||
MyAnnotationListFilter.init('my_attribute'),
|
||||
]
|
||||
"""
|
||||
attrs = dict(attribute_name=attribute_name, nullable=nullable)
|
||||
cls = type('cls.__name__' + attribute_name, (cls,), attrs)
|
||||
return cls
|
||||
|
||||
def __init__(self, attribute_name, request, params, model, model_admin):
|
||||
self.title = attribute_name
|
||||
super().__init__(request, params, model, model_admin)
|
||||
for p in self.expected_parameters():
|
||||
if p in params:
|
||||
value = params.pop(p)
|
||||
self.used_parameters[p] = prepare_lookup_value(p, value)
|
||||
|
||||
def has_output(self):
|
||||
return True
|
||||
|
||||
def queryset(self, request, queryset):
|
||||
try:
|
||||
return queryset.filter(**self.used_parameters)
|
||||
except (ValueError, ValidationError) as e:
|
||||
# Fields may raise a ValueError or ValidationError when converting
|
||||
# the parameters to the correct type.
|
||||
raise IncorrectLookupParameters(e)
|
||||
|
||||
|
||||
class BooleanAnnotationListFilter(AnnotationListFilter):
|
||||
"""
|
||||
Filter for annotated boolean-attributes.
|
||||
|
||||
This is more or less the same than admin.BooleanFieldListFilter but for
|
||||
annotated attributes.
|
||||
"""
|
||||
def __init__(self, request, params, model, model_admin):
|
||||
self.lookup_kwarg = '%s__exact' % self.attribute_name
|
||||
self.lookup_kwarg2 = '%s__isnull' % self.attribute_name
|
||||
self.lookup_val = params.get(self.lookup_kwarg)
|
||||
self.lookup_val2 = params.get(self.lookup_kwarg2)
|
||||
super().__init__(self.attribute_name, request, params, model, model_admin)
|
||||
if (self.used_parameters and self.lookup_kwarg in self.used_parameters and
|
||||
self.used_parameters[self.lookup_kwarg] in ('1', '0')):
|
||||
self.used_parameters[self.lookup_kwarg] = bool(int(self.used_parameters[self.lookup_kwarg]))
|
||||
|
||||
def expected_parameters(self):
|
||||
return [self.lookup_kwarg, self.lookup_kwarg2]
|
||||
|
||||
def choices(self, changelist):
|
||||
for lookup, title in (
|
||||
(None, _('All')),
|
||||
('1', _('Yes')),
|
||||
('0', _('No'))):
|
||||
yield {
|
||||
'selected': self.lookup_val == lookup and not self.lookup_val2,
|
||||
'query_string': changelist.get_query_string({self.lookup_kwarg: lookup}, [self.lookup_kwarg2]),
|
||||
'display': title,
|
||||
}
|
||||
if self.nullable_attribute:
|
||||
yield {
|
||||
'selected': self.lookup_val2 == 'True',
|
||||
'query_string': changelist.get_query_string({self.lookup_kwarg2: 'True'}, [self.lookup_kwarg]),
|
||||
'display': _('Unknown'),
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
{% load i18n admin_urls %}
|
||||
<h3>{% blocktrans with filter_title=title %} By {{ filter_title }} {% endblocktrans %}</h3>
|
||||
|
||||
<ul>
|
||||
{% if choices|slice:"4:" %}
|
||||
<li><select id="{{ title|slugify }}_filter_select" style="width:100%;color:#666">
|
||||
{% for choice in choices %}
|
||||
<option{% if choice.selected %} selected="selected"{% endif %}
|
||||
value="{{ choice.query_string|iriencode }}">{{ choice.display }}</option>
|
||||
{% endfor %}
|
||||
</select></li>
|
||||
{% else %}
|
||||
{% for choice in choices %}
|
||||
<li{% if choice.selected %} class="selected"{% endif %}>
|
||||
<a href="{{ choice.query_string|iriencode }}" title="{{ choice.display }}">{{ choice.display }}</a></li>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
<script>
|
||||
(function($) {
|
||||
$('select#{{ title|slugify }}_filter_select').change(function(evt) {
|
||||
window.location.href = $(this).val();
|
||||
});
|
||||
})(django.jQuery);
|
||||
</script>
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
{% load i18n admin_urls %}
|
||||
<h3>{% blocktrans with filter_title=title %} By {{ filter_title }} {% endblocktrans %}</h3>
|
||||
|
||||
<ul>
|
||||
{% for choice in choices|slice:":1" %}
|
||||
<li{% if choice.selected %} class="selected"{% endif %}>
|
||||
<a href="{{ choice.query_string|iriencode }}" title="{{ choice.display }}">{{ choice.display }}</a></li>
|
||||
</li>
|
||||
{% endfor %}
|
||||
<li>
|
||||
<select id="{{ title|slugify }}_select" multiple style="color:#999;width:100%">
|
||||
{% for choice in choices|slice:"1:" %}
|
||||
<option{% if choice.selected %} selected{% endif %}
|
||||
value="{{ choice.value }}"
|
||||
data-key="{{ choice.key }}"
|
||||
data-query="{{ choice.query_string|iriencode }}">
|
||||
{{ choice.display }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</li>
|
||||
<li>
|
||||
<a id="{{ title|slugify }}_submit" href="" title="filter" class="button"
|
||||
style="background-color:buttonface;color:#666">filter</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<script>
|
||||
(function($) {
|
||||
$('a#{{ title|slugify }}_submit').click(function(evt) {
|
||||
|
||||
// fetch data from selected options
|
||||
var params = {};
|
||||
var query = null;
|
||||
$('select#{{ title|slugify }}_select > option:selected').each(function() {
|
||||
var key = $(this).attr('data-key');
|
||||
if (! params.hasOwnProperty(key)) params[key] = [];
|
||||
params[key].push($(this).val());
|
||||
if (!query) query = $(this).attr('data-query');
|
||||
});
|
||||
|
||||
// do we had selected options at all?
|
||||
if (!query) return false;
|
||||
|
||||
// build up the href
|
||||
var params_query = '';
|
||||
for (var key in params) {
|
||||
if (params.hasOwnProperty(key)) {
|
||||
params_query += '&' + key + '=' + params[key].join()
|
||||
}
|
||||
}
|
||||
this.href = query + params_query;
|
||||
});
|
||||
})(django.jQuery);
|
||||
</script>
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
{% load i18n %}
|
||||
<h3>{% blocktrans with filter_title=title %}{{ filter_title }} {% endblocktrans %}</h3>
|
||||
<ul>
|
||||
{% for choice in choices %}
|
||||
<li{% if choice.selected %} class="selected"{% endif %}>
|
||||
<a href="{{ choice.query_string|iriencode }}"{% if choice.id %} id="{{ choice.id }}"{% endif %} title="{{ choice.display }}">{{ choice.display }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<script>
|
||||
(function($) {
|
||||
$('a#selectfilter').click(function(evt) {
|
||||
var l = [];
|
||||
$('input.action-select:checked').each(function(){l.push(this.value)});
|
||||
if (l.length == 0) return false;
|
||||
var conj = (this.href.slice(-1) == '?') ? '' : '&';
|
||||
this.href += conj + 'selected=' + l.join()
|
||||
});
|
||||
$('a#selectfilter_remove').click(function(evt) {
|
||||
var l = [];
|
||||
var a = this;
|
||||
$('input.action-select:checked').each(function(){l.push(this.value)});
|
||||
if (l.length == 0) return false;
|
||||
$.each(l, function() {
|
||||
var regex = new RegExp('(%2C'+this+'$|'+this+'%2C)');
|
||||
a.href = a.href.replace(regex, '');
|
||||
})
|
||||
});
|
||||
$('a#selectfilter_add').click(function(evt) {
|
||||
var l = [];
|
||||
var a = this;
|
||||
$('input.action-select:checked').each(function(){l.push(this.value)});
|
||||
if (l.length == 0) return false;
|
||||
this.href = this.href.replace('selected=', 'selected=' + l.join() + ',');
|
||||
});
|
||||
})(django.jQuery);
|
||||
</script>
|
||||
Loading…
Reference in New Issue