text: from collections import OrderedDict
  from django.core.exceptions import PermissionDenied, ObjectDoesNotExist
  from django.core.paginator import InvalidPage, Paginator
  from django.db import models
  from django.http import HttpResponseRedirect
  from django.template.response import SimpleTemplateResponse, TemplateResponse
  from django.utils.encoding import force_unicode, smart_unicode
  from django.utils.html import escape, conditional_escape
  from django.utils.safestring import mark_safe
  from django.utils.text import capfirst
  from django.utils.translation import ugettext as _
  
  from xadmin.util import lookup_field, display_for_field, label_for_field, boolean_icon
  
  from base import ModelAdminView, filter_hook, inclusion_tag, csrf_protect_m
  
  # List settings
  ALL_VAR = 'all'
  ORDER_VAR = 'o'
  PAGE_VAR = 'p'
  TO_FIELD_VAR = 't'
  COL_LIST_VAR = '_cols'
  ERROR_FLAG = 'e'
  
  DOT = '.'
  
  # Text to display within change-list table cells if the value is blank.
  EMPTY_CHANGELIST_VALUE = _('Null')
  
  
  class FakeMethodField(object):
      """
      This class used when a column is an model function, wrap function as a fake field to display in select columns.
      """
      def __init__(self, name, verbose_name):
          # Initial comm field attrs
          self.name = name
          self.verbose_name = verbose_name
          self.primary_key = False
  
  
  class ResultRow(dict):
      pass
  
  
  class ResultItem(object):
  
      def __init__(self, field_name, row):
          self.classes = []
          self.text = '&nbsp;'
          self.wraps = []
          self.tag = 'td'
          self.tag_attrs = []
          self.allow_tags = False
          self.btns = []
          self.menus = []
          self.is_display_link = False
          self.row = row
          self.field_name = field_name
          self.field = None
          self.attr = None
          self.value = None
  
      @property
      def label(self):
          text = mark_safe(
              self.text) if self.allow_tags else conditional_escape(self.text)
          if force_unicode(text) == '':
              text = mark_safe('&nbsp;')
          for wrap in self.wraps:
              text = mark_safe(wrap % text)
          return text
  
      @property
      def tagattrs(self):
          return mark_safe(
              '%s%s' % ((self.tag_attrs and ' '.join(self.tag_attrs) or ''),
              (self.classes and (' class="%s"' % ' '.join(self.classes)) or '')))
  
  
  class ResultHeader(ResultItem):
  
      def __init__(self, field_name, row):
          super(ResultHeader, self).__init__(field_name, row)
          self.tag = 'th'
          self.tag_attrs = ['scope="col"']
          self.sortable = False
          self.allow_tags = True
          self.sorted = False
          self.ascending = None
          self.sort_priority = None
          self.url_primary = None
          self.url_remove = None
          self.url_toggle = None
  
  
  class ListAdminView(ModelAdminView):
      """
      Display models objects view. this class has ordering and simple filter features.
      """
      list_display = ('__str__',)
      list_display_links = ()
      list_display_links_details = False
      list_select_related = None
      list_per_page = 50
      list_max_show_all = 200
      list_exclude = ()
      search_fields = ()
      paginator_class = Paginator
      ordering = None
  
      # Change list templates
      object_list_template = None
  
      def init_request(self, *args, **kwargs):
  
          if not self.has_view_permission():
              raise PermissionDenied
  
          request = self.request
          request.session['LIST_QUERY'] = (self.model_info, self.request.META['QUERY_STRING'])
  
          self.pk_attname = self.opts.pk.attname
          self.lookup_opts = self.opts
          self.list_display = self.get_list_display()
          self.list_display_links = self.get_list_display_links()
  
          # Get page number parameters from the query string.
          try:
              self.page_num = int(request.GET.get(PAGE_VAR, 0))
          except ValueError:
              self.page_num = 0
  
          # Get params from request
          self.show_all = ALL_VAR in request.GET
          self.to_field = request.GET.get(TO_FIELD_VAR)
          self.params = dict(request.GET.items())
  
          if PAGE_VAR in self.params:
              del self.params[PAGE_VAR]
          if ERROR_FLAG in self.params:
              del self.params[ERROR_FLAG]
  
      @filter_hook
      def get_list_display(self):
          """
          Return a sequence containing the fields to be displayed on the list.
          """
          self.base_list_display = (COL_LIST_VAR in self.request.GET and self.request.GET[COL_LIST_VAR] != "" and \
              self.request.GET[COL_LIST_VAR].split('.')) or self.list_display
          return list(self.base_list_display)
  
      @filter_hook
      def get_list_display_links(self):
          """
          Return a sequence containing the fields to be displayed as links
          on the changelist. The list_display parameter is the list of fields
          returned by get_list_display().
          """
          if self.list_display_links or not self.list_display:
              return self.list_display_links
          else:
              # Use only the first item in list_display as link
              return list(self.list_display)[:1]
  
      def make_result_list(self):
          # Get search parameters from the query string.
          self.base_queryset = self.queryset()
          self.list_queryset = self.get_list_queryset()
          self.ordering_field_columns = self.get_ordering_field_columns()
          self.paginator = self.get_paginator()
  
          # Get the number of objects, with admin filters applied.
          self.result_count = self.paginator.count
  
          # Get the total number of objects, with no admin filters applied.
          # Perform a slight optimization: Check to see whether any filters were
          # given. If not, use paginator.hits to calculate the number of objects,
          # because we've already done paginator.hits and the value is cached.
          if not self.list_queryset.query.where:
              self.full_result_count = self.result_count
          else:
              self.full_result_count = self.base_queryset.count()
  
          self.can_show_all = self.result_count <= self.list_max_show_all
          self.multi_page = self.result_count > self.list_per_page
  
          # Get the list of objects to display on this page.
          if (self.show_all and self.can_show_all) or not self.multi_page:
              self.result_list = self.list_queryset._clone()
          else:
              try:
                  self.result_list = self.paginator.page(
                      self.page_num + 1).object_list
              except InvalidPage:
                  if ERROR_FLAG in self.request.GET.keys():
                      return SimpleTemplateResponse('xadmin/views/invalid_setup.html', {
                          'title': _('Database error'),
                      })
                  return HttpResponseRedirect(self.request.path + '?' + ERROR_FLAG + '=1')
          self.has_more = self.result_count > (
              self.list_per_page * self.page_num + len(self.result_list))
  
      @filter_hook
      def get_result_list(self):
          return self.make_result_list()
  
      @filter_hook
      def post_result_list(self):
          return self.make_result_list()
  
      @filter_hook
      def get_list_queryset(self):
          """
          Get model queryset. The query has been filted and ordered.
          """
          # First, get queryset from base class.
          queryset = self.queryset()
  
          # Use select_related() if one of the list_display options is a field
          # with a relationship and the provided queryset doesn't already have
          # select_related defined.
          if not queryset.query.select_related:
              if self.list_select_related:
                  queryset = queryset.select_related()
              elif self.list_select_related is None:
                  related_fields = []
                  for field_name in self.list_display:
                      try:
                          field = self.opts.get_field(field_name)
                      except models.FieldDoesNotExist:
                          pass
                      else:
                          if isinstance(field.rel, models.ManyToOneRel):
                              related_fields.append(field_name)
                  if related_fields:
                      queryset = queryset.select_related(*related_fields)
              else:
                  pass
  
          # Then, set queryset ordering.
          queryset = queryset.order_by(*self.get_ordering())
  
          # Return the queryset.
          return queryset
  
      # List ordering
      def _get_default_ordering(self):
          ordering = []
          if self.ordering:
              ordering = self.ordering
          elif self.opts.ordering:
              ordering = self.opts.ordering
          return ordering
  
      @filter_hook
      def get_ordering_field(self, field_name):
          """
          Returns the proper model field name corresponding to the given
          field_name to use for ordering. field_name may either be the name of a
          proper model field or the name of a method (on the admin or model) or a
          callable with the 'admin_order_field' attribute. Returns None if no
          proper model field name can be matched.
          """
          try:
              field = self.opts.get_field(field_name)
              return field.name
          except models.FieldDoesNotExist:
              # See whether field_name is a name of a non-field
              # that allows sorting.
              if callable(field_name):
                  attr = field_name
              elif hasattr(self, field_name):
                  attr = getattr(self, field_name)
              else:
                  attr = getattr(self.model, field_name)
              return getattr(attr, 'admin_order_field', None)
  
      @filter_hook
      def get_ordering(self):
          """
          Returns the list of ordering fields for the change list.
          First we check the get_ordering() method in model admin, then we check
          the object's default ordering. Then, any manually-specified ordering
          from the query string overrides anything. Finally, a deterministic
          order is guaranteed by ensuring the primary key is used as the last
          ordering field.
          """
          ordering = list(super(ListAdminView, self).get_ordering()
                          or self._get_default_ordering())
          if ORDER_VAR in self.params and self.params[ORDER_VAR]:
              # Clear ordering and used params
              ordering = [pfx + self.get_ordering_field(field_name) for n, pfx, field_name in
                          map(
                          lambda p: p.rpartition('-'),
                          self.params[ORDER_VAR].split('.'))
                          if self.get_ordering_field(field_name)]
  
          # Ensure that the primary key is systematically present in the list of
          # ordering fields so we can guarantee a deterministic order across all
          # database backends.
          pk_name = self.opts.pk.name
          if not (set(ordering) & set(['pk', '-pk', pk_name, '-' + pk_name])):
              # The two sets do not intersect, meaning the pk isn't present. So
              # we add it.
              ordering.append('-pk')
  
          return ordering
  
      @filter_hook
      def get_ordering_field_columns(self):
          """
          Returns a OrderedDict of ordering field column numbers and asc/desc
          """
  
          # We must cope with more than one column having the same underlying sort
          # field, so we base things on column numbers.
          ordering = self._get_default_ordering()
          ordering_fields = OrderedDict()
          if ORDER_VAR not in self.params or not self.params[ORDER_VAR]:
              # for ordering specified on ModelAdmin or model Meta, we don't know
              # the right column numbers absolutely, because there might be more
              # than one column associated with that ordering, so we guess.
              for field in ordering:
                  if field.startswith('-'):
                      field = field[1:]
                      order_type = 'desc'
                  else:
                      order_type = 'asc'
                  for attr in self.list_display:
                      if self.get_ordering_field(attr) == field:
                          ordering_fields[field] = order_type
                          break
          else:
              for p in self.params[ORDER_VAR].split('.'):
                  none, pfx, field_name = p.rpartition('-')
                  ordering_fields[field_name] = 'desc' if pfx == '-' else 'asc'
          return ordering_fields
  
      def get_check_field_url(self, f):
          """
          Return the select column menu items link.
          We must use base_list_display, because list_display maybe changed by plugins.
          """
          fields = [fd for fd in self.base_list_display if fd != f.name]
          if len(self.base_list_display) == len(fields):
              if f.primary_key:
                  fields.insert(0, f.name)
              else:
                  fields.append(f.name)
          return self.get_query_string({COL_LIST_VAR: '.'.join(fields)})
  
      def get_model_method_fields(self):
          """
          Return the fields info defined in model. use FakeMethodField class wrap method as a db field.
          """
          methods = []
          for name in dir(self):
              try:
                  if getattr(getattr(self, name), 'is_column', False):
                      methods.append((name, getattr(self, name)))
              except:
                  pass
          return [FakeMethodField(name, getattr(method, 'short_description', capfirst(name.replace('_', ' '))))
                  for name, method in methods]
  
      @filter_hook
      def get_context(self):
          """
          Prepare the context for templates.
          """
          self.title = _('%s List') % force_unicode(self.opts.verbose_name)
          model_fields = [(f, f.name in self.list_display, self.get_check_field_url(f))
                          for f in (list(self.opts.fields) + self.get_model_method_fields()) if f.name not in self.list_exclude]
  
          new_context = {
              'model_name': force_unicode(self.opts.verbose_name_plural),
              'title': self.title,
              'cl': self,
              'model_fields': model_fields,
              'clean_select_field_url': self.get_query_string(remove=[COL_LIST_VAR]),
              'has_add_permission': self.has_add_permission(),
              'app_label': self.app_label,
              'brand_name': self.opts.verbose_name_plural,
              'brand_icon': self.get_model_icon(self.model),
              'add_url': self.model_admin_url('add'),
              'result_headers': self.result_headers(),
              'results': self.results()
          }
          context = super(ListAdminView, self).get_context()
          context.update(new_context)
          return context
  
      @filter_hook
      def get_response(self, context, *args, **kwargs):
          pass
  
      @csrf_protect_m
      @filter_hook
      def get(self, request, *args, **kwargs):
          """
          The 'change list' admin view for this model.
          """
          response = self.get_result_list()
          if response:
              return response
  
          context = self.get_context()
          context.update(kwargs or {})
  
          response = self.get_response(context, *args, **kwargs)
          return response or TemplateResponse(request, self.object_list_template or
                                              self.get_template_list('views/model_list.html'), context)
  
      @filter_hook
      def post_response(self, *args, **kwargs):
          pass
  
      @csrf_protect_m
      @filter_hook
      def post(self, request, *args, **kwargs):
          return self.post_result_list() or self.post_response(*args, **kwargs) or self.get(request, *args, **kwargs)
  
      @filter_hook
      def get_paginator(self):
          return self.paginator_class(self.list_queryset, self.list_per_page, 0, True)
  
      @filter_hook
      def get_page_number(self, i):
          if i == DOT:
              return mark_safe(u'<span class="dot-page">...</span> ')
          elif i == self.page_num:
              return mark_safe(u'<span class="this-page">%d</span> ' % (i + 1))
          else:
              return mark_safe(u'<a href="%s"%s>%d</a> ' % (escape(self.get_query_string({PAGE_VAR: i})), (i == self.paginator.num_pages - 1 and ' class="end"' or ''), i + 1))
  
      # Result List methods
      @filter_hook
      def result_header(self, field_name, row):
          ordering_field_columns = self.ordering_field_columns
          item = ResultHeader(field_name, row)
          text, attr = label_for_field(field_name, self.model,
                                       model_admin=self,
                                       return_attr=True
                                       )
          item.text = text
          item.attr = attr
          if attr and not getattr(attr, "admin_order_field", None):
              return item
  
          # OK, it is sortable if we got this far
          th_classes = ['sortable']
          order_type = ''
          new_order_type = 'desc'
          sort_priority = 0
          sorted = False
          # Is it currently being sorted on?
          if field_name in ordering_field_columns:
              sorted = True
              order_type = ordering_field_columns.get(field_name).lower()
              sort_priority = ordering_field_columns.keys().index(field_name) + 1
              th_classes.append('sorted %sending' % order_type)
              new_order_type = {'asc': 'desc', 'desc': 'asc'}[order_type]
  
          # build new ordering param
          o_list_asc = []  # URL for making this field the primary sort
          o_list_desc = []  # URL for making this field the primary sort
          o_list_remove = []  # URL for removing this field from sort
          o_list_toggle = []  # URL for toggling order type for this field
          make_qs_param = lambda t, n: ('-' if t == 'desc' else '') + str(n)
  
          for j, ot in ordering_field_columns.items():
              if j == field_name:  # Same column
                  param = make_qs_param(new_order_type, j)
                  # We want clicking on this header to bring the ordering to the
                  # front
                  o_list_asc.insert(0, j)
                  o_list_desc.insert(0, '-' + j)
                  o_list_toggle.append(param)
                  # o_list_remove - omit
              else:
                  param = make_qs_param(ot, j)
                  o_list_asc.append(param)
                  o_list_desc.append(param)
                  o_list_toggle.append(param)
                  o_list_remove.append(param)
  
          if field_name not in ordering_field_columns:
              o_list_asc.insert(0, field_name)
              o_list_desc.insert(0, '-' + field_name)
  
          item.sorted = sorted
          item.sortable = True
          item.ascending = (order_type == "asc")
          item.sort_priority = sort_priority
  
          menus = [
              ('asc', o_list_asc, 'caret-up', _(u'Sort ASC')),
              ('desc', o_list_desc, 'caret-down', _(u'Sort DESC')),
          ]
          if sorted:
              row['num_sorted_fields'] = row['num_sorted_fields'] + 1
              menus.append((None, o_list_remove, 'times', _(u'Cancel Sort')))
              item.btns.append('<a class="toggle" href="%s"><i class="fa fa-%s"></i></a>' % (
                  self.get_query_string({ORDER_VAR: '.'.join(o_list_toggle)}), 'sort-up' if order_type == "asc" else 'sort-down'))
  
          item.menus.extend(['<li%s><a href="%s" class="active"><i class="fa fa-%s"></i> %s</a></li>' %
                           (
                               (' class="active"' if sorted and order_type == i[
                                0] else ''),
                             self.get_query_string({ORDER_VAR: '.'.join(i[1])}), i[2], i[3]) for i in menus])
          item.classes.extend(th_classes)
  
          return item
  
      @filter_hook
      def result_headers(self):
          """
          Generates the list column headers.
          """
          row = ResultRow()
          row['num_sorted_fields'] = 0
          row.cells = [self.result_header(
              field_name, row) for field_name in self.list_display]
          return row
  
      @filter_hook
      def result_item(self, obj, field_name, row):
          """
          Generates the actual list of data.
          """
          item = ResultItem(field_name, row)
          try:
              f, attr, value = lookup_field(field_name, obj, self)
          except (AttributeError, ObjectDoesNotExist):
              item.text = mark_safe("<span class='text-muted'>%s</span>" % EMPTY_CHANGELIST_VALUE)
          else:
              if f is None:
                  item.allow_tags = getattr(attr, 'allow_tags', False)
                  boolean = getattr(attr, 'boolean', False)
                  if boolean:
                      item.allow_tags = True
                      item.text = boolean_icon(value)
                  else:
                      item.text = smart_unicode(value)
              else:
                  if isinstance(f.rel, models.ManyToOneRel):
                      field_val = getattr(obj, f.name)
                      if field_val is None:
                          item.text = mark_safe("<span class='text-muted'>%s</span>" % EMPTY_CHANGELIST_VALUE)
                      else:
                          item.text = field_val
                  else:
                      item.text = display_for_field(value, f)
                  if isinstance(f, models.DateField)\
                      or isinstance(f, models.TimeField)\
                          or isinstance(f, models.ForeignKey):
                      item.classes.append('nowrap')
  
              item.field = f
              item.attr = attr
              item.value = value
  
          # If list_display_links not defined, add the link tag to the first field
          if (item.row['is_display_first'] and not self.list_display_links) \
                  or field_name in self.list_display_links:
              item.row['is_display_first'] = False
              item.is_display_link = True
              if self.list_display_links_details:
                  item_res_uri = self.model_admin_url("detail", getattr(obj, self.pk_attname))
                  if item_res_uri:
                      if self.has_change_permission(obj):
                          edit_url = self.model_admin_url("change", getattr(obj, self.pk_attname))
                      else:
                          edit_url = ""
                      item.wraps.append('<a data-res-uri="%s" data-edit-uri="%s" class="details-handler" rel="tooltip" title="%s">%%s</a>'
                                       % (item_res_uri, edit_url, _(u'Details of %s') % str(obj)))
              else:
                  url = self.url_for_result(obj)
                  item.wraps.append(u'<a href="%s">%%s</a>' % url)
  
          return item
  
      @filter_hook
      def result_row(self, obj):
          row = ResultRow()
          row['is_display_first'] = True
          row['object'] = obj
          row.cells = [self.result_item(
              obj, field_name, row) for field_name in self.list_display]
          return row
  
      @filter_hook
      def results(self):
          results = []
          for obj in self.result_list:
              results.append(self.result_row(obj))
          return results
  
      @filter_hook
      def url_for_result(self, result):
          return self.get_object_url(result)
  
      # Media
      @filter_hook
      def get_media(self):
          media = super(ListAdminView, self).get_media() + self.vendor('xadmin.page.list.js', 'xadmin.page.form.js')
          if self.list_display_links_details:
              media += self.vendor('xadmin.plugin.details.js', 'xadmin.form.css')
          return media
  
      # Blocks
      @inclusion_tag('xadmin/includes/pagination.html')
      def block_pagination(self, context, nodes, page_type='normal'):
          """
          Generates the series of links to the pages in a paginated list.
          """
          paginator, page_num = self.paginator, self.page_num
  
          pagination_required = (
              not self.show_all or not self.can_show_all) and self.multi_page
          if not pagination_required:
              page_range = []
          else:
              ON_EACH_SIDE = {'normal': 5, 'small': 3}.get(page_type, 3)
              ON_ENDS = 2
  
              # If there are 10 or fewer pages, display links to every page.
              # Otherwise, do some fancy
              if paginator.num_pages <= 10:
                  page_range = range(paginator.num_pages)
              else:
                  # Insert "smart" pagination links, so that there are always ON_ENDS
                  # links at either end of the list of pages, and there are always
                  # ON_EACH_SIDE links at either end of the "current page" link.
                  page_range = []
                  if page_num > (ON_EACH_SIDE + ON_ENDS):
                      page_range.extend(range(0, ON_EACH_SIDE - 1))
                      page_range.append(DOT)
                      page_range.extend(
                          range(page_num - ON_EACH_SIDE, page_num + 1))
                  else:
                      page_range.extend(range(0, page_num + 1))
                  if page_num < (paginator.num_pages - ON_EACH_SIDE - ON_ENDS - 1):
                      page_range.extend(
                          range(page_num + 1, page_num + ON_EACH_SIDE + 1))
                      page_range.append(DOT)
                      page_range.extend(range(
                          paginator.num_pages - ON_ENDS, paginator.num_pages))
                  else:
                      page_range.extend(range(page_num + 1, paginator.num_pages))
  
          need_show_all_link = self.can_show_all and not self.show_all and self.multi_page
          return {
              'cl': self,
              'pagination_required': pagination_required,
              'show_all_url': need_show_all_link and self.get_query_string({ALL_VAR: ''}),
              'page_range': map(self.get_page_number, page_range),
              'ALL_VAR': ALL_VAR,
              '1': 1,
          }
  