import datetime from django.conf import settings from django.contrib.admin.util import lookup_field, display_for_field, label_for_field from django.contrib.admin.views.main import ALL_VAR, EMPTY_CHANGELIST_VALUE from django.contrib.admin.views.main import ORDER_VAR, ORDER_TYPE_VAR, PAGE_VAR, SEARCH_VAR from django.core.exceptions import ObjectDoesNotExist from django.db import models from django.forms.forms import pretty_name from django.utils import formats 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 django.utils.encoding import smart_unicode, force_unicode from django.template import Library register = Library() DOT = '.' def paginator_number(cl,i): """ Generates an individual page index link in a paginated list. """ if i == DOT: return u'... ' elif i == cl.page_num: return mark_safe(u'%d ' % (i+1)) else: return mark_safe(u'%d ' % (escape(cl.get_query_string({PAGE_VAR: i})), (i == cl.paginator.num_pages-1 and ' class="end"' or ''), i+1)) paginator_number = register.simple_tag(paginator_number) def pagination(cl): """ Generates the series of links to the pages in a paginated list. """ paginator, page_num = cl.paginator, cl.page_num pagination_required = (not cl.show_all or not cl.can_show_all) and cl.multi_page if not pagination_required: page_range = [] else: ON_EACH_SIDE = 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 = cl.can_show_all and not cl.show_all and cl.multi_page return { 'cl': cl, 'pagination_required': pagination_required, 'show_all_url': need_show_all_link and cl.get_query_string({ALL_VAR: ''}), 'page_range': page_range, 'ALL_VAR': ALL_VAR, '1': 1, } pagination = register.inclusion_tag('admin/pagination.html')(pagination) def result_headers(cl): """ Generates the list column headers. """ lookup_opts = cl.lookup_opts for i, field_name in enumerate(cl.list_display): header, attr = label_for_field(field_name, cl.model, model_admin = cl.model_admin, return_attr = True ) if attr: # if the field is the action checkbox: no sorting and special class if field_name == 'action_checkbox': yield { "text": header, "class_attrib": mark_safe(' class="action-checkbox-column"') } continue # It is a non-field, but perhaps one that is sortable admin_order_field = getattr(attr, "admin_order_field", None) if not admin_order_field: yield {"text": header} continue # So this _is_ a sortable non-field. Go to the yield # after the else clause. else: admin_order_field = None th_classes = [] new_order_type = 'asc' if field_name == cl.order_field or admin_order_field == cl.order_field: th_classes.append('sorted %sending' % cl.order_type.lower()) new_order_type = {'asc': 'desc', 'desc': 'asc'}[cl.order_type.lower()] yield { "text": header, "sortable": True, "url": cl.get_query_string({ORDER_VAR: i, ORDER_TYPE_VAR: new_order_type}), "class_attrib": mark_safe(th_classes and ' class="%s"' % ' '.join(th_classes) or '') } def _boolean_icon(field_val): BOOLEAN_MAPPING = {True: 'yes', False: 'no', None: 'unknown'} return mark_safe(u'%s' % (settings.ADMIN_MEDIA_PREFIX, BOOLEAN_MAPPING[field_val], field_val)) def items_for_result(cl, result, form): """ Generates the actual list of data. """ first = True pk = cl.lookup_opts.pk.attname for field_name in cl.list_display: row_class = '' try: f, attr, value = lookup_field(field_name, result, cl.model_admin) except (AttributeError, ObjectDoesNotExist): result_repr = EMPTY_CHANGELIST_VALUE else: if f is None: allow_tags = getattr(attr, 'allow_tags', False) boolean = getattr(attr, 'boolean', False) if boolean: allow_tags = True result_repr = _boolean_icon(value) else: result_repr = smart_unicode(value) # Strip HTML tags in the resulting text, except if the # function has an "allow_tags" attribute set to True. if not allow_tags: result_repr = escape(result_repr) else: result_repr = mark_safe(result_repr) else: if value is None: result_repr = EMPTY_CHANGELIST_VALUE if isinstance(f.rel, models.ManyToOneRel): field_val = getattr(result, f.name) if field_val is None: result_repr = EMPTY_CHANGELIST_VALUE else: result_repr = escape(field_val) else: result_repr = display_for_field(value, f) if isinstance(f, models.DateField) or isinstance(f, models.TimeField): row_class = ' class="nowrap"' if force_unicode(result_repr) == '': result_repr = mark_safe(' ') # If list_display_links not defined, add the link tag to the first field if (first and not cl.list_display_links) or field_name in cl.list_display_links: table_tag = {True:'th', False:'td'}[first] first = False url = cl.url_for_result(result) # Convert the pk to something that can be used in Javascript. # Problem cases are long ints (23L) and non-ASCII strings. if cl.to_field: attr = str(cl.to_field) else: attr = pk value = result.serializable_value(attr) result_id = repr(force_unicode(value))[1:] yield mark_safe(u'<%s%s>%s' % \ (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)) else: # By default the fields come from ModelAdmin.list_editable, but if we pull # the fields out of the form instead of list_editable custom admins # can provide fields on a per request basis if form and field_name in form.fields: bf = form[field_name] result_repr = mark_safe(force_unicode(bf.errors) + force_unicode(bf)) else: result_repr = conditional_escape(result_repr) yield mark_safe(u'%s' % (row_class, result_repr)) if form and not form[cl.model._meta.pk.name].is_hidden: yield mark_safe(u'%s' % force_unicode(form[cl.model._meta.pk.name])) def results(cl): if cl.formset: for res, form in zip(cl.result_list, cl.formset.forms): yield list(items_for_result(cl, res, form)) else: for res in cl.result_list: yield list(items_for_result(cl, res, None)) def result_hidden_fields(cl): if cl.formset: for res, form in zip(cl.result_list, cl.formset.forms): if form[cl.model._meta.pk.name].is_hidden: yield mark_safe(force_unicode(form[cl.model._meta.pk.name])) def result_list(cl): """ Displays the headers and data list together """ return {'cl': cl, 'result_hidden_fields': list(result_hidden_fields(cl)), 'result_headers': list(result_headers(cl)), 'results': list(results(cl))} result_list = register.inclusion_tag("admin/change_list_results.html")(result_list) def date_hierarchy(cl): """ Displays the date hierarchy for date drill-down functionality. """ if cl.date_hierarchy: field_name = cl.date_hierarchy year_field = '%s__year' % field_name month_field = '%s__month' % field_name day_field = '%s__day' % field_name field_generic = '%s__' % field_name year_lookup = cl.params.get(year_field) month_lookup = cl.params.get(month_field) day_lookup = cl.params.get(day_field) link = lambda d: cl.get_query_string(d, [field_generic]) if year_lookup and month_lookup and day_lookup: day = datetime.date(int(year_lookup), int(month_lookup), int(day_lookup)) return { 'show': True, 'back': { 'link': link({year_field: year_lookup, month_field: month_lookup}), 'title': capfirst(formats.date_format(day, 'YEAR_MONTH_FORMAT')) }, 'choices': [{'title': capfirst(formats.date_format(day, 'MONTH_DAY_FORMAT'))}] } elif year_lookup and month_lookup: days = cl.query_set.filter(**{year_field: year_lookup, month_field: month_lookup}).dates(field_name, 'day') return { 'show': True, 'back': { 'link': link({year_field: year_lookup}), 'title': str(year_lookup) }, 'choices': [{ 'link': link({year_field: year_lookup, month_field: month_lookup, day_field: day.day}), 'title': capfirst(formats.date_format(day, 'MONTH_DAY_FORMAT')) } for day in days] } elif year_lookup: months = cl.query_set.filter(**{year_field: year_lookup}).dates(field_name, 'month') return { 'show' : True, 'back': { 'link' : link({}), 'title': _('All dates') }, 'choices': [{ 'link': link({year_field: year_lookup, month_field: month.month}), 'title': capfirst(formats.date_format(month, 'YEAR_MONTH_FORMAT')) } for month in months] } else: years = cl.query_set.dates(field_name, 'year') return { 'show': True, 'choices': [{ 'link': link({year_field: str(year.year)}), 'title': str(year.year), } for year in years] } date_hierarchy = register.inclusion_tag('admin/date_hierarchy.html')(date_hierarchy) def search_form(cl): """ Displays a search form for searching the list. """ return { 'cl': cl, 'show_result_count': cl.result_count != cl.full_result_count, 'search_var': SEARCH_VAR } search_form = register.inclusion_tag('admin/search_form.html')(search_form) def admin_list_filter(cl, spec): return {'title': spec.title(), 'choices' : list(spec.choices(cl))} admin_list_filter = register.inclusion_tag('admin/filter.html')(admin_list_filter) def admin_actions(context): """ Track the number of times the action field has been rendered on the page, so we know which value to use. """ context['action_index'] = context.get('action_index', -1) + 1 return context admin_actions = register.inclusion_tag("admin/actions.html", takes_context=True)(admin_actions)