Statistics
| Branch: | Revision:

root / env / lib / python2.7 / site-packages / django / contrib / admin / templatetags / admin_list.py @ 1a305335

History | View | Annotate | Download (15.3 KB)

1
import datetime
2

    
3
from django.contrib.admin.util import lookup_field, display_for_field, label_for_field
4
from django.contrib.admin.views.main import (ALL_VAR, EMPTY_CHANGELIST_VALUE,
5
    ORDER_VAR, PAGE_VAR, SEARCH_VAR)
6
from django.contrib.admin.templatetags.admin_static import static
7
from django.core.exceptions import ObjectDoesNotExist
8
from django.db import models
9
from django.utils import formats
10
from django.utils.html import escape, conditional_escape
11
from django.utils.safestring import mark_safe
12
from django.utils.text import capfirst
13
from django.utils.translation import ugettext as _
14
from django.utils.encoding import smart_unicode, force_unicode
15
from django.template import Library
16
from django.template.loader import get_template
17
from django.template.context import Context
18

    
19
register = Library()
20

    
21
DOT = '.'
22

    
23
@register.simple_tag
24
def paginator_number(cl,i):
25
    """
26
    Generates an individual page index link in a paginated list.
27
    """
28
    if i == DOT:
29
        return u'... '
30
    elif i == cl.page_num:
31
        return mark_safe(u'<span class="this-page">%d</span> ' % (i+1))
32
    else:
33
        return mark_safe(u'<a href="%s"%s>%d</a> ' % (escape(cl.get_query_string({PAGE_VAR: i})), (i == cl.paginator.num_pages-1 and ' class="end"' or ''), i+1))
34

    
35
@register.inclusion_tag('admin/pagination.html')
36
def pagination(cl):
37
    """
38
    Generates the series of links to the pages in a paginated list.
39
    """
40
    paginator, page_num = cl.paginator, cl.page_num
41

    
42
    pagination_required = (not cl.show_all or not cl.can_show_all) and cl.multi_page
43
    if not pagination_required:
44
        page_range = []
45
    else:
46
        ON_EACH_SIDE = 3
47
        ON_ENDS = 2
48

    
49
        # If there are 10 or fewer pages, display links to every page.
50
        # Otherwise, do some fancy
51
        if paginator.num_pages <= 10:
52
            page_range = range(paginator.num_pages)
53
        else:
54
            # Insert "smart" pagination links, so that there are always ON_ENDS
55
            # links at either end of the list of pages, and there are always
56
            # ON_EACH_SIDE links at either end of the "current page" link.
57
            page_range = []
58
            if page_num > (ON_EACH_SIDE + ON_ENDS):
59
                page_range.extend(range(0, ON_EACH_SIDE - 1))
60
                page_range.append(DOT)
61
                page_range.extend(range(page_num - ON_EACH_SIDE, page_num + 1))
62
            else:
63
                page_range.extend(range(0, page_num + 1))
64
            if page_num < (paginator.num_pages - ON_EACH_SIDE - ON_ENDS - 1):
65
                page_range.extend(range(page_num + 1, page_num + ON_EACH_SIDE + 1))
66
                page_range.append(DOT)
67
                page_range.extend(range(paginator.num_pages - ON_ENDS, paginator.num_pages))
68
            else:
69
                page_range.extend(range(page_num + 1, paginator.num_pages))
70

    
71
    need_show_all_link = cl.can_show_all and not cl.show_all and cl.multi_page
72
    return {
73
        'cl': cl,
74
        'pagination_required': pagination_required,
75
        'show_all_url': need_show_all_link and cl.get_query_string({ALL_VAR: ''}),
76
        'page_range': page_range,
77
        'ALL_VAR': ALL_VAR,
78
        '1': 1,
79
    }
80

    
81
def result_headers(cl):
82
    """
83
    Generates the list column headers.
84
    """
85
    ordering_field_columns = cl.get_ordering_field_columns()
86
    for i, field_name in enumerate(cl.list_display):
