Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/django/contrib/admin/templatetags/admin_list.py: 15%
224 statements
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
1import datetime
3from django.conf import settings
4from django.contrib.admin.templatetags.admin_urls import add_preserved_filters
5from django.contrib.admin.utils import (
6 display_for_field,
7 display_for_value,
8 get_fields_from_path,
9 label_for_field,
10 lookup_field,
11)
12from django.contrib.admin.views.main import (
13 ALL_VAR,
14 IS_POPUP_VAR,
15 ORDER_VAR,
16 PAGE_VAR,
17 SEARCH_VAR,
18)
19from django.core.exceptions import ObjectDoesNotExist
20from django.db import models
21from django.template import Library
22from django.template.loader import get_template
23from django.templatetags.static import static
24from django.urls import NoReverseMatch
25from django.utils import formats, timezone
26from django.utils.html import format_html
27from django.utils.safestring import mark_safe
28from django.utils.text import capfirst
29from django.utils.translation import gettext as _
31from .base import InclusionAdminNode
33register = Library()
36@register.simple_tag
37def paginator_number(cl, i):
38 """
39 Generate an individual page index link in a paginated list.
40 """
41 if i == cl.paginator.ELLIPSIS:
42 return format_html("{} ", cl.paginator.ELLIPSIS)
43 elif i == cl.page_num:
44 return format_html('<span class="this-page">{}</span> ', i)
45 else:
46 return format_html(
47 '<a href="{}"{}>{}</a> ',
48 cl.get_query_string({PAGE_VAR: i}),
49 mark_safe(' class="end"' if i == cl.paginator.num_pages else ""),
50 i,
51 )
54def pagination(cl):
55 """
56 Generate the series of links to the pages in a paginated list.
57 """
58 pagination_required = (not cl.show_all or not cl.can_show_all) and cl.multi_page
59 page_range = (
60 cl.paginator.get_elided_page_range(cl.page_num) if pagination_required else []
61 )
62 need_show_all_link = cl.can_show_all and not cl.show_all and cl.multi_page
63 return {
64 "cl": cl,
65 "pagination_required": pagination_required,
66 "show_all_url": need_show_all_link and cl.get_query_string({ALL_VAR: ""}),
67 "page_range": page_range,
68 "ALL_VAR": ALL_VAR,
69 "1": 1,
70 }
73@register.tag(name="pagination")
74def pagination_tag(parser, token):
75 return InclusionAdminNode(
76 parser,
77 token,
78 func=pagination,
79 template_name="pagination.html",
80 takes_context=False,
81 )
84def result_headers(cl):
85 """
86 Generate the list column headers.
87 """
88 ordering_field_columns = cl.get_ordering_field_columns()
89 for i, field_name in enumerate(cl.list_display):
90 text, attr = label_for_field(
91 field_name, cl.model, model_admin=cl.model_admin, return_attr=True
92 )
93 is_field_sortable = cl.sortable_by is None or field_name in cl.sortable_by
94 if attr:
95 field_name = _coerce_field_name(field_name, i)
96 # Potentially not sortable
98 # if the field is the action checkbox: no sorting and special class
99 if field_name == "action_checkbox":
100 yield {
101 "text": text,
102 "class_attrib": mark_safe(' class="action-checkbox-column"'),
103 "sortable": False,
104 }
105 continue
107 admin_order_field = getattr(attr, "admin_order_field", None)
108 # Set ordering for attr that is a property, if defined.
109 if isinstance(attr, property) and hasattr(attr, "fget"):
110 admin_order_field = getattr(attr.fget, "admin_order_field", None)
111 if not admin_order_field:
112 is_field_sortable = False
114 if not is_field_sortable:
115 # Not sortable
116 yield {
117 "text": text,
118 "class_attrib": format_html(' class="column-{}"', field_name),
119 "sortable": False,
120 }
121 continue
123 # OK, it is sortable if we got this far
124 th_classes = ["sortable", "column-{}".format(field_name)]
125 order_type = ""
126 new_order_type = "asc"
127 sort_priority = 0
128 # Is it currently being sorted on?
129 is_sorted = i in ordering_field_columns
130 if is_sorted:
131 order_type = ordering_field_columns.get(i).lower()
132 sort_priority = list(ordering_field_columns).index(i) + 1
133 th_classes.append("sorted %sending" % order_type)
134 new_order_type = {"asc": "desc", "desc": "asc"}[order_type]
136 # build new ordering param
137 o_list_primary = [] # URL for making this field the primary sort
138 o_list_remove = [] # URL for removing this field from sort
139 o_list_toggle = [] # URL for toggling order type for this field
141 def make_qs_param(t, n):
142 return ("-" if t == "desc" else "") + str(n)
144 for j, ot in ordering_field_columns.items():
145 if j == i: # Same column
146 param = make_qs_param(new_order_type, j)
147 # We want clicking on this header to bring the ordering to the
148 # front
149 o_list_primary.insert(0, param)
150 o_list_toggle.append(param)
151 # o_list_remove - omit
152 else:
153 param = make_qs_param(ot, j)
154 o_list_primary.append(param)
155 o_list_toggle.append(param)
156 o_list_remove.append(param)
158 if i not in ordering_field_columns:
159 o_list_primary.insert(0, make_qs_param(new_order_type, i))
161 yield {
162 "text": text,
163 "sortable": True,
164 "sorted": is_sorted,
165 "ascending": order_type == "asc",
166 "sort_priority": sort_priority,
167 "url_primary": cl.get_query_string({ORDER_VAR: ".".join(o_list_primary)}),
168 "url_remove": cl.get_query_string({ORDER_VAR: ".".join(o_list_remove)}),
169 "url_toggle": cl.get_query_string({ORDER_VAR: ".".join(o_list_toggle)}),
170 "class_attrib": format_html(' class="{}"', " ".join(th_classes))
171 if th_classes
172 else "",
173 }
176def _boolean_icon(field_val):
177 icon_url = static(
178 "admin/img/icon-%s.svg" % {True: "yes", False: "no", None: "unknown"}[field_val]
179 )
180 return format_html('<img src="{}" alt="{}">', icon_url, field_val)
183def _coerce_field_name(field_name, field_index):
184 """
185 Coerce a field_name (which may be a callable) to a string.
186 """
187 if callable(field_name):
188 if field_name.__name__ == "<lambda>":
189 return "lambda" + str(field_index)
190 else:
191 return field_name.__name__
192 return field_name
195def items_for_result(cl, result, form):
196 """
197 Generate the actual list of data.
198 """
200 def link_in_col(is_first, field_name, cl):
201 if cl.list_display_links is None:
202 return False
203 if is_first and not cl.list_display_links:
204 return True
205 return field_name in cl.list_display_links
207 first = True
208 pk = cl.lookup_opts.pk.attname
209 for field_index, field_name in enumerate(cl.list_display):
210 empty_value_display = cl.model_admin.get_empty_value_display()
211 row_classes = ["field-%s" % _coerce_field_name(field_name, field_index)]
212 try:
213 f, attr, value = lookup_field(field_name, result, cl.model_admin)
214 except ObjectDoesNotExist:
215 result_repr = empty_value_display
216 else:
217 empty_value_display = getattr(
218 attr, "empty_value_display", empty_value_display
219 )
220 if f is None or f.auto_created:
221 if field_name == "action_checkbox":
222 row_classes = ["action-checkbox"]
223 boolean = getattr(attr, "boolean", False)
224 result_repr = display_for_value(value, empty_value_display, boolean)
225 if isinstance(value, (datetime.date, datetime.time)):
226 row_classes.append("nowrap")
227 else:
228 if isinstance(f.remote_field, models.ManyToOneRel):
229 field_val = getattr(result, f.name)
230 if field_val is None:
231 result_repr = empty_value_display
232 else:
233 result_repr = field_val
234 else:
235 result_repr = display_for_field(value, f, empty_value_display)
236 if isinstance(
237 f, (models.DateField, models.TimeField, models.ForeignKey)
238 ):
239 row_classes.append("nowrap")
240 row_class = mark_safe(' class="%s"' % " ".join(row_classes))
241 # If list_display_links not defined, add the link tag to the first field
242 if link_in_col(first, field_name, cl):
243 table_tag = "th" if first else "td"
244 first = False
246 # Display link to the result's change_view if the url exists, else
247 # display just the result's representation.
248 try:
249 url = cl.url_for_result(result)
250 except NoReverseMatch:
251 link_or_text = result_repr
252 else:
253 url = add_preserved_filters(
254 {"preserved_filters": cl.preserved_filters, "opts": cl.opts}, url
255 )
256 # Convert the pk to something that can be used in JavaScript.
257 # Problem cases are non-ASCII strings.
258 if cl.to_field:
259 attr = str(cl.to_field)
260 else:
261 attr = pk
262 value = result.serializable_value(attr)
263 link_or_text = format_html(
264 '<a href="{}"{}>{}</a>',
265 url,
266 format_html(' data-popup-opener="{}"', value)
267 if cl.is_popup
268 else "",
269 result_repr,
270 )
272 yield format_html(
273 "<{}{}>{}</{}>", table_tag, row_class, link_or_text, table_tag
274 )
275 else:
276 # By default the fields come from ModelAdmin.list_editable, but if we pull
277 # the fields out of the form instead of list_editable custom admins
278 # can provide fields on a per request basis
279 if (
280 form
281 and field_name in form.fields
282 and not (
283 field_name == cl.model._meta.pk.name
284 and form[cl.model._meta.pk.name].is_hidden
285 )
286 ):
287 bf = form[field_name]
288 result_repr = mark_safe(str(bf.errors) + str(bf))
289 yield format_html("<td{}>{}</td>", row_class, result_repr)
290 if form and not form[cl.model._meta.pk.name].is_hidden:
291 yield format_html("<td>{}</td>", form[cl.model._meta.pk.name])
294class ResultList(list):
295 """
296 Wrapper class used to return items in a list_editable changelist, annotated
297 with the form object for error reporting purposes. Needed to maintain
298 backwards compatibility with existing admin templates.
299 """
301 def __init__(self, form, *items):
302 self.form = form
303 super().__init__(*items)
306def results(cl):
307 if cl.formset:
308 for res, form in zip(cl.result_list, cl.formset.forms):
309 yield ResultList(form, items_for_result(cl, res, form))
310 else:
311 for res in cl.result_list:
312 yield ResultList(None, items_for_result(cl, res, None))
315def result_hidden_fields(cl):
316 if cl.formset:
317 for res, form in zip(cl.result_list, cl.formset.forms):
318 if form[cl.model._meta.pk.name].is_hidden:
319 yield mark_safe(form[cl.model._meta.pk.name])
322def result_list(cl):
323 """
324 Display the headers and data list together.
325 """
326 headers = list(result_headers(cl))
327 num_sorted_fields = 0
328 for h in headers:
329 if h["sortable"] and h["sorted"]:
330 num_sorted_fields += 1
331 return {
332 "cl": cl,
333 "result_hidden_fields": list(result_hidden_fields(cl)),
334 "result_headers": headers,
335 "num_sorted_fields": num_sorted_fields,
336 "results": list(results(cl)),
337 }
340@register.tag(name="result_list")
341def result_list_tag(parser, token):
342 return InclusionAdminNode(
343 parser,
344 token,
345 func=result_list,
346 template_name="change_list_results.html",
347 takes_context=False,
348 )
351def date_hierarchy(cl):
352 """
353 Display the date hierarchy for date drill-down functionality.
354 """
355 if cl.date_hierarchy:
356 field_name = cl.date_hierarchy
357 field = get_fields_from_path(cl.model, field_name)[-1]
358 if isinstance(field, models.DateTimeField):
359 dates_or_datetimes = "datetimes"
360 qs_kwargs = {"is_dst": True} if settings.USE_DEPRECATED_PYTZ else {}
361 else:
362 dates_or_datetimes = "dates"
363 qs_kwargs = {}
364 year_field = "%s__year" % field_name
365 month_field = "%s__month" % field_name
366 day_field = "%s__day" % field_name
367 field_generic = "%s__" % field_name
368 year_lookup = cl.params.get(year_field)
369 month_lookup = cl.params.get(month_field)
370 day_lookup = cl.params.get(day_field)
372 def link(filters):
373 return cl.get_query_string(filters, [field_generic])
375 if not (year_lookup or month_lookup or day_lookup):
376 # select appropriate start level
377 date_range = cl.queryset.aggregate(
378 first=models.Min(field_name), last=models.Max(field_name)
379 )
380 if date_range["first"] and date_range["last"]:
381 if dates_or_datetimes == "datetimes":
382 date_range = {
383 k: timezone.localtime(v) if timezone.is_aware(v) else v
384 for k, v in date_range.items()
385 }
386 if date_range["first"].year == date_range["last"].year:
387 year_lookup = date_range["first"].year
388 if date_range["first"].month == date_range["last"].month:
389 month_lookup = date_range["first"].month
391 if year_lookup and month_lookup and day_lookup:
392 day = datetime.date(int(year_lookup), int(month_lookup), int(day_lookup))
393 return {
394 "show": True,
395 "back": {
396 "link": link({year_field: year_lookup, month_field: month_lookup}),
397 "title": capfirst(formats.date_format(day, "YEAR_MONTH_FORMAT")),
398 },
399 "choices": [
400 {"title": capfirst(formats.date_format(day, "MONTH_DAY_FORMAT"))}
401 ],
402 }
403 elif year_lookup and month_lookup:
404 days = getattr(cl.queryset, dates_or_datetimes)(
405 field_name, "day", **qs_kwargs
406 )
407 return {
408 "show": True,
409 "back": {
410 "link": link({year_field: year_lookup}),
411 "title": str(year_lookup),
412 },
413 "choices": [
414 {
415 "link": link(
416 {
417 year_field: year_lookup,
418 month_field: month_lookup,
419 day_field: day.day,
420 }
421 ),
422 "title": capfirst(formats.date_format(day, "MONTH_DAY_FORMAT")),
423 }
424 for day in days
425 ],
426 }
427 elif year_lookup:
428 months = getattr(cl.queryset, dates_or_datetimes)(
429 field_name, "month", **qs_kwargs
430 )
431 return {
432 "show": True,
433 "back": {"link": link({}), "title": _("All dates")},
434 "choices": [
435 {
436 "link": link(
437 {year_field: year_lookup, month_field: month.month}
438 ),
439 "title": capfirst(
440 formats.date_format(month, "YEAR_MONTH_FORMAT")
441 ),
442 }
443 for month in months
444 ],
445 }
446 else:
447 years = getattr(cl.queryset, dates_or_datetimes)(
448 field_name, "year", **qs_kwargs
449 )
450 return {
451 "show": True,
452 "back": None,
453 "choices": [
454 {
455 "link": link({year_field: str(year.year)}),
456 "title": str(year.year),
457 }
458 for year in years
459 ],
460 }
463@register.tag(name="date_hierarchy")
464def date_hierarchy_tag(parser, token):
465 return InclusionAdminNode(
466 parser,
467 token,
468 func=date_hierarchy,
469 template_name="date_hierarchy.html",
470 takes_context=False,
471 )
474def search_form(cl):
475 """
476 Display a search form for searching the list.
477 """
478 return {
479 "cl": cl,
480 "show_result_count": cl.result_count != cl.full_result_count,
481 "search_var": SEARCH_VAR,
482 "is_popup_var": IS_POPUP_VAR,
483 }
486@register.tag(name="search_form")
487def search_form_tag(parser, token):
488 return InclusionAdminNode(
489 parser,
490 token,
491 func=search_form,
492 template_name="search_form.html",
493 takes_context=False,
494 )
497@register.simple_tag
498def admin_list_filter(cl, spec):
499 tpl = get_template(spec.template)
500 return tpl.render(
501 {
502 "title": spec.title,
503 "choices": list(spec.choices(cl)),
504 "spec": spec,
505 }
506 )
509def admin_actions(context):
510 """
511 Track the number of times the action field has been rendered on the page,
512 so we know which value to use.
513 """
514 context["action_index"] = context.get("action_index", -1) + 1
515 return context
518@register.tag(name="admin_actions")
519def admin_actions_tag(parser, token):
520 return InclusionAdminNode(
521 parser, token, func=admin_actions, template_name="actions.html"
522 )
525@register.tag(name="change_list_object_tools")
526def change_list_object_tools_tag(parser, token):
527 """Display the row of change list object tools."""
528 return InclusionAdminNode(
529 parser,
530 token,
531 func=lambda context: context,
532 template_name="change_list_object_tools.html",
533 )