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.
- df_site/__init__.py +1 -0
- df_site/__main__.py +37 -0
- df_site/admin.py +130 -0
- df_site/apps.py +57 -0
- df_site/components/__init__.py +1 -0
- df_site/components/base.py +82 -0
- df_site/components/detail.py +191 -0
- df_site/components/list.py +446 -0
- df_site/components/list_filters.py +74 -0
- df_site/components/registry.py +55 -0
- df_site/constants.py +71 -0
- df_site/context_processors.py +61 -0
- df_site/defaults.py +319 -0
- df_site/dynamic_settings.py +37 -0
- df_site/form_fields.py +138 -0
- df_site/management/__init__.py +1 -0
- df_site/management/commands/__init__.py +1 -0
- df_site/management/commands/add_image.py +104 -0
- df_site/management/commands/generate_favicon.py +47 -0
- df_site/middleware.py +20 -0
- df_site/migrations/0001_initial.py +220 -0
- df_site/migrations/0002_alter_alertribbon_message_alter_alertribbon_summary.py +23 -0
- df_site/migrations/__init__.py +0 -0
- df_site/model_fields.py +35 -0
- df_site/models.py +130 -0
- df_site/postman/__init__.py +1 -0
- df_site/postman/forms.py +38 -0
- df_site/postman/urls.py +75 -0
- df_site/postman/views.py +65 -0
- df_site/static/css/app.css +0 -0
- df_site/static/css/base.css +22208 -0
- df_site/static/css/ckeditor5.css +422 -0
- df_site/static/favicon/android-chrome-192x192.png +0 -0
- df_site/static/favicon/android-chrome-512x512.png +0 -0
- df_site/static/favicon/apple-touch-icon.png +0 -0
- df_site/static/favicon/favicon-16x16.png +0 -0
- df_site/static/favicon/favicon-32x32.png +0 -0
- df_site/static/favicon/favicon.ico +0 -0
- df_site/static/favicon/mstile-150x150.png +0 -0
- df_site/static/favicon/safari-pinned-tab.svg +46 -0
- df_site/static/images/accessibility.svg +1 -0
- df_site/static/images/align-bottom.svg +1 -0
- df_site/static/images/align-center.svg +1 -0
- df_site/static/images/align-justify.svg +1 -0
- df_site/static/images/align-left.svg +1 -0
- df_site/static/images/align-middle.svg +1 -0
- df_site/static/images/align-right.svg +1 -0
- df_site/static/images/align-top.svg +1 -0
- df_site/static/images/bold.svg +1 -0
- df_site/static/images/browse-files.svg +1 -0
- df_site/static/images/bulletedlist.svg +1 -0
- df_site/static/images/cancel.svg +1 -0
- df_site/static/images/caption.svg +1 -0
- df_site/static/images/check.svg +1 -0
- df_site/static/images/code.svg +1 -0
- df_site/static/images/codeblock.svg +1 -0
- df_site/static/images/cog.svg +1 -0
- df_site/static/images/color-palette.svg +1 -0
- df_site/static/images/color-tile-check.svg +1 -0
- df_site/static/images/drag-handle.svg +1 -0
- df_site/static/images/drag-indicator.svg +1 -0
- df_site/static/images/dropdown-arrow.svg +1 -0
- df_site/static/images/eraser.svg +1 -0
- df_site/static/images/file-arrow-up-solid.svg +1 -0
- df_site/static/images/find-replace.svg +1 -0
- df_site/static/images/font-background.svg +1 -0
- df_site/static/images/font-color.svg +1 -0
- df_site/static/images/font-family.svg +1 -0
- df_site/static/images/font-size.svg +1 -0
- df_site/static/images/heading1.svg +1 -0
- df_site/static/images/heading2.svg +1 -0
- df_site/static/images/heading3.svg +1 -0
- df_site/static/images/heading4.svg +1 -0
- df_site/static/images/heading5.svg +1 -0
- df_site/static/images/heading6.svg +1 -0
- df_site/static/images/history.svg +1 -0
- df_site/static/images/horizontalline.svg +1 -0
- df_site/static/images/html.svg +1 -0
- df_site/static/images/image-asset-manager.svg +1 -0
- df_site/static/images/image-upload.svg +1 -0
- df_site/static/images/image-url.svg +1 -0
- df_site/static/images/image.svg +1 -0
- df_site/static/images/importexport.svg +1 -0
- df_site/static/images/indent.svg +1 -0
- df_site/static/images/italic.svg +1 -0
- df_site/static/images/link.svg +1 -0
- df_site/static/images/liststylecircle.svg +1 -0
- df_site/static/images/liststyledecimal.svg +1 -0
- df_site/static/images/liststyledecimalleadingzero.svg +1 -0
- df_site/static/images/liststyledisc.svg +1 -0
- df_site/static/images/liststylelowerlatin.svg +1 -0
- df_site/static/images/liststylelowerroman.svg +1 -0
- df_site/static/images/liststylesquare.svg +1 -0
- df_site/static/images/liststyleupperlatin.svg +1 -0
- df_site/static/images/liststyleupperroman.svg +1 -0
- df_site/static/images/loupe.svg +1 -0
- df_site/static/images/low-vision.svg +1 -0
- df_site/static/images/marker.svg +1 -0
- df_site/static/images/media-placeholder.svg +1 -0
- df_site/static/images/media.svg +1 -0
- df_site/static/images/next-arrow.svg +1 -0
- df_site/static/images/numberedlist.svg +1 -0
- df_site/static/images/object-center.svg +1 -0
- df_site/static/images/object-full-width.svg +1 -0
- df_site/static/images/object-inline-left.svg +1 -0
- df_site/static/images/object-inline-right.svg +1 -0
- df_site/static/images/object-inline.svg +1 -0
- df_site/static/images/object-left.svg +1 -0
- df_site/static/images/object-right.svg +1 -0
- df_site/static/images/object-size-custom.svg +1 -0
- df_site/static/images/object-size-full.svg +1 -0
- df_site/static/images/object-size-large.svg +1 -0
- df_site/static/images/object-size-medium.svg +1 -0
- df_site/static/images/object-size-small.svg +1 -0
- df_site/static/images/outdent.svg +1 -0
- df_site/static/images/paragraph.svg +1 -0
- df_site/static/images/pen.svg +1 -0
- df_site/static/images/pencil.svg +1 -0
- df_site/static/images/pilcrow.svg +1 -0
- df_site/static/images/plus.svg +1 -0
- df_site/static/images/previous-arrow.svg +1 -0
- df_site/static/images/project-logo.svg +1 -0
- df_site/static/images/quote.svg +1 -0
- df_site/static/images/redo.svg +1 -0
- df_site/static/images/remove-format.svg +1 -0
- df_site/static/images/return-arrow.svg +1 -0
- df_site/static/images/select-all.svg +1 -0
- df_site/static/images/show-blocks.svg +1 -0
- df_site/static/images/source-editing.svg +1 -0
- df_site/static/images/specialcharacters.svg +1 -0
- df_site/static/images/strikethrough.svg +1 -0
- df_site/static/images/subscript.svg +1 -0
- df_site/static/images/superscript.svg +1 -0
- df_site/static/images/table-cell-properties.svg +1 -0
- df_site/static/images/table-column.svg +1 -0
- df_site/static/images/table-merge-cell.svg +1 -0
- df_site/static/images/table-properties.svg +1 -0
- df_site/static/images/table-row.svg +1 -0
- df_site/static/images/table.svg +1 -0
- df_site/static/images/text-alternative.svg +1 -0
- df_site/static/images/text.svg +1 -0
- df_site/static/images/three-vertical-dots.svg +1 -0
- df_site/static/images/todolist.svg +1 -0
- df_site/static/images/underline.svg +1 -0
- df_site/static/images/undo.svg +1 -0
- df_site/static/images/unlink.svg +1 -0
- df_site/static/js/app.js +98 -0
- df_site/static/js/app.js.map +1 -0
- df_site/static/js/base.js +161181 -0
- df_site/static/js/base.js.map +1 -0
- df_site/static/translations/af.js +1 -0
- df_site/static/translations/ar.js +1 -0
- df_site/static/translations/ast.js +1 -0
- df_site/static/translations/az.js +1 -0
- df_site/static/translations/bg.js +1 -0
- df_site/static/translations/bn.js +1 -0
- df_site/static/translations/bs.js +1 -0
- df_site/static/translations/ca.js +1 -0
- df_site/static/translations/cs.js +1 -0
- df_site/static/translations/da.js +1 -0
- df_site/static/translations/de-ch.js +1 -0
- df_site/static/translations/de.js +1 -0
- df_site/static/translations/el.js +1 -0
- df_site/static/translations/en-au.js +1 -0
- df_site/static/translations/en-gb.js +1 -0
- df_site/static/translations/en.js +1 -0
- df_site/static/translations/eo.js +1 -0
- df_site/static/translations/es-co.js +1 -0
- df_site/static/translations/es.js +1 -0
- df_site/static/translations/et.js +1 -0
- df_site/static/translations/eu.js +1 -0
- df_site/static/translations/fa.js +1 -0
- df_site/static/translations/fi.js +1 -0
- df_site/static/translations/gl.js +1 -0
- df_site/static/translations/gu.js +1 -0
- df_site/static/translations/he.js +1 -0
- df_site/static/translations/hi.js +1 -0
- df_site/static/translations/hr.js +1 -0
- df_site/static/translations/hu.js +1 -0
- df_site/static/translations/hy.js +1 -0
- df_site/static/translations/id.js +1 -0
- df_site/static/translations/it.js +1 -0
- df_site/static/translations/ja.js +1 -0
- df_site/static/translations/jv.js +1 -0
- df_site/static/translations/kk.js +1 -0
- df_site/static/translations/km.js +1 -0
- df_site/static/translations/kn.js +1 -0
- df_site/static/translations/ko.js +1 -0
- df_site/static/translations/ku.js +1 -0
- df_site/static/translations/lt.js +1 -0
- df_site/static/translations/lv.js +1 -0
- df_site/static/translations/ms.js +1 -0
- df_site/static/translations/nb.js +1 -0
- df_site/static/translations/ne.js +1 -0
- df_site/static/translations/nl.js +1 -0
- df_site/static/translations/no.js +1 -0
- df_site/static/translations/oc.js +1 -0
- df_site/static/translations/pl.js +1 -0
- df_site/static/translations/pt-br.js +1 -0
- df_site/static/translations/pt.js +1 -0
- df_site/static/translations/ro.js +1 -0
- df_site/static/translations/ru.js +1 -0
- df_site/static/translations/si.js +1 -0
- df_site/static/translations/sk.js +1 -0
- df_site/static/translations/sl.js +1 -0
- df_site/static/translations/sq.js +1 -0
- df_site/static/translations/sr-latn.js +1 -0
- df_site/static/translations/sr.js +1 -0
- df_site/static/translations/sv.js +1 -0
- df_site/static/translations/th.js +1 -0
- df_site/static/translations/ti.js +1 -0
- df_site/static/translations/tk.js +1 -0
- df_site/static/translations/tr.js +1 -0
- df_site/static/translations/tt.js +1 -0
- df_site/static/translations/ug.js +1 -0
- df_site/static/translations/uk.js +1 -0
- df_site/static/translations/ur.js +1 -0
- df_site/static/translations/uz.js +1 -0
- df_site/static/translations/vi.js +1 -0
- df_site/static/translations/zh-cn.js +1 -0
- df_site/static/translations/zh.js +1 -0
- df_site/static/webfonts/fa-brands-400.ttf +0 -0
- df_site/static/webfonts/fa-brands-400.woff2 +0 -0
- df_site/static/webfonts/fa-regular-400.ttf +0 -0
- df_site/static/webfonts/fa-regular-400.woff2 +0 -0
- df_site/static/webfonts/fa-solid-900.ttf +0 -0
- df_site/static/webfonts/fa-solid-900.woff2 +0 -0
- df_site/static/webfonts/fa-v4compatibility.ttf +0 -0
- df_site/static/webfonts/fa-v4compatibility.woff2 +0 -0
- df_site/templates/account/email.html +78 -0
- df_site/templates/account/password_change.html +28 -0
- df_site/templates/account/snippets/warn_no_email.html +6 -0
- df_site/templates/allauth/elements/alert.html +6 -0
- df_site/templates/allauth/elements/badge.html +4 -0
- df_site/templates/allauth/elements/button.html +14 -0
- df_site/templates/allauth/elements/button_group.html +5 -0
- df_site/templates/allauth/elements/field.html +72 -0
- df_site/templates/allauth/elements/fields.html +3 -0
- df_site/templates/allauth/elements/form.html +10 -0
- df_site/templates/allauth/elements/h1.html +1 -0
- df_site/templates/allauth/elements/h2.html +1 -0
- df_site/templates/allauth/elements/img.html +4 -0
- df_site/templates/allauth/elements/p.html +1 -0
- df_site/templates/allauth/elements/panel.html +14 -0
- df_site/templates/allauth/elements/provider.html +6 -0
- df_site/templates/allauth/elements/provider_list.html +5 -0
- df_site/templates/allauth/elements/table.html +5 -0
- df_site/templates/allauth/layouts/base.html +14 -0
- df_site/templates/allauth/layouts/entrance.html +20 -0
- df_site/templates/allauth/layouts/manage.html +1 -0
- df_site/templates/cookie_consent/_cookie_group.html +64 -0
- df_site/templates/cookie_consent/cookiegroup_list.html +23 -0
- df_site/templates/df_components/base.html +0 -0
- df_site/templates/df_components/detail.html +12 -0
- df_site/templates/df_components/detail_fieldset.html +46 -0
- df_site/templates/df_components/list.html +42 -0
- df_site/templates/df_components/list_filter.html +13 -0
- df_site/templates/df_components/list_filters.html +36 -0
- df_site/templates/df_components/list_hierarchy.html +25 -0
- df_site/templates/df_components/list_pagination.html +39 -0
- df_site/templates/df_components/list_search_form.html +38 -0
- df_site/templates/df_components/list_table.html +35 -0
- df_site/templates/df_site/app.html +1 -0
- df_site/templates/df_site/base.html +221 -0
- df_site/templates/df_site/detail.html +8 -0
- df_site/templates/df_site/humans.txt +11 -0
- df_site/templates/df_site/manage_base.html +51 -0
- df_site/templates/df_site/popup_app.html +1 -0
- df_site/templates/df_site/popup_base.html +29 -0
- df_site/templates/df_site/security.txt +5 -0
- df_site/templates/django_bootstrap5/breadcrumb.html +17 -0
- df_site/templates/django_bootstrap5/pagination.html +40 -0
- df_site/templates/django_ckeditor_5/widget.html +13 -0
- df_site/templates/favicon/browserconfig.xml +9 -0
- df_site/templates/mfa/index.html +115 -0
- df_site/templates/mfa/recovery_codes/index.html +33 -0
- df_site/templates/mfa/webauthn/authenticator_list.html +74 -0
- df_site/templates/pipeline/css.html +1 -0
- df_site/templates/pipeline/js.html +1 -0
- df_site/templates/postman/archives.html +8 -0
- df_site/templates/postman/base.html +20 -0
- df_site/templates/postman/base_folder.html +71 -0
- df_site/templates/postman/base_write.html +26 -0
- df_site/templates/postman/inbox.html +7 -0
- df_site/templates/postman/inc_subject_ex.html +21 -0
- df_site/templates/postman/trash.html +12 -0
- df_site/templates/postman/view.html +64 -0
- df_site/templates/users/settings.html +26 -0
- df_site/templates/usersessions/usersession_list.html +70 -0
- df_site/templatetags/__init__.py +1 -0
- df_site/templatetags/df_site.py +241 -0
- df_site/templatetags/images.py +515 -0
- df_site/templatetags/pipeline_sri.py +97 -0
- df_site/testing/__init__.py +1 -0
- df_site/testing/multiple_views.py +369 -0
- df_site/testing/requests.py +299 -0
- df_site/urls.py +41 -0
- df_site/user_settings.py +69 -0
- df_site/users/__init__.py +1 -0
- df_site/users/forms.py +35 -0
- df_site/users/notifications.py +14 -0
- df_site/users/urls.py +17 -0
- df_site/users/views.py +75 -0
- df_site/views.py +122 -0
- df_site-0.1.0.dist-info/LICENSE +519 -0
- df_site-0.1.0.dist-info/METADATA +217 -0
- df_site-0.1.0.dist-info/RECORD +309 -0
- df_site-0.1.0.dist-info/WHEEL +4 -0
- 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
|