ConstructionConsultationSystem/xadmin/plugins/quickfilter.py
2019-03-18 19:45:47 +08:00

169 lines
7.1 KiB
Python

'''
Created on Mar 26, 2014
@author: LAB_ADM
'''
from future.utils import iteritems
from django.utils import six
from django.utils.translation import ugettext_lazy as _
from xadmin.filters import manager, MultiSelectFieldListFilter
from xadmin.plugins.filters import *
from xadmin.util import is_related_field
@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(part)
except FieldDoesNotExist:
# Lookups on non-existants fields are ok, since they're ignored
# later.
return True
if hasattr(field, 'remote_field'):
model = field.remote_field.model
rel_name = field.remote_field.get_related_field().name
elif is_related_field(field):
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 iteritems(lookup_params):
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 as 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
obj = filter(lambda f: f.is_used, self.filter_specs)
if six.PY3:
obj = list(obj)
self.admin_view.quickfilter['used_filter_num'] = len(obj)
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',
get_context_dict(context)))
site.register_plugin(QuickFilterPlugin, ListAdminView)