df_site 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (309) hide show
  1. df_site/__init__.py +1 -0
  2. df_site/__main__.py +37 -0
  3. df_site/admin.py +130 -0
  4. df_site/apps.py +57 -0
  5. df_site/components/__init__.py +1 -0
  6. df_site/components/base.py +82 -0
  7. df_site/components/detail.py +191 -0
  8. df_site/components/list.py +446 -0
  9. df_site/components/list_filters.py +74 -0
  10. df_site/components/registry.py +55 -0
  11. df_site/constants.py +71 -0
  12. df_site/context_processors.py +61 -0
  13. df_site/defaults.py +319 -0
  14. df_site/dynamic_settings.py +37 -0
  15. df_site/form_fields.py +138 -0
  16. df_site/management/__init__.py +1 -0
  17. df_site/management/commands/__init__.py +1 -0
  18. df_site/management/commands/add_image.py +104 -0
  19. df_site/management/commands/generate_favicon.py +47 -0
  20. df_site/middleware.py +20 -0
  21. df_site/migrations/0001_initial.py +220 -0
  22. df_site/migrations/0002_alter_alertribbon_message_alter_alertribbon_summary.py +23 -0
  23. df_site/migrations/__init__.py +0 -0
  24. df_site/model_fields.py +35 -0
  25. df_site/models.py +130 -0
  26. df_site/postman/__init__.py +1 -0
  27. df_site/postman/forms.py +38 -0
  28. df_site/postman/urls.py +75 -0
  29. df_site/postman/views.py +65 -0
  30. df_site/static/css/app.css +0 -0
  31. df_site/static/css/base.css +22208 -0
  32. df_site/static/css/ckeditor5.css +422 -0
  33. df_site/static/favicon/android-chrome-192x192.png +0 -0
  34. df_site/static/favicon/android-chrome-512x512.png +0 -0
  35. df_site/static/favicon/apple-touch-icon.png +0 -0
  36. df_site/static/favicon/favicon-16x16.png +0 -0
  37. df_site/static/favicon/favicon-32x32.png +0 -0
  38. df_site/static/favicon/favicon.ico +0 -0
  39. df_site/static/favicon/mstile-150x150.png +0 -0
  40. df_site/static/favicon/safari-pinned-tab.svg +46 -0
  41. df_site/static/images/accessibility.svg +1 -0
  42. df_site/static/images/align-bottom.svg +1 -0
  43. df_site/static/images/align-center.svg +1 -0
  44. df_site/static/images/align-justify.svg +1 -0
  45. df_site/static/images/align-left.svg +1 -0
  46. df_site/static/images/align-middle.svg +1 -0
  47. df_site/static/images/align-right.svg +1 -0
  48. df_site/static/images/align-top.svg +1 -0
  49. df_site/static/images/bold.svg +1 -0
  50. df_site/static/images/browse-files.svg +1 -0
  51. df_site/static/images/bulletedlist.svg +1 -0
  52. df_site/static/images/cancel.svg +1 -0
  53. df_site/static/images/caption.svg +1 -0
  54. df_site/static/images/check.svg +1 -0
  55. df_site/static/images/code.svg +1 -0
  56. df_site/static/images/codeblock.svg +1 -0
  57. df_site/static/images/cog.svg +1 -0
  58. df_site/static/images/color-palette.svg +1 -0
  59. df_site/static/images/color-tile-check.svg +1 -0
  60. df_site/static/images/drag-handle.svg +1 -0
  61. df_site/static/images/drag-indicator.svg +1 -0
  62. df_site/static/images/dropdown-arrow.svg +1 -0
  63. df_site/static/images/eraser.svg +1 -0
  64. df_site/static/images/file-arrow-up-solid.svg +1 -0
  65. df_site/static/images/find-replace.svg +1 -0
  66. df_site/static/images/font-background.svg +1 -0
  67. df_site/static/images/font-color.svg +1 -0
  68. df_site/static/images/font-family.svg +1 -0
  69. df_site/static/images/font-size.svg +1 -0
  70. df_site/static/images/heading1.svg +1 -0
  71. df_site/static/images/heading2.svg +1 -0
  72. df_site/static/images/heading3.svg +1 -0
  73. df_site/static/images/heading4.svg +1 -0
  74. df_site/static/images/heading5.svg +1 -0
  75. df_site/static/images/heading6.svg +1 -0
  76. df_site/static/images/history.svg +1 -0
  77. df_site/static/images/horizontalline.svg +1 -0
  78. df_site/static/images/html.svg +1 -0
  79. df_site/static/images/image-asset-manager.svg +1 -0
  80. df_site/static/images/image-upload.svg +1 -0
  81. df_site/static/images/image-url.svg +1 -0
  82. df_site/static/images/image.svg +1 -0
  83. df_site/static/images/importexport.svg +1 -0
  84. df_site/static/images/indent.svg +1 -0
  85. df_site/static/images/italic.svg +1 -0
  86. df_site/static/images/link.svg +1 -0
  87. df_site/static/images/liststylecircle.svg +1 -0
  88. df_site/static/images/liststyledecimal.svg +1 -0
  89. df_site/static/images/liststyledecimalleadingzero.svg +1 -0
  90. df_site/static/images/liststyledisc.svg +1 -0
  91. df_site/static/images/liststylelowerlatin.svg +1 -0
  92. df_site/static/images/liststylelowerroman.svg +1 -0
  93. df_site/static/images/liststylesquare.svg +1 -0
  94. df_site/static/images/liststyleupperlatin.svg +1 -0
  95. df_site/static/images/liststyleupperroman.svg +1 -0
  96. df_site/static/images/loupe.svg +1 -0
  97. df_site/static/images/low-vision.svg +1 -0
  98. df_site/static/images/marker.svg +1 -0
  99. df_site/static/images/media-placeholder.svg +1 -0
  100. df_site/static/images/media.svg +1 -0
  101. df_site/static/images/next-arrow.svg +1 -0
  102. df_site/static/images/numberedlist.svg +1 -0
  103. df_site/static/images/object-center.svg +1 -0
  104. df_site/static/images/object-full-width.svg +1 -0
  105. df_site/static/images/object-inline-left.svg +1 -0
  106. df_site/static/images/object-inline-right.svg +1 -0
  107. df_site/static/images/object-inline.svg +1 -0
  108. df_site/static/images/object-left.svg +1 -0
  109. df_site/static/images/object-right.svg +1 -0
  110. df_site/static/images/object-size-custom.svg +1 -0
  111. df_site/static/images/object-size-full.svg +1 -0
  112. df_site/static/images/object-size-large.svg +1 -0
  113. df_site/static/images/object-size-medium.svg +1 -0
  114. df_site/static/images/object-size-small.svg +1 -0
  115. df_site/static/images/outdent.svg +1 -0
  116. df_site/static/images/paragraph.svg +1 -0
  117. df_site/static/images/pen.svg +1 -0
  118. df_site/static/images/pencil.svg +1 -0
  119. df_site/static/images/pilcrow.svg +1 -0
  120. df_site/static/images/plus.svg +1 -0
  121. df_site/static/images/previous-arrow.svg +1 -0
  122. df_site/static/images/project-logo.svg +1 -0
  123. df_site/static/images/quote.svg +1 -0
  124. df_site/static/images/redo.svg +1 -0
  125. df_site/static/images/remove-format.svg +1 -0
  126. df_site/static/images/return-arrow.svg +1 -0
  127. df_site/static/images/select-all.svg +1 -0
  128. df_site/static/images/show-blocks.svg +1 -0
  129. df_site/static/images/source-editing.svg +1 -0
  130. df_site/static/images/specialcharacters.svg +1 -0
  131. df_site/static/images/strikethrough.svg +1 -0
  132. df_site/static/images/subscript.svg +1 -0
  133. df_site/static/images/superscript.svg +1 -0
  134. df_site/static/images/table-cell-properties.svg +1 -0
  135. df_site/static/images/table-column.svg +1 -0
  136. df_site/static/images/table-merge-cell.svg +1 -0
  137. df_site/static/images/table-properties.svg +1 -0
  138. df_site/static/images/table-row.svg +1 -0
  139. df_site/static/images/table.svg +1 -0
  140. df_site/static/images/text-alternative.svg +1 -0
  141. df_site/static/images/text.svg +1 -0
  142. df_site/static/images/three-vertical-dots.svg +1 -0
  143. df_site/static/images/todolist.svg +1 -0
  144. df_site/static/images/underline.svg +1 -0
  145. df_site/static/images/undo.svg +1 -0
  146. df_site/static/images/unlink.svg +1 -0
  147. df_site/static/js/app.js +98 -0
  148. df_site/static/js/app.js.map +1 -0
  149. df_site/static/js/base.js +161181 -0
  150. df_site/static/js/base.js.map +1 -0
  151. df_site/static/translations/af.js +1 -0
  152. df_site/static/translations/ar.js +1 -0
  153. df_site/static/translations/ast.js +1 -0
  154. df_site/static/translations/az.js +1 -0
  155. df_site/static/translations/bg.js +1 -0
  156. df_site/static/translations/bn.js +1 -0
  157. df_site/static/translations/bs.js +1 -0
  158. df_site/static/translations/ca.js +1 -0
  159. df_site/static/translations/cs.js +1 -0
  160. df_site/static/translations/da.js +1 -0
  161. df_site/static/translations/de-ch.js +1 -0
  162. df_site/static/translations/de.js +1 -0
  163. df_site/static/translations/el.js +1 -0
  164. df_site/static/translations/en-au.js +1 -0
  165. df_site/static/translations/en-gb.js +1 -0
  166. df_site/static/translations/en.js +1 -0
  167. df_site/static/translations/eo.js +1 -0
  168. df_site/static/translations/es-co.js +1 -0
  169. df_site/static/translations/es.js +1 -0
  170. df_site/static/translations/et.js +1 -0
  171. df_site/static/translations/eu.js +1 -0
  172. df_site/static/translations/fa.js +1 -0
  173. df_site/static/translations/fi.js +1 -0
  174. df_site/static/translations/gl.js +1 -0
  175. df_site/static/translations/gu.js +1 -0
  176. df_site/static/translations/he.js +1 -0
  177. df_site/static/translations/hi.js +1 -0
  178. df_site/static/translations/hr.js +1 -0
  179. df_site/static/translations/hu.js +1 -0
  180. df_site/static/translations/hy.js +1 -0
  181. df_site/static/translations/id.js +1 -0
  182. df_site/static/translations/it.js +1 -0
  183. df_site/static/translations/ja.js +1 -0
  184. df_site/static/translations/jv.js +1 -0
  185. df_site/static/translations/kk.js +1 -0
  186. df_site/static/translations/km.js +1 -0
  187. df_site/static/translations/kn.js +1 -0
  188. df_site/static/translations/ko.js +1 -0
  189. df_site/static/translations/ku.js +1 -0
  190. df_site/static/translations/lt.js +1 -0
  191. df_site/static/translations/lv.js +1 -0
  192. df_site/static/translations/ms.js +1 -0
  193. df_site/static/translations/nb.js +1 -0
  194. df_site/static/translations/ne.js +1 -0
  195. df_site/static/translations/nl.js +1 -0
  196. df_site/static/translations/no.js +1 -0
  197. df_site/static/translations/oc.js +1 -0
  198. df_site/static/translations/pl.js +1 -0
  199. df_site/static/translations/pt-br.js +1 -0
  200. df_site/static/translations/pt.js +1 -0
  201. df_site/static/translations/ro.js +1 -0
  202. df_site/static/translations/ru.js +1 -0
  203. df_site/static/translations/si.js +1 -0
  204. df_site/static/translations/sk.js +1 -0
  205. df_site/static/translations/sl.js +1 -0
  206. df_site/static/translations/sq.js +1 -0
  207. df_site/static/translations/sr-latn.js +1 -0
  208. df_site/static/translations/sr.js +1 -0
  209. df_site/static/translations/sv.js +1 -0
  210. df_site/static/translations/th.js +1 -0
  211. df_site/static/translations/ti.js +1 -0
  212. df_site/static/translations/tk.js +1 -0
  213. df_site/static/translations/tr.js +1 -0
  214. df_site/static/translations/tt.js +1 -0
  215. df_site/static/translations/ug.js +1 -0
  216. df_site/static/translations/uk.js +1 -0
  217. df_site/static/translations/ur.js +1 -0
  218. df_site/static/translations/uz.js +1 -0
  219. df_site/static/translations/vi.js +1 -0
  220. df_site/static/translations/zh-cn.js +1 -0
  221. df_site/static/translations/zh.js +1 -0
  222. df_site/static/webfonts/fa-brands-400.ttf +0 -0
  223. df_site/static/webfonts/fa-brands-400.woff2 +0 -0
  224. df_site/static/webfonts/fa-regular-400.ttf +0 -0
  225. df_site/static/webfonts/fa-regular-400.woff2 +0 -0
  226. df_site/static/webfonts/fa-solid-900.ttf +0 -0
  227. df_site/static/webfonts/fa-solid-900.woff2 +0 -0
  228. df_site/static/webfonts/fa-v4compatibility.ttf +0 -0
  229. df_site/static/webfonts/fa-v4compatibility.woff2 +0 -0
  230. df_site/templates/account/email.html +78 -0
  231. df_site/templates/account/password_change.html +28 -0
  232. df_site/templates/account/snippets/warn_no_email.html +6 -0
  233. df_site/templates/allauth/elements/alert.html +6 -0
  234. df_site/templates/allauth/elements/badge.html +4 -0
  235. df_site/templates/allauth/elements/button.html +14 -0
  236. df_site/templates/allauth/elements/button_group.html +5 -0
  237. df_site/templates/allauth/elements/field.html +72 -0
  238. df_site/templates/allauth/elements/fields.html +3 -0
  239. df_site/templates/allauth/elements/form.html +10 -0
  240. df_site/templates/allauth/elements/h1.html +1 -0
  241. df_site/templates/allauth/elements/h2.html +1 -0
  242. df_site/templates/allauth/elements/img.html +4 -0
  243. df_site/templates/allauth/elements/p.html +1 -0
  244. df_site/templates/allauth/elements/panel.html +14 -0
  245. df_site/templates/allauth/elements/provider.html +6 -0
  246. df_site/templates/allauth/elements/provider_list.html +5 -0
  247. df_site/templates/allauth/elements/table.html +5 -0
  248. df_site/templates/allauth/layouts/base.html +14 -0
  249. df_site/templates/allauth/layouts/entrance.html +20 -0
  250. df_site/templates/allauth/layouts/manage.html +1 -0
  251. df_site/templates/cookie_consent/_cookie_group.html +64 -0
  252. df_site/templates/cookie_consent/cookiegroup_list.html +23 -0
  253. df_site/templates/df_components/base.html +0 -0
  254. df_site/templates/df_components/detail.html +12 -0
  255. df_site/templates/df_components/detail_fieldset.html +46 -0
  256. df_site/templates/df_components/list.html +42 -0
  257. df_site/templates/df_components/list_filter.html +13 -0
  258. df_site/templates/df_components/list_filters.html +36 -0
  259. df_site/templates/df_components/list_hierarchy.html +25 -0
  260. df_site/templates/df_components/list_pagination.html +39 -0
  261. df_site/templates/df_components/list_search_form.html +38 -0
  262. df_site/templates/df_components/list_table.html +35 -0
  263. df_site/templates/df_site/app.html +1 -0
  264. df_site/templates/df_site/base.html +221 -0
  265. df_site/templates/df_site/detail.html +8 -0
  266. df_site/templates/df_site/humans.txt +11 -0
  267. df_site/templates/df_site/manage_base.html +51 -0
  268. df_site/templates/df_site/popup_app.html +1 -0
  269. df_site/templates/df_site/popup_base.html +29 -0
  270. df_site/templates/df_site/security.txt +5 -0
  271. df_site/templates/django_bootstrap5/breadcrumb.html +17 -0
  272. df_site/templates/django_bootstrap5/pagination.html +40 -0
  273. df_site/templates/django_ckeditor_5/widget.html +13 -0
  274. df_site/templates/favicon/browserconfig.xml +9 -0
  275. df_site/templates/mfa/index.html +115 -0
  276. df_site/templates/mfa/recovery_codes/index.html +33 -0
  277. df_site/templates/mfa/webauthn/authenticator_list.html +74 -0
  278. df_site/templates/pipeline/css.html +1 -0
  279. df_site/templates/pipeline/js.html +1 -0
  280. df_site/templates/postman/archives.html +8 -0
  281. df_site/templates/postman/base.html +20 -0
  282. df_site/templates/postman/base_folder.html +71 -0
  283. df_site/templates/postman/base_write.html +26 -0
  284. df_site/templates/postman/inbox.html +7 -0
  285. df_site/templates/postman/inc_subject_ex.html +21 -0
  286. df_site/templates/postman/trash.html +12 -0
  287. df_site/templates/postman/view.html +64 -0
  288. df_site/templates/users/settings.html +26 -0
  289. df_site/templates/usersessions/usersession_list.html +70 -0
  290. df_site/templatetags/__init__.py +1 -0
  291. df_site/templatetags/df_site.py +241 -0
  292. df_site/templatetags/images.py +515 -0
  293. df_site/templatetags/pipeline_sri.py +97 -0
  294. df_site/testing/__init__.py +1 -0
  295. df_site/testing/multiple_views.py +369 -0
  296. df_site/testing/requests.py +299 -0
  297. df_site/urls.py +41 -0
  298. df_site/user_settings.py +69 -0
  299. df_site/users/__init__.py +1 -0
  300. df_site/users/forms.py +35 -0
  301. df_site/users/notifications.py +14 -0
  302. df_site/users/urls.py +17 -0
  303. df_site/users/views.py +75 -0
  304. df_site/views.py +122 -0
  305. df_site-0.1.0.dist-info/LICENSE +519 -0
  306. df_site-0.1.0.dist-info/METADATA +217 -0
  307. df_site-0.1.0.dist-info/RECORD +309 -0
  308. df_site-0.1.0.dist-info/WHEEL +4 -0
  309. df_site-0.1.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,70 @@