87
        text, attr = label_for_field(field_name, cl.model,
88
            model_admin = cl.model_admin,
89
            return_attr = True
90
        )
91
        if attr:
92
            # Potentially not sortable
93

    
94
            # if the field is the action checkbox: no sorting and special class
95
            if field_name == 'action_checkbox':
96
                yield {
97
                    "text": text,
98
                    "class_attrib": mark_safe(' class="action-checkbox-column"'),
99
                    "sortable": False,
100
                }
101
                continue
102

    
103
            admin_order_field = getattr(attr, "admin_order_field", None)
104
            if not admin_order_field:
105
                # Not sortable
106
                yield {
107
                    "text": text,
108
                    "sortable": False,
109
                }
110
                continue
111

    
112
        # OK, it is sortable if we got this far
113
        th_classes = ['sortable']
114
        order_type = ''
115
        new_order_type = 'asc'
116
        sort_priority = 0
117
        sorted = False
118
        # Is it currently being sorted on?
119
        if i in ordering_field_columns:
120
            sorted = True
121
            order_type = ordering_field_columns.get(i).lower()
122
            sort_priority = ordering_field_columns.keys().index(i) + 1
123
            th_classes.append('sorted %sending' % order_type)
124
            new_order_type = {'asc': 'desc', 'desc': 'asc'}[order_type]
125

    
126
        # build new ordering param
127
        o_list_primary = [] # URL for making this field the primary sort
128
        o_list_remove  = [] # URL for removing this field from sort
129
        o_list_toggle  = [] # URL for toggling order type for this field
130
        make_qs_param = lambda t, n: ('-' if t == 'desc' else '') + str(n)
131

    
132
        for j, ot in ordering_field_columns.items():
133
            if j == i: # Same column
134
                param = make_qs_param(new_order_type, j)
135
                # We want clicking on this header to bring the ordering to the
136
                # front
137
                o_list_primary.insert(0, param)
138
                o_list_toggle.append(param)
139
                # o_list_remove - omit
140
            else:
141
                param = make_qs_param(ot, j)
142
                o_list_primary.append(param)
143
                o_list_toggle.append(param)
144
                o_list_remove.append(param)
145

    
146
        if i not in ordering_field_columns:
147
            o_list_primary.insert(0, make_qs_param(new_order_type, i))
148

    
149

    
150
        yield {
151
            "text": text,
152
            "sortable": True,
153
            "sorted": sorted,
154
            "ascending": order_type == "asc",
155
            "sort_priority": sort_priority,
156
            "url_primary": cl.get_query_string({ORDER_VAR: '.'.join(o_list_primary)}),
157
            "url_remove": cl.get_query_string({ORDER_VAR: '.'.join(o_list_remove)}),
158
            "url_toggle": cl.get_query_string({ORDER_VAR: '.'.join(o_list_toggle)}),
159
            "class_attrib": mark_safe(th_classes and ' class="%s"' % ' '.join(th_classes) or '')
160
        }
161

    
162
def _boolean_icon(field_val):
163
    icon_url = static('admin/img/icon-%s.gif' %
164
                      {True: 'yes', False: 'no', None: 'unknown'}[field_val])
165
    return mark_safe(u'<img src="%s" alt="%s" />' % (icon_url, field_val))
166

    
167
def items_for_result(cl, result, form):
168
    """
169
    Generates the actual list of data.
170
    """
171
    first = True
172
    pk = cl.lookup_opts.pk.attname
173
    for field_name in cl.list_display:
174
        row_class = ''
175
        try:
176
            f, attr, value = lookup_field(field_name, result, cl.model_admin)
177
        except (AttributeError, ObjectDoesNotExist):
178
            result_repr = EMPTY_CHANGELIST_VALUE
179
        else:
180
            if f is None:
181
                if field_name == u'action_checkbox':
182
                    row_class = ' class="action-checkbox"'
183
                allow_tags = getattr(attr, 'allow_tags', False)
184
                boolean = getattr(attr, 'boolean', False)
185
                if boolean:
186
                    allow_tags = True
187
                    result_repr = _boolean_icon(value)
188
                else:
