arthexis 0.1.19__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.19.dist-info → arthexis-0.1.20.dist-info}/METADATA +3 -3
- {arthexis-0.1.19.dist-info → arthexis-0.1.20.dist-info}/RECORD +38 -38
- 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/tasks.py +24 -1
- core/tests.py +1 -0
- core/views.py +70 -36
- nodes/admin.py +133 -1
- nodes/models.py +294 -48
- nodes/rfid_sync.py +1 -1
- nodes/tasks.py +100 -2
- nodes/tests.py +532 -15
- nodes/urls.py +4 -0
- nodes/views.py +500 -95
- ocpp/admin.py +101 -3
- ocpp/consumers.py +106 -9
- ocpp/models.py +83 -1
- ocpp/tasks.py +4 -0
- ocpp/test_export_import.py +1 -0
- ocpp/test_rfid.py +3 -1
- ocpp/tests.py +100 -9
- ocpp/transactions_io.py +9 -1
- ocpp/urls.py +3 -3
- ocpp/views.py +101 -28
- pages/context_processors.py +15 -9
- pages/defaults.py +1 -1
- pages/module_defaults.py +5 -5
- pages/tests.py +110 -38
- pages/urls.py +1 -0
- pages/views.py +108 -8
- {arthexis-0.1.19.dist-info → arthexis-0.1.20.dist-info}/WHEEL +0 -0
- {arthexis-0.1.19.dist-info → arthexis-0.1.20.dist-info}/licenses/LICENSE +0 -0
- {arthexis-0.1.19.dist-info → arthexis-0.1.20.dist-info}/top_level.txt +0 -0
ocpp/urls.py
CHANGED
|
@@ -3,8 +3,8 @@ from django.urls import include, path
|
|
|
3
3
|
from . import views
|
|
4
4
|
|
|
5
5
|
urlpatterns = [
|
|
6
|
-
path("", views.dashboard, name="ocpp-dashboard"),
|
|
7
|
-
path("simulator/", views.cp_simulator, name="cp-simulator"),
|
|
6
|
+
path("cpms/dashboard/", views.dashboard, name="ocpp-dashboard"),
|
|
7
|
+
path("evcs/simulator/", views.cp_simulator, name="cp-simulator"),
|
|
8
8
|
path("chargers/", views.charger_list, name="charger-list"),
|
|
9
9
|
path("chargers/<str:cid>/", views.charger_detail, name="charger-detail"),
|
|
10
10
|
path(
|
|
@@ -46,5 +46,5 @@ urlpatterns = [
|
|
|
46
46
|
views.charger_status,
|
|
47
47
|
name="charger-status-connector",
|
|
48
48
|
),
|
|
49
|
-
path("rfid/", include("ocpp.rfid.urls")),
|
|
49
|
+
path("rfid/validator/", include("ocpp.rfid.urls")),
|
|
50
50
|
]
|
ocpp/views.py
CHANGED
|
@@ -224,24 +224,75 @@ def _transaction_rfid_details(
|
|
|
224
224
|
if not tx_obj:
|
|
225
225
|
return None
|
|
226
226
|
rfid_value = getattr(tx_obj, "rfid", None)
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
normalized
|
|
230
|
-
|
|
227
|
+
normalized = str(rfid_value or "").strip().upper()
|
|
228
|
+
cache_key = normalized
|
|
229
|
+
if normalized:
|
|
230
|
+
if cache is not None and cache_key in cache:
|
|
231
|
+
return cache[cache_key]
|
|
232
|
+
tag = (
|
|
233
|
+
RFID.matching_queryset(normalized)
|
|
234
|
+
.only("pk", "label_id", "custom_label")
|
|
235
|
+
.first()
|
|
236
|
+
)
|
|
237
|
+
rfid_url = None
|
|
238
|
+
label_value = None
|
|
239
|
+
canonical_value = normalized
|
|
240
|
+
if tag:
|
|
241
|
+
try:
|
|
242
|
+
rfid_url = reverse("admin:core_rfid_change", args=[tag.pk])
|
|
243
|
+
except NoReverseMatch: # pragma: no cover - admin may be disabled
|
|
244
|
+
rfid_url = None
|
|
245
|
+
custom_label = (tag.custom_label or "").strip()
|
|
246
|
+
if custom_label:
|
|
247
|
+
label_value = custom_label
|
|
248
|
+
elif tag.label_id is not None:
|
|
249
|
+
label_value = str(tag.label_id)
|
|
250
|
+
canonical_value = tag.rfid or canonical_value
|
|
251
|
+
display_value = label_value or canonical_value
|
|
252
|
+
details = {
|
|
253
|
+
"value": display_value,
|
|
254
|
+
"url": rfid_url,
|
|
255
|
+
"uid": canonical_value,
|
|
256
|
+
"type": "rfid",
|
|
257
|
+
"display_label": gettext("RFID"),
|
|
258
|
+
}
|
|
259
|
+
if label_value:
|
|
260
|
+
details["label"] = label_value
|
|
261
|
+
if cache is not None:
|
|
262
|
+
cache[cache_key] = details
|
|
263
|
+
return details
|
|
264
|
+
|
|
265
|
+
identifier_value = getattr(tx_obj, "vehicle_identifier", None)
|
|
266
|
+
normalized_identifier = str(identifier_value or "").strip()
|
|
267
|
+
if not normalized_identifier:
|
|
268
|
+
vid_value = getattr(tx_obj, "vid", None)
|
|
269
|
+
vin_value = getattr(tx_obj, "vin", None)
|
|
270
|
+
normalized_identifier = str(vid_value or vin_value or "").strip()
|
|
271
|
+
if not normalized_identifier:
|
|
231
272
|
return None
|
|
232
|
-
|
|
233
|
-
if
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
273
|
+
source = getattr(tx_obj, "vehicle_identifier_source", "") or "vid"
|
|
274
|
+
if source not in {"vid", "vin"}:
|
|
275
|
+
vid_raw = getattr(tx_obj, "vid", None)
|
|
276
|
+
vin_raw = getattr(tx_obj, "vin", None)
|
|
277
|
+
if str(vid_raw or "").strip():
|
|
278
|
+
source = "vid"
|
|
279
|
+
elif str(vin_raw or "").strip():
|
|
280
|
+
source = "vin"
|
|
281
|
+
else:
|
|
282
|
+
source = "vid"
|
|
283
|
+
cache_key = f"{source}:{normalized_identifier}"
|
|
284
|
+
if cache is not None and cache_key in cache:
|
|
285
|
+
return cache[cache_key]
|
|
286
|
+
label = gettext("VID") if source == "vid" else gettext("VIN")
|
|
287
|
+
details = {
|
|
288
|
+
"value": normalized_identifier,
|
|
289
|
+
"url": None,
|
|
290
|
+
"uid": None,
|
|
291
|
+
"type": source,
|
|
292
|
+
"display_label": label,
|
|
293
|
+
}
|
|
243
294
|
if cache is not None:
|
|
244
|
-
cache[
|
|
295
|
+
cache[cache_key] = details
|
|
245
296
|
return details
|
|
246
297
|
|
|
247
298
|
|
|
@@ -509,8 +560,12 @@ def charger_list(request):
|
|
|
509
560
|
"meterStart": tx_obj.meter_start,
|
|
510
561
|
"startTime": tx_obj.start_time.isoformat(),
|
|
511
562
|
}
|
|
512
|
-
|
|
513
|
-
|
|
563
|
+
identifier = str(getattr(tx_obj, "vehicle_identifier", "") or "").strip()
|
|
564
|
+
if identifier:
|
|
565
|
+
tx_data["vid"] = identifier
|
|
566
|
+
legacy_vin = str(getattr(tx_obj, "vin", "") or "").strip()
|
|
567
|
+
if legacy_vin:
|
|
568
|
+
tx_data["vin"] = legacy_vin
|
|
514
569
|
if tx_obj.meter_stop is not None:
|
|
515
570
|
tx_data["meterStop"] = tx_obj.meter_stop
|
|
516
571
|
if tx_obj.stop_time is not None:
|
|
@@ -525,8 +580,12 @@ def charger_list(request):
|
|
|
525
580
|
"meterStart": session_tx.meter_start,
|
|
526
581
|
"startTime": session_tx.start_time.isoformat(),
|
|
527
582
|
}
|
|
528
|
-
|
|
529
|
-
|
|
583
|
+
identifier = str(getattr(session_tx, "vehicle_identifier", "") or "").strip()
|
|
584
|
+
if identifier:
|
|
585
|
+
active_payload["vid"] = identifier
|
|
586
|
+
legacy_vin = str(getattr(session_tx, "vin", "") or "").strip()
|
|
587
|
+
if legacy_vin:
|
|
588
|
+
active_payload["vin"] = legacy_vin
|
|
530
589
|
if session_tx.meter_stop is not None:
|
|
531
590
|
active_payload["meterStop"] = session_tx.meter_stop
|
|
532
591
|
if session_tx.stop_time is not None:
|
|
@@ -606,8 +665,12 @@ def charger_detail(request, cid, connector=None):
|
|
|
606
665
|
"meterStart": tx_obj.meter_start,
|
|
607
666
|
"startTime": tx_obj.start_time.isoformat(),
|
|
608
667
|
}
|
|
609
|
-
|
|
610
|
-
|
|
668
|
+
identifier = str(getattr(tx_obj, "vehicle_identifier", "") or "").strip()
|
|
669
|
+
if identifier:
|
|
670
|
+
tx_data["vid"] = identifier
|
|
671
|
+
legacy_vin = str(getattr(tx_obj, "vin", "") or "").strip()
|
|
672
|
+
if legacy_vin:
|
|
673
|
+
tx_data["vin"] = legacy_vin
|
|
611
674
|
if tx_obj.meter_stop is not None:
|
|
612
675
|
tx_data["meterStop"] = tx_obj.meter_stop
|
|
613
676
|
if tx_obj.stop_time is not None:
|
|
@@ -623,8 +686,12 @@ def charger_detail(request, cid, connector=None):
|
|
|
623
686
|
"meterStart": session_tx.meter_start,
|
|
624
687
|
"startTime": session_tx.start_time.isoformat(),
|
|
625
688
|
}
|
|
626
|
-
|
|
627
|
-
|
|
689
|
+
identifier = str(getattr(session_tx, "vehicle_identifier", "") or "").strip()
|
|
690
|
+
if identifier:
|
|
691
|
+
payload["vid"] = identifier
|
|
692
|
+
legacy_vin = str(getattr(session_tx, "vin", "") or "").strip()
|
|
693
|
+
if legacy_vin:
|
|
694
|
+
payload["vin"] = legacy_vin
|
|
628
695
|
if session_tx.meter_stop is not None:
|
|
629
696
|
payload["meterStop"] = session_tx.meter_stop
|
|
630
697
|
if session_tx.stop_time is not None:
|
|
@@ -679,12 +746,12 @@ def dashboard(request):
|
|
|
679
746
|
node = Node.get_local()
|
|
680
747
|
role = node.role if node else None
|
|
681
748
|
role_name = role.name if role else ""
|
|
682
|
-
allow_anonymous_roles = {"Constellation", "Satellite"}
|
|
749
|
+
allow_anonymous_roles = {"Watchtower", "Constellation", "Satellite"}
|
|
683
750
|
if not request.user.is_authenticated and role_name not in allow_anonymous_roles:
|
|
684
751
|
return redirect_to_login(
|
|
685
752
|
request.get_full_path(), login_url=reverse("pages:login")
|
|
686
753
|
)
|
|
687
|
-
|
|
754
|
+
is_watchtower = role_name in {"Watchtower", "Constellation"}
|
|
688
755
|
chargers = []
|
|
689
756
|
for charger in _visible_chargers(request.user):
|
|
690
757
|
tx_obj = store.get_transaction(charger.charger_id, charger.connector_id)
|
|
@@ -701,7 +768,7 @@ def dashboard(request):
|
|
|
701
768
|
ws_url = f"{scheme}://{host}/ocpp/<CHARGE_POINT_ID>/"
|
|
702
769
|
context = {
|
|
703
770
|
"chargers": chargers,
|
|
704
|
-
"show_demo_notice":
|
|
771
|
+
"show_demo_notice": is_watchtower,
|
|
705
772
|
"demo_ws_url": ws_url,
|
|
706
773
|
"ws_rate_limit": store.MAX_CONNECTIONS_PER_IP,
|
|
707
774
|
}
|
|
@@ -1049,7 +1116,10 @@ def charger_status(request, cid, connector=None):
|
|
|
1049
1116
|
"connector_id": connector_id,
|
|
1050
1117
|
}
|
|
1051
1118
|
)
|
|
1052
|
-
|
|
1119
|
+
rfid_cache: dict[str, dict[str, str | None]] = {}
|
|
1120
|
+
overview = _connector_overview(
|
|
1121
|
+
charger, request.user, rfid_cache=rfid_cache
|
|
1122
|
+
)
|
|
1053
1123
|
connector_links = [
|
|
1054
1124
|
{
|
|
1055
1125
|
"slug": item["slug"],
|
|
@@ -1090,12 +1160,15 @@ def charger_status(request, cid, connector=None):
|
|
|
1090
1160
|
action_url = _reverse_connector_url("charger-action", cid, connector_slug)
|
|
1091
1161
|
chart_should_animate = bool(has_active_session and not past_session)
|
|
1092
1162
|
|
|
1163
|
+
tx_rfid_details = _transaction_rfid_details(tx_obj, cache=rfid_cache)
|
|
1164
|
+
|
|
1093
1165
|
return render(
|
|
1094
1166
|
request,
|
|
1095
1167
|
"ocpp/charger_status.html",
|
|
1096
1168
|
{
|
|
1097
1169
|
"charger": charger,
|
|
1098
1170
|
"tx": tx_obj,
|
|
1171
|
+
"tx_rfid_details": tx_rfid_details,
|
|
1099
1172
|
"state": state,
|
|
1100
1173
|
"color": color,
|
|
1101
1174
|
"transactions": transactions,
|
pages/context_processors.py
CHANGED
|
@@ -11,7 +11,8 @@ from .models import Module
|
|
|
11
11
|
_FAVICON_DIR = Path(settings.BASE_DIR) / "pages" / "fixtures" / "data"
|
|
12
12
|
_FAVICON_FILENAMES = {
|
|
13
13
|
"default": "favicon.txt",
|
|
14
|
-
"
|
|
14
|
+
"Watchtower": "favicon_watchtower.txt",
|
|
15
|
+
"Constellation": "favicon_watchtower.txt",
|
|
15
16
|
"Control": "favicon_control.txt",
|
|
16
17
|
"Satellite": "favicon_satellite.txt",
|
|
17
18
|
}
|
|
@@ -72,19 +73,24 @@ def nav_links(request):
|
|
|
72
73
|
required_groups = getattr(
|
|
73
74
|
view_func, "required_security_groups", frozenset()
|
|
74
75
|
)
|
|
76
|
+
blocked_reason = None
|
|
75
77
|
if required_groups:
|
|
76
78
|
requires_login = True
|
|
77
|
-
setattr(landing, "requires_login", True)
|
|
78
79
|
if not user_is_authenticated:
|
|
79
|
-
|
|
80
|
-
|
|
80
|
+
blocked_reason = "login"
|
|
81
|
+
elif not user_is_superuser and not (
|
|
81
82
|
user_group_names & set(required_groups)
|
|
82
83
|
):
|
|
83
|
-
|
|
84
|
+
blocked_reason = "permission"
|
|
84
85
|
elif requires_login and not user_is_authenticated:
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
blocked_reason = "login"
|
|
87
|
+
|
|
88
|
+
if staff_only and not getattr(request.user, "is_staff", False):
|
|
89
|
+
if blocked_reason != "login":
|
|
90
|
+
blocked_reason = "permission"
|
|
91
|
+
|
|
92
|
+
landing.nav_is_locked = bool(blocked_reason)
|
|
93
|
+
landing.nav_lock_reason = blocked_reason
|
|
88
94
|
landings.append(landing)
|
|
89
95
|
if landings:
|
|
90
96
|
normalized_module_path = module.path.rstrip("/") or "/"
|
|
@@ -100,7 +106,7 @@ def nav_links(request):
|
|
|
100
106
|
landings = [landings[0]]
|
|
101
107
|
app_name = getattr(module.application, "name", "").lower()
|
|
102
108
|
if app_name == "awg":
|
|
103
|
-
module.menu = "
|
|
109
|
+
module.menu = "Calculators"
|
|
104
110
|
elif module.path.rstrip("/").lower() == "/man":
|
|
105
111
|
module.menu = "Manual"
|
|
106
112
|
module.enabled_landings = landings
|
pages/defaults.py
CHANGED
|
@@ -7,7 +7,7 @@ DEFAULT_APPLICATION_DESCRIPTIONS: Dict[str, str] = {
|
|
|
7
7
|
"awg": "Power, Energy and Cost calculations.",
|
|
8
8
|
"core": "Support for Business Processes and monetization.",
|
|
9
9
|
"ocpp": "Compatibility with Standards and Good Practices.",
|
|
10
|
-
"nodes": "System and Node-level operations
|
|
10
|
+
"nodes": "System and Node-level operations.",
|
|
11
11
|
"pages": "User QA, Continuity Design and Chaos Testing.",
|
|
12
12
|
"teams": "Identity, Entitlements and Access Controls.",
|
|
13
13
|
}
|
pages/module_defaults.py
CHANGED
|
@@ -10,15 +10,15 @@ LandingDefinition = tuple[str, str]
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
ROLE_MODULE_DEFAULTS: Mapping[str, tuple[ModuleDefinition, ...]] = {
|
|
13
|
-
"
|
|
13
|
+
"Watchtower": (
|
|
14
14
|
{
|
|
15
15
|
"application": "ocpp",
|
|
16
16
|
"path": "/ocpp/",
|
|
17
17
|
"menu": "Chargers",
|
|
18
18
|
"landings": (
|
|
19
|
-
("/ocpp/", "CPMS Online Dashboard"),
|
|
20
|
-
("/ocpp/simulator/", "Charge Point Simulator"),
|
|
21
|
-
("/ocpp/rfid/", "RFID Tag Validator"),
|
|
19
|
+
("/ocpp/cpms/dashboard/", "CPMS Online Dashboard"),
|
|
20
|
+
("/ocpp/evcs/simulator/", "Charge Point Simulator"),
|
|
21
|
+
("/ocpp/rfid/validator/", "RFID Tag Validator"),
|
|
22
22
|
),
|
|
23
23
|
},
|
|
24
24
|
{
|
|
@@ -26,7 +26,7 @@ ROLE_MODULE_DEFAULTS: Mapping[str, tuple[ModuleDefinition, ...]] = {
|
|
|
26
26
|
"path": "/awg/",
|
|
27
27
|
"menu": "",
|
|
28
28
|
"landings": (
|
|
29
|
-
("/awg/", "AWG Calculator"),
|
|
29
|
+
("/awg/", "AWG Cable Calculator"),
|
|
30
30
|
("/awg/energy-tariff/", "Energy Tariff Calculator"),
|
|
31
31
|
),
|
|
32
32
|
},
|