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
df_site/__init__.py ADDED
@@ -0,0 +1 @@
1
+ """Main package for the df_site application."""
df_site/__main__.py ADDED
@@ -0,0 +1,37 @@
1
+ """Allow to use "python3 -m df_site" to run the manage command."""
2
+
3
+ import multiprocessing
4
+ import os
5
+ import sys
6
+ import warnings
7
+
8
+ from df_config.manage import DEFAULT_SETTINGS_MODULE, manage, set_env
9
+
10
+
11
+ def setup(module_name: str = None, settings_module=DEFAULT_SETTINGS_MODULE):
12
+ """Setup the Django environment."""
13
+ set_env(module_name=module_name, settings_module=settings_module)
14
+ import django
15
+
16
+ django.setup()
17
+ from django.conf import settings
18
+
19
+ return settings
20
+
21
+
22
+ def main(module_name: str = "df_site"):
23
+ """Run the manage command."""
24
+ if not sys.warnoptions:
25
+ warnings.simplefilter("default") # Change the filter in this process
26
+ os.environ["PYTHONWARNINGS"] = "default" # Also affect subprocesses
27
+ # avoid "django.core.exceptions.AppRegistryNotReady: Apps aren't loaded yet." during parallel testing
28
+ multiprocessing.set_start_method("fork")
29
+
30
+ # required for parallel testing on macOS and Python 3.8+
31
+ os.environ["OBJC_DISABLE_INITIALIZE_FORK_SAFETY"] = "YES"
32
+ manage(module_name=module_name)
33
+
34
+
35
+ if __name__ == "__main__":
36
+ """Allow to use "python3 -m df_site"."""
37
+ main()
df_site/admin.py ADDED
@@ -0,0 +1,130 @@
1
+ """Admin classes for the df_site app."""
2
+
3
+ from functools import cached_property
4
+
5
+ from allauth.account.models import EmailAddress
6
+ from allauth.mfa.models import Authenticator
7
+ from allauth.socialaccount.models import SocialAccount
8
+ from allauth.usersessions.models import UserSession
9
+ from django.conf import settings
10
+ from django.contrib import admin
11
+ from django.contrib.auth.admin import UserAdmin
12
+ from django.utils.translation import gettext as _
13
+
14
+ from df_site.models import AlertRibbon, PreferencesUser
15
+
16
+
17
+ @admin.register(AlertRibbon)
18
+ class AlertRibbonAdmin(admin.ModelAdmin):
19
+ """Admin class for the alert ribbon model."""
20
+
21
+ list_display = ("message", "color", "start_date", "end_date", "is_active")
22
+ list_filter = ("color", "start_date", "end_date", "is_active", "position")
23
+ search_fields = ("summary",)
24
+ fields = ["summary", "url", "message", "color", "start_date", "end_date", "is_active", "position"]
25
+
26
+
27
+ class EmailAddressInline(admin.TabularInline):
28
+ """Inline for the email address model."""
29
+
30
+ model = EmailAddress
31
+ extra = 0
32
+
33
+
34
+ class SocialAccountInline(admin.TabularInline):
35
+ """Inline for the social account model."""
36
+
37
+ model = SocialAccount
38
+ extra = 0
39
+ fields = ["provider", "last_login", "date_joined"]
40
+ readonly_fields = ["provider", "last_login", "date_joined"]
41
+
42
+ def has_add_permission(self, request, obj):
43
+ """Return False to prevent adding new social accounts."""
44
+ return False
45
+
46
+
47
+ class AuthenticatorInline(admin.TabularInline):
48
+ """Inline for the authenticator model."""
49
+
50
+ model = Authenticator
51
+ extra = 0
52
+ fields = ["type", "created_at", "last_used_at"]
53
+ readonly_fields = ["type", "created_at", "last_used_at"]
54
+
55
+ def has_add_permission(self, request, obj):
56
+ """Return False to prevent adding new authenticators."""
57
+ return False
58
+
59
+
60
+ class UserSessionInline(admin.TabularInline):
61
+ """Inline for the user session model."""
62
+
63
+ model = UserSession
64
+ extra = 0
65
+ fields = [
66
+ "created_at",
67
+ "ip",
68
+ "last_seen_at",
69
+ ]
70
+ readonly_fields = [
71
+ "created_at",
72
+ "ip",
73
+ "last_seen_at",
74
+ ]
75
+
76
+ def has_add_permission(self, request, obj):
77
+ """Return False to prevent adding new user sessions."""
78
+ return False
79
+
80
+
81
+ @admin.register(PreferencesUser)
82
+ class PreferencesUserAdmin(UserAdmin):
83
+ """Admin class for the preferences user model."""
84
+
85
+ inlines = [EmailAddressInline, AuthenticatorInline, SocialAccountInline, UserSessionInline]
86
+ list_display = ("username", "email", "first_name", "last_name", "is_staff", "date_joined")
87
+ list_filter = ("is_staff", "is_superuser", "is_active", "date_joined", "groups")
88
+ readonly_fields = ["last_login", "date_joined"]
89
+ fieldsets = (
90
+ (None, {"fields": ("username", "password")}),
91
+ (
92
+ _("Personal info"),
93
+ {"fields": ("first_name", "last_name", "email", "color_theme", "display_online", "email_notifications")},
94
+ ),
95
+ (
96
+ _("Permissions"),
97
+ {
98
+ "fields": (
99
+ "is_active",
100
+ "is_staff",
101
+ "is_superuser",
102
+ "groups",
103
+ "user_permissions",
104
+ ),
105
+ },
106
+ ),
107
+ (_("Important dates"), {"fields": (("last_login", "date_joined"),)}),
108
+ )
109
+
110
+ @cached_property
111
+ def excluded_inlines(self):
112
+ """Exclude inlines of which the corresponding app is not installed."""
113
+ excluded = []
114
+ if "allauth.mfa" not in settings.INSTALLED_APPS:
115
+ excluded.append(AuthenticatorInline)
116
+ if "allauth.socialaccount" not in settings.INSTALLED_APPS:
117
+ excluded.append(SocialAccountInline)
118
+ if "allauth.usersessions" not in settings.INSTALLED_APPS:
119
+ excluded.append(UserSessionInline)
120
+ if "allauth.account" not in settings.INSTALLED_APPS:
121
+ excluded.append(EmailAddressInline)
122
+ return excluded
123
+
124
+ def get_inlines(self, request, obj):
125
+ """Return inlines excluding those that correspond to apps not installed."""
126
+ inlines = super().get_inlines(request, obj)
127
+ if obj is None:
128
+ return []
129
+ inlines = [x for x in inlines if x not in self.excluded_inlines]
130
+ return inlines
df_site/apps.py ADDED
@@ -0,0 +1,57 @@
1
+ """Configuration for the df_site app."""
2
+
3
+ from typing import Type
4
+
5
+ from django.apps import AppConfig
6
+ from django.db.models.signals import post_migrate, pre_save
7
+ from django.utils.translation import gettext_lazy as _
8
+
9
+
10
+ class DFSiteApp(AppConfig):
11
+ """Configuration for the df_site app."""
12
+
13
+ default_auto_field = "django.db.models.AutoField"
14
+ name = "df_site"
15
+ verbose_name = _("df_site")
16
+
17
+ def ready(self):
18
+ """Run code when the app is ready."""
19
+ super().ready()
20
+ post_migrate.connect(auto_create_site_object, sender=self)
21
+ from django.contrib.auth import get_user_model
22
+
23
+ user_model = get_user_model()
24
+ pre_save.connect(create_admin_user, sender=user_model)
25
+
26
+
27
+ def create_admin_user(sender, instance, **kwargs):
28
+ """Create an admin user if none already exists."""
29
+ from django.contrib.auth.models import AbstractUser
30
+
31
+ sender: Type[AbstractUser]
32
+ instance: AbstractUser
33
+ if hasattr(create_admin_user, "run"):
34
+ return
35
+ if not hasattr(instance, "is_superuser") or sender.objects.filter(is_superuser=True, is_active=True).exists():
36
+ return
37
+ instance.is_superuser = True
38
+ instance.is_staff = True
39
+ setattr(create_admin_user, "run", True)
40
+
41
+
42
+ # noinspection PyUnusedLocal
43
+ def auto_create_site_object(sender, **kwargs):
44
+ """Create a Site object if it does not exist."""
45
+ from django.conf import settings
46
+ from django.contrib.sites.models import Site
47
+
48
+ site, created = Site.objects.get_or_create(
49
+ id=1,
50
+ defaults={
51
+ "domain": settings.SERVER_NAME,
52
+ "name": settings.DF_SITE_TITLE,
53
+ },
54
+ )
55
+ site.domain = settings.SERVER_NAME
56
+ site.name = settings.DF_SITE_TITLE
57
+ site.save()
@@ -0,0 +1 @@
1
+ """Contains components to use in templates."""
@@ -0,0 +1,82 @@
1
+ """Define a HTML component class, a elaborated piece of template.
2
+
3
+ A component should be instantiated once and not have any request-specific attribute.
4
+ Request-specific data should be passed as arguments to methods.
5
+ """
6
+
7
+ from typing import List, Optional, Type
8
+
9
+ from django.db import models
10
+ from django.http import HttpRequest
11
+
12
+
13
+ class Component:
14
+ """Base component class."""
15
+
16
+ template_name: str = "components/base.html"
17
+
18
+ def render(self, context, **kwargs) -> str:
19
+ """Render the component in a HTML template.
20
+
21
+ Context the complete template context.
22
+ Kwargs is a dictionary of data provided to the template tag.
23
+ """
24
+ self.update_render_context(context, **kwargs)
25
+ names = self.get_template_names()
26
+ template = context.template.engine.select_template(names)
27
+ return template.render(context)
28
+
29
+ def get_template_names(self) -> List[str]:
30
+ """Return a list of template names to be used for the request."""
31
+ return [self.template_name]
32
+
33
+ def update_render_context(self, context, **kwargs):
34
+ """Update the context before rendering the component.
35
+
36
+ Context the complete template context.
37
+ Kwargs is a dictionary of data provided to the template tag.
38
+ """
39
+ pass
40
+
41
+
42
+ class ModelComponent(Component):
43
+ """Base component class for model-related components."""
44
+
45
+ template_name: Optional[str] = None
46
+
47
+ def __init__(self, model: Type[models.Model], base_template: str):
48
+ """Initialize the component."""
49
+ super().__init__()
50
+ self.model: Type[models.Model] = model
51
+ # noinspection PyProtectedMember
52
+ self.opts = model._meta
53
+ self.base_template: str = base_template
54
+
55
+ @property
56
+ def id_prefix(self) -> str:
57
+ """Propose a prefix for the HTML IDs linked to this component."""
58
+ return f"{self.opts.app_label}_{self.opts.model_name}"
59
+
60
+ # noinspection PyUnusedLocal
61
+ def get_base_queryset(self, request: HttpRequest, **kwargs) -> models.QuerySet:
62
+ """Return the base queryset to use for this component.
63
+
64
+ Any extra filter based on kwargs should be applied here.
65
+ """
66
+ # noinspection PyProtectedMember
67
+ return self.model._default_manager.get_queryset()
68
+
69
+ # noinspection PyMethodMayBeStatic
70
+ def get_empty_value_display(self) -> str:
71
+ """Return the value to display for an empty field."""
72
+ return "-"
73
+
74
+ def get_template_names(self) -> List[str]:
75
+ """Return a list of template names to be used for the request."""
76
+ if self.template_name:
77
+ return [self.template_name]
78
+ return [
79
+ f"df_components/{self.opts.app_label}/{self.opts.model_name}/{self.base_template}",
80
+ f"df_components/{self.opts.app_label}/{self.base_template}",
81
+ f"df_components/{self.base_template}",
82
+ ]
@@ -0,0 +1,191 @@
1
+ """Component and generic View for displaying the details of a model instance."""
2
+
3
+ from typing import Dict, List, Optional, Type, Union
4
+
5
+ from df_websockets.tasks import set_websocket_topics
6
+ from django import forms
7
+ from django.contrib.admin import helpers
8
+ from django.contrib.admin.utils import flatten_fieldsets
9
+ from django.core.exceptions import ValidationError
10
+ from django.db import models
11
+ from django.forms import fields_for_model
12
+ from django.urls import reverse
13
+ from django.utils.translation import gettext as _
14
+ from django.views.generic import DetailView
15
+
16
+ from df_site.components.base import ModelComponent
17
+
18
+
19
+ class ModelDetailComponent(ModelComponent):
20
+ """A component that displays the details of a model instance."""
21
+
22
+ model: Type[models.Model]
23
+
24
+ def __init__(
25
+ self,
26
+ model: Type[models.Model],
27
+ base_template: str = "detail.html",
28
+ inlines: List = None,
29
+ fields=None,
30
+ exclude=None,
31
+ fieldsets=None,
32
+ filter_vertical=(),
33
+ filter_horizontal=(),
34
+ ):
35
+ """Initialize the component."""
36
+ super().__init__(model, base_template)
37
+ self.inlines = inlines or []
38
+ self.fields = fields
39
+ self.exclude = exclude
40
+ self.fieldsets = fieldsets
41
+ self.filter_vertical = filter_vertical
42
+ self.filter_horizontal = filter_horizontal
43
+ self.opts = model._meta
44
+
45
+ def get_queryset(self, request):
46
+ """Return a QuerySet of all model instances that can be viewed."""
47
+ # noinspection PyProtectedMember
48
+ return self.model._default_manager.get_queryset()
49
+
50
+ def get_fields(self, request, obj=None):
51
+ """Return the list of selected fields."""
52
+ if self.fields:
53
+ return self.fields
54
+ # _get_form_for_get_fields() is implemented in subclasses.
55
+ fields = fields_for_model(self.model)
56
+ return [*fields]
57
+
58
+ def get_fieldsets(self, request, obj=None):
59
+ """Return the specified fieldsets."""
60
+ if self.fieldsets:
61
+ return self.fieldsets
62
+ return [(None, {"fields": self.get_fields(request, obj)})]
63
+
64
+ def get_inlines(self, request, obj):
65
+ """Hook for specifying custom inlines."""
66
+ return self.inlines
67
+
68
+ def get_object(self, request, object_id, from_field=None):
69
+ """Return an instance matching the field and value provided.
70
+
71
+ The primary key is used if no field is provided. Return ``None`` if no match is
72
+ found or the object_id fails validation.
73
+ """
74
+ queryset = self.get_queryset(request)
75
+ model = queryset.model
76
+ field = self.opts.pk if from_field is None else self.opts.get_field(from_field)
77
+ try:
78
+ object_id = field.to_python(object_id)
79
+ return queryset.get(**{field.name: object_id})
80
+ except (model.DoesNotExist, ValidationError, ValueError):
81
+ return None
82
+
83
+ def update_render_context(self, context, **kwargs):
84
+ """Update the context before rendering the component.
85
+
86
+ The displayed object can be specified either in the context as "object",
87
+ in the kwargs as "obj" or its ID as "object_id" in kwargs.
88
+ """
89
+ request = context["request"]
90
+ if "object" in context:
91
+ obj = context["object"]
92
+ elif "obj" in kwargs:
93
+ obj = kwargs["obj"]
94
+ else:
95
+ object_id = kwargs.get("object_id")
96
+ from_field = kwargs.get("from_field")
97
+ obj = self.get_object(request, object_id=object_id, from_field=from_field)
98
+ fieldsets = self.get_fieldsets(request, obj)
99
+ readonly_fields = flatten_fieldsets(fieldsets)
100
+
101
+ class AdminForm(forms.ModelForm):
102
+ class Meta:
103
+ model = self.model
104
+ fields = []
105
+
106
+ form = AdminForm(instance=obj)
107
+ admin_form = helpers.AdminForm(
108
+ form,
109
+ list(fieldsets),
110
+ {},
111
+ readonly_fields=readonly_fields,
112
+ model_admin=self,
113
+ )
114
+ context["adminform"] = admin_form
115
+
116
+
117
+ class ModelDetailView(DetailView):
118
+ """A view that displays the details of a model instance."""
119
+
120
+ component: ModelDetailComponent
121
+ model: Type[models.Model]
122
+ component_template: str = "detail.html"
123
+ inlines: List = None
124
+ fields = None
125
+ exclude = None
126
+ fieldsets = None
127
+ filter_vertical = ()
128
+ filter_horizontal = ()
129
+
130
+ @classmethod
131
+ def as_view(cls, **initkwargs):
132
+ """Main entry point for a request-response process."""
133
+ cls.component = ModelDetailComponent(
134
+ model=cls.model,
135
+ base_template=cls.component_template,
136
+ inlines=cls.inlines,
137
+ fields=cls.fields,
138
+ exclude=cls.exclude,
139
+ fieldsets=cls.fieldsets,
140
+ filter_vertical=cls.filter_vertical,
141
+ filter_horizontal=cls.filter_horizontal,
142
+ )
143
+ return super().as_view(**initkwargs)
144
+
145
+ def get_template_names(self):
146
+ """Return a list of template names to be used for the request."""
147
+ if self.template_name:
148
+ return [self.template_name]
149
+ return [
150
+ f"df_site/{self.component.opts.app_label}/{self.component.opts.model_name}/detail.html",
151
+ f"df_site/{self.component.opts.app_label}/detail.html",
152
+ "df_site/detail.html",
153
+ ]
154
+
155
+ def get_breadcrumb(self) -> List[Dict[str, Union[str, bool]]]:
156
+ """Return the breadcrumb for the view.
157
+
158
+ The breadcrumb is a list of dictionaries with the following keys:
159
+ * `link`: URL to link to
160
+ * `title`: Text to display
161
+ * `active`: boolean, element is active, meaning that the link is not displayed
162
+ """
163
+ return [
164
+ {"link": reverse("index"), "title": _("Home"), "active": False},
165
+ {"link": None, "title": str(self.object), "active": True},
166
+ ]
167
+
168
+ def get_context_data(self, **kwargs):
169
+ """Get the context data for the view."""
170
+ context = super().get_context_data(**kwargs)
171
+ context["detail_component"] = self.component
172
+ context["detail_breadcrumb"] = self.get_breadcrumb()
173
+ context["PAGE_TITLE"] = self.get_page_title()
174
+ context["PAGE_DESCRIPTION"] = self.get_page_description()
175
+ context["PAGE_URL"] = self.get_page_url(context)
176
+ set_websocket_topics(self.request, self.object)
177
+ return context
178
+
179
+ def get_page_url(self, context) -> Optional[str]:
180
+ """Return the URL of the current page."""
181
+ if hasattr(self.object, "get_absolute_url"):
182
+ return self.object.get_absolute_url()
183
+
184
+ def get_page_title(self) -> Optional[str]:
185
+ """Return the title of the current page."""
186
+ return str(self.object)
187
+
188
+ def get_page_description(self) -> Optional[str]:
189
+ """Return the description of the current page."""
190
+ # noinspection PyProtectedMember
191
+ return self.object._meta.verbose_name