189
                    result_repr = smart_unicode(value)
190
                # Strip HTML tags in the resulting text, except if the
191
                # function has an "allow_tags" attribute set to True.
192
                if not allow_tags:
193
                    result_repr = escape(result_repr)
194
                else:
195
                    result_repr = mark_safe(result_repr)
196
            else:
197
                if isinstance(f.rel, models.ManyToOneRel):
198
                    field_val = getattr(result, f.name)
199
                    if field_val is None:
200
                        result_repr = EMPTY_CHANGELIST_VALUE
201
                    else:
202
                        result_repr = escape(field_val)
203
                else:
204
                    result_repr = display_for_field(value, f)
205
                if isinstance(f, models.DateField)\
206
                or isinstance(f, models.TimeField)\
207
                or isinstance(f, models.ForeignKey):
208
                    row_class = ' class="nowrap"'
209
        if force_unicode(result_repr) == '':
210
            result_repr = mark_safe('&nbsp;')
211
        # If list_display_links not defined, add the link tag to the first field
212
        if (first and not cl.list_display_links) or field_name in cl.list_display_links:
213
            table_tag = {True:'th', False:'td'}[first]
214
            first = False
215
            url = cl.url_for_result(result)
216
            # Convert the pk to something that can be used in Javascript.
217
            # Problem cases are long ints (23L) and non-ASCII strings.
218
            if cl.to_field:
219
                attr = str(cl.to_field)
220
            else:
221
                attr = pk
222
            value = result.serializable_value(attr)
223
            result_id = repr(force_unicode(value))[1:]
224
            yield mark_safe(u'<%s%s><a href="%s"%s>%s</a></%s>' % \
225
                (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))
226
        else:
227
            # By default the fields come from ModelAdmin.list_editable, but if we pull
228
            # the fields out of the form instead of list_editable custom admins
229
            # can provide fields on a per request basis
230
            if (form and field_name in form.fields and not (
231
                    field_name == cl.model._meta.pk.name and
232
                        form[cl.model._meta.pk.name].is_hidden)):
233
                bf = form[field_name]
234
                result_repr = mark_safe(force_unicode(bf.errors) + force_unicode(bf))
235
            else:
236
                result_repr = conditional_escape(result_repr)
237
            yield mark_safe(u'<td%s>%s</td>' % (row_class, result_repr))
238
    if form and not form[cl.model._meta.pk.name].is_hidden:
239
        yield mark_safe(u'<td>%s</td>' % force_unicode(form[cl.model._meta.pk.name]))
240

    
241
class ResultList(list):
242
    # Wrapper class used to return items in a list_editable
243
    # changelist, annotated with the form object for error
244
    # reporting purposes. Needed to maintain backwards
245
    # compatibility with existing admin templates.
246
    def __init__(self, form, *items):
247
        self.form = form
248
        super(ResultList, self).__init__(*items)
249

    
250
def results(cl):
251
    if cl.formset:
252
        for res, form in zip(cl.result_list, cl.formset.forms):
253
            yield ResultList(form, items_for_result(cl, res, form))
254
    else:
255
        for res in cl.result_list:
256
            yield ResultList(None, items_for_result(cl, res, None))
257

    
258
def result_hidden_fields(cl):
259
    if cl.formset:
260
        for res, form in zip(cl.result_list, cl.formset.forms):
261
            if form[cl.model._meta.pk.name].is_hidden:
262
                yield mark_safe(force_unicode(form[cl.model._meta.pk.name]))
263

    
264
@register.inclusion_tag("admin/change_list_results.html")
265
def result_list(cl):
266
    """
267
    Displays the headers and data list together
268
    """
269
    headers = list(result_headers(cl))
270
    num_sorted_fields = 0
271
    for h in headers:
272
        if h['sortable'] and h['sorted']:
273
            num_sorted_fields += 1
274
    return {'cl': cl,
275
            'result_hidden_fields': list(result_hidden_fields(cl)),
276
            'result_headers': headers,
277
            'num_sorted_fields': num_sorted_fields,
278
            'results': list(results(cl))}
