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
@@ -0,0 +1,61 @@
|
|
1
|
+
"""Define the context processors with global variables about the site."""
|
2
|
+
|
3
|
+
import datetime
|
4
|
+
import logging
|
5
|
+
from functools import lru_cache
|
6
|
+
from typing import Any, Dict, Iterable, Optional
|
7
|
+
|
8
|
+
from django.conf import settings
|
9
|
+
from django.db.models import Q
|
10
|
+
from django.http import HttpRequest
|
11
|
+
from django.utils.timezone import now
|
12
|
+
|
13
|
+
from df_site.models import AlertRibbon
|
14
|
+
from df_site.user_settings import get_user_setting
|
15
|
+
|
16
|
+
logger = logging.getLogger(__name__)
|
17
|
+
|
18
|
+
|
19
|
+
@lru_cache(maxsize=1)
|
20
|
+
def get_current_ribbons(current_date: datetime.datetime) -> Iterable[AlertRibbon]:
|
21
|
+
"""Get the active ribbons for the current date.
|
22
|
+
|
23
|
+
The current date will be rounded to the nearest hour.
|
24
|
+
"""
|
25
|
+
return list(
|
26
|
+
AlertRibbon.objects.filter(
|
27
|
+
Q(Q(end_date__gte=current_date) & Q(end_date__isnull=False)) | Q(end_date__isnull=True),
|
28
|
+
Q(Q(start_date__lte=current_date) & Q(start_date__isnull=False)) | Q(start_date__isnull=True),
|
29
|
+
is_active=True,
|
30
|
+
)
|
31
|
+
)
|
32
|
+
|
33
|
+
|
34
|
+
def global_site_infos(request: HttpRequest, current_date: Optional[datetime.datetime] = None) -> Dict[str, Any]:
|
35
|
+
"""Adds a few values to the request context."""
|
36
|
+
try:
|
37
|
+
color_theme = get_user_setting("color_theme", request=request)
|
38
|
+
except Exception as e:
|
39
|
+
logger.error("Error getting color theme: %s", e)
|
40
|
+
color_theme = settings.DF_SITE_THEMES[0][0]
|
41
|
+
if current_date is None:
|
42
|
+
current_date = now()
|
43
|
+
current_date = current_date.replace(minute=0, second=0, microsecond=0)
|
44
|
+
ribbons = get_current_ribbons(current_date)
|
45
|
+
x_url = settings.DF_SITE_SOCIAL_NETWORKS.get("twitter", "")
|
46
|
+
__, __, df_site_x_account = x_url.rpartition("/")
|
47
|
+
return {
|
48
|
+
"DF_SITE_TITLE": settings.DF_SITE_TITLE,
|
49
|
+
"DF_SITE_DESCRIPTION": settings.DF_SITE_DESCRIPTION,
|
50
|
+
"DF_SITE_KEYWORDS": settings.DF_SITE_KEYWORDS,
|
51
|
+
"DF_SITE_AUTHOR": settings.DF_SITE_AUTHOR,
|
52
|
+
"DF_SITE_ORGANIZATION": settings.DF_SITE_ORGANIZATION,
|
53
|
+
"DF_SITE_X_ACCOUNT": df_site_x_account,
|
54
|
+
"DF_SITE_SOCIAL_NETWORKS": settings.DF_SITE_SOCIAL_NETWORKS.items(),
|
55
|
+
"DF_COLOR_THEMES": settings.DF_SITE_THEMES,
|
56
|
+
"DF_MICROSOFT_BACKGROUND_COLOR": settings.DF_MICROSOFT_BACKGROUND_COLOR,
|
57
|
+
"DF_ANDROID_THEME_COLOR": settings.DF_ANDROID_THEME_COLOR,
|
58
|
+
"DF_SAFARI_PINNED_COLOR": settings.DF_SAFARI_PINNED_COLOR,
|
59
|
+
"COLOR_THEME": color_theme,
|
60
|
+
"DF_RIBBONS": ribbons,
|
61
|
+
}
|
df_site/defaults.py
ADDED
@@ -0,0 +1,319 @@
|
|
1
|
+
"""Django settings for the project."""
|
2
|
+
|
3
|
+
from typing import List, Tuple
|
4
|
+
|
5
|
+
from df_config.config.dynamic_settings import CallableSetting, SettingReference
|
6
|
+
from django.utils.translation import gettext_lazy as _
|
7
|
+
|
8
|
+
from df_site.dynamic_settings import allauth_signup_form, are_tests_running, load_tox_environment
|
9
|
+
|
10
|
+
load_tox_environment()
|
11
|
+
DF_SITE_TITLE = "More batteries to Django"
|
12
|
+
DF_SITE_SECURITY_EMAIL = SettingReference("ADMIN_EMAIL")
|
13
|
+
DF_SITE_SECURITY_LANGUAGE_CODE = SettingReference("LANGUAGE_CODE")
|
14
|
+
DF_SITE_SECURITY_GPG_CONTENT = None
|
15
|
+
|
16
|
+
DF_SITE_DESCRIPTION = "A simple Django site with lots of batteries included."
|
17
|
+
DF_SITE_KEYWORDS = ["Django", "Bootstrap", "WebSockets", "HTMX", "Django Channels"]
|
18
|
+
DF_SITE_AUTHOR = "d9pouces"
|
19
|
+
DF_SITE_ORGANIZATION = "d9pouces"
|
20
|
+
DF_SITE_SOCIAL_NETWORKS = {
|
21
|
+
"instagram": "https://www.instagram.com/d9pouces/",
|
22
|
+
"twitter": "https://x.com/d9pouces/",
|
23
|
+
"github": "https://github.com/d9pouces/",
|
24
|
+
}
|
25
|
+
|
26
|
+
DF_SITE_THEMES: List[Tuple[str, str, str]] = [ # ('theme name', 'theme label', 'icon name')
|
27
|
+
("auto", _("Auto"), "toggle-on"),
|
28
|
+
("light", _("Light"), "sun"),
|
29
|
+
("dark", _("Dark"), "moon"),
|
30
|
+
]
|
31
|
+
DF_ANDROID_THEME_COLOR = "#ffffff"
|
32
|
+
DF_ANDROID_BACKGROUND_COLOR = "#ffffff"
|
33
|
+
DF_MICROSOFT_BACKGROUND_COLOR = "#da532c"
|
34
|
+
DF_SAFARI_PINNED_COLOR = "#5bbad5"
|
35
|
+
|
36
|
+
DF_IMAGE_THUMBNAILS = {
|
37
|
+
"media": {
|
38
|
+
"src_storage": "default",
|
39
|
+
"dst_storage": "staticfiles",
|
40
|
+
"cache": "default",
|
41
|
+
"prefix": "T",
|
42
|
+
"reversible": True,
|
43
|
+
},
|
44
|
+
"static": {
|
45
|
+
"src_storage": "staticfiles",
|
46
|
+
"dst_storage": "staticfiles",
|
47
|
+
"cache": "default",
|
48
|
+
"prefix": "S",
|
49
|
+
"reversible": True,
|
50
|
+
},
|
51
|
+
}
|
52
|
+
|
53
|
+
CSP_IMG_SRC = ["'self'", "data: w3.org/svg/2000", "https://log.pinterest.com"]
|
54
|
+
# "https://log.pinterest.com" is used for Pinterest
|
55
|
+
CSP_STYLE_SRC = ["'self'"]
|
56
|
+
CSP_FONT_SRC = ["'self'"]
|
57
|
+
CSP_DEFAULT_SRC = ["'none'"]
|
58
|
+
CSP_SCRIPT_SRC = [
|
59
|
+
"'self'",
|
60
|
+
"https://www.google.com",
|
61
|
+
"https://www.gstatic.com",
|
62
|
+
"https://assets.pinterest.com",
|
63
|
+
"'unsafe-inline'",
|
64
|
+
]
|
65
|
+
# unsafe-inline is used for django-allauth (inline JS)
|
66
|
+
# "https://www.google.com", "https://www.gstatic.com" are used for reCAPTCHA
|
67
|
+
# "https://assets.pinterest.com" is used for Pinterest
|
68
|
+
CSP_OBJECT_SRC = ["'self'"]
|
69
|
+
CSP_MEDIA_SRC = ["'self'"]
|
70
|
+
CSP_FRAME_SRC = ["'self'", "https://www.google.com", "https://assets.pinterest.com"]
|
71
|
+
# "https://www.google.com" is used for reCAPTCHA
|
72
|
+
# "https://assets.pinterest.com" is used for Pinterest
|
73
|
+
CSP_CHILD_SRC = ["'self'"]
|
74
|
+
CSP_FRAME_ANCESTORS = ["'self'"]
|
75
|
+
CSP_FORM_ACTION = ["'self'"]
|
76
|
+
CSP_MANIFEST_SRC = ["'self'"]
|
77
|
+
CSP_BASE_URI = ["'self'"]
|
78
|
+
CSP_REPORT_URI = "/csp-report/"
|
79
|
+
CSP_REPORT_TO = None
|
80
|
+
DF_TEMPLATE_CONTEXT_PROCESSORS = [
|
81
|
+
"df_site.context_processors.global_site_infos",
|
82
|
+
"django.template.context_processors.request",
|
83
|
+
]
|
84
|
+
TESTING = CallableSetting(are_tests_running)
|
85
|
+
RECAPTCHA_PUBLIC_KEY = ""
|
86
|
+
RECAPTCHA_PRIVATE_KEY = ""
|
87
|
+
DF_INSTALLED_APPS = [
|
88
|
+
"django_bootstrap5",
|
89
|
+
"df_site.apps.DFSiteApp",
|
90
|
+
"cookie_consent",
|
91
|
+
"postman",
|
92
|
+
"allauth.mfa",
|
93
|
+
"allauth.usersessions",
|
94
|
+
"django_ckeditor_5",
|
95
|
+
"django_recaptcha",
|
96
|
+
]
|
97
|
+
|
98
|
+
DF_MIDDLEWARE = [
|
99
|
+
"allauth.usersessions.middleware.UserSessionsMiddleware",
|
100
|
+
"df_websockets.middleware.WebsocketMiddleware",
|
101
|
+
"df_site.middleware.websocket_middleware",
|
102
|
+
]
|
103
|
+
MIDDLEWARE = [
|
104
|
+
"django_prometheus.middleware.PrometheusBeforeMiddleware",
|
105
|
+
"django.middleware.cache.UpdateCacheMiddleware",
|
106
|
+
"debug_toolbar.middleware.DebugToolbarMiddleware",
|
107
|
+
"django.contrib.sessions.middleware.SessionMiddleware",
|
108
|
+
"django.middleware.common.CommonMiddleware",
|
109
|
+
"django.middleware.csrf.CsrfViewMiddleware",
|
110
|
+
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
111
|
+
"django.middleware.locale.LocaleMiddleware",
|
112
|
+
"django.contrib.messages.middleware.MessageMiddleware",
|
113
|
+
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
114
|
+
"django.middleware.security.SecurityMiddleware",
|
115
|
+
"df_config.apps.middleware.DFConfigMiddleware",
|
116
|
+
"allauth.usersessions.middleware.UserSessionsMiddleware",
|
117
|
+
"df_websockets.middleware.WebsocketMiddleware",
|
118
|
+
"df_site.middleware.websocket_middleware",
|
119
|
+
"csp.middleware.CSPMiddleware",
|
120
|
+
"allauth.account.middleware.AccountMiddleware",
|
121
|
+
"django.middleware.cache.FetchFromCacheMiddleware",
|
122
|
+
"django_prometheus.middleware.PrometheusAfterMiddleware",
|
123
|
+
]
|
124
|
+
USERSESSIONS_TRACK_ACTIVITY = True
|
125
|
+
POSTMAN_DISALLOW_ANONYMOUS = True
|
126
|
+
POSTMAN_AUTO_MODERATE_AS = True
|
127
|
+
POSTMAN_I18N_URLS = False
|
128
|
+
AUTH_USER_MODEL = "df_site.PreferencesUser"
|
129
|
+
AUTH_USER_SETTINGS_VIEW = "df_site.users.views.UserSettingsView"
|
130
|
+
COOKIE_CONSENT_SECURE = SettingReference("USE_SSL")
|
131
|
+
COOKIE_CONSENT_DOMAIN = "{SERVER_NAME}"
|
132
|
+
COOKIE_CONSENT_SAMESITE = "Strict"
|
133
|
+
PIPELINE = {
|
134
|
+
"PIPELINE_ENABLED": SettingReference("PIPELINE_ENABLED"),
|
135
|
+
"JAVASCRIPT": {
|
136
|
+
"base": {
|
137
|
+
"source_filenames": [
|
138
|
+
"js/base.js",
|
139
|
+
"js/df_websockets.min.js",
|
140
|
+
# "django_ckeditor_5/dist/bundle.js"
|
141
|
+
],
|
142
|
+
"output_filename": "js/base.min.js",
|
143
|
+
# "integrity": "sha384",
|
144
|
+
"crossorigin": "anonymous",
|
145
|
+
"extra_context": {
|
146
|
+
"defer": True,
|
147
|
+
},
|
148
|
+
},
|
149
|
+
"app": {
|
150
|
+
"source_filenames": ["js/app.ts"],
|
151
|
+
"output_filename": "js/app.min.js",
|
152
|
+
# "integrity": "sha384",
|
153
|
+
"crossorigin": "anonymous",
|
154
|
+
"extra_context": {
|
155
|
+
"defer": True,
|
156
|
+
},
|
157
|
+
},
|
158
|
+
},
|
159
|
+
"STYLESHEETS": {
|
160
|
+
"base": {
|
161
|
+
"source_filenames": [
|
162
|
+
"django_ckeditor_5/src/override-django.css",
|
163
|
+
"css/ckeditor5.css",
|
164
|
+
"css/base.css",
|
165
|
+
],
|
166
|
+
"output_filename": "css/base.min.css",
|
167
|
+
"extra_context": {"media": "all"},
|
168
|
+
# "integrity": "sha384",
|
169
|
+
"crossorigin": "anonymous",
|
170
|
+
},
|
171
|
+
"app": {
|
172
|
+
"source_filenames": ["css/app.css"],
|
173
|
+
"output_filename": "css/app.min.css",
|
174
|
+
"extra_context": {"media": "all"},
|
175
|
+
# "integrity": "sha384",
|
176
|
+
"crossorigin": "anonymous",
|
177
|
+
},
|
178
|
+
},
|
179
|
+
"CSS_COMPRESSOR": "pipeline.compressors.yuglify.YuglifyCompressor",
|
180
|
+
"JS_COMPRESSOR": "pipeline.compressors.uglifyjs.UglifyJSCompressor",
|
181
|
+
"COMPILERS": [],
|
182
|
+
}
|
183
|
+
SOCIALACCOUNT_EMAIL_AUTHENTICATION_AUTO_CONNECT = True
|
184
|
+
SOCIALACCOUNT_LOGIN_ON_GET = True
|
185
|
+
MFA_SUPPORTED_TYPES = ["totp", "webauthn", "recovery_codes"]
|
186
|
+
MFA_PASSKEY_LOGIN_ENABLED = True
|
187
|
+
MFA_WEBAUTHN_ALLOW_INSECURE_ORIGIN = SettingReference("DEBUG")
|
188
|
+
ACCOUNT_LOGIN_BY_CODE_ENABLED = True
|
189
|
+
ACCOUNT_EMAIL_VERIFICATION = "optional"
|
190
|
+
ACCOUNT_SIGNUP_FORM_CLASS = CallableSetting(allauth_signup_form)
|
191
|
+
|
192
|
+
CKEDITOR_5_USER_LANGUAGE = True
|
193
|
+
special_chars = [
|
194
|
+
{"title": _("smiley face"), "character": "😊"},
|
195
|
+
{"title": _("smiley face"), "character": ":)"},
|
196
|
+
]
|
197
|
+
CK_EDITOR_5_UPLOAD_FILE_VIEW = "django_ckeditor_5.views.upload_file"
|
198
|
+
CK_EDITOR_5_UPLOAD_FILE_VIEW_NAME = "upload_file"
|
199
|
+
CKEDITOR_5_CONFIGS = {
|
200
|
+
"inline": {
|
201
|
+
"specialChars": special_chars,
|
202
|
+
"toolbar": [
|
203
|
+
"bold",
|
204
|
+
"italic",
|
205
|
+
"underline",
|
206
|
+
"strikethrough",
|
207
|
+
"subscript",
|
208
|
+
"superscript",
|
209
|
+
"|",
|
210
|
+
"specialCharacters",
|
211
|
+
"removeFormat",
|
212
|
+
],
|
213
|
+
"plugins": [
|
214
|
+
"Essentials",
|
215
|
+
"Autoformat",
|
216
|
+
"Bold",
|
217
|
+
"Italic",
|
218
|
+
"Underline",
|
219
|
+
"Strikethrough",
|
220
|
+
"Code",
|
221
|
+
"Subscript",
|
222
|
+
"Superscript",
|
223
|
+
"Paragraph",
|
224
|
+
"Font",
|
225
|
+
"PasteFromOffice",
|
226
|
+
"RemoveFormat",
|
227
|
+
"Highlight",
|
228
|
+
"SpecialCharacters",
|
229
|
+
"SpecialCharactersEssentials",
|
230
|
+
"ShowBlocks",
|
231
|
+
"SelectAll",
|
232
|
+
],
|
233
|
+
},
|
234
|
+
"inline_link": {
|
235
|
+
"specialChars": special_chars,
|
236
|
+
"toolbar": [
|
237
|
+
"bold",
|
238
|
+
"italic",
|
239
|
+
"underline",
|
240
|
+
"strikethrough",
|
241
|
+
"subscript",
|
242
|
+
"superscript",
|
243
|
+
"link",
|
244
|
+
"|",
|
245
|
+
"specialCharacters",
|
246
|
+
"removeFormat",
|
247
|
+
],
|
248
|
+
"plugins": [
|
249
|
+
"Essentials",
|
250
|
+
"Autoformat",
|
251
|
+
"Bold",
|
252
|
+
"Italic",
|
253
|
+
"Underline",
|
254
|
+
"Strikethrough",
|
255
|
+
"Code",
|
256
|
+
"Subscript",
|
257
|
+
"Superscript",
|
258
|
+
"Link",
|
259
|
+
"Paragraph",
|
260
|
+
"Font",
|
261
|
+
"PasteFromOffice",
|
262
|
+
"RemoveFormat",
|
263
|
+
"Highlight",
|
264
|
+
"SpecialCharacters",
|
265
|
+
"SpecialCharactersEssentials",
|
266
|
+
"ShowBlocks",
|
267
|
+
"SelectAll",
|
268
|
+
],
|
269
|
+
},
|
270
|
+
"default": {
|
271
|
+
"specialChars": special_chars,
|
272
|
+
"toolbar": [
|
273
|
+
"heading",
|
274
|
+
"bulletedList",
|
275
|
+
"numberedList",
|
276
|
+
"blockQuote",
|
277
|
+
"|",
|
278
|
+
"bold",
|
279
|
+
"italic",
|
280
|
+
"underline",
|
281
|
+
"strikethrough",
|
282
|
+
"subscript",
|
283
|
+
"superscript",
|
284
|
+
"link",
|
285
|
+
"highlight",
|
286
|
+
"|",
|
287
|
+
"insertTable",
|
288
|
+
"insertImage",
|
289
|
+
"specialCharacters",
|
290
|
+
"removeFormat",
|
291
|
+
"undo",
|
292
|
+
"redo",
|
293
|
+
],
|
294
|
+
"image": {
|
295
|
+
"toolbar": [
|
296
|
+
"imageTextAlternative",
|
297
|
+
"|",
|
298
|
+
"imageStyle:full",
|
299
|
+
"imageStyle:alignLeft",
|
300
|
+
"imageStyle:alignRight",
|
301
|
+
"imageStyle:alignCenter",
|
302
|
+
"imageStyle:side",
|
303
|
+
"|",
|
304
|
+
],
|
305
|
+
"styles": [
|
306
|
+
"full",
|
307
|
+
"side",
|
308
|
+
"alignLeft",
|
309
|
+
"alignRight",
|
310
|
+
"alignCenter",
|
311
|
+
],
|
312
|
+
},
|
313
|
+
"list": {"properties": {"styles": False, "startIndex": True, "reversed": True}},
|
314
|
+
"table": {
|
315
|
+
"defaultHeadings": {"rows": 1, "columns": 1},
|
316
|
+
"contentToolbar": ["tableColumn", "tableRow", "mergeTableCells"],
|
317
|
+
},
|
318
|
+
},
|
319
|
+
}
|
@@ -0,0 +1,37 @@
|
|
1
|
+
"""Python functions to provide Django settings depending on other settings."""
|
2
|
+
|
3
|
+
import os
|
4
|
+
from typing import Any, Optional
|
5
|
+
|
6
|
+
|
7
|
+
def allauth_signup_form(values: dict[str, Any]) -> Optional[str]:
|
8
|
+
"""Return the form class to use for signing up."""
|
9
|
+
if values.get("RECAPTCHA_PRIVATE_KEY") and values.get("RECAPTCHA_PUBLIC_KEY"):
|
10
|
+
return "df_site.users.forms.ReCaptchaForm"
|
11
|
+
return None
|
12
|
+
|
13
|
+
|
14
|
+
allauth_signup_form.required_settings = ["RECAPTCHA_PRIVATE_KEY", "RECAPTCHA_PUBLIC_KEY"]
|
15
|
+
|
16
|
+
|
17
|
+
def are_tests_running(values: dict[str, Any]) -> bool:
|
18
|
+
"""Return True if we are running unit tests."""
|
19
|
+
return "testserver" in values.get("ALLOWED_HOSTS", [])
|
20
|
+
|
21
|
+
|
22
|
+
are_tests_running.required_settings = ["ALLOWED_HOSTS"]
|
23
|
+
|
24
|
+
|
25
|
+
def load_tox_environment():
|
26
|
+
"""Is a workaround for https://github.com/tox-dev/tox-docker/issues/55."""
|
27
|
+
if os.environ.get("REDIS_HOST") and os.environ.get("REDIS_6379_TCP_PORT"):
|
28
|
+
os.environ["REDIS_URL"] = f'redis://:p_df_site@{os.environ["REDIS_HOST"]}:{os.environ["REDIS_6379_TCP_PORT"]}/1'
|
29
|
+
if os.environ.get("POSTGRES_HOST") and os.environ.get("POSTGRES_5432_TCP_PORT"):
|
30
|
+
os.environ["DATABASE_URL"] = (
|
31
|
+
f'postgresql://u_df_site:p_df_site@{os.environ["POSTGRES_HOST"]}:'
|
32
|
+
f'{os.environ["POSTGRES_5432_TCP_PORT"]}/d_df_site'
|
33
|
+
)
|
34
|
+
if os.environ.get("MINIO_HOST") and os.environ.get("MINIO_9000_TCP_PORT"):
|
35
|
+
os.environ["MAIN_STORAGE_DIR"] = (
|
36
|
+
f's3:http://u_df_site:p_df_site@127.0.0.1:' f'{os.environ["MINIO_9000_TCP_PORT"]}/f_df_site'
|
37
|
+
)
|
df_site/form_fields.py
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
"""Custom form fields for the df_site app."""
|
2
|
+
|
3
|
+
import html
|
4
|
+
from functools import lru_cache
|
5
|
+
from typing import Dict, Iterable, List, Set
|
6
|
+
|
7
|
+
import nh3
|
8
|
+
from django import forms
|
9
|
+
from django.conf import settings
|
10
|
+
from django.core.exceptions import ValidationError
|
11
|
+
from django.utils.html import strip_tags
|
12
|
+
from django_ckeditor_5.widgets import CKEditor5Widget
|
13
|
+
from lxml_html_clean import autolink_html
|
14
|
+
|
15
|
+
# list of CKEditor5 plugins and associated HTML tags
|
16
|
+
BLOCK = "$block"
|
17
|
+
TAGS_BY_PLUGIN: Dict[str, Dict[str, List[str]]] = {
|
18
|
+
"Alignment": {BLOCK: ["class"]},
|
19
|
+
"BlockQuote": {"blockquote": []},
|
20
|
+
"Bold": {"strong": []},
|
21
|
+
"Code": {"code": []},
|
22
|
+
"CodeBlock": {"pre": ["class"], "code": ["class"]},
|
23
|
+
"Essentials": {"br": []},
|
24
|
+
"Font": {"span": ["class"]},
|
25
|
+
"Heading": {"h1": [], "h2": [], "h3": [], "h4": []},
|
26
|
+
"Highlight": {"mark": ["class"]},
|
27
|
+
"HorizontalLine": {"hr": []},
|
28
|
+
"HtmlEmbed": {"div": ["class"]},
|
29
|
+
"Image": {"figure": ["class"], "img": []},
|
30
|
+
"ImageCaption": {"figcaption": []},
|
31
|
+
"Indent": {BLOCK: ["class"]},
|
32
|
+
"Italic": {"i": []},
|
33
|
+
"Link": {"a": ["rel", "href"]},
|
34
|
+
"List": {"ol": [], "ul": [], "li": []},
|
35
|
+
"ListProperties": {"ol": [], "ul": [], "li": []},
|
36
|
+
"MediaEmbed": {
|
37
|
+
"figure": ["class"],
|
38
|
+
"oembed": ["url"],
|
39
|
+
"div": ["data-oembed-url"],
|
40
|
+
"iframe": ["frameborder", "src", "allow"],
|
41
|
+
},
|
42
|
+
"Mention": {"span": ["class", "data-mention"]},
|
43
|
+
"Paragraph": {"p": []},
|
44
|
+
"Strikethrough": {"s": []},
|
45
|
+
"Subscript": {"sub": []},
|
46
|
+
"Superscript": {"sup": []},
|
47
|
+
"Table": {
|
48
|
+
"figure": ["class"],
|
49
|
+
"table": [],
|
50
|
+
"thead": [],
|
51
|
+
"tbody": [],
|
52
|
+
"tr": [],
|
53
|
+
"td": ["colspan", "rowspan"],
|
54
|
+
"th": ["colspan", "rowspan"],
|
55
|
+
},
|
56
|
+
"TableCaption": {"figcaption": ["data-placeholder"]},
|
57
|
+
"TableCellProperties": {"td": ["class"]},
|
58
|
+
"TableProperties": {"figure": ["class"], "table": ["class"]},
|
59
|
+
"TodoList": {"ul": ["class"], "label": ["class"], "span": ["class"], "li": ["class"], "input": ["class"]},
|
60
|
+
"Underline": {"u": []},
|
61
|
+
}
|
62
|
+
BLOCKS = {"blockquote", "pre", "div", "figure", "ol", "ul", "p", "table", "tr", "td", "th"}
|
63
|
+
|
64
|
+
|
65
|
+
@lru_cache
|
66
|
+
def get_allowed_tags(config_name: str) -> Set[str]:
|
67
|
+
"""Return the list of allowed HTML tags for a given CKEditor 5 configuration."""
|
68
|
+
return set(get_allowed_attributes(config_name))
|
69
|
+
|
70
|
+
|
71
|
+
def get_allowed_attributes(config_name: str) -> Dict[str, Set[str]]:
|
72
|
+
"""Return the list of allowed HTML tags and attributes for a given CKEditor 5 configuration."""
|
73
|
+
config = settings.CKEDITOR_5_CONFIGS.get(config_name, {})
|
74
|
+
plugins: Iterable[str] = config.get("plugins", TAGS_BY_PLUGIN.keys())
|
75
|
+
allowed_attributes: Dict[str, Set[str]] = {}
|
76
|
+
for plugin in plugins:
|
77
|
+
for key, attributes in TAGS_BY_PLUGIN.get(plugin, {}).items():
|
78
|
+
if key == BLOCK:
|
79
|
+
keys = BLOCKS
|
80
|
+
else:
|
81
|
+
keys = [key]
|
82
|
+
for key_ in keys:
|
83
|
+
allowed_attributes.setdefault(key_, set())
|
84
|
+
allowed_attributes[key_] |= set(attributes)
|
85
|
+
return allowed_attributes
|
86
|
+
|
87
|
+
|
88
|
+
class CKEditor5Field(forms.CharField):
|
89
|
+
"""A form field using CKEditor 5 as text editor."""
|
90
|
+
|
91
|
+
default_ckeditor5_config = "default"
|
92
|
+
|
93
|
+
def __init__(self, *args, config_name=None, **kwargs) -> None:
|
94
|
+
"""Initialize the field."""
|
95
|
+
self.ckeditor5_config = config_name or self.default_ckeditor5_config
|
96
|
+
self.required_ = kwargs.get("required", True)
|
97
|
+
kwargs["required"] = False
|
98
|
+
css_class = "django_ckeditor_5"
|
99
|
+
self.is_inline_widget = "inline" in self.ckeditor5_config
|
100
|
+
if self.is_inline_widget:
|
101
|
+
css_class += " ck-editor__singleline"
|
102
|
+
kwargs["widget"] = CKEditor5Widget(attrs={"class": css_class}, config_name=self.ckeditor5_config)
|
103
|
+
super().__init__(*args, **kwargs)
|
104
|
+
|
105
|
+
def to_python(self, value):
|
106
|
+
"""Convert the value to a string."""
|
107
|
+
allowed_tags = get_allowed_tags(self.ckeditor5_config)
|
108
|
+
allowed_attributes = get_allowed_attributes(self.ckeditor5_config)
|
109
|
+
if value is None:
|
110
|
+
raise ValidationError(self.error_messages["required"], code="required")
|
111
|
+
elif len(value) <= 50 and self.required_:
|
112
|
+
striped = html.unescape(strip_tags(value)).strip()
|
113
|
+
if not striped:
|
114
|
+
raise ValidationError(self.error_messages["required"], code="required")
|
115
|
+
if self.is_inline_widget and "p" in allowed_tags:
|
116
|
+
allowed_tags.remove("p")
|
117
|
+
value = nh3.clean(value, strip_comments=True, tags=allowed_tags, attributes=allowed_attributes)
|
118
|
+
value = autolink_html(value)
|
119
|
+
value = value.replace("\n", " ")
|
120
|
+
return super().to_python(value)
|
121
|
+
|
122
|
+
def prepare_value(self, value):
|
123
|
+
"""Prepare the value for the widget."""
|
124
|
+
if value is None:
|
125
|
+
return ""
|
126
|
+
return value
|
127
|
+
|
128
|
+
|
129
|
+
class InlineCKEditor5Field(CKEditor5Field):
|
130
|
+
"""A form field using CKEditor 5 as inline text editor."""
|
131
|
+
|
132
|
+
default_ckeditor5_config = "inline"
|
133
|
+
|
134
|
+
|
135
|
+
class InlineLinkCKEditor5Field(CKEditor5Field):
|
136
|
+
"""A form field using CKEditor 5 as inline text editor."""
|
137
|
+
|
138
|
+
default_ckeditor5_config = "inline_link"
|
@@ -0,0 +1 @@
|
|
1
|
+
"""Management module for df_site."""
|
@@ -0,0 +1 @@
|
|
1
|
+
"""Some management commands."""
|
@@ -0,0 +1,104 @@
|
|
1
|
+
"""Add an image to the media storage."""
|
2
|
+
|
3
|
+
import pathlib
|
4
|
+
|
5
|
+
from django.core.files.storage import Storage, storages
|
6
|
+
from django.core.management import BaseCommand
|
7
|
+
from django.urls import reverse
|
8
|
+
|
9
|
+
from df_site.templatetags.images import AspectPolicy, CachedImage
|
10
|
+
|
11
|
+
|
12
|
+
class Command(BaseCommand):
|
13
|
+
"""Add an image to the media storage."""
|
14
|
+
|
15
|
+
help = "Add an image to the media storage."
|
16
|
+
|
17
|
+
def add_arguments(self, parser):
|
18
|
+
"""Add arguments to the command."""
|
19
|
+
parser.add_argument("path", type=pathlib.Path)
|
20
|
+
parser.add_argument("--storage", choices=["default", "staticfiles"], default="default")
|
21
|
+
parser.add_argument("--name", type=str, default=None)
|
22
|
+
parser.add_argument("--height", type=int, default=None)
|
23
|
+
parser.add_argument("--width", type=int, default=None)
|
24
|
+
parser.add_argument("--format", type=str, default="webp", choices=CachedImage.formats)
|
25
|
+
parser.add_argument("--overwrite", action="store_true", default=False)
|
26
|
+
parser.add_argument("--print", action="store_true", default=False)
|
27
|
+
parser.add_argument(
|
28
|
+
"--policy",
|
29
|
+
type=int,
|
30
|
+
default=AspectPolicy.FORCE_MAX_TRANSPARENT.value,
|
31
|
+
choices=[x.value for x in AspectPolicy],
|
32
|
+
)
|
33
|
+
|
34
|
+
def handle(self, *args, **options):
|
35
|
+
"""Add an image to the media storage and generate thumbnails."""
|
36
|
+
aspect_policy = AspectPolicy.get_policy(options["policy"])
|
37
|
+
src_path: pathlib.Path = options["path"]
|
38
|
+
storage: str = options["storage"]
|
39
|
+
name: str = options["name"] or src_path.name
|
40
|
+
# we need to check if the file exists
|
41
|
+
if not src_path.exists():
|
42
|
+
raise FileNotFoundError(f"File {src_path} does not exist.")
|
43
|
+
# we need to check if the file exists in the destination storage
|
44
|
+
storage_obj: Storage = storages[storage]
|
45
|
+
exists = storage_obj.exists(name)
|
46
|
+
cache_engine = "locmem"
|
47
|
+
fmt = options["format"]
|
48
|
+
if exists and not options["overwrite"]:
|
49
|
+
self.stderr.write(f"File {name} already exists in storage {storage}.")
|
50
|
+
return
|
51
|
+
elif exists:
|
52
|
+
storage_obj.delete(name)
|
53
|
+
# we save the file to the destination storage
|
54
|
+
with src_path.open("rb") as src_file:
|
55
|
+
storage_obj.save(name, src_file)
|
56
|
+
#
|
57
|
+
img = CachedImage.from_target_path(f"T/600x600_4/{name}.{fmt}")
|
58
|
+
img.process()
|
59
|
+
self.stdout.write(f"Generated thumbnails: {img.created_sizes}.")
|
60
|
+
if options["print"]:
|
61
|
+
self.stdout.write(img.as_html_tag())
|
62
|
+
|
63
|
+
def url(p):
|
64
|
+
"""Return the URL of the thumbnail."""
|
65
|
+
return reverse("thumbnails", kwargs={"path": p})
|
66
|
+
|
67
|
+
img = CachedImage(
|
68
|
+
CachedImage.sources["media"]["src_storage"],
|
69
|
+
CachedImage.sources["media"]["dst_storage"],
|
70
|
+
cache_engine,
|
71
|
+
name,
|
72
|
+
CachedImage.sources["media"]["prefix"],
|
73
|
+
url=url,
|
74
|
+
height=options["height"],
|
75
|
+
width=options["width"],
|
76
|
+
widths=None,
|
77
|
+
aspect_policy=aspect_policy,
|
78
|
+
fmt=fmt,
|
79
|
+
write_thumbnails=True,
|
80
|
+
)
|
81
|
+
img.process()
|
82
|
+
self.stdout.write(f"File {name} saved to storage {storage}.")
|
83
|
+
self.stdout.write(f"Generated thumbnails: {img.created_sizes}.")
|
84
|
+
if options["print"]:
|
85
|
+
self.stdout.write(img.as_html_tag())
|
86
|
+
img = CachedImage(
|
87
|
+
CachedImage.sources["media"]["src_storage"],
|
88
|
+
CachedImage.sources["media"]["dst_storage"],
|
89
|
+
cache_engine,
|
90
|
+
name,
|
91
|
+
CachedImage.sources["media"]["prefix"],
|
92
|
+
url=url,
|
93
|
+
height=options["height"],
|
94
|
+
width=options["width"],
|
95
|
+
widths=[320, 480, 640, 1024, 2048],
|
96
|
+
aspect_policy=aspect_policy,
|
97
|
+
fmt=fmt,
|
98
|
+
write_thumbnails=True,
|
99
|
+
)
|
100
|
+
img.process()
|
101
|
+
self.stdout.write(f"File {name} saved to storage {storage}.")
|
102
|
+
self.stdout.write(f"Generated thumbnails: {img.created_sizes}.")
|
103
|
+
if options["print"]:
|
104
|
+
self.stdout.write(img.as_html_tag())
|