arthexis 0.1.17__py3-none-any.whl → 0.1.19__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.17.dist-info → arthexis-0.1.19.dist-info}/METADATA +37 -10
- {arthexis-0.1.17.dist-info → arthexis-0.1.19.dist-info}/RECORD +35 -34
- config/middleware.py +47 -1
- config/settings.py +2 -5
- config/urls.py +5 -0
- core/admin.py +1 -1
- core/models.py +31 -1
- core/system.py +125 -0
- core/tasks.py +0 -22
- core/tests.py +9 -0
- core/views.py +87 -19
- nodes/admin.py +1 -2
- nodes/models.py +18 -23
- nodes/tests.py +42 -34
- nodes/urls.py +0 -1
- nodes/views.py +2 -15
- ocpp/admin.py +23 -2
- ocpp/consumers.py +63 -19
- ocpp/models.py +7 -0
- ocpp/store.py +6 -4
- ocpp/test_rfid.py +70 -0
- ocpp/tests.py +107 -1
- ocpp/views.py +84 -10
- pages/admin.py +150 -15
- pages/apps.py +3 -0
- pages/context_processors.py +11 -0
- pages/middleware.py +3 -0
- pages/models.py +35 -0
- pages/site_config.py +137 -0
- pages/tests.py +352 -30
- pages/urls.py +2 -1
- pages/views.py +70 -23
- {arthexis-0.1.17.dist-info → arthexis-0.1.19.dist-info}/WHEEL +0 -0
- {arthexis-0.1.17.dist-info → arthexis-0.1.19.dist-info}/licenses/LICENSE +0 -0
- {arthexis-0.1.17.dist-info → arthexis-0.1.19.dist-info}/top_level.txt +0 -0
pages/models.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import base64
|
|
1
2
|
import logging
|
|
2
3
|
from pathlib import Path
|
|
3
4
|
|
|
@@ -212,6 +213,7 @@ class Landing(Entity):
|
|
|
212
213
|
path = models.CharField(max_length=200)
|
|
213
214
|
label = models.CharField(max_length=100)
|
|
214
215
|
enabled = models.BooleanField(default=True)
|
|
216
|
+
track_leads = models.BooleanField(default=False)
|
|
215
217
|
description = models.TextField(blank=True)
|
|
216
218
|
|
|
217
219
|
objects = LandingManager()
|
|
@@ -435,6 +437,39 @@ class UserManual(Entity):
|
|
|
435
437
|
def natural_key(self): # pragma: no cover - simple representation
|
|
436
438
|
return (self.slug,)
|
|
437
439
|
|
|
440
|
+
def _ensure_pdf_is_base64(self) -> None:
|
|
441
|
+
"""Normalize ``content_pdf`` so stored values are base64 strings."""
|
|
442
|
+
|
|
443
|
+
value = self.content_pdf
|
|
444
|
+
if value in {None, ""}:
|
|
445
|
+
self.content_pdf = "" if value is None else value
|
|
446
|
+
return
|
|
447
|
+
|
|
448
|
+
if isinstance(value, (bytes, bytearray, memoryview)):
|
|
449
|
+
self.content_pdf = base64.b64encode(bytes(value)).decode("ascii")
|
|
450
|
+
return
|
|
451
|
+
|
|
452
|
+
reader = getattr(value, "read", None)
|
|
453
|
+
if callable(reader):
|
|
454
|
+
data = reader()
|
|
455
|
+
if hasattr(value, "seek"):
|
|
456
|
+
try:
|
|
457
|
+
value.seek(0)
|
|
458
|
+
except Exception: # pragma: no cover - best effort reset
|
|
459
|
+
pass
|
|
460
|
+
self.content_pdf = base64.b64encode(data).decode("ascii")
|
|
461
|
+
return
|
|
462
|
+
|
|
463
|
+
if isinstance(value, str):
|
|
464
|
+
stripped = value.strip()
|
|
465
|
+
if stripped.startswith("data:"):
|
|
466
|
+
_, _, encoded = stripped.partition(",")
|
|
467
|
+
self.content_pdf = encoded.strip()
|
|
468
|
+
|
|
469
|
+
def save(self, *args, **kwargs):
|
|
470
|
+
self._ensure_pdf_is_base64()
|
|
471
|
+
super().save(*args, **kwargs)
|
|
472
|
+
|
|
438
473
|
|
|
439
474
|
class ViewHistory(Entity):
|
|
440
475
|
"""Record of public site visits."""
|
pages/site_config.py
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Customizations for :mod:`django.contrib.sites`."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from django.apps import apps
|
|
10
|
+
from django.conf import settings
|
|
11
|
+
from django.contrib.sites.models import Site
|
|
12
|
+
from django.db import DatabaseError, models
|
|
13
|
+
from django.db.models.signals import post_delete, post_migrate, post_save
|
|
14
|
+
from django.dispatch import receiver
|
|
15
|
+
from django.utils.translation import gettext_lazy as _
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
_FIELD_DEFINITIONS: tuple[tuple[str, models.Field], ...] = (
|
|
22
|
+
(
|
|
23
|
+
"managed",
|
|
24
|
+
models.BooleanField(
|
|
25
|
+
default=False,
|
|
26
|
+
db_default=False,
|
|
27
|
+
verbose_name=_("Managed by local NGINX"),
|
|
28
|
+
help_text=_(
|
|
29
|
+
"Include this site when staging the local NGINX configuration."
|
|
30
|
+
),
|
|
31
|
+
),
|
|
32
|
+
),
|
|
33
|
+
(
|
|
34
|
+
"require_https",
|
|
35
|
+
models.BooleanField(
|
|
36
|
+
default=False,
|
|
37
|
+
db_default=False,
|
|
38
|
+
verbose_name=_("Require HTTPS"),
|
|
39
|
+
help_text=_(
|
|
40
|
+
"Redirect HTTP traffic to HTTPS when the staged NGINX configuration is applied."
|
|
41
|
+
),
|
|
42
|
+
),
|
|
43
|
+
),
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _sites_config_path() -> Path:
|
|
48
|
+
return Path(settings.BASE_DIR) / "scripts" / "generated" / "nginx-sites.json"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _ensure_directories(path: Path) -> bool:
|
|
52
|
+
try:
|
|
53
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
54
|
+
except OSError as exc: # pragma: no cover - filesystem errors
|
|
55
|
+
logger.warning("Unable to create directory for %s: %s", path, exc)
|
|
56
|
+
return False
|
|
57
|
+
return True
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def update_local_nginx_scripts() -> None:
|
|
61
|
+
"""Serialize managed site configuration for the network setup script."""
|
|
62
|
+
|
|
63
|
+
SiteModel = apps.get_model("sites", "Site")
|
|
64
|
+
data: list[dict[str, object]] = []
|
|
65
|
+
seen_domains: set[str] = set()
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
sites = list(
|
|
69
|
+
SiteModel.objects.filter(managed=True)
|
|
70
|
+
.only("domain", "require_https")
|
|
71
|
+
.order_by("domain")
|
|
72
|
+
)
|
|
73
|
+
except DatabaseError: # pragma: no cover - database not ready
|
|
74
|
+
return
|
|
75
|
+
|
|
76
|
+
for site in sites:
|
|
77
|
+
domain = (site.domain or "").strip()
|
|
78
|
+
if not domain:
|
|
79
|
+
continue
|
|
80
|
+
if domain.lower() in seen_domains:
|
|
81
|
+
continue
|
|
82
|
+
seen_domains.add(domain.lower())
|
|
83
|
+
data.append({"domain": domain, "require_https": bool(site.require_https)})
|
|
84
|
+
|
|
85
|
+
output_path = _sites_config_path()
|
|
86
|
+
if not _ensure_directories(output_path):
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
if data:
|
|
90
|
+
try:
|
|
91
|
+
output_path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
|
|
92
|
+
except OSError as exc: # pragma: no cover - filesystem errors
|
|
93
|
+
logger.warning("Failed to write managed site configuration: %s", exc)
|
|
94
|
+
else:
|
|
95
|
+
try:
|
|
96
|
+
output_path.unlink()
|
|
97
|
+
except FileNotFoundError:
|
|
98
|
+
pass
|
|
99
|
+
except OSError as exc: # pragma: no cover - filesystem errors
|
|
100
|
+
logger.warning("Failed to remove managed site configuration: %s", exc)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _install_fields() -> None:
|
|
104
|
+
for name, field in _FIELD_DEFINITIONS:
|
|
105
|
+
if hasattr(Site, name):
|
|
106
|
+
continue
|
|
107
|
+
Site.add_to_class(name, field.clone())
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def ensure_site_fields() -> None:
|
|
111
|
+
"""Ensure the custom ``Site`` fields are installed."""
|
|
112
|
+
|
|
113
|
+
_install_fields()
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@receiver(post_save, sender=Site, dispatch_uid="pages_site_save_update_nginx")
|
|
117
|
+
def _site_saved(sender, **kwargs) -> None: # pragma: no cover - signal wrapper
|
|
118
|
+
update_local_nginx_scripts()
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@receiver(post_delete, sender=Site, dispatch_uid="pages_site_delete_update_nginx")
|
|
122
|
+
def _site_deleted(sender, **kwargs) -> None: # pragma: no cover - signal wrapper
|
|
123
|
+
update_local_nginx_scripts()
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _run_post_migrate_update(**kwargs) -> None: # pragma: no cover - signal wrapper
|
|
127
|
+
update_local_nginx_scripts()
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def ready() -> None:
|
|
131
|
+
"""Apply customizations and connect signal handlers."""
|
|
132
|
+
|
|
133
|
+
ensure_site_fields()
|
|
134
|
+
post_migrate.connect(
|
|
135
|
+
_run_post_migrate_update,
|
|
136
|
+
dispatch_uid="pages_site_post_migrate_update",
|
|
137
|
+
)
|