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(' ')
|
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
|