-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added quickfilter plugin. Only made for in default menu style
- Loading branch information
Jairus Martin
committed
Apr 10, 2014
1 parent
6c0f854
commit 5270606
Showing
9 changed files
with
290 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
''' | ||
Created on Mar 26, 2014 | ||
@author: LAB_ADM | ||
''' | ||
from django.utils.translation import ugettext_lazy as _ | ||
from xadmin.filters import manager,MultiSelectFieldListFilter | ||
from xadmin.plugins.filters import * | ||
|
||
@manager.register | ||
class QuickFilterMultiSelectFieldListFilter(MultiSelectFieldListFilter): | ||
""" Delegates the filter to the default filter and ors the results of each | ||
Lists the distinct values of each field as a checkbox | ||
Uses the default spec for each | ||
""" | ||
template = 'xadmin/filters/quickfilter.html' | ||
|
||
class QuickFilterPlugin(BaseAdminPlugin): | ||
""" Add a filter menu to the left column of the page """ | ||
list_quick_filter = () # these must be a subset of list_filter to work | ||
quickfilter = {} | ||
search_fields = () | ||
free_query_filter = True | ||
|
||
def init_request(self, *args, **kwargs): | ||
menu_style_accordian = hasattr(self.admin_view,'menu_style') and self.admin_view.menu_style == 'accordion' | ||
return bool(self.list_quick_filter) and not menu_style_accordian | ||
|
||
# Media | ||
def get_media(self, media): | ||
return media + self.vendor('xadmin.plugin.quickfilter.js','xadmin.plugin.quickfilter.css') | ||
|
||
def lookup_allowed(self, lookup, value): | ||
model = self.model | ||
# Check FKey lookups that are allowed, so that popups produced by | ||
# ForeignKeyRawIdWidget, on the basis of ForeignKey.limit_choices_to, | ||
# are allowed to work. | ||
for l in model._meta.related_fkey_lookups: | ||
for k, v in widgets.url_params_from_lookup_dict(l).items(): | ||
if k == lookup and v == value: | ||
return True | ||
|
||
parts = lookup.split(LOOKUP_SEP) | ||
|
||
# Last term in lookup is a query term (__exact, __startswith etc) | ||
# This term can be ignored. | ||
if len(parts) > 1 and parts[-1] in QUERY_TERMS: | ||
parts.pop() | ||
|
||
# Special case -- foo__id__exact and foo__id queries are implied | ||
# if foo has been specificially included in the lookup list; so | ||
# drop __id if it is the last part. However, first we need to find | ||
# the pk attribute name. | ||
rel_name = None | ||
for part in parts[:-1]: | ||
try: | ||
field, _, _, _ = model._meta.get_field_by_name(part) | ||
except FieldDoesNotExist: | ||
# Lookups on non-existants fields are ok, since they're ignored | ||
# later. | ||
return True | ||
if hasattr(field, 'rel'): | ||
model = field.rel.to | ||
rel_name = field.rel.get_related_field().name | ||
elif isinstance(field, RelatedObject): | ||
model = field.model | ||
rel_name = model._meta.pk.name | ||
else: | ||
rel_name = None | ||
if rel_name and len(parts) > 1 and parts[-1] == rel_name: | ||
parts.pop() | ||
|
||
if len(parts) == 1: | ||
return True | ||
clean_lookup = LOOKUP_SEP.join(parts) | ||
return clean_lookup in self.list_quick_filter | ||
|
||
def get_list_queryset(self, queryset): | ||
lookup_params = dict([(smart_str(k)[len(FILTER_PREFIX):], v) for k, v in self.admin_view.params.items() if smart_str(k).startswith(FILTER_PREFIX) and v != '']) | ||
for p_key, p_val in lookup_params.iteritems(): | ||
if p_val == "False": | ||
lookup_params[p_key] = False | ||
use_distinct = False | ||
|
||
if not hasattr(self.admin_view,'quickfilter'): | ||
self.admin_view.quickfilter = {} | ||
|
||
# for clean filters | ||
self.admin_view.quickfilter['has_query_param'] = bool(lookup_params) | ||
self.admin_view.quickfilter['clean_query_url'] = self.admin_view.get_query_string(remove=[k for k in self.request.GET.keys() if k.startswith(FILTER_PREFIX)]) | ||
|
||
# Normalize the types of keys | ||
if not self.free_query_filter: | ||
for key, value in lookup_params.items(): | ||
if not self.lookup_allowed(key, value): | ||
raise SuspiciousOperation("Filtering by %s not allowed" % key) | ||
|
||
self.filter_specs = [] | ||
if self.list_quick_filter: | ||
for list_quick_filter in self.list_quick_filter: | ||
field_path = None | ||
field_order_by = None | ||
field_limit = None | ||
field_parts = [] | ||
sort_key = None | ||
cache_config = None | ||
|
||
if type(list_quick_filter)==dict and 'field' in list_quick_filter: | ||
field = list_quick_filter['field'] | ||
if 'order_by' in list_quick_filter: | ||
field_order_by = list_quick_filter['order_by'] | ||
if 'limit' in list_quick_filter: | ||
field_limit = list_quick_filter['limit'] | ||
if 'sort' in list_quick_filter and callable(list_quick_filter['sort']): | ||
sort_key = list_quick_filter['sort'] | ||
if 'cache' in list_quick_filter and type(list_quick_filter)==dict: | ||
cache_config = list_quick_filter['cache'] | ||
|
||
else: | ||
field = list_quick_filter # This plugin only uses MultiselectFieldListFilter | ||
|
||
if not isinstance(field, models.Field): | ||
field_path = field | ||
field_parts = get_fields_from_path(self.model, field_path) | ||
field = field_parts[-1] | ||
spec = QuickFilterMultiSelectFieldListFilter(field, self.request, lookup_params,self.model, self.admin_view, field_path=field_path,field_order_by=field_order_by,field_limit=field_limit,sort_key=sort_key,cache_config=cache_config) | ||
|
||
if len(field_parts)>1: | ||
spec.title = "%s %s"%(field_parts[-2].name,spec.title) | ||
|
||
# Check if we need to use distinct() | ||
use_distinct = True#(use_distinct orlookup_needs_distinct(self.opts, field_path)) | ||
if spec and spec.has_output(): | ||
try: | ||
new_qs = spec.do_filte(queryset) | ||
except ValidationError, e: | ||
new_qs = None | ||
self.admin_view.message_user(_("<b>Filtering error:</b> %s") % e.messages[0], 'error') | ||
if new_qs is not None: | ||
queryset = new_qs | ||
|
||
self.filter_specs.append(spec) | ||
|
||
self.has_filters = bool(self.filter_specs) | ||
self.admin_view.quickfilter['filter_specs'] = self.filter_specs | ||
self.admin_view.quickfilter['used_filter_num'] = len(filter(lambda f: f.is_used, self.filter_specs)) | ||
|
||
if use_distinct: | ||
return queryset.distinct() | ||
else: | ||
return queryset | ||
|
||
def block_left_navbar(self, context, nodes): | ||
nodes.append(loader.render_to_string('xadmin/blocks/modal_list.left_navbar.quickfilter.html',context)) | ||
|
||
site.register_plugin(QuickFilterPlugin, ListAdminView) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
.nav-quickfilter .filter-item { | ||
white-space: nowrap; | ||
overflow: hidden; | ||
padding: 5px; | ||
} | ||
|
||
.nav-quickfilter .filter-col-1 { | ||
margin: 3px 2px 0 -2px; | ||
float: left; | ||
} | ||
|
||
.nav-quickfilter .filter-col-2 { | ||
} | ||
|
||
.nav-quickfilter .nav-expand { | ||
z-index:100; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
;(function($){ | ||
$('[data-toggle=tooltip]').tooltip(); | ||
var max=10; | ||
|
||
function addShowMore($,v){ | ||
$(v).nextUntil('li.nav-header').last().after( | ||
$('<li class="filter-multiselect"><a class="small filter-item" href="#"><input class="filter-col-1" type="checkbox"><span class="filter-col-2">Show more</span></a></li>').click(function(e){ | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
$(v).nextUntil('li.nav-header').show(); | ||
$(v).nextUntil('li.nav-header').last().remove(); | ||
addShowLess($,v); | ||
}) | ||
); | ||
$(v).nextUntil('li.nav-header').last().show(); | ||
} | ||
|
||
function addShowLess($,v){ | ||
$(v).nextUntil('li.nav-header').last().after( | ||
$('<li class="filter-multiselect"><a class="small filter-item" href="#"><input class="filter-col-1" type="checkbox"><span class="filter-col-2">Show less</span></a></li>').click(function(e){ | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
$(v).nextUntil('li.nav-header').filter(function(i){return !$(this).find('input').is(':checked');}).slice(max).hide(); | ||
$(v).nextUntil('li.nav-header').last().remove(); | ||
$(v).scrollMinimal(3000); | ||
addShowMore($,v); | ||
}) | ||
); | ||
$(v).nextUntil('li.nav-header').last().show(); | ||
} | ||
|
||
$.each($('.nav-quickfilter li.nav-header'),function(i,v){ | ||
if ($(v).nextUntil('li.nav-header').size()>max) { | ||
$(v).nextUntil('li.nav-header').filter(function(i){return !$(this).find('input').is(':checked');}).slice(max).hide(); | ||
addShowMore($,v); | ||
} | ||
}); | ||
|
||
$('.nav-quickfilter li.nav-header').on('click',function(e) { | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
$('.nav-quickfilter li.nav-header i').toggleClass('icon-chevron-right'); | ||
$('.nav-quickfilter li.nav-header i').toggleClass('icon-chevron-left'); | ||
$('#left-side').toggleClass('col-md-2'); | ||
$('#left-side').toggleClass('col-md-4'); | ||
$('#content-block').toggleClass('col-md-10'); | ||
$('#content-block').toggleClass('col-md-8'); | ||
}); | ||
})(jQuery) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
3 changes: 3 additions & 0 deletions
3
xadmin/templates/xadmin/blocks/modal_list.left_navbar.quickfilter.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
<ul class="well nav nav-pills nav-stacked nav-quickfilter hide-sm"> | ||
{% for spec in cl.quickfilter.filter_specs %}{{ spec|safe }}{% endfor %} | ||
</ul> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{% load i18n %} | ||
<li class="nav-header ">{{title}} <i class="icon-chevron-right pull-right"></i></li> | ||
{% for choice in choices %} | ||
<li class="filter-multiselect"> | ||
<a class="small filter-item" {% if choice.selected %} href="{{ choice.remove_query_string|iriencode }}" {% else %} href="{{ choice.query_string|iriencode }}" {% endif %} data-toggle="tooltip" data-placement="right" title="{{ choice.display }}"> | ||
<input class="filter-col-1" type="checkbox" {% if choice.selected %} checked="checked"{% endif %}> | ||
<span class="filter-col-2">{{ choice.display }}</span> | ||
</a> | ||
</li> | ||
{% endfor %} |