arthexis 0.1.18__py3-none-any.whl → 0.1.20__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.
- {arthexis-0.1.18.dist-info → arthexis-0.1.20.dist-info}/METADATA +39 -12
- {arthexis-0.1.18.dist-info → arthexis-0.1.20.dist-info}/RECORD +44 -44
- config/settings.py +1 -5
- core/admin.py +142 -1
- core/backends.py +8 -2
- core/environment.py +221 -4
- core/models.py +124 -25
- core/notifications.py +1 -1
- core/reference_utils.py +10 -11
- core/sigil_builder.py +2 -2
- core/system.py +125 -0
- core/tasks.py +24 -23
- core/tests.py +1 -0
- core/views.py +105 -40
- nodes/admin.py +134 -3
- nodes/models.py +310 -69
- nodes/rfid_sync.py +1 -1
- nodes/tasks.py +100 -2
- nodes/tests.py +573 -48
- nodes/urls.py +4 -1
- nodes/views.py +498 -106
- ocpp/admin.py +124 -5
- ocpp/consumers.py +106 -9
- ocpp/models.py +90 -1
- ocpp/store.py +6 -4
- ocpp/tasks.py +4 -0
- ocpp/test_export_import.py +1 -0
- ocpp/test_rfid.py +3 -1
- ocpp/tests.py +114 -10
- ocpp/transactions_io.py +9 -1
- ocpp/urls.py +3 -3
- ocpp/views.py +166 -40
- pages/admin.py +63 -10
- pages/context_processors.py +26 -9
- pages/defaults.py +1 -1
- pages/middleware.py +3 -0
- pages/models.py +35 -0
- pages/module_defaults.py +5 -5
- pages/tests.py +280 -65
- pages/urls.py +3 -1
- pages/views.py +176 -29
- {arthexis-0.1.18.dist-info → arthexis-0.1.20.dist-info}/WHEEL +0 -0
- {arthexis-0.1.18.dist-info → arthexis-0.1.20.dist-info}/licenses/LICENSE +0 -0
- {arthexis-0.1.18.dist-info → arthexis-0.1.20.dist-info}/top_level.txt +0 -0
pages/views.py
CHANGED
|
@@ -439,43 +439,133 @@ def admin_model_graph(request, app_label: str):
|
|
|
439
439
|
return response
|
|
440
440
|
|
|
441
441
|
|
|
442
|
-
def
|
|
442
|
+
def _locate_readme_document(role, doc: str | None, lang: str) -> SimpleNamespace:
|
|
443
443
|
app = (
|
|
444
444
|
Module.objects.filter(node_role=role, is_default=True)
|
|
445
445
|
.select_related("application")
|
|
446
446
|
.first()
|
|
447
447
|
)
|
|
448
448
|
app_slug = app.path.strip("/") if app else ""
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
449
|
+
root_base = Path(settings.BASE_DIR).resolve()
|
|
450
|
+
readme_base = (root_base / app_slug).resolve() if app_slug else root_base
|
|
451
|
+
candidates: list[Path] = []
|
|
452
|
+
|
|
453
|
+
if doc:
|
|
454
|
+
normalized = doc.strip().replace("\\", "/")
|
|
455
|
+
while normalized.startswith("./"):
|
|
456
|
+
normalized = normalized[2:]
|
|
457
|
+
normalized = normalized.lstrip("/")
|
|
458
|
+
if not normalized:
|
|
459
|
+
raise Http404("Document not found")
|
|
460
|
+
doc_path = Path(normalized)
|
|
461
|
+
if doc_path.is_absolute() or any(part == ".." for part in doc_path.parts):
|
|
462
|
+
raise Http404("Document not found")
|
|
463
|
+
|
|
464
|
+
relative_candidates: list[Path] = []
|
|
465
|
+
|
|
466
|
+
def add_candidate(path: Path) -> None:
|
|
467
|
+
if path not in relative_candidates:
|
|
468
|
+
relative_candidates.append(path)
|
|
469
|
+
|
|
470
|
+
add_candidate(doc_path)
|
|
471
|
+
if doc_path.suffix.lower() != ".md" or doc_path.suffix != ".md":
|
|
472
|
+
add_candidate(doc_path.with_suffix(".md"))
|
|
473
|
+
if doc_path.suffix.lower() != ".md":
|
|
474
|
+
add_candidate(doc_path / "README.md")
|
|
475
|
+
|
|
476
|
+
search_roots = [readme_base]
|
|
477
|
+
if readme_base != root_base:
|
|
478
|
+
search_roots.append(root_base)
|
|
479
|
+
|
|
480
|
+
for relative in relative_candidates:
|
|
481
|
+
for base in search_roots:
|
|
482
|
+
base_resolved = base.resolve()
|
|
483
|
+
candidate = (base_resolved / relative).resolve(strict=False)
|
|
484
|
+
try:
|
|
485
|
+
candidate.relative_to(base_resolved)
|
|
486
|
+
except ValueError:
|
|
487
|
+
continue
|
|
488
|
+
candidates.append(candidate)
|
|
489
|
+
else:
|
|
463
490
|
if lang:
|
|
464
|
-
candidates.append(
|
|
491
|
+
candidates.append(readme_base / f"README.{lang}.md")
|
|
465
492
|
short = lang.split("-")[0]
|
|
466
493
|
if short != lang:
|
|
467
|
-
candidates.append(
|
|
468
|
-
candidates.append(
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
494
|
+
candidates.append(readme_base / f"README.{short}.md")
|
|
495
|
+
candidates.append(readme_base / "README.md")
|
|
496
|
+
if readme_base != root_base:
|
|
497
|
+
if lang:
|
|
498
|
+
candidates.append(root_base / f"README.{lang}.md")
|
|
499
|
+
short = lang.split("-")[0]
|
|
500
|
+
if short != lang:
|
|
501
|
+
candidates.append(root_base / f"README.{short}.md")
|
|
502
|
+
candidates.append(root_base / "README.md")
|
|
503
|
+
|
|
504
|
+
readme_file = next((p for p in candidates if p.exists()), None)
|
|
505
|
+
if readme_file is None:
|
|
506
|
+
raise Http404("Document not found")
|
|
507
|
+
|
|
472
508
|
title = "README" if readme_file.name.startswith("README") else readme_file.stem
|
|
473
|
-
|
|
509
|
+
return SimpleNamespace(
|
|
510
|
+
file=readme_file,
|
|
511
|
+
title=title,
|
|
512
|
+
root_base=root_base,
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
def _relative_readme_path(readme_file: Path, root_base: Path) -> str | None:
|
|
517
|
+
try:
|
|
518
|
+
return readme_file.relative_to(root_base).as_posix()
|
|
519
|
+
except ValueError:
|
|
520
|
+
return None
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
def _render_readme(request, role, doc: str | None = None):
|
|
524
|
+
lang = getattr(request, "LANGUAGE_CODE", "")
|
|
525
|
+
lang = lang.replace("_", "-").lower()
|
|
526
|
+
document = _locate_readme_document(role, doc, lang)
|
|
527
|
+
text = document.file.read_text(encoding="utf-8")
|
|
528
|
+
html, toc_html = _render_markdown_with_toc(text)
|
|
529
|
+
relative_path = _relative_readme_path(document.file, document.root_base)
|
|
530
|
+
user = getattr(request, "user", None)
|
|
531
|
+
can_edit = bool(
|
|
532
|
+
relative_path
|
|
533
|
+
and user
|
|
534
|
+
and user.is_authenticated
|
|
535
|
+
and user.is_superuser
|
|
536
|
+
)
|
|
537
|
+
edit_url = None
|
|
538
|
+
if can_edit:
|
|
539
|
+
try:
|
|
540
|
+
edit_url = reverse("pages:readme-edit", kwargs={"doc": relative_path})
|
|
541
|
+
except NoReverseMatch:
|
|
542
|
+
edit_url = None
|
|
543
|
+
context = {
|
|
544
|
+
"content": html,
|
|
545
|
+
"title": document.title,
|
|
546
|
+
"toc": toc_html,
|
|
547
|
+
"page_url": request.build_absolute_uri(),
|
|
548
|
+
"edit_url": edit_url,
|
|
549
|
+
}
|
|
474
550
|
response = render(request, "pages/readme.html", context)
|
|
475
551
|
patch_vary_headers(response, ["Accept-Language", "Cookie"])
|
|
476
552
|
return response
|
|
477
553
|
|
|
478
554
|
|
|
555
|
+
class MarkdownDocumentForm(forms.Form):
|
|
556
|
+
content = forms.CharField(
|
|
557
|
+
widget=forms.Textarea(
|
|
558
|
+
attrs={
|
|
559
|
+
"class": "form-control",
|
|
560
|
+
"rows": 24,
|
|
561
|
+
"spellcheck": "false",
|
|
562
|
+
}
|
|
563
|
+
),
|
|
564
|
+
required=False,
|
|
565
|
+
strip=False,
|
|
566
|
+
)
|
|
567
|
+
|
|
568
|
+
|
|
479
569
|
@landing("Home")
|
|
480
570
|
@never_cache
|
|
481
571
|
def index(request):
|
|
@@ -525,10 +615,61 @@ def index(request):
|
|
|
525
615
|
|
|
526
616
|
|
|
527
617
|
@never_cache
|
|
528
|
-
def readme(request):
|
|
618
|
+
def readme(request, doc=None):
|
|
529
619
|
node = Node.get_local()
|
|
530
620
|
role = node.role if node else None
|
|
531
|
-
return _render_readme(request, role)
|
|
621
|
+
return _render_readme(request, role, doc)
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
def readme_edit(request, doc):
|
|
625
|
+
user = getattr(request, "user", None)
|
|
626
|
+
if not (user and user.is_authenticated and user.is_superuser):
|
|
627
|
+
raise PermissionDenied
|
|
628
|
+
|
|
629
|
+
node = Node.get_local()
|
|
630
|
+
role = node.role if node else None
|
|
631
|
+
lang = getattr(request, "LANGUAGE_CODE", "")
|
|
632
|
+
lang = lang.replace("_", "-").lower()
|
|
633
|
+
document = _locate_readme_document(role, doc, lang)
|
|
634
|
+
relative_path = _relative_readme_path(document.file, document.root_base)
|
|
635
|
+
if relative_path:
|
|
636
|
+
read_url = reverse("pages:readme-document", kwargs={"doc": relative_path})
|
|
637
|
+
else:
|
|
638
|
+
read_url = reverse("pages:readme")
|
|
639
|
+
|
|
640
|
+
if request.method == "POST":
|
|
641
|
+
form = MarkdownDocumentForm(request.POST)
|
|
642
|
+
if form.is_valid():
|
|
643
|
+
content = form.cleaned_data["content"]
|
|
644
|
+
try:
|
|
645
|
+
document.file.write_text(content, encoding="utf-8")
|
|
646
|
+
except OSError:
|
|
647
|
+
logger.exception("Failed to update markdown document %s", document.file)
|
|
648
|
+
messages.error(
|
|
649
|
+
request,
|
|
650
|
+
_("Unable to save changes. Please try again."),
|
|
651
|
+
)
|
|
652
|
+
else:
|
|
653
|
+
messages.success(request, _("Document saved successfully."))
|
|
654
|
+
if relative_path:
|
|
655
|
+
return redirect("pages:readme-edit", doc=relative_path)
|
|
656
|
+
return redirect("pages:readme")
|
|
657
|
+
else:
|
|
658
|
+
try:
|
|
659
|
+
initial_text = document.file.read_text(encoding="utf-8")
|
|
660
|
+
except OSError:
|
|
661
|
+
logger.exception("Failed to read markdown document %s", document.file)
|
|
662
|
+
messages.error(request, _("Unable to load the document for editing."))
|
|
663
|
+
return redirect("pages:readme")
|
|
664
|
+
form = MarkdownDocumentForm(initial={"content": initial_text})
|
|
665
|
+
|
|
666
|
+
context = {
|
|
667
|
+
"form": form,
|
|
668
|
+
"title": document.title,
|
|
669
|
+
"relative_path": relative_path,
|
|
670
|
+
"read_url": read_url,
|
|
671
|
+
}
|
|
672
|
+
return render(request, "pages/readme_edit.html", context)
|
|
532
673
|
|
|
533
674
|
|
|
534
675
|
def sitemap(request):
|
|
@@ -963,23 +1104,24 @@ class ClientReportForm(forms.Form):
|
|
|
963
1104
|
label=_("Month"),
|
|
964
1105
|
required=False,
|
|
965
1106
|
widget=forms.DateInput(attrs={"type": "month"}),
|
|
1107
|
+
input_formats=["%Y-%m"],
|
|
966
1108
|
help_text=_("Generates the report for the calendar month that you select."),
|
|
967
1109
|
)
|
|
968
1110
|
owner = forms.ModelChoiceField(
|
|
969
1111
|
queryset=get_user_model().objects.all(),
|
|
970
1112
|
required=False,
|
|
971
1113
|
help_text=_(
|
|
972
|
-
"Sets who owns the report schedule and is listed as the
|
|
1114
|
+
"Sets who owns the report schedule and is listed as the requester."
|
|
973
1115
|
),
|
|
974
1116
|
)
|
|
975
1117
|
destinations = forms.CharField(
|
|
976
1118
|
label=_("Email destinations"),
|
|
977
1119
|
required=False,
|
|
978
1120
|
widget=forms.Textarea(attrs={"rows": 2}),
|
|
979
|
-
help_text=_("Separate addresses with commas or new lines."),
|
|
1121
|
+
help_text=_("Separate addresses with commas, whitespace, or new lines."),
|
|
980
1122
|
)
|
|
981
1123
|
recurrence = forms.ChoiceField(
|
|
982
|
-
label=_("
|
|
1124
|
+
label=_("Recurrence"),
|
|
983
1125
|
choices=RECURRENCE_CHOICES,
|
|
984
1126
|
initial=ClientReportSchedule.PERIODICITY_NONE,
|
|
985
1127
|
help_text=_("Defines how often the report should be generated automatically."),
|
|
@@ -1006,8 +1148,13 @@ class ClientReportForm(forms.Form):
|
|
|
1006
1148
|
week_str = cleaned.get("week")
|
|
1007
1149
|
if not week_str:
|
|
1008
1150
|
raise forms.ValidationError(_("Please select a week."))
|
|
1009
|
-
|
|
1010
|
-
|
|
1151
|
+
try:
|
|
1152
|
+
year_str, week_num_str = week_str.split("-W", 1)
|
|
1153
|
+
start = datetime.date.fromisocalendar(
|
|
1154
|
+
int(year_str), int(week_num_str), 1
|
|
1155
|
+
)
|
|
1156
|
+
except (TypeError, ValueError):
|
|
1157
|
+
raise forms.ValidationError(_("Please select a week."))
|
|
1011
1158
|
cleaned["start"] = start
|
|
1012
1159
|
cleaned["end"] = start + datetime.timedelta(days=6)
|
|
1013
1160
|
elif period == "month":
|
|
File without changes
|
|
File without changes
|
|
File without changes
|