Django

Code

root/django/branches/newforms-admin/django/contrib/admin/templatetags/admin_list.py

Revision 7394, 12.5 kB (checked in by brosner, 8 months ago)

newforms-admin: Fixed #6226 -- allow_tags is now correctly honored and marks the data as safe. Thanks, michelts and jgelens.

  • Property svn:eol-style set to native
Line 
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(cl.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.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.list_display_links) or field_name in cl.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.date_hierarchy:
216         field_name = cl.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 admin_list_filter(cl, spec):
284     return {'title': spec.title(), 'choices' : list(spec.choices(cl))}
285 admin_list_filter = register.inclusion_tag('admin/filter.html')(admin_list_filter)
Note: See TracBrowser for help on using the browser.