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/urls.py ADDED
@@ -0,0 +1,41 @@
1
+ """List of URLs for the df_site app."""
2
+
3
+ from django.conf import settings
4
+ from django.urls import include, path
5
+ from django.utils.module_loading import import_string
6
+ from django.views.generic import RedirectView
7
+ from django_prometheus import exports
8
+
9
+ from df_site.views import (
10
+ BrowserConfigView,
11
+ HumansTxtView,
12
+ SecurityTxtView,
13
+ csp_report_view,
14
+ security_gpg_view,
15
+ site_webmanifest_view,
16
+ thumbnail_view,
17
+ )
18
+
19
+ urlpatterns = [
20
+ path(
21
+ ".well-known/change-password",
22
+ RedirectView.as_view(pattern_name="account_change_password", permanent=True),
23
+ name="well-known-change-password",
24
+ ),
25
+ path(".well-known/security.txt", SecurityTxtView.as_view(), name="well-known-security"),
26
+ path(".well-known/humans.txt", HumansTxtView.as_view(), name="well-known-humans"),
27
+ path(".well-known/gpg.txt", security_gpg_view, name="well-known-gpg"),
28
+ path("site.webmanifest", site_webmanifest_view, name="site_webmanifest"),
29
+ path("browserconfig.xml", BrowserConfigView.as_view(), name="browserconfig"),
30
+ path("metrics", exports.ExportToDjangoView, name="prometheus-django-metrics"),
31
+ path(settings.CSP_REPORT_URI[1:], csp_report_view, name="csp_report"),
32
+ path("users/", include("df_site.users.urls", namespace="users")),
33
+ path("thumbnails/<path:path>", thumbnail_view, name="thumbnails"),
34
+ path("messages/", include("df_site.postman.urls", namespace="postman")),
35
+ path("cookies/", include("cookie_consent.urls")),
36
+ path(
37
+ "upload_file/",
38
+ import_string(settings.CK_EDITOR_5_UPLOAD_FILE_VIEW),
39
+ name=settings.CK_EDITOR_5_UPLOAD_FILE_VIEW_NAME,
40
+ ),
41
+ ]
@@ -0,0 +1,69 @@
1
+ """Manage user settings."""
2
+
3
+ from functools import lru_cache
4
+ from typing import Any, Optional
5
+
6
+ from cookie_consent.util import get_cookie_value_from_request
7
+ from django.conf import settings
8
+ from django.contrib.auth import get_user_model
9
+ from django.core.exceptions import ValidationError
10
+ from django.db.models import Field
11
+ from django.forms import Widget
12
+ from django.http import HttpRequest, HttpResponse
13
+
14
+ from df_site.models import AbstractPreferences
15
+
16
+
17
+ @lru_cache
18
+ def get_user_instance() -> AbstractPreferences:
19
+ """Return a blank user instance, required for validation purposes."""
20
+ return get_user_model()()
21
+
22
+
23
+ def get_user_setting(
24
+ attr: str, user: Optional[AbstractPreferences] = None, request: Optional[HttpRequest] = None
25
+ ) -> Any:
26
+ """Return the specified user setting.
27
+
28
+ If the user is given, the value is read from the user instance.
29
+ If the cookie provides a value, it is validated and used.
30
+ Otherwise, the default value is returned.
31
+ """
32
+ if user:
33
+ return getattr(user, attr)
34
+ elif request and request.user.is_authenticated:
35
+ return getattr(request.user, attr)
36
+ user_instance = get_user_instance()
37
+ opts = user_instance._meta
38
+ field = opts.get_field(attr)
39
+ raw_value = request.COOKIES.get(attr) if request else None
40
+ value = field.default
41
+ if raw_value is not None:
42
+ try:
43
+ value = field.formfield().clean(raw_value)
44
+ except ValidationError:
45
+ pass
46
+ return value
47
+
48
+
49
+ def set_user_setting(
50
+ attr: str,
51
+ value: Any,
52
+ user: Optional[AbstractPreferences] = None,
53
+ request: Optional[HttpRequest] = None,
54
+ response: Optional[HttpResponse] = None,
55
+ ) -> None:
56
+ """Set the user's preferences."""
57
+ if user:
58
+ setattr(user, attr, value)
59
+ user.save()
60
+ elif request and request.user.is_authenticated:
61
+ setattr(request.user, attr, value)
62
+ request.user.save()
63
+ elif get_cookie_value_from_request(request, "user-settings"):
64
+ user_instance = get_user_instance()
65
+ opts = user_instance._meta
66
+ field: Field = opts.get_field(attr)
67
+ widget: Widget = field.formfield().hidden_widget()
68
+ raw_value: str = widget.format_value(value)
69
+ response.set_cookie(attr, raw_value, samesite="Strict", secure=settings.USE_SSL)
@@ -0,0 +1 @@
1
+ """Module for user management."""
df_site/users/forms.py ADDED
@@ -0,0 +1,35 @@
1
+ """Forms for the users app."""
2
+
3
+ from django import forms
4
+ from django.contrib.auth import get_user_model
5
+ from django.http import HttpRequest
6
+ from django.utils.translation import gettext_lazy as _
7
+ from django_recaptcha.fields import ReCaptchaField
8
+ from django_recaptcha.widgets import ReCaptchaV2Checkbox
9
+
10
+
11
+ class UserSettingsForm(forms.ModelForm):
12
+ """Form for the user settings page."""
13
+
14
+ class Meta:
15
+ """Meta options for the form."""
16
+
17
+ model = get_user_model()
18
+ fields = [
19
+ "first_name",
20
+ "last_name",
21
+ "color_theme",
22
+ "email_notifications",
23
+ "display_online",
24
+ ]
25
+
26
+
27
+ class ReCaptchaForm(forms.Form):
28
+ """Form for the reCAPTCHA field."""
29
+
30
+ captcha = ReCaptchaField(widget=ReCaptchaV2Checkbox, label=_("I'm not a robot"))
31
+
32
+ # noinspection PyMethodMayBeStatic,PyUnusedLocal
33
+ def signup(self, request: HttpRequest, user):
34
+ """Configure the user as required by django-allauth."""
35
+ return user
@@ -0,0 +1,14 @@
1
+ """This module contains the functions related to the notifications of the users."""
2
+
3
+ from typing import Optional, Union
4
+
5
+ from django.contrib.auth.models import AbstractUser
6
+ from django.contrib.sites.models import Site
7
+
8
+
9
+ # noinspection PyUnusedLocal
10
+ def email_address_on_message(user: Optional[AbstractUser], action: str, site: Site) -> Union[bool, str]:
11
+ """Return the email address of the user if it is authenticated and if its accepts emails."""
12
+ if user and user.is_authenticated and user.email_notifications:
13
+ return user.email
14
+ return False
df_site/users/urls.py ADDED
@@ -0,0 +1,17 @@
1
+ """List of URLs for the df_site app."""
2
+
3
+ from django.conf import settings
4
+ from django.urls import path
5
+ from django.utils.module_loading import import_string
6
+
7
+ from df_site.users.views import theme_switch
8
+
9
+ app_name = "users"
10
+ urlpatterns = [
11
+ path("theme-switch", theme_switch, name="theme_switch"),
12
+ ]
13
+ if settings.AUTH_USER_SETTINGS_VIEW:
14
+ user_settings_view = import_string(settings.AUTH_USER_SETTINGS_VIEW)
15
+ urlpatterns.append(
16
+ path("settings", user_settings_view.as_view(), name="settings"),
17
+ )
df_site/users/views.py ADDED
@@ -0,0 +1,75 @@
1
+ """Groups all views related to the users."""
2
+
3
+ from django.conf import settings
4
+ from django.contrib import messages
5
+ from django.http import HttpRequest, HttpResponse, HttpResponseRedirect, JsonResponse
6
+ from django.urls import reverse
7
+ from django.utils.http import url_has_allowed_host_and_scheme, urlencode
8
+ from django.utils.translation import gettext as _
9
+ from django.views.decorators.cache import never_cache
10
+ from django.views.generic import FormView
11
+
12
+ from df_site.user_settings import set_user_setting
13
+ from df_site.users.forms import UserSettingsForm
14
+
15
+
16
+ class UserSettingsView(FormView):
17
+ """View for the user settings page."""
18
+
19
+ template_name = "users/settings.html"
20
+ form_class = UserSettingsForm
21
+
22
+ def get_success_url(self):
23
+ """Get the URL to redirect to after a successful form submission."""
24
+ return reverse("users:settings")
25
+
26
+ def get_form_kwargs(self):
27
+ """Return the keyword arguments for instantiating the form."""
28
+ kwargs = super().get_form_kwargs()
29
+ kwargs["instance"] = self.request.user
30
+ return kwargs
31
+
32
+ def form_valid(self, form):
33
+ """Save the form data and redirect to the success URL."""
34
+ form.save()
35
+ messages.success(self.request, _("Your settings have been saved."))
36
+ return super().form_valid(form)
37
+
38
+
39
+ @never_cache
40
+ def theme_switch(request: HttpRequest) -> HttpResponse:
41
+ """Change the color theme of the site."""
42
+ # noinspection PyTypeChecker
43
+ current_theme: str = request.GET.get("current", "auto")
44
+ # noinspection PyTypeChecker
45
+ redirect_to = sanitize_redirection(request)
46
+
47
+ if settings.DF_SITE_THEMES:
48
+ next_theme, __, next_icon = settings.DF_SITE_THEMES[-1]
49
+ for theme, label, icon in settings.DF_SITE_THEMES:
50
+ if theme == current_theme:
51
+ break
52
+ next_theme, next_icon = theme, icon
53
+ else:
54
+ next_theme, next_icon = "auto", "toggle-on"
55
+ next_url = reverse("users:theme_switch")
56
+ args = urlencode({"next": redirect_to, "current": next_theme})
57
+ result = {"theme": next_theme, "icon": next_icon, "href": f"{next_url}?{args}"}
58
+ if request.META["HTTP_ACCEPT"] == "application/json":
59
+ response = JsonResponse(result)
60
+ else:
61
+ response = HttpResponseRedirect(redirect_to)
62
+ set_user_setting("color_theme", next_theme, request=request, response=response)
63
+ return response
64
+
65
+
66
+ def sanitize_redirection(request, param="next"):
67
+ """Sanitize the redirection URL, only keeping allowed hosts."""
68
+ redirect_to: str = request.GET.get(param, "/")
69
+ if not url_has_allowed_host_and_scheme(
70
+ url=redirect_to,
71
+ allowed_hosts={request.get_host()},
72
+ require_https=request.is_secure(),
73
+ ):
74
+ redirect_to = "/"
75
+ return redirect_to
df_site/views.py ADDED
@@ -0,0 +1,122 @@
1
+ """Views for the df_site app."""
2
+
3
+ import datetime
4
+ import json
5
+ import logging
6
+
7
+ from django.conf import settings
8
+ from django.contrib import messages
9
+ from django.http import Http404, HttpRequest, HttpResponse, JsonResponse
10
+ from django.templatetags.static import static
11
+ from django.urls import reverse
12
+ from django.utils.safestring import mark_safe
13
+ from django.views.decorators.csrf import csrf_exempt
14
+ from django.views.generic import TemplateView
15
+ from django.views.static import serve
16
+
17
+ from df_site.templatetags.df_site import abs_url
18
+ from df_site.templatetags.images import CachedImage
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ def site_webmanifest_view(request: HttpRequest) -> HttpResponse:
24
+ """Generate a site.webmanifest view."""
25
+ result = {
26
+ "name": settings.DF_SITE_TITLE,
27
+ "short_name": settings.DF_SITE_TITLE,
28
+ "icons": [
29
+ {"src": static("favicon/android-chrome-192x192.png"), "sizes": "192x192", "type": "image/png"},
30
+ {"src": static("favicon/android-chrome-512x512.png"), "sizes": "512x512", "type": "image/png"},
31
+ ],
32
+ "theme_color": settings.DF_ANDROID_THEME_COLOR,
33
+ "background_color": settings.DF_ANDROID_BACKGROUND_COLOR,
34
+ "display": "standalone",
35
+ }
36
+ return JsonResponse(result)
37
+
38
+
39
+ @csrf_exempt
40
+ def csp_report_view(request: HttpRequest) -> HttpResponse:
41
+ """View to receive CSP reports, displaying them to the user in DEBUG mode."""
42
+ logger.info("CSP report: %s", request.body)
43
+ if settings.DEBUG:
44
+ try:
45
+ content = json.loads(request.body)
46
+ csp_report = content["csp-report"]
47
+ msg = (
48
+ f"<strong>CSP violation</strong> on this <a href='{csp_report['document-uri']}'>page</a>:"
49
+ f" {csp_report['effective-directive']} forbids the use of"
50
+ f" '{csp_report['blocked-uri']}' URIs."
51
+ )
52
+ messages.error(request, mark_safe(msg)) # noqa
53
+ except ValueError:
54
+ pass
55
+ return HttpResponse(status=204)
56
+
57
+
58
+ class BrowserConfigView(TemplateView):
59
+ """View for the browserconfig.xml file."""
60
+
61
+ template_name = "favicon/browserconfig.xml"
62
+ content_type = "application/xml"
63
+
64
+
65
+ def security_gpg_view(request: HttpRequest) -> HttpResponse:
66
+ """Return the GPG key to communicate with about security problems."""
67
+ if settings.DF_SITE_SECURITY_GPG_CONTENT:
68
+ raise Http404
69
+ response = HttpResponse(settings.DF_SITE_SECURITY_GPG_CONTENT, content_type="text/plain")
70
+ response["Content-Disposition"] = 'attachment; filename="gpg.txt"'
71
+ return response
72
+
73
+
74
+ class SecurityTxtView(TemplateView):
75
+ """View for the ./well-known/security.txt file."""
76
+
77
+ template_name = "df_site/security.txt"
78
+ content_type = "plain/text"
79
+
80
+ def get_context_data(self, **kwargs):
81
+ """Return the context data for the view."""
82
+ context = super().get_context_data(**kwargs)
83
+ context["security_txt_path"] = abs_url(reverse("well-known-security"))
84
+ context["security_email"] = settings.DF_SITE_SECURITY_EMAIL
85
+ context["security_language_code"] = settings.DF_SITE_SECURITY_LANGUAGE_CODE
86
+ gpg_key = None
87
+ if settings.DF_SITE_SECURITY_GPG_CONTENT:
88
+ gpg_key = abs_url(reverse("well-known-gpg"))
89
+ context["security_gpg_key"] = gpg_key
90
+ now = datetime.datetime.now(tz=datetime.UTC)
91
+ now = now - datetime.timedelta(microseconds=now.microsecond)
92
+ context["security_expires"] = now + datetime.timedelta(days=30)
93
+ return context
94
+
95
+
96
+ class HumansTxtView(TemplateView):
97
+ """View for the ./well-known/humans.txt file."""
98
+
99
+ template_name = "df_site/humans.txt"
100
+ content_type = "plain/text"
101
+
102
+ def get_context_data(self, **kwargs):
103
+ """Return the context data for the view."""
104
+ context = super().get_context_data(**kwargs)
105
+ now = datetime.datetime.now(tz=datetime.UTC)
106
+ now = now - datetime.timedelta(microseconds=now.microsecond)
107
+ context["humans_description"] = settings.DF_SITE_DESCRIPTION
108
+ context["humans_social_networks"] = settings.DF_SITE_SOCIAL_NETWORKS
109
+ context["humans_keywords"] = settings.DF_SITE_KEYWORDS
110
+ context["humans_update"] = now
111
+ return context
112
+
113
+
114
+ def thumbnail_view(request: HttpRequest, path: str) -> HttpResponse:
115
+ """Return a thumbnail image."""
116
+ img = CachedImage.from_target_path(path)
117
+ if not img.src_storage_obj.exists(path):
118
+ img.process()
119
+ return serve(request, path=path, document_root=settings.STATIC_ROOT)
120
+
121
+
122
+ #