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