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,47 @@
|
|
1
|
+
"""Generate the favicons."""
|
2
|
+
|
3
|
+
import importlib
|
4
|
+
import os
|
5
|
+
import pathlib
|
6
|
+
from argparse import ArgumentParser
|
7
|
+
|
8
|
+
from django.conf import settings
|
9
|
+
from django.core.management import BaseCommand
|
10
|
+
from PIL import Image
|
11
|
+
|
12
|
+
|
13
|
+
class Command(BaseCommand):
|
14
|
+
"""Generate the favicons."""
|
15
|
+
|
16
|
+
specs = [
|
17
|
+
("android-chrome-192x192.png", 192, 192),
|
18
|
+
("android-chrome-512x512.png", 512, 512),
|
19
|
+
("apple-touch-icon.png", 256, 256),
|
20
|
+
("favicon.ico", 64, 64),
|
21
|
+
("favicon-16x16.png", 16, 32),
|
22
|
+
("favicon-32x32.png", 32, 32),
|
23
|
+
("mstile-150x150.png", 150, 150),
|
24
|
+
]
|
25
|
+
|
26
|
+
def add_arguments(self, parser: ArgumentParser):
|
27
|
+
"""Add the arguments."""
|
28
|
+
mod = importlib.import_module(settings.DF_MODULE_NAME)
|
29
|
+
default = pathlib.Path(mod.__path__[0]) / "static/favicon"
|
30
|
+
parser.add_argument("input", help="Path of the image to add.", type=pathlib.Path)
|
31
|
+
parser.add_argument("--output", help="Destination directory.", type=pathlib.Path, default=default)
|
32
|
+
|
33
|
+
def handle(self, *args, **options):
|
34
|
+
"""Generate the favicons from an original image to the destination directory."""
|
35
|
+
src_path: pathlib.Path = options["input"]
|
36
|
+
dst_dir: pathlib.Path = options["output"]
|
37
|
+
img_src = Image.open(src_path)
|
38
|
+
for dst_name, width, height in self.specs:
|
39
|
+
img_dst = img_src.resize((width, height))
|
40
|
+
dst_path: pathlib.Path = dst_dir / dst_name
|
41
|
+
basename, sep, ext = dst_name.rpartition(".")
|
42
|
+
os.makedirs(dst_path.parent, exist_ok=True)
|
43
|
+
with open(dst_path, "wb") as fd:
|
44
|
+
img_dst.save(fd, format=ext.upper())
|
45
|
+
self.stdout.write(f"Generated {dst_path}")
|
46
|
+
|
47
|
+
img_src.close()
|
df_site/middleware.py
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
"""Set default topics for websockets."""
|
2
|
+
|
3
|
+
from df_websockets.tasks import set_websocket_topics
|
4
|
+
from django.http import HttpRequest, HttpResponse
|
5
|
+
|
6
|
+
|
7
|
+
def websocket_middleware(get_response):
|
8
|
+
"""Set default topics for websockets, when not set yet."""
|
9
|
+
|
10
|
+
def middleware(request: HttpRequest):
|
11
|
+
# Code to be executed for each request before
|
12
|
+
# the view (and later middleware) are called.
|
13
|
+
|
14
|
+
response: HttpResponse = get_response(request)
|
15
|
+
# noinspection PyUnresolvedReferences
|
16
|
+
if not request.has_websocket_topics:
|
17
|
+
set_websocket_topics(request)
|
18
|
+
return response
|
19
|
+
|
20
|
+
return middleware
|
@@ -0,0 +1,220 @@
|
|
1
|
+
# Generated by Django 5.1 on 2024-08-22 17:06
|
2
|
+
|
3
|
+
import django.contrib.auth.models
|
4
|
+
import django.contrib.auth.validators
|
5
|
+
import django.utils.timezone
|
6
|
+
from django.db import migrations, models
|
7
|
+
|
8
|
+
|
9
|
+
class Migration(migrations.Migration):
|
10
|
+
initial = True
|
11
|
+
|
12
|
+
dependencies = [
|
13
|
+
("auth", "0012_alter_user_first_name_max_length"),
|
14
|
+
]
|
15
|
+
|
16
|
+
operations = [
|
17
|
+
migrations.CreateModel(
|
18
|
+
name="AlertRibbon",
|
19
|
+
fields=[
|
20
|
+
(
|
21
|
+
"id",
|
22
|
+
models.AutoField(
|
23
|
+
auto_created=True,
|
24
|
+
primary_key=True,
|
25
|
+
serialize=False,
|
26
|
+
verbose_name="ID",
|
27
|
+
),
|
28
|
+
),
|
29
|
+
(
|
30
|
+
"color",
|
31
|
+
models.CharField(
|
32
|
+
choices=[
|
33
|
+
("info", "Info"),
|
34
|
+
("success", "Success"),
|
35
|
+
("warning", "Warning"),
|
36
|
+
("danger", "Danger"),
|
37
|
+
("primary", "Primary"),
|
38
|
+
("secondary", "Secondary"),
|
39
|
+
("dark", "Dark"),
|
40
|
+
("light", "Light"),
|
41
|
+
("tricolor", "Tricolor"),
|
42
|
+
],
|
43
|
+
db_index=True,
|
44
|
+
default="info",
|
45
|
+
max_length=10,
|
46
|
+
verbose_name="Color",
|
47
|
+
),
|
48
|
+
),
|
49
|
+
("message", models.TextField(verbose_name="Message")),
|
50
|
+
("url", models.URLField(blank=True, null=True, verbose_name="URL")),
|
51
|
+
(
|
52
|
+
"summary",
|
53
|
+
models.CharField(blank=True, default="", max_length=100, verbose_name="Summary"),
|
54
|
+
),
|
55
|
+
(
|
56
|
+
"start_date",
|
57
|
+
models.DateTimeField(blank=True, db_index=True, null=True, verbose_name="Start date"),
|
58
|
+
),
|
59
|
+
(
|
60
|
+
"end_date",
|
61
|
+
models.DateTimeField(blank=True, db_index=True, null=True, verbose_name="End date"),
|
62
|
+
),
|
63
|
+
(
|
64
|
+
"is_active",
|
65
|
+
models.BooleanField(db_index=True, default=True, verbose_name="Is active"),
|
66
|
+
),
|
67
|
+
(
|
68
|
+
"position",
|
69
|
+
models.PositiveSmallIntegerField(
|
70
|
+
choices=[
|
71
|
+
(0, "Top left"),
|
72
|
+
(1, "Bottom left"),
|
73
|
+
(2, "Top right"),
|
74
|
+
(3, "Bottom right"),
|
75
|
+
(4, "Top center"),
|
76
|
+
(5, "Bottom center"),
|
77
|
+
],
|
78
|
+
db_index=True,
|
79
|
+
default=2,
|
80
|
+
verbose_name="Position",
|
81
|
+
),
|
82
|
+
),
|
83
|
+
],
|
84
|
+
options={
|
85
|
+
"verbose_name": "Alert ribbon",
|
86
|
+
"verbose_name_plural": "Alert ribbons",
|
87
|
+
"ordering": ("-start_date",),
|
88
|
+
},
|
89
|
+
),
|
90
|
+
migrations.CreateModel(
|
91
|
+
name="PreferencesUser",
|
92
|
+
fields=[
|
93
|
+
(
|
94
|
+
"id",
|
95
|
+
models.AutoField(
|
96
|
+
auto_created=True,
|
97
|
+
primary_key=True,
|
98
|
+
serialize=False,
|
99
|
+
verbose_name="ID",
|
100
|
+
),
|
101
|
+
),
|
102
|
+
("password", models.CharField(max_length=128, verbose_name="password")),
|
103
|
+
(
|
104
|
+
"last_login",
|
105
|
+
models.DateTimeField(blank=True, null=True, verbose_name="last login"),
|
106
|
+
),
|
107
|
+
(
|
108
|
+
"is_superuser",
|
109
|
+
models.BooleanField(
|
110
|
+
default=False,
|
111
|
+
help_text="Designates that this user has all permissions without explicitly assigning them.",
|
112
|
+
verbose_name="superuser status",
|
113
|
+
),
|
114
|
+
),
|
115
|
+
(
|
116
|
+
"username",
|
117
|
+
models.CharField(
|
118
|
+
error_messages={"unique": "A user with that username already exists."},
|
119
|
+
help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
|
120
|
+
max_length=150,
|
121
|
+
unique=True,
|
122
|
+
validators=[django.contrib.auth.validators.UnicodeUsernameValidator()],
|
123
|
+
verbose_name="username",
|
124
|
+
),
|
125
|
+
),
|
126
|
+
(
|
127
|
+
"first_name",
|
128
|
+
models.CharField(blank=True, max_length=150, verbose_name="first name"),
|
129
|
+
),
|
130
|
+
(
|
131
|
+
"last_name",
|
132
|
+
models.CharField(blank=True, max_length=150, verbose_name="last name"),
|
133
|
+
),
|
134
|
+
(
|
135
|
+
"email",
|
136
|
+
models.EmailField(blank=True, max_length=254, verbose_name="email address"),
|
137
|
+
),
|
138
|
+
(
|
139
|
+
"is_staff",
|
140
|
+
models.BooleanField(
|
141
|
+
default=False,
|
142
|
+
help_text="Designates whether the user can log into this admin site.",
|
143
|
+
verbose_name="staff status",
|
144
|
+
),
|
145
|
+
),
|
146
|
+
(
|
147
|
+
"is_active",
|
148
|
+
models.BooleanField(
|
149
|
+
default=True,
|
150
|
+
help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
|
151
|
+
verbose_name="active",
|
152
|
+
),
|
153
|
+
),
|
154
|
+
(
|
155
|
+
"date_joined",
|
156
|
+
models.DateTimeField(default=django.utils.timezone.now, verbose_name="date joined"),
|
157
|
+
),
|
158
|
+
(
|
159
|
+
"color_theme",
|
160
|
+
models.CharField(
|
161
|
+
choices=[
|
162
|
+
("auto", "Auto"),
|
163
|
+
("light", "Light"),
|
164
|
+
("dark", "Dark"),
|
165
|
+
],
|
166
|
+
db_index=True,
|
167
|
+
default="auto",
|
168
|
+
max_length=10,
|
169
|
+
verbose_name="Color theme",
|
170
|
+
),
|
171
|
+
),
|
172
|
+
(
|
173
|
+
"display_online",
|
174
|
+
models.BooleanField(
|
175
|
+
db_index=True,
|
176
|
+
default=False,
|
177
|
+
verbose_name="Display online status",
|
178
|
+
),
|
179
|
+
),
|
180
|
+
(
|
181
|
+
"email_notifications",
|
182
|
+
models.BooleanField(
|
183
|
+
db_index=True,
|
184
|
+
default=False,
|
185
|
+
verbose_name="Receive notifications by email",
|
186
|
+
),
|
187
|
+
),
|
188
|
+
(
|
189
|
+
"groups",
|
190
|
+
models.ManyToManyField(
|
191
|
+
blank=True,
|
192
|
+
help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
|
193
|
+
related_name="user_set",
|
194
|
+
related_query_name="user",
|
195
|
+
to="auth.group",
|
196
|
+
verbose_name="groups",
|
197
|
+
),
|
198
|
+
),
|
199
|
+
(
|
200
|
+
"user_permissions",
|
201
|
+
models.ManyToManyField(
|
202
|
+
blank=True,
|
203
|
+
help_text="Specific permissions for this user.",
|
204
|
+
related_name="user_set",
|
205
|
+
related_query_name="user",
|
206
|
+
to="auth.permission",
|
207
|
+
verbose_name="user permissions",
|
208
|
+
),
|
209
|
+
),
|
210
|
+
],
|
211
|
+
options={
|
212
|
+
"verbose_name": "user",
|
213
|
+
"verbose_name_plural": "users",
|
214
|
+
"abstract": False,
|
215
|
+
},
|
216
|
+
managers=[
|
217
|
+
("objects", django.contrib.auth.models.UserManager()),
|
218
|
+
],
|
219
|
+
),
|
220
|
+
]
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# Generated by Django 5.1 on 2024-08-23 16:32
|
2
|
+
|
3
|
+
import df_site.model_fields
|
4
|
+
from django.db import migrations
|
5
|
+
|
6
|
+
|
7
|
+
class Migration(migrations.Migration):
|
8
|
+
dependencies = [
|
9
|
+
("df_site", "0001_initial"),
|
10
|
+
]
|
11
|
+
|
12
|
+
operations = [
|
13
|
+
migrations.AlterField(
|
14
|
+
model_name="alertribbon",
|
15
|
+
name="message",
|
16
|
+
field=df_site.model_fields.CKEditor5TextField(blank=True, default="", verbose_name="Message"),
|
17
|
+
),
|
18
|
+
migrations.AlterField(
|
19
|
+
model_name="alertribbon",
|
20
|
+
name="summary",
|
21
|
+
field=df_site.model_fields.CKEditor5CharField(db_index=True, max_length=100, verbose_name="Summary"),
|
22
|
+
),
|
23
|
+
]
|
File without changes
|
df_site/model_fields.py
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
"""Define custom model fields with the CKEditor5 widget."""
|
2
|
+
|
3
|
+
from django.db import models
|
4
|
+
|
5
|
+
from df_site import form_fields
|
6
|
+
|
7
|
+
|
8
|
+
class CKEditor5TextField(models.TextField):
|
9
|
+
"""Use a CKEditor5 widget for a TextField."""
|
10
|
+
|
11
|
+
def formfield(self, **kwargs):
|
12
|
+
"""Replace the default widget with a CKEditor5 widget."""
|
13
|
+
defaults = {"form_class": form_fields.CKEditor5Field}
|
14
|
+
defaults.update(kwargs)
|
15
|
+
return super().formfield(**defaults)
|
16
|
+
|
17
|
+
|
18
|
+
class CKEditor5CharField(models.CharField):
|
19
|
+
"""Use a CKEditor5 widget for a CharField."""
|
20
|
+
|
21
|
+
def formfield(self, **kwargs):
|
22
|
+
"""Replace the default widget with a CKEditor5 widget."""
|
23
|
+
defaults = {"form_class": form_fields.InlineCKEditor5Field}
|
24
|
+
defaults.update(kwargs)
|
25
|
+
return super().formfield(**defaults)
|
26
|
+
|
27
|
+
|
28
|
+
class CKEditor5LinkCharField(models.CharField):
|
29
|
+
"""Use a CKEditor5 widget for a CharField, allowing to write URLs."""
|
30
|
+
|
31
|
+
def formfield(self, **kwargs):
|
32
|
+
"""Replace the default widget with a CKEditor5 widget."""
|
33
|
+
defaults = {"form_class": form_fields.InlineLinkCKEditor5Field}
|
34
|
+
defaults.update(kwargs)
|
35
|
+
return super().formfield(**defaults)
|
df_site/models.py
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
"""Models for the df_site app."""
|
2
|
+
|
3
|
+
from django.conf import settings
|
4
|
+
from django.contrib.auth.models import AbstractUser
|
5
|
+
from django.db import models
|
6
|
+
from django.urls import reverse
|
7
|
+
from django.utils.translation import gettext_lazy as _
|
8
|
+
|
9
|
+
from df_site.model_fields import CKEditor5CharField, CKEditor5TextField
|
10
|
+
|
11
|
+
|
12
|
+
class AlertRibbon(models.Model):
|
13
|
+
"""Model for alert messages that are shown to users."""
|
14
|
+
|
15
|
+
LEVELS = (
|
16
|
+
("info", _("Info")),
|
17
|
+
("success", _("Success")),
|
18
|
+
("warning", _("Warning")),
|
19
|
+
("danger", _("Danger")),
|
20
|
+
("primary", _("Primary")),
|
21
|
+
("secondary", _("Secondary")),
|
22
|
+
("dark", _("Dark")),
|
23
|
+
("light", _("Light")),
|
24
|
+
("tricolor", _("Tricolor")),
|
25
|
+
)
|
26
|
+
TOP_LEFT = 0
|
27
|
+
BOTTOM_LEFT = 1
|
28
|
+
TOP_RIGHT = 2
|
29
|
+
BOTTOM_RIGHT = 3
|
30
|
+
TOP_CENTER = 4
|
31
|
+
BOTTOM_CENTER = 5
|
32
|
+
POSITIONS = (
|
33
|
+
(TOP_LEFT, _("Top left")),
|
34
|
+
(BOTTOM_LEFT, _("Bottom left")),
|
35
|
+
(TOP_RIGHT, _("Top right")),
|
36
|
+
(BOTTOM_RIGHT, _("Bottom right")),
|
37
|
+
(TOP_CENTER, _("Top center")),
|
38
|
+
(BOTTOM_CENTER, _("Bottom center")),
|
39
|
+
)
|
40
|
+
|
41
|
+
color = models.CharField(max_length=10, choices=LEVELS, default="info", db_index=True, verbose_name=_("Color"))
|
42
|
+
message = CKEditor5TextField(verbose_name=_("Message"), blank=True, default="")
|
43
|
+
url = models.URLField(null=True, blank=True, verbose_name=_("URL")) # noqa: DJ001
|
44
|
+
summary = CKEditor5CharField(max_length=100, verbose_name=_("Summary"), db_index=True)
|
45
|
+
start_date = models.DateTimeField(null=True, blank=True, db_index=True, verbose_name=_("Start date"))
|
46
|
+
end_date = models.DateTimeField(null=True, blank=True, db_index=True, verbose_name=_("End date"))
|
47
|
+
is_active = models.BooleanField(default=True, db_index=True, verbose_name=_("Is active"))
|
48
|
+
position = models.PositiveSmallIntegerField(
|
49
|
+
default=TOP_RIGHT, choices=POSITIONS, db_index=True, verbose_name=_("Position")
|
50
|
+
)
|
51
|
+
|
52
|
+
class Meta:
|
53
|
+
"""Meta options for the model."""
|
54
|
+
|
55
|
+
ordering = ("-start_date",)
|
56
|
+
verbose_name = _("Alert ribbon")
|
57
|
+
verbose_name_plural = _("Alert ribbons")
|
58
|
+
|
59
|
+
def __str__(self):
|
60
|
+
"""Return the string representation of the alert message."""
|
61
|
+
return self.summary
|
62
|
+
|
63
|
+
def get_absolute_url(self):
|
64
|
+
"""Return the URL for the alert message."""
|
65
|
+
return reverse("ribbon", kwargs={"pk": self.pk})
|
66
|
+
|
67
|
+
@property
|
68
|
+
def html_tag(self):
|
69
|
+
"""Return the HTML tag for the alert message."""
|
70
|
+
if self.position in (self.TOP_CENTER, self.BOTTOM_CENTER):
|
71
|
+
return "div"
|
72
|
+
elif self.url:
|
73
|
+
return "a"
|
74
|
+
return "span"
|
75
|
+
|
76
|
+
@property
|
77
|
+
def css_classes(self):
|
78
|
+
"""Return the CSS classes for the alert message."""
|
79
|
+
if self.position == self.TOP_CENTER:
|
80
|
+
classes = f"container-fluid btn btn-{self.color} "
|
81
|
+
elif self.position == self.BOTTOM_CENTER:
|
82
|
+
classes = f"container-fluid btn btn-{self.color} fixed-bottom "
|
83
|
+
else:
|
84
|
+
classes = f"alert-ribbon fixed alert-ribbon-{self.color} "
|
85
|
+
if self.position in (self.TOP_LEFT, self.BOTTOM_LEFT):
|
86
|
+
classes += "left-"
|
87
|
+
elif self.position in (self.TOP_RIGHT, self.BOTTOM_RIGHT):
|
88
|
+
classes += "right-"
|
89
|
+
elif self.position in (self.TOP_CENTER, self.BOTTOM_CENTER):
|
90
|
+
classes += "center-"
|
91
|
+
if self.position in (self.TOP_LEFT, self.TOP_RIGHT, self.TOP_CENTER):
|
92
|
+
classes += "top"
|
93
|
+
elif self.position in (self.BOTTOM_LEFT, self.BOTTOM_RIGHT, self.BOTTOM_CENTER):
|
94
|
+
classes += "bottom"
|
95
|
+
return classes
|
96
|
+
|
97
|
+
|
98
|
+
class AbstractPreferences(models.Model):
|
99
|
+
"""User preferences for the df_site app."""
|
100
|
+
|
101
|
+
COLOR_THEMES = {x[0]: x[1] for x in settings.DF_SITE_THEMES}
|
102
|
+
color_theme = models.CharField(
|
103
|
+
max_length=10,
|
104
|
+
default=settings.DF_SITE_THEMES[0][0],
|
105
|
+
db_index=True,
|
106
|
+
choices=COLOR_THEMES,
|
107
|
+
verbose_name=_("Color theme"),
|
108
|
+
)
|
109
|
+
display_online = models.BooleanField(default=False, verbose_name=_("Display online status"), db_index=True)
|
110
|
+
email_notifications = models.BooleanField(
|
111
|
+
default=False, verbose_name=_("Receive notifications by email"), db_index=True
|
112
|
+
)
|
113
|
+
|
114
|
+
class Meta:
|
115
|
+
"""Meta options for the model."""
|
116
|
+
|
117
|
+
abstract = True
|
118
|
+
|
119
|
+
|
120
|
+
class PreferencesUser(AbstractUser, AbstractPreferences):
|
121
|
+
"""User model for the df_site app."""
|
122
|
+
|
123
|
+
class Meta(AbstractUser.Meta):
|
124
|
+
"""Meta options for the model."""
|
125
|
+
|
126
|
+
swappable = "AUTH_USER_MODEL"
|
127
|
+
|
128
|
+
def __str__(self):
|
129
|
+
"""Return the string representation of the user."""
|
130
|
+
return super().__str__()
|
@@ -0,0 +1 @@
|
|
1
|
+
"""Customized postman views and forms."""
|
df_site/postman/forms.py
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
"""Forms for the Postman application."""
|
2
|
+
|
3
|
+
from django import forms
|
4
|
+
from django.utils.translation import gettext_lazy as _
|
5
|
+
from postman.forms import AnonymousWriteForm, FullReplyForm, QuickReplyForm, WriteForm
|
6
|
+
|
7
|
+
from df_site.form_fields import CKEditor5Field
|
8
|
+
|
9
|
+
|
10
|
+
class CKEditor5Form(forms.Form):
|
11
|
+
"""Form with a CKEditor 5 widget."""
|
12
|
+
|
13
|
+
body = CKEditor5Field(label=_("body"), required=True, config_name="default")
|
14
|
+
subject = CKEditor5Field(label=_("subject"), required=True, config_name="inline")
|
15
|
+
|
16
|
+
|
17
|
+
class HTMLWriteForm(CKEditor5Form, WriteForm):
|
18
|
+
"""Form used by authenticated users."""
|
19
|
+
|
20
|
+
pass
|
21
|
+
|
22
|
+
|
23
|
+
class HTMLAnonymousWriteForm(CKEditor5Form, AnonymousWriteForm):
|
24
|
+
"""Form used by anonymous users."""
|
25
|
+
|
26
|
+
pass
|
27
|
+
|
28
|
+
|
29
|
+
class HTMLFullReplyForm(CKEditor5Form, FullReplyForm):
|
30
|
+
"""Form for complete Postman replies."""
|
31
|
+
|
32
|
+
pass
|
33
|
+
|
34
|
+
|
35
|
+
class HTMLQuickReplyForm(CKEditor5Form, QuickReplyForm):
|
36
|
+
"""Form for postman quick replies."""
|
37
|
+
|
38
|
+
pass
|
df_site/postman/urls.py
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
"""URLs for customized views (mainly customized forms)."""
|
2
|
+
|
3
|
+
from django.conf import settings
|
4
|
+
from django.urls import include, re_path
|
5
|
+
from postman import api_urls
|
6
|
+
from postman.views import (
|
7
|
+
ArchivesView,
|
8
|
+
ArchiveView,
|
9
|
+
ConversationView,
|
10
|
+
DeleteView,
|
11
|
+
InboxView,
|
12
|
+
IndexView,
|
13
|
+
MarkReadView,
|
14
|
+
MarkUnreadView,
|
15
|
+
MessageView,
|
16
|
+
ReplyView,
|
17
|
+
SentView,
|
18
|
+
TrashView,
|
19
|
+
UndeleteView,
|
20
|
+
WriteView,
|
21
|
+
)
|
22
|
+
|
23
|
+
from df_site.postman.forms import HTMLAnonymousWriteForm, HTMLFullReplyForm, HTMLQuickReplyForm, HTMLWriteForm
|
24
|
+
from df_site.postman.views import MessageUpdateView
|
25
|
+
|
26
|
+
if settings.POSTMAN_I18N_URLS:
|
27
|
+
from django.utils.translation import pgettext_lazy
|
28
|
+
else:
|
29
|
+
|
30
|
+
def pgettext_lazy(context: str, message: str):
|
31
|
+
"""Does nothing when POSTMAN_I18N_URLS is off."""
|
32
|
+
return message
|
33
|
+
|
34
|
+
|
35
|
+
app_name = "postman"
|
36
|
+
urlpatterns = [
|
37
|
+
# Translators: keep consistency of the <option> parameter with the translation for 'm'
|
38
|
+
re_path(pgettext_lazy("postman_url", r"^inbox/(?:(?P<option>m)/)?$"), InboxView.as_view(), name="inbox"),
|
39
|
+
# Translators: keep consistency of the <option> parameter with the translation for 'm'
|
40
|
+
re_path(pgettext_lazy("postman_url", r"^sent/(?:(?P<option>m)/)?$"), SentView.as_view(), name="sent"),
|
41
|
+
# Translators: keep consistency of the <option> parameter with the translation for 'm'
|
42
|
+
re_path(pgettext_lazy("postman_url", r"^archives/(?:(?P<option>m)/)?$"), ArchivesView.as_view(), name="archives"),
|
43
|
+
# Translators: keep consistency of the <option> parameter with the translation for 'm'
|
44
|
+
re_path(pgettext_lazy("postman_url", r"^trash/(?:(?P<option>m)/)?$"), TrashView.as_view(), name="trash"),
|
45
|
+
re_path(
|
46
|
+
pgettext_lazy("postman_url", r"^write/(?:(?P<recipients>[^/#]+)/)?$"),
|
47
|
+
WriteView.as_view(form_classes=(HTMLWriteForm, HTMLAnonymousWriteForm)),
|
48
|
+
name="write",
|
49
|
+
),
|
50
|
+
re_path(
|
51
|
+
pgettext_lazy("postman_url", r"^reply/(?P<message_id>[\d]+)/$"),
|
52
|
+
ReplyView.as_view(form_class=HTMLFullReplyForm),
|
53
|
+
name="reply",
|
54
|
+
),
|
55
|
+
re_path(
|
56
|
+
pgettext_lazy("postman_url", r"^view/(?P<message_id>[\d]+)/$"),
|
57
|
+
MessageView.as_view(form_class=HTMLQuickReplyForm),
|
58
|
+
name="view",
|
59
|
+
),
|
60
|
+
# Translators: 't' stands for 'thread'
|
61
|
+
re_path(
|
62
|
+
pgettext_lazy("postman_url", r"^view/t/(?P<thread_id>[\d]+)/$"),
|
63
|
+
ConversationView.as_view(form_class=HTMLQuickReplyForm),
|
64
|
+
name="view_conversation",
|
65
|
+
),
|
66
|
+
re_path(pgettext_lazy("postman_url", r"^archive/$"), ArchiveView.as_view(), name="archive"),
|
67
|
+
re_path(pgettext_lazy("postman_url", r"^delete/$"), DeleteView.as_view(), name="delete"),
|
68
|
+
re_path(pgettext_lazy("postman_url", r"^undelete/$"), UndeleteView.as_view(), name="undelete"),
|
69
|
+
re_path(pgettext_lazy("postman_url", r"^mark-read/$"), MarkReadView.as_view(), name="mark-read"),
|
70
|
+
re_path(pgettext_lazy("postman_url", r"^mark-unread/$"), MarkUnreadView.as_view(), name="mark-unread"),
|
71
|
+
# this view is not a part of the original postman views
|
72
|
+
re_path(pgettext_lazy("postman_url", r"^mark-message/$"), MessageUpdateView.as_view(), name="update-message"),
|
73
|
+
re_path(r"^$", IndexView.as_view()),
|
74
|
+
re_path(pgettext_lazy("postman_url", r"^api/"), include(api_urls, namespace="api")),
|
75
|
+
]
|
df_site/postman/views.py
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
"""Customized postman views."""
|
2
|
+
|
3
|
+
from django.contrib.auth.base_user import AbstractBaseUser
|
4
|
+
from django.core.exceptions import PermissionDenied
|
5
|
+
from django.db.models import Q
|
6
|
+
from django.utils.timezone import now
|
7
|
+
from django.utils.translation import gettext as _
|
8
|
+
from django.views.generic import View
|
9
|
+
from postman.models import Message
|
10
|
+
from postman.views import UpdateDualMixin
|
11
|
+
|
12
|
+
|
13
|
+
class MessageUpdateView(UpdateDualMixin, View):
|
14
|
+
"""View for updating messages or conversations.
|
15
|
+
|
16
|
+
This single view replaces the following views: `ArchiveView`, `DeleteView`,
|
17
|
+
`UndeleteView`, 'MarkReadView', 'MarkUnreadView'.
|
18
|
+
This is required to avoid the onclick attribute in the template (forbidden by CSP).
|
19
|
+
"""
|
20
|
+
|
21
|
+
@property
|
22
|
+
def selected_action(self):
|
23
|
+
"""Get the action to perform on the selected messages or conversations."""
|
24
|
+
action = self.request.POST.get("action")
|
25
|
+
if action not in {"delete", "archive", "undelete", "read", "unread"}:
|
26
|
+
raise PermissionDenied
|
27
|
+
return action
|
28
|
+
|
29
|
+
def _action(self, user: AbstractBaseUser, filter_: Q):
|
30
|
+
"""Perform the action on the selected messages or conversations."""
|
31
|
+
if self.selected_action in {"read", "unread"}:
|
32
|
+
Message.objects.as_recipient(user, filter_).filter(
|
33
|
+
**{f"{self.field_bit}__isnull": bool(self.field_value)}
|
34
|
+
).update(**{self.field_bit: self.field_value})
|
35
|
+
else:
|
36
|
+
super()._action(user, filter_)
|
37
|
+
# an empty set cannot be estimated as an error, it may be just a badly chosen selection
|
38
|
+
|
39
|
+
@property
|
40
|
+
def field_bit(self):
|
41
|
+
"""Get the field to update."""
|
42
|
+
return {
|
43
|
+
"delete": "deleted_at",
|
44
|
+
"archive": "archived",
|
45
|
+
"undelete": "deleted_at",
|
46
|
+
"read": "read_at",
|
47
|
+
"unread": "read_at",
|
48
|
+
}[self.selected_action]
|
49
|
+
|
50
|
+
@property
|
51
|
+
def success_msg(self):
|
52
|
+
"""Get the success message to display."""
|
53
|
+
return {
|
54
|
+
"delete": _("Messages or conversations successfully deleted."),
|
55
|
+
"archive": _("Messages or conversations successfully archived."),
|
56
|
+
"undelete": _("Messages or conversations successfully recovered."),
|
57
|
+
"read": _("Messages or conversations successfully marked as read."),
|
58
|
+
"unread": _("Messages or conversations successfully marked as unread."),
|
59
|
+
}[self.selected_action]
|
60
|
+
|
61
|
+
@property
|
62
|
+
def field_value(self):
|
63
|
+
"""Get the value to set the field to."""
|
64
|
+
n = now()
|
65
|
+
return {"delete": n, "archive": True, "undelete": None, "read": n, "unread": None}[self.selected_action]
|
File without changes
|