1
+ {% extends "usersessions/base_manage.html" %}
2
+ {% load allauth %}
3
+ {% load i18n %}
4
+ {% load humanize %}
5
+ {% block head_title %}
6
+ {% trans "Sessions" %}
7
+ {% endblock head_title %}
8
+ {% block content %}
9
+ {% element h1 tags="usersessions,list" %}
10
+ {% trans "Sessions" %}
11
+ {% endelement %}
12
+ {% if session_count > 1 %}
13
+ {% url 'usersessions_list' as action_url %}
14
+ {% else %}
15
+ {% url 'account_logout' as action_url %}
16
+ {% endif %}
17
+ {% element form action=action_url method="post" tags="sessions" no_visible_fields=True %}
18
+ {% slot body %}
19
+ {% csrf_token %}
20
+ {% element table tags="sessions" %}
21
+ <thead>
22
+ <tr>
23
+ <th class="fs-6">{% translate "Started At" %}</th>
24
+ <th class="">{% translate "IP Address" %}</th>
25
+ <th class="">{% translate "Browser" %}</th>
26
+ {% if show_last_seen_at %}
27
+ <th class="">{% translate "Last seen at" %}</th>
28
+ {% endif %}
29
+ <th></th>
30
+ </tr>
31
+ </thead>
32
+ <tbody>
33
+ {% for session in sessions %}
34
+ <tr>
35
+ <td>
36
+ <span title="{{ session.created_at }}">{{ session.created_at|naturaltime }}</span>
37
+ </td>
38
+ <td>{{ session.ip }}</td>
39
+ <td>{{ session.user_agent }}</td>
40
+ {% if show_last_seen_at %}
41
+ <td>
42
+ <span title="{{ session.last_seen_at }}">{{ session.last_seen_at|naturaltime }}</span>
43
+ </td>
44
+ {% endif %}
45
+ <td>
46
+ {% if session.is_current %}
47
+ {% element badge tags="session,current" %}
48
+ {% translate "Current" %}
49
+ {% endelement %}
50
+ {% else %}
51
+ {% endif %}
52
+ </td>
53
+ </tr>
54
+ {% endfor %}
55
+ </tbody>
56
+ {% endelement %}
57
+ {% endslot %}
58
+ {% slot actions %}
59
+ {% if session_count > 1 %}
60
+ {% element button type="submit" %}
61
+ {% translate "Sign Out Other Sessions" %}
62
+ {% endelement %}
63
+ {% else %}
64
+ {% element button type="submit" %}
65
+ {% translate "Sign Out" %}
66
+ {% endelement %}
67
+ {% endif %}
68
+ {% endslot %}
69
+ {% endelement %}
70
+ {% endblock content %}
@@ -0,0 +1 @@
1
+ """Template tags for df_site app."""
@@ -0,0 +1,241 @@
1
+ """Custom template tags for the df_site app."""
2
+
3
+ import datetime
4
+ import re
5
+ from html import escape
6
+ from typing import Dict, List, Optional, Union
7
+
8
+ from django import template
9
+ from django.conf import settings
10
+ from django.contrib.admin.views.main import PAGE_VAR
11
+ from django.core.paginator import Paginator
12
+ from django.template.base import kwarg_re
13
+ from django.template.defaultfilters import date
14
+ from django.template.defaulttags import URLNode
15
+ from django.template.exceptions import TemplateSyntaxError
16
+ from django.template.loader import render_to_string
17
+ from django.utils.safestring import mark_safe
18
+
19
+ from df_site.components.base import Component
20
+ from df_site.constants import BRAND_ICONS, INT_RE
21
+
22
+ register = template.Library()
23
+
24
+
25
+ @register.filter()
26
+ def abs_url(value):
27
+ """Given a relative URL, return an absolute URL."""
28
+ if value and value.startswith("/"):
29
+ base_url = settings.SERVER_BASE_URL
30
+ if base_url.endswith("/"):
31
+ base_url = base_url[:-1]
32
+ return mark_safe(f"{base_url}{value}") # noqa
33
+ return value
34
+
35
+
36
+ class AbsoluteURLNode(URLNode):
37
+ """A template node that renders an absolute URL."""
38
+
39
+ def render(self, context):
40
+ """Render the URL node prefixed with the server base URL."""
41
+ rendered = super().render(context)
42
+ return abs_url(rendered)
43
+
44
+
45
+ @register.simple_tag(takes_context=True)
46
+ def paginate_qs(context, queryset, per_page=10, page_attr="page"):
47
+ """Paginate a queryset and store the pagination in the context."""
48
+ paginator = Paginator(queryset, per_page)
49
+ page_number = context["request"].GET.get(page_attr, "1")
50
+ if INT_RE.match(page_number):
51
+ page = paginator.get_page(int(page_number))
52
+ else:
53
+ page = paginator.get_page(1)
54
+ return page
55
+
56
+
57
+ @register.tag(name="abs_url")
58
+ def abs_url_tag(parser, token):
59
+ """Like the default Django's url tag, but return absolute URLs."""
60
+ bits = token.split_contents()
61
+ if len(bits) < 2:
62
+ raise TemplateSyntaxError(f"'{bits[0]}' takes at least one argument, a URL pattern name.")
63
+ viewname = parser.compile_filter(bits[1])
64
+ args = []
65
+ kwargs = {}
66
+ asvar = None
67
+ bits = bits[2:]
68
+ if len(bits) >= 2 and bits[-2] == "as":
69
+ asvar = bits[-1]
70
+ bits = bits[:-2]
71
+
72
+ for bit in bits:
73
+ match = kwarg_re.match(bit)
74
+ if not match:
75
+ raise TemplateSyntaxError("Malformed arguments to url tag")
76
+ name, value = match.groups()
77
+ if name:
78
+ kwargs[name] = parser.compile_filter(value)
79
+ else:
80
+ args.append(parser.compile_filter(value))
81
+ return AbsoluteURLNode(viewname, args, kwargs, asvar)
82
+
83
+
84
+ @register.filter
85
+ def fa6_allauth_icon(provider_id):
86
+ """Return the font-awesome icon name for a given allauth provider."""
87
+ return BRAND_ICONS.get(provider_id, "key")
88
+
89
+
90
+ @register.simple_tag
91
+ def fa6_icon(
92
+ name,
93
+ prefix="fa",
94
+ large: Union[int, bool] = False,
95
+ fixed: bool = False,
96
+ spin: bool = False,
97
+ li: bool = False,
98
+ rotate: Union[int, bool] = None,
99
+ border: bool = False,
100
+ color: str = None,
101
+ ):
102
+ """Add font-awesome icons in your HTML code."""
103
+ if isinstance(large, int) and 2 <= large <= 5:
104
+ large = f" fa-{large:d}x"
105
+ elif large:
106
+ large = " fa-lg"
107
+ else:
108
+ large = ""
109
+ content = '<i class="{prefix} fa-{name}{large}{fixed}{spin}{li}{rotate}{border}{color}"></i>'.format(
110
+ prefix=prefix,
111
+ name=name,
112
+ large=large,
113
+ fixed=" fa-fw" if fixed else "",
114
+ spin=" fa-spin" if spin else "",
115
+ li=" fa-li" if li else "",
116
+ rotate=f" fa-rotate-{rotate}" if rotate else "",
117
+ border=" fa-border" if border else "",
118
+ color=f"text-{color}" if color else "",
119
+ )
120
+ return mark_safe(content) # noqa
121
+
122
+
123
+ @register.simple_tag
124
+ def bootstrap_breadcrumb(
125
+ breadcrumbs: List[Dict[str, Union[str, bool]]], last_active: bool = True, extra_classes: str = ""
126
+ ):
127
+ """Render an breadcrumb.
128
+
129
+ **Tag name**::
130
+
131
+ bootstrap_breadcrumb
132
+
133
+ **Parameters**::
134
+
135
+ breadcrumbs
136
+ List of dictionaries with the following keys:
137
+ `link`: URL to link to
138
+ `title`: Text to display
139
+ `active`: boolean, element is active, meaning that the link is not displayed
140
+
141
+ last_active
142
+ boolean, last element is the active one
143
+
144
+ :default: ``True``
145
+
146
+ extra_classes
147
+ string, extra CSS classes for elements
148
+
149
+ :default: ""
150
+
151
+ **Usage**::
152
+
153
+ {% bootstrap_breadcrumb [{content}] %}
154
+
155
+ **Example**::
156
+
157
+ {% bootstrap_breadcrumb crumbs %}
158
+ """
159
+ context = {
160
+ "breadcrumbs": breadcrumbs,
161
+ "last_active": last_active,
162
+ "extra_classes": extra_classes,
163
+ }
164
+ content = render_to_string("django_bootstrap5/breadcrumb.html", context)
165
+ return mark_safe(content) # noqa S308
166
+
167
+
168
+ @register.simple_tag(takes_context=True)
169
+ def component(context, comp: Component, **kwargs):
170
+ """Render a HTML component in a template."""
171
+ text = comp.render(context, **kwargs)
172
+ return mark_safe(text) # noqa S308
173
+
174
+
175
+ @register.simple_tag
176
+ def component_list_url(cl, page: Optional[int] = None):
177
+ """Generate an individual page index link in a paginated list."""
178
+ kwargs = {}
179
+ if page is not None:
180
+ kwargs[PAGE_VAR] = page
181
+ return cl.get_query_string(kwargs)
182
+
183
+
184
+ auto_pill_re = re.compile(r"^(.*)\((\d+)\)\s*$")
185
+
186
+
187
+ @register.simple_tag
188
+ def component_auto_badge(
189
+ value: str, color: str = "primary", extra_class: str = "rounded-pill", regex: re.Pattern = auto_pill_re
190
+ ):
191
+ """Automatically extracts suffixes between parenthesis `(.*)` and put them into badges."""
192
+ if hasattr(value, "__html__"):
193
+ raw_value = str(value)
194
+ else:
195
+ raw_value = escape(str(value))
196
+ if match := regex.match(raw_value):
197
+ prefix = match.group(1)
198
+ suffix = match.group(2)
199
+ new_value = f'{prefix}<span class="badge text-bg-{color} {extra_class}">{suffix}</span>'
200
+ value = mark_safe(new_value) # noqa S308
201
+ return value
202
+
203
+
204
+ @register.simple_tag
205
+ @register.filter
206
+ def schema_date(
207
+ value_: Optional[Union[datetime.date, datetime.datetime, datetime.timedelta]], fmt_: Optional[str] = None, **kwargs
208
+ ):
209
+ """Show a date in a <time></time> element.
210
+
211
+ Can be used as a filter or as a tag. In the latter case, value and format are the first two arguments.
212
+ Extra arguments are passed as attributes to the generated HTML.
213
+
214
+ `{% schema_date value_ fmt_ itemprop="start" %}` is rendered as
215
+ `<time itemprop="start" datetime="date">date</time>`
216
+ `{{ value_|schema_date:fmt_ }}` is rendered as `<time datetime="date">date</time>`
217
+ """
218
+ content = date(value_, fmt_)
219
+ if isinstance(value_, datetime.date):
220
+ kwargs["datetime"] = value_.strftime("%Y-%m-%d")
221
+ elif isinstance(value_, datetime.datetime):
222
+ kwargs["datetime"] = value_.isoformat()
223
+ elif isinstance(value_, datetime.timedelta):
224
+ attr = "P"
225
+ ts = value_.total_seconds()
226
+ days = int(ts // 86400)
227
+ ts %= 86400
228
+ attr += f"{days}D"
229
+ hours = int(ts // 3600)
230
+ ts %= 3600
231
+ attr += f"{hours}H"
232
+ minutes = int(ts // 60)
233
+ ts %= 60
234
+ attr += f"{minutes}M{ts}S"
235
+ kwargs["datetime"] = attr
236
+ else:
237
+ return content
238
+ escaped = {escape(k): escape(v) for k, v in kwargs.items()}
239
+ attr = " ".join(f'{k}="{v}"' for k, v in escaped.items())
240
+ msg = f"<time {attr}>{content}</time>"
241
+ return mark_safe(msg) # noqa S308