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.
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
- if not rfid_value:
228
- return None
229
- normalized = str(rfid_value).strip()
230
- if not normalized:
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
- normalized = normalized.upper()
233
- if cache is not None and normalized in cache:
234
- return cache[normalized]
235
- tag = RFID.objects.filter(rfid=normalized).only("pk").first()
236
- rfid_url = None
237
- if tag:
238
- try:
239
- rfid_url = reverse("admin:core_rfid_change", args=[tag.pk])
240
- except NoReverseMatch: # pragma: no cover - admin may be disabled
241
- rfid_url = None
242
- details = {"value": normalized, "url": rfid_url}
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[normalized] = details
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
- if tx_obj.vin:
513
- tx_data["vin"] = tx_obj.vin
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
- if session_tx.vin:
529
- active_payload["vin"] = session_tx.vin
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
- if tx_obj.vin:
610
- tx_data["vin"] = tx_obj.vin
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
- if session_tx.vin:
627
- payload["vin"] = session_tx.vin
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
- is_constellation = role_name == "Constellation"
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": is_constellation,
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
- overview = _connector_overview(charger, request.user)
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,
@@ -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
- "Constellation": "favicon_constellation.txt",
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
- continue
80
- if not user_is_superuser and not (
80
+ blocked_reason = "login"
81
+ elif not user_is_superuser and not (
81
82
  user_group_names & set(required_groups)
82
83
  ):
83
- continue
84
+ blocked_reason = "permission"
84
85
  elif requires_login and not user_is_authenticated:
85
- setattr(landing, "requires_login", True)
86
- if staff_only and not request.user.is_staff:
87
- continue
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 = "Calculate"
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
- "Constellation": (
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
  },