arthexis 0.1.10__py3-none-any.whl → 0.1.11__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.
Potentially problematic release.
This version of arthexis might be problematic. Click here for more details.
- {arthexis-0.1.10.dist-info → arthexis-0.1.11.dist-info}/METADATA +36 -26
- {arthexis-0.1.10.dist-info → arthexis-0.1.11.dist-info}/RECORD +42 -38
- config/context_processors.py +1 -0
- config/settings.py +24 -3
- config/urls.py +5 -4
- core/admin.py +184 -22
- core/apps.py +27 -2
- core/backends.py +38 -0
- core/environment.py +23 -5
- core/mailer.py +3 -1
- core/models.py +270 -31
- core/reference_utils.py +19 -8
- core/sigil_builder.py +7 -2
- core/sigil_resolver.py +35 -4
- core/system.py +247 -1
- core/temp_passwords.py +181 -0
- core/test_system_info.py +62 -2
- core/tests.py +105 -3
- core/user_data.py +51 -8
- core/views.py +245 -8
- nodes/admin.py +137 -2
- nodes/backends.py +21 -6
- nodes/dns.py +203 -0
- nodes/models.py +293 -7
- nodes/tests.py +312 -2
- nodes/views.py +14 -0
- ocpp/consumers.py +11 -8
- ocpp/models.py +3 -0
- ocpp/reference_utils.py +42 -0
- ocpp/test_rfid.py +169 -7
- ocpp/tests.py +30 -0
- ocpp/views.py +8 -0
- pages/admin.py +9 -1
- pages/context_processors.py +6 -6
- pages/defaults.py +14 -0
- pages/models.py +53 -14
- pages/tests.py +19 -4
- pages/urls.py +3 -0
- pages/views.py +86 -19
- {arthexis-0.1.10.dist-info → arthexis-0.1.11.dist-info}/WHEEL +0 -0
- {arthexis-0.1.10.dist-info → arthexis-0.1.11.dist-info}/licenses/LICENSE +0 -0
- {arthexis-0.1.10.dist-info → arthexis-0.1.11.dist-info}/top_level.txt +0 -0
pages/views.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import base64
|
|
2
2
|
import logging
|
|
3
3
|
from pathlib import Path
|
|
4
|
+
from types import SimpleNamespace
|
|
4
5
|
import datetime
|
|
5
6
|
import calendar
|
|
6
7
|
import io
|
|
@@ -19,9 +20,10 @@ from django import forms
|
|
|
19
20
|
from django.apps import apps as django_apps
|
|
20
21
|
from utils.sites import get_site
|
|
21
22
|
from django.http import Http404, HttpResponse
|
|
22
|
-
from django.shortcuts import redirect, render
|
|
23
|
+
from django.shortcuts import get_object_or_404, redirect, render
|
|
23
24
|
from nodes.models import Node
|
|
24
25
|
from django.template.response import TemplateResponse
|
|
26
|
+
from django.test import RequestFactory
|
|
25
27
|
from django.urls import NoReverseMatch, reverse
|
|
26
28
|
from django.utils import timezone
|
|
27
29
|
from django.utils.encoding import force_bytes, force_str
|
|
@@ -47,13 +49,37 @@ except ImportError: # pragma: no cover - handled gracefully in views
|
|
|
47
49
|
CalledProcessError = ExecutableNotFound = None
|
|
48
50
|
|
|
49
51
|
import markdown
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
MARKDOWN_EXTENSIONS = ["toc", "tables", "mdx_truly_sane_lists"]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _render_markdown_with_toc(text: str) -> tuple[str, str]:
|
|
58
|
+
"""Render ``text`` to HTML and return the HTML and stripped TOC."""
|
|
59
|
+
|
|
60
|
+
md = markdown.Markdown(extensions=MARKDOWN_EXTENSIONS)
|
|
61
|
+
html = md.convert(text)
|
|
62
|
+
toc_html = md.toc
|
|
63
|
+
toc_html = _strip_toc_wrapper(toc_html)
|
|
64
|
+
return html, toc_html
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _strip_toc_wrapper(toc_html: str) -> str:
|
|
68
|
+
"""Normalize ``markdown``'s TOC output by removing the wrapper ``div``."""
|
|
69
|
+
|
|
70
|
+
toc_html = toc_html.strip()
|
|
71
|
+
if toc_html.startswith('<div class="toc">'):
|
|
72
|
+
toc_html = toc_html[len('<div class="toc">') :]
|
|
73
|
+
if toc_html.endswith("</div>"):
|
|
74
|
+
toc_html = toc_html[: -len("</div>")]
|
|
75
|
+
return toc_html.strip()
|
|
50
76
|
from pages.utils import landing
|
|
51
77
|
from core.liveupdate import live_update
|
|
52
78
|
from django_otp import login as otp_login
|
|
53
79
|
from django_otp.plugins.otp_totp.models import TOTPDevice
|
|
54
80
|
import qrcode
|
|
55
81
|
from .forms import AuthenticatorEnrollmentForm, AuthenticatorLoginForm
|
|
56
|
-
from .models import Module
|
|
82
|
+
from .models import Module, UserManual
|
|
57
83
|
|
|
58
84
|
|
|
59
85
|
logger = logging.getLogger(__name__)
|
|
@@ -407,14 +433,7 @@ def index(request):
|
|
|
407
433
|
candidates.append(root_base / "README.md")
|
|
408
434
|
readme_file = next((p for p in candidates if p.exists()), root_base / "README.md")
|
|
409
435
|
text = readme_file.read_text(encoding="utf-8")
|
|
410
|
-
|
|
411
|
-
html = md.convert(text)
|
|
412
|
-
toc_html = md.toc
|
|
413
|
-
if toc_html.strip().startswith('<div class="toc">'):
|
|
414
|
-
toc_html = toc_html.strip()[len('<div class="toc">') :]
|
|
415
|
-
if toc_html.endswith("</div>"):
|
|
416
|
-
toc_html = toc_html[: -len("</div>")]
|
|
417
|
-
toc_html = toc_html.strip()
|
|
436
|
+
html, toc_html = _render_markdown_with_toc(text)
|
|
418
437
|
title = "README" if readme_file.name.startswith("README") else readme_file.stem
|
|
419
438
|
context = {"content": html, "title": title, "toc": toc_html}
|
|
420
439
|
response = render(request, "pages/readme.html", context)
|
|
@@ -447,14 +466,7 @@ def release_checklist(request):
|
|
|
447
466
|
if not file_path.exists():
|
|
448
467
|
raise Http404("Release checklist not found")
|
|
449
468
|
text = file_path.read_text(encoding="utf-8")
|
|
450
|
-
|
|
451
|
-
html = md.convert(text)
|
|
452
|
-
toc_html = md.toc
|
|
453
|
-
if toc_html.strip().startswith('<div class="toc">'):
|
|
454
|
-
toc_html = toc_html.strip()[len('<div class="toc">') :]
|
|
455
|
-
if toc_html.endswith("</div>"):
|
|
456
|
-
toc_html = toc_html[: -len("</div>")]
|
|
457
|
-
toc_html = toc_html.strip()
|
|
469
|
+
html, toc_html = _render_markdown_with_toc(text)
|
|
458
470
|
context = {"content": html, "title": "Release Checklist", "toc": toc_html}
|
|
459
471
|
response = render(request, "pages/readme.html", context)
|
|
460
472
|
patch_vary_headers(response, ["Accept-Language", "Cookie"])
|
|
@@ -709,11 +721,14 @@ def request_invite(request):
|
|
|
709
721
|
try:
|
|
710
722
|
node_error = None
|
|
711
723
|
node = Node.get_local()
|
|
724
|
+
outbox = getattr(node, "email_outbox", None) if node else None
|
|
712
725
|
if node:
|
|
713
726
|
try:
|
|
714
727
|
result = node.send_mail(subject, body, [email])
|
|
728
|
+
lead.sent_via_outbox = outbox
|
|
715
729
|
except Exception as exc:
|
|
716
730
|
node_error = exc
|
|
731
|
+
lead.sent_via_outbox = None
|
|
717
732
|
logger.exception(
|
|
718
733
|
"Node send_mail failed, falling back to default backend"
|
|
719
734
|
)
|
|
@@ -724,6 +739,7 @@ def request_invite(request):
|
|
|
724
739
|
result = mailer.send(
|
|
725
740
|
subject, body, [email], settings.DEFAULT_FROM_EMAIL
|
|
726
741
|
)
|
|
742
|
+
lead.sent_via_outbox = None
|
|
727
743
|
lead.sent_on = timezone.now()
|
|
728
744
|
if node_error:
|
|
729
745
|
lead.error = (
|
|
@@ -738,9 +754,10 @@ def request_invite(request):
|
|
|
738
754
|
)
|
|
739
755
|
except Exception as exc:
|
|
740
756
|
lead.error = f"{exc}. Ensure the email service is reachable and settings are correct."
|
|
757
|
+
lead.sent_via_outbox = None
|
|
741
758
|
logger.exception("Failed to send invitation email to %s", email)
|
|
742
759
|
if lead.sent_on or lead.error:
|
|
743
|
-
lead.save(update_fields=["sent_on", "error"])
|
|
760
|
+
lead.save(update_fields=["sent_on", "error", "sent_via_outbox"])
|
|
744
761
|
sent = True
|
|
745
762
|
return render(request, "pages/request_invite.html", {"form": form, "sent": sent})
|
|
746
763
|
|
|
@@ -1002,3 +1019,53 @@ def csrf_failure(request, reason=""):
|
|
|
1002
1019
|
"""Custom CSRF failure view with a friendly message."""
|
|
1003
1020
|
logger.warning("CSRF failure on %s: %s", request.path, reason)
|
|
1004
1021
|
return render(request, "pages/csrf_failure.html", status=403)
|
|
1022
|
+
|
|
1023
|
+
|
|
1024
|
+
def _admin_context(request):
|
|
1025
|
+
context = admin.site.each_context(request)
|
|
1026
|
+
if not context.get("has_permission"):
|
|
1027
|
+
rf = RequestFactory()
|
|
1028
|
+
mock_request = rf.get(request.path)
|
|
1029
|
+
mock_request.user = SimpleNamespace(
|
|
1030
|
+
is_active=True,
|
|
1031
|
+
is_staff=True,
|
|
1032
|
+
is_superuser=True,
|
|
1033
|
+
has_perm=lambda perm, obj=None: True,
|
|
1034
|
+
has_module_perms=lambda app_label: True,
|
|
1035
|
+
)
|
|
1036
|
+
context["available_apps"] = admin.site.get_app_list(mock_request)
|
|
1037
|
+
context["has_permission"] = True
|
|
1038
|
+
return context
|
|
1039
|
+
|
|
1040
|
+
|
|
1041
|
+
def admin_manual_list(request):
|
|
1042
|
+
manuals = UserManual.objects.order_by("title")
|
|
1043
|
+
context = _admin_context(request)
|
|
1044
|
+
context["manuals"] = manuals
|
|
1045
|
+
return render(request, "admin_doc/manuals.html", context)
|
|
1046
|
+
|
|
1047
|
+
|
|
1048
|
+
def admin_manual_detail(request, slug):
|
|
1049
|
+
manual = get_object_or_404(UserManual, slug=slug)
|
|
1050
|
+
context = _admin_context(request)
|
|
1051
|
+
context["manual"] = manual
|
|
1052
|
+
return render(request, "admin_doc/manual_detail.html", context)
|
|
1053
|
+
|
|
1054
|
+
|
|
1055
|
+
def manual_pdf(request, slug):
|
|
1056
|
+
manual = get_object_or_404(UserManual, slug=slug)
|
|
1057
|
+
pdf_data = base64.b64decode(manual.content_pdf)
|
|
1058
|
+
response = HttpResponse(pdf_data, content_type="application/pdf")
|
|
1059
|
+
response["Content-Disposition"] = f'attachment; filename="{manual.slug}.pdf"'
|
|
1060
|
+
return response
|
|
1061
|
+
|
|
1062
|
+
|
|
1063
|
+
@landing(_("Manuals"))
|
|
1064
|
+
def manual_list(request):
|
|
1065
|
+
manuals = UserManual.objects.order_by("title")
|
|
1066
|
+
return render(request, "pages/manual_list.html", {"manuals": manuals})
|
|
1067
|
+
|
|
1068
|
+
|
|
1069
|
+
def manual_detail(request, slug):
|
|
1070
|
+
manual = get_object_or_404(UserManual, slug=slug)
|
|
1071
|
+
return render(request, "pages/manual_detail.html", {"manual": manual})
|
|
File without changes
|
|
File without changes
|
|
File without changes
|