text: from __future__ import absolute_import
  from django.db import models
  from django.core.exceptions import ImproperlyConfigured
  from django.utils.encoding import smart_text
  from django.utils.translation import ugettext_lazy as _
  from django.utils import timezone
  from django.template.loader import get_template
  from django.template.context import Context
  from django.utils import six
  from django.utils.safestring import mark_safe
  from django.utils.html import escape, format_html
  from django.utils.text import Truncator
  from django.core.cache import cache, caches
  
  from xadmin.views.list import EMPTY_CHANGELIST_VALUE
  from xadmin.util import is_related_field, is_related_field2
  import datetime
  
  FILTER_PREFIX = '_p_'
  SEARCH_VAR = '_q_'
  
  from .util import (get_model_from_relation,
                     reverse_field_path, get_limit_choices_to_from_path, prepare_lookup_value)
  
  
  class BaseFilter(object):
      title = None
      template = 'xadmin/filters/list.html'
  
      @classmethod
      def test(cls, field, request, params, model, admin_view, field_path):
          pass
  
      def __init__(self, request, params, model, admin_view):
          self.used_params = {}
          self.request = request
          self.params = params
          self.model = model
          self.admin_view = admin_view
  
          if self.title is None:
              raise ImproperlyConfigured(
                  "The filter '%s' does not specify "
                  "a 'title'." % self.__class__.__name__)
  
      def query_string(self, new_params=None, remove=None):
          return self.admin_view.get_query_string(new_params, remove)
  
      def form_params(self):
          arr = map(lambda k: FILTER_PREFIX + k, self.used_params.keys())
          if six.PY3:
              arr = list(arr)
          return self.admin_view.get_form_params(remove=arr)
  
      def has_output(self):
          """
          Returns True if some choices would be output for this filter.
          """
          raise NotImplementedError
  
      @property
      def is_used(self):
          return len(self.used_params) > 0
  
      def do_filte(self, queryset):
          """
          Returns the filtered queryset.
          """
          raise NotImplementedError
  
      def get_context(self):
          return {'title': self.title, 'spec': self, 'form_params': self.form_params()}
  
      def __str__(self):
          tpl = get_template(self.template)
          return mark_safe(tpl.render(context=self.get_context()))
  
  
  class FieldFilterManager(object):
      _field_list_filters = []
      _take_priority_index = 0
  
      def register(self, list_filter_class, take_priority=False):
          if take_priority:
              # This is to allow overriding the default filters for certain types
              # of fields with some custom filters. The first found in the list
              # is used in priority.
              self._field_list_filters.insert(
                  self._take_priority_index, list_filter_class)
              self._take_priority_index += 1
          else:
              self._field_list_filters.append(list_filter_class)
          return list_filter_class
  
      def create(self, field, request, params, model, admin_view, field_path):
          for list_filter_class in self._field_list_filters:
              if not list_filter_class.test(field, request, params, model, admin_view, field_path):
                  continue
              return list_filter_class(field, request, params,
                                       model, admin_view, field_path=field_path)
  
  manager = FieldFilterManager()
  
  
  class FieldFilter(BaseFilter):
  
      lookup_formats = {}
  
      def __init__(self, field, request, params, model, admin_view, field_path):
          self.field = field
          self.field_path = field_path
          self.title = getattr(field, 'verbose_name', field_path)
          self.context_params = {}
  
          super(FieldFilter, self).__init__(request, params, model, admin_view)
  
          for name, format in self.lookup_formats.items():
              p = format % field_path
              self.context_params["%s_name" % name] = FILTER_PREFIX + p
              if p in params:
                  value = prepare_lookup_value(p, params.pop(p))
                  self.used_params[p] = value
                  self.context_params["%s_val" % name] = value
              else:
                  self.context_params["%s_val" % name] = ''
  
          arr = map(
              lambda kv: setattr(self, 'lookup_' + kv[0], kv[1]),
              self.context_params.items()
          )
          if six.PY3:
              list(arr)
  
      def get_context(self):
          context = super(FieldFilter, self).get_context()
          context.update(self.context_params)
          obj = map(lambda k: FILTER_PREFIX + k, self.used_params.keys())
          if six.PY3:
              obj = list(obj)
          context['remove_url'] = self.query_string({}, obj)
          return context
  
      def has_output(self):
          return True
  
      def do_filte(self, queryset):
          return queryset.filter(**self.used_params)
  
  
  class ListFieldFilter(FieldFilter):
      template = 'xadmin/filters/list.html'
  
      def get_context(self):
          context = super(ListFieldFilter, self).get_context()
          context['choices'] = list(self.choices())
          return context
  
  
  @manager.register
  class BooleanFieldListFilter(ListFieldFilter):
      lookup_formats = {'exact': '%s__exact', 'isnull': '%s__isnull'}
  
      @classmethod
      def test(cls, field, request, params, model, admin_view, field_path):
          return isinstance(field, (models.BooleanField, models.NullBooleanField))
  
      def choices(self):
          for lookup, title in (
                  ('', _('All')),
                  ('1', _('Yes')),
                  ('0', _('No')),
          ):
              yield {
                  'selected': (
                      self.lookup_exact_val == lookup
                      and not self.lookup_isnull_val
                  ),
                  'query_string': self.query_string(
                      {self.lookup_exact_name: lookup},
                      [self.lookup_isnull_name],
                  ),
                  'display': title,
              }
          if isinstance(self.field, models.NullBooleanField):
              yield {
                  'selected': self.lookup_isnull_val == 'True',
                  'query_string': self.query_string(
                      {self.lookup_isnull_name: 'True'},
                      [self.lookup_exact_name],
                  ),
                  'display': _('Unknown'),
              }
  
  
  @manager.register
  class ChoicesFieldListFilter(ListFieldFilter):
      lookup_formats = {'exact': '%s__exact'}
  
      @classmethod
      def test(cls, field, request, params, model, admin_view, field_path):
          return bool(field.choices)
  
      def choices(self):
          yield {
              'selected': self.lookup_exact_val is '',
              'query_string': self.query_string({}, [self.lookup_exact_name]),
              'display': _('All')
          }
          for lookup, title in self.field.flatchoices:
              yield {
                  'selected': smart_text(lookup) == self.lookup_exact_val,
                  'query_string': self.query_string({self.lookup_exact_name: lookup}),
                  'display': title,
              }
  
  
  @manager.register
  class TextFieldListFilter(FieldFilter):
      template = 'xadmin/filters/char.html'
      lookup_formats = {'in': '%s__in', 'search': '%s__contains'}
  
      @classmethod
      def test(cls, field, request, params, model, admin_view, field_path):
          return (
              isinstance(field, models.CharField)
              and field.max_length > 20
              or isinstance(field, models.TextField)
          )
  
  
  @manager.register
  class NumberFieldListFilter(FieldFilter):
      template = 'xadmin/filters/number.html'
      lookup_formats = {'equal': '%s__exact', 'lt': '%s__lt', 'gt': '%s__gt',
                        'ne': '%s__ne', 'lte': '%s__lte', 'gte': '%s__gte',
                        }
  
      @classmethod
      def test(cls, field, request, params, model, admin_view, field_path):
          return isinstance(field, (models.DecimalField, models.FloatField, models.IntegerField))
  
      def do_filte(self, queryset):
          params = self.used_params.copy()
          ne_key = '%s__ne' % self.field_path
          if ne_key in params:
              queryset = queryset.exclude(
                  **{self.field_path: params.pop(ne_key)})
          return queryset.filter(**params)
  
  
  @manager.register
  class DateFieldListFilter(ListFieldFilter):
      template = 'xadmin/filters/date.html'
      lookup_formats = {'since': '%s__gte', 'until': '%s__lt',
                        'year': '%s__year', 'month': '%s__month', 'day': '%s__day',
                        'isnull': '%s__isnull'}
  
      @classmethod
      def test(cls, field, request, params, model, admin_view, field_path):
          return isinstance(field, models.DateField)
  
      def __init__(self, field, request, params, model, admin_view, field_path):
          self.field_generic = '%s__' % field_path
          self.date_params = dict([(FILTER_PREFIX + k, v) for k, v in params.items()
                                   if k.startswith(self.field_generic)])
  
          super(DateFieldListFilter, self).__init__(
              field, request, params, model, admin_view, field_path)
  
          now = timezone.now()
          # When time zone support is enabled, convert "now" to the user's time
          # zone so Django's definition of "Today" matches what the user expects.
          if now.tzinfo is not None:
              current_tz = timezone.get_current_timezone()
              now = now.astimezone(current_tz)
              if hasattr(current_tz, 'normalize'):
                  # available for pytz time zones
                  now = current_tz.normalize(now)
  
          if isinstance(field, models.DateTimeField):
              today = now.replace(hour=0, minute=0, second=0, microsecond=0)
          else:       # field is a models.DateField
              today = now.date()
          tomorrow = today + datetime.timedelta(days=1)
  
          self.links = (
              (_('Any date'), {}),
              (_('Has date'), {
                  self.lookup_isnull_name: False
              }),
              (_('Has no date'), {
                  self.lookup_isnull_name: 'True'
              }),
              (_('Today'), {
                  self.lookup_since_name: str(today),
                  self.lookup_until_name: str(tomorrow),
              }),
              (_('Past 7 days'), {
                  self.lookup_since_name: str(today - datetime.timedelta(days=7)),
                  self.lookup_until_name: str(tomorrow),
              }),
              (_('This month'), {
                  self.lookup_since_name: str(today.replace(day=1)),
                  self.lookup_until_name: str(tomorrow),
              }),
              (_('This year'), {
                  self.lookup_since_name: str(today.replace(month=1, day=1)),
                  self.lookup_until_name: str(tomorrow),
              }),
          )
  
      def get_context(self):
          context = super(DateFieldListFilter, self).get_context()
          context['choice_selected'] = bool(self.lookup_year_val) or bool(self.lookup_month_val) \
              or bool(self.lookup_day_val)
          return context
  
      def choices(self):
          for title, param_dict in self.links:
              yield {
                  'selected': self.date_params == param_dict,
                  'query_string': self.query_string(
                      param_dict, [FILTER_PREFIX + self.field_generic]),
                  'display': title,
              }
  
  
  @manager.register
  class RelatedFieldSearchFilter(FieldFilter):
      template = 'xadmin/filters/fk_search.html'
  
      @classmethod
      def test(cls, field, request, params, model, admin_view, field_path):
          if not is_related_field2(field):
              return False
          related_modeladmin = admin_view.admin_site._registry.get(
              get_model_from_relation(field))
          return related_modeladmin and getattr(related_modeladmin, 'relfield_style', None) in ('fk-ajax', 'fk-select')
  
      def __init__(self, field, request, params, model, model_admin, field_path):
          print('-------------------------')
          other_model = get_model_from_relation(field)
          if hasattr(field, 'remote_field'):
              rel_name = field.remote_field.get_related_field().name
          else:
              rel_name = other_model._meta.pk.name
  
          self.lookup_formats = {'in': '%%s__%s__in' % rel_name, 'exact': '%%s__%s__exact' % rel_name}
          super(RelatedFieldSearchFilter, self).__init__(
              field, request, params, model, model_admin, field_path)
  
          related_modeladmin = self.admin_view.admin_site._registry.get(other_model)
          self.relfield_style = related_modeladmin.relfield_style
  
          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.search_url = model_admin.get_admin_url('%s_%s_changelist' % (
              other_model._meta.app_label, other_model._meta.model_name))
          self.label = self.label_for_value(other_model, rel_name, self.lookup_exact_val) if self.lookup_exact_val else ""
          self.choices = '?'
          if field.remote_field.limit_choices_to:
              for i in list(field.remote_field.limit_choices_to):
                  self.choices += "&_p_%s=%s" % (i, field.remote_field.limit_choices_to[i])
              self.choices = format_html(self.choices)
  
      def label_for_value(self, other_model, rel_name, value):
          try:
              obj = other_model._default_manager.get(**{rel_name: value})
              return '%s' % escape(Truncator(obj).words(14, truncate='...'))
          except (ValueError, other_model.DoesNotExist):
              return ""
  
      def get_context(self):
          context = super(RelatedFieldSearchFilter, self).get_context()
          context['search_url'] = self.search_url
          context['label'] = self.label
          context['choices'] = self.choices
          context['relfield_style'] = self.relfield_style
          return context
  
  
  @manager.register
  class RelatedFieldListFilter(ListFieldFilter):
  
      @classmethod
      def test(cls, field, request, params, model, admin_view, field_path):
          return is_related_field2(field)
  
      def __init__(self, field, request, params, model, model_admin, field_path):
          other_model = get_model_from_relation(field)
          if hasattr(field, 'remote_field'):
              rel_name = field.remote_field.get_related_field().name
          else:
              rel_name = other_model._meta.pk.name
  
          self.lookup_formats = {'in': '%%s__%s__in' % rel_name, 'exact': '%%s__%s__exact' %
                                 rel_name, 'isnull': '%s__isnull'}
          self.lookup_choices = field.get_choices(include_blank=False)
          super(RelatedFieldListFilter, self).__init__(
              field, request, params, model, model_admin, field_path)
  
          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
  
      def has_output(self):
          if (is_related_field(self.field)
                  and self.field.field.null or hasattr(self.field, 'remote_field')
                  and self.field.null):
              extra = 1
          else:
              extra = 0
          return len(self.lookup_choices) + extra > 1
  
      def expected_parameters(self):
          return [self.lookup_kwarg, self.lookup_kwarg_isnull]
  
      def choices(self):
          yield {
              'selected': self.lookup_exact_val == '' and not self.lookup_isnull_val,
              'query_string': self.query_string({},
                                                [self.lookup_exact_name, self.lookup_isnull_name]),
              'display': _('All'),
          }
          for pk_val, val in self.lookup_choices:
              yield {
                  'selected': self.lookup_exact_val == smart_text(pk_val),
                  'query_string': self.query_string({
                      self.lookup_exact_name: pk_val,
                  }, [self.lookup_isnull_name]),
                  'display': val,
              }
          if (is_related_field(self.field)
                  and self.field.field.null or hasattr(self.field, 'remote_field')
                  and self.field.null):
              yield {
                  'selected': bool(self.lookup_isnull_val),
                  'query_string': self.query_string({
                      self.lookup_isnull_name: 'True',
                  }, [self.lookup_exact_name]),
                  'display': EMPTY_CHANGELIST_VALUE,
              }
  
  
  @manager.register
  class MultiSelectFieldListFilter(ListFieldFilter):
      """ 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/checklist.html'
      lookup_formats = {'in': '%s__in'}
      cache_config = {'enabled': False, 'key': 'quickfilter_%s', 'timeout': 3600, 'cache': 'default'}
  
      @classmethod
      def test(cls, field, request, params, model, admin_view, field_path):
          return True
  
      def get_cached_choices(self):
          if not self.cache_config['enabled']:
              return None
          c = caches(self.cache_config['cache'])
          return c.get(self.cache_config['key'] % self.field_path)
  
      def set_cached_choices(self, choices):
          if not self.cache_config['enabled']:
              return
          c = caches(self.cache_config['cache'])
          return c.set(self.cache_config['key'] % self.field_path, choices)
  
      def __init__(self, field, request, params, model, model_admin, field_path, field_order_by=None, field_limit=None, sort_key=None, cache_config=None):
          super(MultiSelectFieldListFilter, self).__init__(field, request, params, model, model_admin, field_path)
  
          # Check for it in the cachce
          if cache_config is not None and type(cache_config) == dict:
              self.cache_config.update(cache_config)
  
          if self.cache_config['enabled']:
              self.field_path = field_path
              choices = self.get_cached_choices()
              if choices:
                  self.lookup_choices = choices
                  return
  
          # Else rebuild it
          queryset = self.admin_view.queryset().exclude(**{"%s__isnull" % field_path: True}).values_list(field_path, flat=True).distinct()
          #queryset = self.admin_view.queryset().distinct(field_path).exclude(**{"%s__isnull"%field_path:True})
  
          if field_order_by is not None:
              # Do a subquery to order the distinct set
              queryset = self.admin_view.queryset().filter(id__in=queryset).order_by(field_order_by)
  
          if field_limit is not None and type(field_limit) == int and queryset.count() > field_limit:
              queryset = queryset[:field_limit]
  
          self.lookup_choices = [str(it) for it in queryset.values_list(field_path, flat=True) if str(it).strip() != ""]
          if sort_key is not None:
              self.lookup_choices = sorted(self.lookup_choices, key=sort_key)
  
          if self.cache_config['enabled']:
              self.set_cached_choices(self.lookup_choices)
  
      def choices(self):
          self.lookup_in_val = (type(self.lookup_in_val) in (tuple, list)) and self.lookup_in_val or list(self.lookup_in_val)
          yield {
              'selected': len(self.lookup_in_val) == 0,
              'query_string': self.query_string({}, [self.lookup_in_name]),
              'display': _('All'),
          }
          for val in self.lookup_choices:
              yield {
                  'selected': smart_text(val) in self.lookup_in_val,
                  'query_string': self.query_string({self.lookup_in_name: ",".join([val] + self.lookup_in_val), }),
                  'remove_query_string': self.query_string({self.lookup_in_name: ",".join([v for v in self.lookup_in_val if v != val]), }),
                  'display': val,
              }
  
  
  @manager.register
  class AllValuesFieldListFilter(ListFieldFilter):
      lookup_formats = {'exact': '%s__exact', 'isnull': '%s__isnull'}
  
      @classmethod
      def test(cls, field, request, params, model, admin_view, field_path):
          return True
  
      def __init__(self, field, request, params, model, admin_view, field_path):
          parent_model, reverse_path = reverse_field_path(model, field_path)
          queryset = parent_model._default_manager.all()
          # optional feature: limit choices base on existing relationships
          # queryset = queryset.complex_filter(
          #    {'%s__isnull' % reverse_path: False})
          limit_choices_to = get_limit_choices_to_from_path(model, field_path)
          queryset = queryset.filter(limit_choices_to)
  
          self.lookup_choices = (queryset
                                 .distinct()
                                 .order_by(field.name)
                                 .values_list(field.name, flat=True))
          super(AllValuesFieldListFilter, self).__init__(
              field, request, params, model, admin_view, field_path)
  
      def choices(self):
          yield {
              'selected': (self.lookup_exact_val is '' and self.lookup_isnull_val is ''),
              'query_string': self.query_string({}, [self.lookup_exact_name, self.lookup_isnull_name]),
              'display': _('All'),
          }
          include_none = False
          for val in self.lookup_choices:
              if val is None:
                  include_none = True
                  continue
              val = smart_text(val)
              yield {
                  'selected': self.lookup_exact_val == val,
                  'query_string': self.query_string({self.lookup_exact_name: val},
                                                    [self.lookup_isnull_name]),
                  'display': val,
              }
          if include_none:
              yield {
                  'selected': bool(self.lookup_isnull_val),
                  'query_string': self.query_string({self.lookup_isnull_name: 'True'},
                                                    [self.lookup_exact_name]),
                  'display': EMPTY_CHANGELIST_VALUE,
              }
  