arthexis 0.1.16__py3-none-any.whl → 0.1.26__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.16.dist-info → arthexis-0.1.26.dist-info}/METADATA +84 -35
- arthexis-0.1.26.dist-info/RECORD +111 -0
- config/asgi.py +1 -15
- config/middleware.py +47 -1
- config/settings.py +15 -30
- config/urls.py +53 -1
- core/admin.py +540 -450
- core/apps.py +0 -6
- core/auto_upgrade.py +19 -4
- core/backends.py +13 -3
- core/changelog.py +66 -5
- core/environment.py +4 -5
- core/models.py +1566 -203
- core/notifications.py +1 -1
- core/reference_utils.py +10 -11
- core/release.py +55 -7
- core/sigil_builder.py +2 -2
- core/sigil_resolver.py +1 -66
- core/system.py +268 -2
- core/tasks.py +174 -48
- core/tests.py +314 -16
- core/user_data.py +42 -2
- core/views.py +278 -183
- nodes/admin.py +557 -65
- nodes/apps.py +11 -0
- nodes/models.py +658 -113
- nodes/rfid_sync.py +1 -1
- nodes/tasks.py +97 -2
- nodes/tests.py +1212 -116
- nodes/urls.py +15 -1
- nodes/utils.py +51 -3
- nodes/views.py +1239 -154
- ocpp/admin.py +979 -152
- ocpp/consumers.py +268 -28
- ocpp/models.py +488 -3
- ocpp/network.py +398 -0
- ocpp/store.py +6 -4
- ocpp/tasks.py +296 -2
- ocpp/test_export_import.py +1 -0
- ocpp/test_rfid.py +121 -4
- ocpp/tests.py +950 -11
- ocpp/transactions_io.py +9 -1
- ocpp/urls.py +3 -3
- ocpp/views.py +596 -51
- pages/admin.py +262 -30
- pages/apps.py +35 -0
- pages/context_processors.py +26 -21
- pages/defaults.py +1 -1
- pages/forms.py +31 -8
- pages/middleware.py +6 -2
- pages/models.py +77 -2
- pages/module_defaults.py +5 -5
- pages/site_config.py +137 -0
- pages/tests.py +885 -109
- pages/urls.py +13 -2
- pages/utils.py +70 -0
- pages/views.py +558 -55
- arthexis-0.1.16.dist-info/RECORD +0 -111
- core/workgroup_urls.py +0 -17
- core/workgroup_views.py +0 -94
- {arthexis-0.1.16.dist-info → arthexis-0.1.26.dist-info}/WHEEL +0 -0
- {arthexis-0.1.16.dist-info → arthexis-0.1.26.dist-info}/licenses/LICENSE +0 -0
- {arthexis-0.1.16.dist-info → arthexis-0.1.26.dist-info}/top_level.txt +0 -0
core/tasks.py
CHANGED
|
@@ -2,24 +2,51 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
4
|
import shutil
|
|
5
|
+
import re
|
|
5
6
|
import subprocess
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
import urllib.error
|
|
8
9
|
import urllib.request
|
|
9
10
|
|
|
10
11
|
from celery import shared_task
|
|
11
|
-
from django.conf import settings
|
|
12
|
-
from django.contrib.auth import get_user_model
|
|
13
|
-
from core import mailer
|
|
14
12
|
from core import github_issues
|
|
13
|
+
from django.db import DatabaseError
|
|
15
14
|
from django.utils import timezone
|
|
16
15
|
|
|
17
|
-
from nodes.models import NetMessage
|
|
18
|
-
|
|
19
16
|
|
|
20
17
|
AUTO_UPGRADE_HEALTH_DELAY_SECONDS = 30
|
|
21
18
|
AUTO_UPGRADE_SKIP_LOCK_NAME = "auto_upgrade_skip_revisions.lck"
|
|
22
19
|
|
|
20
|
+
SEVERITY_NORMAL = "normal"
|
|
21
|
+
SEVERITY_LOW = "low"
|
|
22
|
+
SEVERITY_CRITICAL = "critical"
|
|
23
|
+
|
|
24
|
+
_PackageReleaseModel = None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _get_package_release_model():
|
|
28
|
+
"""Return the :class:`core.models.PackageRelease` model when available."""
|
|
29
|
+
|
|
30
|
+
global _PackageReleaseModel
|
|
31
|
+
|
|
32
|
+
if _PackageReleaseModel is not None:
|
|
33
|
+
return _PackageReleaseModel
|
|
34
|
+
|
|
35
|
+
try:
|
|
36
|
+
from core.models import PackageRelease # noqa: WPS433 - runtime import
|
|
37
|
+
except Exception: # pragma: no cover - app registry not ready
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
_PackageReleaseModel = PackageRelease
|
|
41
|
+
return PackageRelease
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
model = _get_package_release_model()
|
|
45
|
+
if model is not None: # pragma: no branch - runtime constant setup
|
|
46
|
+
SEVERITY_NORMAL = model.Severity.NORMAL
|
|
47
|
+
SEVERITY_LOW = model.Severity.LOW
|
|
48
|
+
SEVERITY_CRITICAL = model.Severity.CRITICAL
|
|
49
|
+
|
|
23
50
|
|
|
24
51
|
logger = logging.getLogger(__name__)
|
|
25
52
|
|
|
@@ -30,23 +57,6 @@ def heartbeat() -> None:
|
|
|
30
57
|
logger.info("Heartbeat task executed")
|
|
31
58
|
|
|
32
59
|
|
|
33
|
-
@shared_task
|
|
34
|
-
def birthday_greetings() -> None:
|
|
35
|
-
"""Send birthday greetings to users via Net Message and email."""
|
|
36
|
-
User = get_user_model()
|
|
37
|
-
today = timezone.localdate()
|
|
38
|
-
for user in User.objects.filter(birthday=today):
|
|
39
|
-
NetMessage.broadcast("Happy bday!", user.username)
|
|
40
|
-
if user.email:
|
|
41
|
-
mailer.send(
|
|
42
|
-
"Happy bday!",
|
|
43
|
-
f"Happy bday! {user.username}",
|
|
44
|
-
[user.email],
|
|
45
|
-
settings.DEFAULT_FROM_EMAIL,
|
|
46
|
-
fail_silently=True,
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
|
|
50
60
|
def _auto_upgrade_log_path(base_dir: Path) -> Path:
|
|
51
61
|
"""Return the log file used for auto-upgrade events."""
|
|
52
62
|
|
|
@@ -67,6 +77,65 @@ def _append_auto_upgrade_log(base_dir: Path, message: str) -> None:
|
|
|
67
77
|
logger.warning("Failed to append auto-upgrade log entry: %s", message)
|
|
68
78
|
|
|
69
79
|
|
|
80
|
+
def _resolve_release_severity(version: str | None) -> str:
|
|
81
|
+
"""Return the stored severity for *version*, defaulting to normal."""
|
|
82
|
+
|
|
83
|
+
if not version:
|
|
84
|
+
return SEVERITY_NORMAL
|
|
85
|
+
|
|
86
|
+
model = _get_package_release_model()
|
|
87
|
+
if model is None:
|
|
88
|
+
return SEVERITY_NORMAL
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
queryset = model.objects.filter(version=version)
|
|
92
|
+
release = (
|
|
93
|
+
queryset.filter(package__is_active=True).first() or queryset.first()
|
|
94
|
+
)
|
|
95
|
+
except DatabaseError: # pragma: no cover - depends on DB availability
|
|
96
|
+
return SEVERITY_NORMAL
|
|
97
|
+
|
|
98
|
+
if not release:
|
|
99
|
+
return SEVERITY_NORMAL
|
|
100
|
+
|
|
101
|
+
severity = getattr(release, "severity", None)
|
|
102
|
+
if not severity:
|
|
103
|
+
return SEVERITY_NORMAL
|
|
104
|
+
return severity
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _read_local_version(base_dir: Path) -> str | None:
|
|
108
|
+
"""Return the local VERSION file contents when readable."""
|
|
109
|
+
|
|
110
|
+
version_path = base_dir / "VERSION"
|
|
111
|
+
if not version_path.exists():
|
|
112
|
+
return None
|
|
113
|
+
try:
|
|
114
|
+
return version_path.read_text().strip()
|
|
115
|
+
except OSError: # pragma: no cover - filesystem error
|
|
116
|
+
return None
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _read_remote_version(base_dir: Path, branch: str) -> str | None:
|
|
120
|
+
"""Return the VERSION file from ``origin/<branch>`` when available."""
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
return (
|
|
124
|
+
subprocess.check_output(
|
|
125
|
+
[
|
|
126
|
+
"git",
|
|
127
|
+
"show",
|
|
128
|
+
f"origin/{branch}:VERSION",
|
|
129
|
+
],
|
|
130
|
+
cwd=base_dir,
|
|
131
|
+
)
|
|
132
|
+
.decode()
|
|
133
|
+
.strip()
|
|
134
|
+
)
|
|
135
|
+
except subprocess.CalledProcessError: # pragma: no cover - git failure
|
|
136
|
+
return None
|
|
137
|
+
|
|
138
|
+
|
|
70
139
|
def _skip_lock_path(base_dir: Path) -> Path:
|
|
71
140
|
return base_dir / "locks" / AUTO_UPGRADE_SKIP_LOCK_NAME
|
|
72
141
|
|
|
@@ -124,6 +193,21 @@ def _resolve_service_url(base_dir: Path) -> str:
|
|
|
124
193
|
return f"http://127.0.0.1:{port}/"
|
|
125
194
|
|
|
126
195
|
|
|
196
|
+
def _parse_major_minor(version: str) -> tuple[int, int] | None:
|
|
197
|
+
match = re.match(r"^\s*(\d+)\.(\d+)", version)
|
|
198
|
+
if not match:
|
|
199
|
+
return None
|
|
200
|
+
return int(match.group(1)), int(match.group(2))
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _shares_stable_series(local: str, remote: str) -> bool:
|
|
204
|
+
local_parts = _parse_major_minor(local)
|
|
205
|
+
remote_parts = _parse_major_minor(remote)
|
|
206
|
+
if not local_parts or not remote_parts:
|
|
207
|
+
return False
|
|
208
|
+
return local_parts == remote_parts
|
|
209
|
+
|
|
210
|
+
|
|
127
211
|
@shared_task
|
|
128
212
|
def check_github_updates() -> None:
|
|
129
213
|
"""Check the GitHub repo for updates and upgrade if needed."""
|
|
@@ -179,48 +263,71 @@ def check_github_updates() -> None:
|
|
|
179
263
|
startup()
|
|
180
264
|
return
|
|
181
265
|
|
|
266
|
+
remote_version = _read_remote_version(base_dir, branch)
|
|
267
|
+
local_version = _read_local_version(base_dir)
|
|
268
|
+
remote_severity = _resolve_release_severity(remote_version)
|
|
269
|
+
|
|
182
270
|
upgrade_stamp = timezone.now().strftime("@ %Y%m%d %H:%M")
|
|
183
271
|
|
|
184
272
|
upgrade_was_applied = False
|
|
185
273
|
|
|
186
274
|
if mode == "latest":
|
|
187
|
-
|
|
275
|
+
local_revision = (
|
|
188
276
|
subprocess.check_output(["git", "rev-parse", branch], cwd=base_dir)
|
|
189
277
|
.decode()
|
|
190
278
|
.strip()
|
|
191
279
|
)
|
|
192
|
-
if
|
|
280
|
+
if local_revision == remote_revision:
|
|
193
281
|
if startup:
|
|
194
282
|
startup()
|
|
195
283
|
return
|
|
284
|
+
|
|
285
|
+
if (
|
|
286
|
+
remote_version
|
|
287
|
+
and local_version
|
|
288
|
+
and remote_version != local_version
|
|
289
|
+
and remote_severity == SEVERITY_LOW
|
|
290
|
+
and _shares_stable_series(local_version, remote_version)
|
|
291
|
+
):
|
|
292
|
+
_append_auto_upgrade_log(
|
|
293
|
+
base_dir,
|
|
294
|
+
f"Skipping auto-upgrade for low severity patch {remote_version}",
|
|
295
|
+
)
|
|
296
|
+
if startup:
|
|
297
|
+
startup()
|
|
298
|
+
return
|
|
299
|
+
|
|
196
300
|
if notify:
|
|
197
301
|
notify("Upgrading...", upgrade_stamp)
|
|
198
302
|
args = ["./upgrade.sh", "--latest", "--no-restart"]
|
|
199
303
|
upgrade_was_applied = True
|
|
200
304
|
else:
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
remote = (
|
|
206
|
-
subprocess.check_output(
|
|
207
|
-
[
|
|
208
|
-
"git",
|
|
209
|
-
"show",
|
|
210
|
-
f"origin/{branch}:VERSION",
|
|
211
|
-
],
|
|
212
|
-
cwd=base_dir,
|
|
213
|
-
)
|
|
214
|
-
.decode()
|
|
215
|
-
.strip()
|
|
216
|
-
)
|
|
217
|
-
if local == remote:
|
|
305
|
+
local_value = local_version or "0"
|
|
306
|
+
remote_value = remote_version or local_value
|
|
307
|
+
|
|
308
|
+
if local_value == remote_value:
|
|
218
309
|
if startup:
|
|
219
310
|
startup()
|
|
220
311
|
return
|
|
312
|
+
|
|
313
|
+
if (
|
|
314
|
+
mode == "stable"
|
|
315
|
+
and local_version
|
|
316
|
+
and remote_version
|
|
317
|
+
and remote_version != local_version
|
|
318
|
+
and _shares_stable_series(local_version, remote_version)
|
|
319
|
+
and remote_severity != SEVERITY_CRITICAL
|
|
320
|
+
):
|
|
321
|
+
if startup:
|
|
322
|
+
startup()
|
|
323
|
+
return
|
|
324
|
+
|
|
221
325
|
if notify:
|
|
222
326
|
notify("Upgrading...", upgrade_stamp)
|
|
223
|
-
|
|
327
|
+
if mode == "stable":
|
|
328
|
+
args = ["./upgrade.sh", "--stable", "--no-restart"]
|
|
329
|
+
else:
|
|
330
|
+
args = ["./upgrade.sh", "--no-restart"]
|
|
224
331
|
upgrade_was_applied = True
|
|
225
332
|
|
|
226
333
|
with log_file.open("a") as fh:
|
|
@@ -230,12 +337,6 @@ def check_github_updates() -> None:
|
|
|
230
337
|
|
|
231
338
|
subprocess.run(args, cwd=base_dir, check=True)
|
|
232
339
|
|
|
233
|
-
if shutil.which("gway"):
|
|
234
|
-
try:
|
|
235
|
-
subprocess.run(["gway", "upgrade"], check=True)
|
|
236
|
-
except subprocess.CalledProcessError:
|
|
237
|
-
logger.warning("gway upgrade failed; continuing anyway", exc_info=True)
|
|
238
|
-
|
|
239
340
|
service_file = base_dir / "locks/service.lck"
|
|
240
341
|
if service_file.exists():
|
|
241
342
|
service = service_file.read_text().strip()
|
|
@@ -410,3 +511,28 @@ def run_client_report_schedule(schedule_id: int) -> None:
|
|
|
410
511
|
except Exception:
|
|
411
512
|
logger.exception("ClientReportSchedule %s failed", schedule_id)
|
|
412
513
|
raise
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
@shared_task
|
|
517
|
+
def ensure_recurring_client_reports() -> None:
|
|
518
|
+
"""Ensure scheduled consumer reports run for the current period."""
|
|
519
|
+
|
|
520
|
+
from core.models import ClientReportSchedule
|
|
521
|
+
|
|
522
|
+
reference = timezone.localdate()
|
|
523
|
+
schedules = ClientReportSchedule.objects.filter(
|
|
524
|
+
periodicity__in=[
|
|
525
|
+
ClientReportSchedule.PERIODICITY_DAILY,
|
|
526
|
+
ClientReportSchedule.PERIODICITY_WEEKLY,
|
|
527
|
+
ClientReportSchedule.PERIODICITY_MONTHLY,
|
|
528
|
+
]
|
|
529
|
+
).prefetch_related("chargers")
|
|
530
|
+
|
|
531
|
+
for schedule in schedules:
|
|
532
|
+
try:
|
|
533
|
+
schedule.generate_missing_reports(reference=reference)
|
|
534
|
+
except Exception:
|
|
535
|
+
logger.exception(
|
|
536
|
+
"Automatic consumer report generation failed for schedule %s",
|
|
537
|
+
schedule.pk,
|
|
538
|
+
)
|