app/django/contrib/admin/templatetags/admin_list.py
changeset 54 03e267d67478
child 323 ff1a9aa48cfd
equal deleted inserted replaced
53:57b4279d8c4e 54:03e267d67478
       
     1 from django.conf import settings
       
     2 from django.contrib.admin.views.main import ALL_VAR, EMPTY_CHANGELIST_VALUE
       
     3 from django.contrib.admin.views.main import ORDER_VAR, ORDER_TYPE_VAR, PAGE_VAR, SEARCH_VAR
       
     4 from django.core.exceptions import ObjectDoesNotExist
       
     5 from django.db import models
       
     6 from django.utils import dateformat
       
     7 from django.utils.html import escape, conditional_escape
       
     8 from django.utils.text import capfirst
       
     9 from django.utils.safestring import mark_safe
       
    10 from django.utils.translation import get_date_formats, get_partial_date_formats, ugettext as _
       
    11 from django.utils.encoding import smart_unicode, smart_str, force_unicode
       
    12 from django.template import Library
       
    13 import datetime
       
    14 
       
    15 register = Library()
       
    16 
       
    17 DOT = '.'
       
    18 
       
    19 def paginator_number(cl,i):
       
    20     if i == DOT:
       
    21         return u'... '
       
    22     elif i == cl.page_num:
       
    23         return mark_safe(u'<span class="this-page">%d</span> ' % (i+1))
       
    24     else:
       
    25         return mark_safe(u'<a href="%s"%s>%d</a> ' % (cl.get_query_string({PAGE_VAR: i}), (i == cl.paginator.num_pages-1 and ' class="end"' or ''), i+1))
       
    26 paginator_number = register.simple_tag(paginator_number)
       
    27 
       
    28 def pagination(cl):
       
    29     paginator, page_num = cl.paginator, cl.page_num
       
    30 
       
    31     pagination_required = (not cl.show_all or not cl.can_show_all) and cl.multi_page
       
    32     if not pagination_required:
       
    33         page_range = []
       
    34     else:
       
    35         ON_EACH_SIDE = 3
       
    36         ON_ENDS = 2
       
    37 
       
    38         # If there are 10 or fewer pages, display links to every page.
       
    39         # Otherwise, do some fancy
       
    40         if paginator.num_pages <= 10:
       
    41             page_range = range(paginator.num_pages)
       
    42         else:
       
    43             # Insert "smart" pagination links, so that there are always ON_ENDS
       
    44             # links at either end of the list of pages, and there are always
       
    45             # ON_EACH_SIDE links at either end of the "current page" link.
       
    46             page_range = []
       
    47             if page_num > (ON_EACH_SIDE + ON_ENDS):
       
    48                 page_range.extend(range(0, ON_EACH_SIDE - 1))
       
    49                 page_range.append(DOT)
       
    50                 page_range.extend(range(page_num - ON_EACH_SIDE, page_num + 1))
       
    51             else:
       
    52                 page_range.extend(range(0, page_num + 1))
       
    53             if page_num < (paginator.num_pages - ON_EACH_SIDE - ON_ENDS - 1):
       
    54                 page_range.extend(range(page_num + 1, page_num + ON_EACH_SIDE + 1))
       
    55                 page_range.append(DOT)
       
    56                 page_range.extend(range(paginator.num_pages - ON_ENDS, paginator.num_pages))
       
    57             else:
       
    58                 page_range.extend(range(page_num + 1, paginator.num_pages))
       
    59 
       
    60     need_show_all_link = cl.can_show_all and not cl.show_all and cl.multi_page
       
    61     return {
       
    62         'cl': cl,
       
    63         'pagination_required': pagination_required,
       
    64         'show_all_url': need_show_all_link and cl.get_query_string({ALL_VAR: ''}),
       
    65         'page_range': page_range,
       
    66         'ALL_VAR': ALL_VAR,
       
    67         '1': 1,
       
    68     }
       
    69 pagination = register.inclusion_tag('admin/pagination.html')(pagination)
       
    70 
       
    71 def result_headers(cl):
       
    72     lookup_opts = cl.lookup_opts
       
    73 
       
    74     for i, field_name in enumerate(lookup_opts.admin.list_display):
       
    75         try:
       
    76             f = lookup_opts.get_field(field_name)
       
    77             admin_order_field = None
       
    78         except models.FieldDoesNotExist:
       
    79             # For non-field list_display values, check for the function
       
    80             # attribute "short_description". If that doesn't exist, fall back
       
    81             # to the method name. And __str__ and __unicode__ are special-cases.
       
    82             if field_name == '__unicode__':
       
    83                 header = force_unicode(lookup_opts.verbose_name)
       
    84             elif field_name == '__str__':
       
    85                 header = smart_str(lookup_opts.verbose_name)
       
    86             else:
       
    87                 attr = getattr(cl.model, field_name) # Let AttributeErrors propagate.
       
    88                 try:
       
    89                     header = attr.short_description
       
    90                 except AttributeError:
       
    91                     header = field_name.replace('_', ' ')
       
    92 
       
    93             # It is a non-field, but perhaps one that is sortable
       
    94             admin_order_field = getattr(getattr(cl.model, field_name), "admin_order_field", None)
       
    95             if not admin_order_field:
       
    96                 yield {"text": header}
       
    97                 continue
       
    98 
       
    99             # So this _is_ a sortable non-field.  Go to the yield
       
   100             # after the else clause.
       
   101         else:
       
   102             if isinstance(f.rel, models.ManyToOneRel) and f.null:
       
   103                 yield {"text": f.verbose_name}
       
   104                 continue
       
   105             else:
       
   106                 header = f.verbose_name
       
   107 
       
   108         th_classes = []
       
   109         new_order_type = 'asc'
       
   110         if field_name == cl.order_field or admin_order_field == cl.order_field:
       
   111             th_classes.append('sorted %sending' % cl.order_type.lower())
       
   112             new_order_type = {'asc': 'desc', 'desc': 'asc'}[cl.order_type.lower()]
       
   113 
       
   114         yield {"text": header,
       
   115                "sortable": True,
       
   116                "url": cl.get_query_string({ORDER_VAR: i, ORDER_TYPE_VAR: new_order_type}),
       
   117                "class_attrib": mark_safe(th_classes and ' class="%s"' % ' '.join(th_classes) or '')}
       
   118 
       
   119 def _boolean_icon(field_val):
       
   120     BOOLEAN_MAPPING = {True: 'yes', False: 'no', None: 'unknown'}
       
   121     return mark_safe(u'<img src="%simg/admin/icon-%s.gif" alt="%s" />' % (settings.ADMIN_MEDIA_PREFIX, BOOLEAN_MAPPING[field_val], field_val))
       
   122 
       
   123 def items_for_result(cl, result):
       
   124     first = True
       
   125     pk = cl.lookup_opts.pk.attname
       
   126     for field_name in cl.lookup_opts.admin.list_display:
       
   127         row_class = ''
       
   128         try:
       
   129             f = cl.lookup_opts.get_field(field_name)
       
   130         except models.FieldDoesNotExist:
       
   131             # For non-field list_display values, the value is either a method
       
   132             # or a property.
       
   133             try:
       
   134                 attr = getattr(result, field_name)
       
   135                 allow_tags = getattr(attr, 'allow_tags', False)
       
   136                 boolean = getattr(attr, 'boolean', False)
       
   137                 if callable(attr):
       
   138                     attr = attr()
       
   139                 if boolean:
       
   140                     allow_tags = True
       
   141                     result_repr = _boolean_icon(attr)
       
   142                 else:
       
   143                     result_repr = smart_unicode(attr)
       
   144             except (AttributeError, ObjectDoesNotExist):
       
   145                 result_repr = EMPTY_CHANGELIST_VALUE
       
   146             else:
       
   147                 # Strip HTML tags in the resulting text, except if the
       
   148                 # function has an "allow_tags" attribute set to True.
       
   149                 if not allow_tags:
       
   150                     result_repr = escape(result_repr)
       
   151                 else:
       
   152                     result_repr = mark_safe(result_repr)
       
   153         else:
       
   154             field_val = getattr(result, f.attname)
       
   155 
       
   156             if isinstance(f.rel, models.ManyToOneRel):
       
   157                 if field_val is not None:
       
   158                     result_repr = escape(getattr(result, f.name))
       
   159                 else:
       
   160                     result_repr = EMPTY_CHANGELIST_VALUE
       
   161             # Dates and times are special: They're formatted in a certain way.
       
   162             elif isinstance(f, models.DateField) or isinstance(f, models.TimeField):
       
   163                 if field_val:
       
   164                     (date_format, datetime_format, time_format) = get_date_formats()
       
   165                     if isinstance(f, models.DateTimeField):
       
   166                         result_repr = capfirst(dateformat.format(field_val, datetime_format))
       
   167                     elif isinstance(f, models.TimeField):
       
   168                         result_repr = capfirst(dateformat.time_format(field_val, time_format))
       
   169                     else:
       
   170                         result_repr = capfirst(dateformat.format(field_val, date_format))
       
   171                 else:
       
   172                     result_repr = EMPTY_CHANGELIST_VALUE
       
   173                 row_class = ' class="nowrap"'
       
   174             # Booleans are special: We use images.
       
   175             elif isinstance(f, models.BooleanField) or isinstance(f, models.NullBooleanField):
       
   176                 result_repr = _boolean_icon(field_val)
       
   177             # DecimalFields are special: Zero-pad the decimals.
       
   178             elif isinstance(f, models.DecimalField):
       
   179                 if field_val is not None:
       
   180                     result_repr = ('%%.%sf' % f.decimal_places) % field_val
       
   181                 else:
       
   182                     result_repr = EMPTY_CHANGELIST_VALUE
       
   183             # Fields with choices are special: Use the representation
       
   184             # of the choice.
       
   185             elif f.choices:
       
   186                 result_repr = dict(f.choices).get(field_val, EMPTY_CHANGELIST_VALUE)
       
   187             else:
       
   188                 result_repr = escape(field_val)
       
   189         if force_unicode(result_repr) == '':
       
   190             result_repr = mark_safe('&nbsp;')
       
   191         # If list_display_links not defined, add the link tag to the first field
       
   192         if (first and not cl.lookup_opts.admin.list_display_links) or field_name in cl.lookup_opts.admin.list_display_links:
       
   193             table_tag = {True:'th', False:'td'}[first]
       
   194             first = False
       
   195             url = cl.url_for_result(result)
       
   196             # Convert the pk to something that can be used in Javascript.
       
   197             # Problem cases are long ints (23L) and non-ASCII strings.
       
   198             result_id = repr(force_unicode(getattr(result, pk)))[1:]
       
   199             yield mark_safe(u'<%s%s><a href="%s"%s>%s</a></%s>' % \
       
   200                 (table_tag, row_class, url, (cl.is_popup and ' onclick="opener.dismissRelatedLookupPopup(window, %s); return false;"' % result_id or ''), conditional_escape(result_repr), table_tag))
       
   201         else:
       
   202             yield mark_safe(u'<td%s>%s</td>' % (row_class, conditional_escape(result_repr)))
       
   203 
       
   204 def results(cl):
       
   205     for res in cl.result_list:
       
   206         yield list(items_for_result(cl,res))
       
   207 
       
   208 def result_list(cl):
       
   209     return {'cl': cl,
       
   210             'result_headers': list(result_headers(cl)),
       
   211             'results': list(results(cl))}
       
   212 result_list = register.inclusion_tag("admin/change_list_results.html")(result_list)
       
   213 
       
   214 def date_hierarchy(cl):
       
   215     if cl.lookup_opts.admin.date_hierarchy:
       
   216         field_name = cl.lookup_opts.admin.date_hierarchy
       
   217         year_field = '%s__year' % field_name
       
   218         month_field = '%s__month' % field_name
       
   219         day_field = '%s__day' % field_name
       
   220         field_generic = '%s__' % field_name
       
   221         year_lookup = cl.params.get(year_field)
       
   222         month_lookup = cl.params.get(month_field)
       
   223         day_lookup = cl.params.get(day_field)
       
   224         year_month_format, month_day_format = get_partial_date_formats()
       
   225 
       
   226         link = lambda d: mark_safe(cl.get_query_string(d, [field_generic]))
       
   227 
       
   228         if year_lookup and month_lookup and day_lookup:
       
   229             day = datetime.date(int(year_lookup), int(month_lookup), int(day_lookup))
       
   230             return {
       
   231                 'show': True,
       
   232                 'back': {
       
   233                     'link': link({year_field: year_lookup, month_field: month_lookup}),
       
   234                     'title': dateformat.format(day, year_month_format)
       
   235                 },
       
   236                 'choices': [{'title': dateformat.format(day, month_day_format)}]
       
   237             }
       
   238         elif year_lookup and month_lookup:
       
   239             days = cl.query_set.filter(**{year_field: year_lookup, month_field: month_lookup}).dates(field_name, 'day')
       
   240             return {
       
   241                 'show': True,
       
   242                 'back': {
       
   243                     'link': link({year_field: year_lookup}),
       
   244                     'title': year_lookup
       
   245                 },
       
   246                 'choices': [{
       
   247                     'link': link({year_field: year_lookup, month_field: month_lookup, day_field: day.day}),
       
   248                     'title': dateformat.format(day, month_day_format)
       
   249                 } for day in days]
       
   250             }
       
   251         elif year_lookup:
       
   252             months = cl.query_set.filter(**{year_field: year_lookup}).dates(field_name, 'month')
       
   253             return {
       
   254                 'show' : True,
       
   255                 'back': {
       
   256                     'link' : link({}),
       
   257                     'title': _('All dates')
       
   258                 },
       
   259                 'choices': [{
       
   260                     'link': link({year_field: year_lookup, month_field: month.month}),
       
   261                     'title': dateformat.format(month, year_month_format)
       
   262                 } for month in months]
       
   263             }
       
   264         else:
       
   265             years = cl.query_set.dates(field_name, 'year')
       
   266             return {
       
   267                 'show': True,
       
   268                 'choices': [{
       
   269                     'link': link({year_field: year.year}),
       
   270                     'title': year.year
       
   271                 } for year in years]
       
   272             }
       
   273 date_hierarchy = register.inclusion_tag('admin/date_hierarchy.html')(date_hierarchy)
       
   274 
       
   275 def search_form(cl):
       
   276     return {
       
   277         'cl': cl,
       
   278         'show_result_count': cl.result_count != cl.full_result_count and not cl.opts.one_to_one_field,
       
   279         'search_var': SEARCH_VAR
       
   280     }
       
   281 search_form = register.inclusion_tag('admin/search_form.html')(search_form)
       
   282 
       
   283 def filter(cl, spec):
       
   284     return {'title': spec.title(), 'choices' : list(spec.choices(cl))}
       
   285 filter = register.inclusion_tag('admin/filter.html')(filter)
       
   286 
       
   287 def filters(cl):
       
   288     return {'cl': cl}
       
   289 filters = register.inclusion_tag('admin/filters.html')(filters)