279

    
280
@register.inclusion_tag('admin/date_hierarchy.html')
281
def date_hierarchy(cl):
282
    """
283
    Displays the date hierarchy for date drill-down functionality.
284
    """
285
    if cl.date_hierarchy:
286
        field_name = cl.date_hierarchy
287
        year_field = '%s__year' % field_name
288
        month_field = '%s__month' % field_name
289
        day_field = '%s__day' % field_name
290
        field_generic = '%s__' % field_name
291
        year_lookup = cl.params.get(year_field)
292
        month_lookup = cl.params.get(month_field)
293
        day_lookup = cl.params.get(day_field)
294

    
295
        link = lambda d: cl.get_query_string(d, [field_generic])
296

    
297
        if not (year_lookup or month_lookup or day_lookup):
298
            # select appropriate start level
299
            date_range = cl.query_set.aggregate(first=models.Min(field_name),
300
                                                last=models.Max(field_name))
301
            if date_range['first'] and date_range['last']:
302
                if date_range['first'].year == date_range['last'].year:
303
                    year_lookup = date_range['first'].year
304
                    if date_range['first'].month == date_range['last'].month:
305
                        month_lookup = date_range['first'].month
306

    
307
        if year_lookup and month_lookup and day_lookup:
308
            day = datetime.date(int(year_lookup), int(month_lookup), int(day_lookup))
309
            return {
310
                'show': True,
311
                'back': {
312
                    'link': link({year_field: year_lookup, month_field: month_lookup}),
313
                    'title': capfirst(formats.date_format(day, 'YEAR_MONTH_FORMAT'))
314
                },
315
                'choices': [{'title': capfirst(formats.date_format(day, 'MONTH_DAY_FORMAT'))}]
316
            }
317
        elif year_lookup and month_lookup:
318
            days = cl.query_set.filter(**{year_field: year_lookup, month_field: month_lookup}).dates(field_name, 'day')
319
            return {
320
                'show': True,
321
                'back': {
322
                    'link': link({year_field: year_lookup}),
323
                    'title': str(year_lookup)
324
                },
325
                'choices': [{
326
                    'link': link({year_field: year_lookup, month_field: month_lookup, day_field: day.day}),
327
                    'title': capfirst(formats.date_format(day, 'MONTH_DAY_FORMAT'))
328
                } for day in days]
329
            }
330
        elif year_lookup:
331
            months = cl.query_set.filter(**{year_field: year_lookup}).dates(field_name, 'month')
332
            return {
333
                'show' : True,
334
                'back': {
335
                    'link' : link({}),
336
                    'title': _('All dates')
337
                },
338
                'choices': [{
339
                    'link': link({year_field: year_lookup, month_field: month.month}),
340
                    'title': capfirst(formats.date_format(month, 'YEAR_MONTH_FORMAT'))
341
                } for month in months]
342
            }
343
        else:
344
            years = cl.query_set.dates(field_name, 'year')
345
            return {
346
                'show': True,
347
                'choices': [{
348
                    'link': link({year_field: str(year.year)}),
349
                    'title': str(year.year),
350
                } for year in years]
351
            }
352

    
353
@register.inclusion_tag('admin/search_form.html')
354
def search_form(cl):
355
    """
356
    Displays a search form for searching the list.
357
    """
358
    return {
359
        'cl': cl,
360
        'show_result_count': cl.result_count != cl.full_result_count,
361
        'search_var': SEARCH_VAR
362
    }
363

    
364
@register.simple_tag
365
def admin_list_filter(cl, spec):
366
    tpl = get_template(spec.template)
367
    return tpl.render(Context({
368
        'title': spec.title,
369
        'choices' : list(spec.choices(cl)),
370
        'spec': spec,
371
    }))
372

    
373
@register.inclusion_tag('admin/actions.html', takes_context=True)
374
def admin_actions(context):
375
    """
376
    Track the number of times the action field has been rendered on the page,
377
    so we know which value to use.
378
    """
379
    context['action_index'] = context.get('action_index', -1) + 1
380
    return context