arthexis 0.1.9__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.

Files changed (112) hide show
  1. arthexis-0.1.26.dist-info/METADATA +272 -0
  2. arthexis-0.1.26.dist-info/RECORD +111 -0
  3. {arthexis-0.1.9.dist-info → arthexis-0.1.26.dist-info}/licenses/LICENSE +674 -674
  4. config/__init__.py +5 -5
  5. config/active_app.py +15 -15
  6. config/asgi.py +29 -29
  7. config/auth_app.py +7 -7
  8. config/celery.py +32 -25
  9. config/context_processors.py +67 -68
  10. config/horologia_app.py +7 -7
  11. config/loadenv.py +11 -11
  12. config/logging.py +59 -48
  13. config/middleware.py +71 -25
  14. config/offline.py +49 -49
  15. config/settings.py +676 -492
  16. config/settings_helpers.py +109 -0
  17. config/urls.py +228 -159
  18. config/wsgi.py +17 -17
  19. core/admin.py +4052 -2066
  20. core/admin_history.py +50 -50
  21. core/admindocs.py +192 -151
  22. core/apps.py +350 -223
  23. core/auto_upgrade.py +72 -0
  24. core/backends.py +311 -124
  25. core/changelog.py +403 -0
  26. core/entity.py +149 -133
  27. core/environment.py +60 -43
  28. core/fields.py +168 -75
  29. core/form_fields.py +75 -0
  30. core/github_helper.py +188 -25
  31. core/github_issues.py +183 -172
  32. core/github_repos.py +72 -0
  33. core/lcd_screen.py +78 -78
  34. core/liveupdate.py +25 -25
  35. core/log_paths.py +114 -100
  36. core/mailer.py +89 -83
  37. core/middleware.py +91 -91
  38. core/models.py +5041 -2195
  39. core/notifications.py +105 -105
  40. core/public_wifi.py +267 -227
  41. core/reference_utils.py +107 -0
  42. core/release.py +940 -346
  43. core/rfid_import_export.py +113 -0
  44. core/sigil_builder.py +149 -131
  45. core/sigil_context.py +20 -20
  46. core/sigil_resolver.py +250 -284
  47. core/system.py +1425 -230
  48. core/tasks.py +538 -199
  49. core/temp_passwords.py +181 -0
  50. core/test_system_info.py +202 -43
  51. core/tests.py +2673 -1069
  52. core/tests_liveupdate.py +17 -17
  53. core/urls.py +11 -11
  54. core/user_data.py +681 -495
  55. core/views.py +2484 -789
  56. core/widgets.py +213 -51
  57. nodes/admin.py +2236 -445
  58. nodes/apps.py +98 -70
  59. nodes/backends.py +160 -53
  60. nodes/dns.py +203 -0
  61. nodes/feature_checks.py +133 -0
  62. nodes/lcd.py +165 -165
  63. nodes/models.py +2375 -870
  64. nodes/reports.py +411 -0
  65. nodes/rfid_sync.py +210 -0
  66. nodes/signals.py +18 -0
  67. nodes/tasks.py +141 -46
  68. nodes/tests.py +5045 -1489
  69. nodes/urls.py +29 -13
  70. nodes/utils.py +172 -73
  71. nodes/views.py +1768 -304
  72. ocpp/admin.py +1775 -481
  73. ocpp/apps.py +25 -25
  74. ocpp/consumers.py +1843 -630
  75. ocpp/evcs.py +844 -928
  76. ocpp/evcs_discovery.py +158 -0
  77. ocpp/models.py +1417 -640
  78. ocpp/network.py +398 -0
  79. ocpp/reference_utils.py +42 -0
  80. ocpp/routing.py +11 -9
  81. ocpp/simulator.py +745 -368
  82. ocpp/status_display.py +26 -0
  83. ocpp/store.py +603 -403
  84. ocpp/tasks.py +479 -31
  85. ocpp/test_export_import.py +131 -130
  86. ocpp/test_rfid.py +1072 -540
  87. ocpp/tests.py +5494 -2296
  88. ocpp/transactions_io.py +197 -165
  89. ocpp/urls.py +50 -50
  90. ocpp/views.py +2024 -912
  91. pages/admin.py +1123 -396
  92. pages/apps.py +45 -10
  93. pages/checks.py +40 -40
  94. pages/context_processors.py +151 -85
  95. pages/defaults.py +13 -0
  96. pages/forms.py +221 -0
  97. pages/middleware.py +213 -153
  98. pages/models.py +720 -252
  99. pages/module_defaults.py +156 -0
  100. pages/site_config.py +137 -0
  101. pages/tasks.py +74 -0
  102. pages/tests.py +4009 -1389
  103. pages/urls.py +38 -20
  104. pages/utils.py +93 -12
  105. pages/views.py +1736 -762
  106. arthexis-0.1.9.dist-info/METADATA +0 -168
  107. arthexis-0.1.9.dist-info/RECORD +0 -92
  108. core/workgroup_urls.py +0 -17
  109. core/workgroup_views.py +0 -94
  110. nodes/actions.py +0 -70
  111. {arthexis-0.1.9.dist-info → arthexis-0.1.26.dist-info}/WHEEL +0 -0
  112. {arthexis-0.1.9.dist-info → arthexis-0.1.26.dist-info}/top_level.txt +0 -0
ocpp/transactions_io.py CHANGED
@@ -1,165 +1,197 @@
1
- from __future__ import annotations
2
-
3
- from datetime import datetime
4
- from typing import Iterable
5
-
6
- from django.utils import timezone
7
- from django.utils.dateparse import parse_datetime
8
-
9
- from .models import Charger, Transaction, MeterValue
10
-
11
-
12
- def export_transactions(
13
- start: datetime | None = None,
14
- end: datetime | None = None,
15
- chargers: Iterable[str] | None = None,
16
- ) -> dict:
17
- """Return transaction export data."""
18
- qs = (
19
- Transaction.objects.all()
20
- .select_related("charger")
21
- .prefetch_related("meter_values")
22
- )
23
- if start:
24
- qs = qs.filter(start_time__gte=start)
25
- if end:
26
- qs = qs.filter(start_time__lte=end)
27
- if chargers:
28
- qs = qs.filter(charger__charger_id__in=chargers)
29
-
30
- export_chargers = set(qs.values_list("charger__charger_id", flat=True))
31
- data = {"chargers": [], "transactions": []}
32
-
33
- for charger in Charger.objects.filter(charger_id__in=export_chargers):
34
- data["chargers"].append(
35
- {
36
- "charger_id": charger.charger_id,
37
- "connector_id": charger.connector_id,
38
- "require_rfid": charger.require_rfid,
39
- }
40
- )
41
-
42
- for tx in qs:
43
- data["transactions"].append(
44
- {
45
- "charger": tx.charger.charger_id if tx.charger else None,
46
- "account": tx.account_id,
47
- "rfid": tx.rfid,
48
- "vin": tx.vin,
49
- "meter_start": tx.meter_start,
50
- "meter_stop": tx.meter_stop,
51
- "voltage_start": tx.voltage_start,
52
- "voltage_stop": tx.voltage_stop,
53
- "current_import_start": tx.current_import_start,
54
- "current_import_stop": tx.current_import_stop,
55
- "current_offered_start": tx.current_offered_start,
56
- "current_offered_stop": tx.current_offered_stop,
57
- "temperature_start": tx.temperature_start,
58
- "temperature_stop": tx.temperature_stop,
59
- "soc_start": tx.soc_start,
60
- "soc_stop": tx.soc_stop,
61
- "start_time": tx.start_time.isoformat(),
62
- "stop_time": tx.stop_time.isoformat() if tx.stop_time else None,
63
- "meter_values": [
64
- {
65
- "connector_id": mv.connector_id,
66
- "timestamp": mv.timestamp.isoformat(),
67
- "context": mv.context,
68
- "energy": str(mv.energy) if mv.energy is not None else None,
69
- "voltage": str(mv.voltage) if mv.voltage is not None else None,
70
- "current_import": (
71
- str(mv.current_import)
72
- if mv.current_import is not None
73
- else None
74
- ),
75
- "current_offered": (
76
- str(mv.current_offered)
77
- if mv.current_offered is not None
78
- else None
79
- ),
80
- "temperature": (
81
- str(mv.temperature) if mv.temperature is not None else None
82
- ),
83
- "soc": str(mv.soc) if mv.soc is not None else None,
84
- }
85
- for mv in tx.meter_values.all()
86
- ],
87
- }
88
- )
89
- return data
90
-
91
-
92
- def _parse_dt(value: str | None) -> datetime | None:
93
- if value is None:
94
- return None
95
- dt = parse_datetime(value)
96
- if dt is None:
97
- raise ValueError(f"Invalid datetime: {value}")
98
- if timezone.is_naive(dt):
99
- dt = timezone.make_aware(dt)
100
- return dt
101
-
102
-
103
- def import_transactions(data: dict) -> int:
104
- """Import transactions from export data.
105
-
106
- Returns number of imported transactions.
107
- """
108
- charger_map: dict[str, Charger] = {}
109
- for item in data.get("chargers", []):
110
- connector_value = item.get("connector_id", None)
111
- if connector_value in ("", None):
112
- connector_value = None
113
- elif isinstance(connector_value, str):
114
- connector_value = int(connector_value)
115
- charger, _ = Charger.objects.get_or_create(
116
- charger_id=item["charger_id"],
117
- defaults={
118
- "connector_id": connector_value,
119
- "require_rfid": item.get("require_rfid", False),
120
- },
121
- )
122
- charger_map[item["charger_id"]] = charger
123
-
124
- imported = 0
125
- for tx in data.get("transactions", []):
126
- charger = charger_map.get(tx.get("charger"))
127
- transaction = Transaction.objects.create(
128
- charger=charger,
129
- account_id=tx.get("account"),
130
- rfid=tx.get("rfid", ""),
131
- vin=tx.get("vin", ""),
132
- meter_start=tx.get("meter_start"),
133
- meter_stop=tx.get("meter_stop"),
134
- voltage_start=tx.get("voltage_start"),
135
- voltage_stop=tx.get("voltage_stop"),
136
- current_import_start=tx.get("current_import_start"),
137
- current_import_stop=tx.get("current_import_stop"),
138
- current_offered_start=tx.get("current_offered_start"),
139
- current_offered_stop=tx.get("current_offered_stop"),
140
- temperature_start=tx.get("temperature_start"),
141
- temperature_stop=tx.get("temperature_stop"),
142
- soc_start=tx.get("soc_start"),
143
- soc_stop=tx.get("soc_stop"),
144
- start_time=_parse_dt(tx.get("start_time")),
145
- stop_time=_parse_dt(tx.get("stop_time")),
146
- )
147
- for mv in tx.get("meter_values", []):
148
- connector_id = mv.get("connector_id")
149
- if isinstance(connector_id, str):
150
- connector_id = int(connector_id)
151
- MeterValue.objects.create(
152
- charger=charger,
153
- transaction=transaction,
154
- connector_id=connector_id,
155
- timestamp=_parse_dt(mv.get("timestamp")),
156
- context=mv.get("context", ""),
157
- energy=mv.get("energy"),
158
- voltage=mv.get("voltage"),
159
- current_import=mv.get("current_import"),
160
- current_offered=mv.get("current_offered"),
161
- temperature=mv.get("temperature"),
162
- soc=mv.get("soc"),
163
- )
164
- imported += 1
165
- return imported
1
+ from __future__ import annotations
2
+
3
+ from datetime import datetime
4
+ from typing import Iterable
5
+
6
+ from django.core.exceptions import ValidationError
7
+ from django.utils import timezone
8
+ from django.utils.dateparse import parse_datetime
9
+
10
+ from .models import Charger, Transaction, MeterValue
11
+
12
+
13
+ def export_transactions(
14
+ start: datetime | None = None,
15
+ end: datetime | None = None,
16
+ chargers: Iterable[str] | None = None,
17
+ ) -> dict:
18
+ """Return transaction export data."""
19
+ qs = (
20
+ Transaction.objects.all()
21
+ .select_related("charger")
22
+ .prefetch_related("meter_values")
23
+ )
24
+ if start:
25
+ qs = qs.filter(start_time__gte=start)
26
+ if end:
27
+ qs = qs.filter(start_time__lte=end)
28
+ if chargers:
29
+ qs = qs.filter(charger__charger_id__in=chargers)
30
+
31
+ export_chargers = set(qs.values_list("charger__charger_id", flat=True))
32
+ data = {"chargers": [], "transactions": []}
33
+
34
+ for charger in Charger.objects.filter(charger_id__in=export_chargers):
35
+ data["chargers"].append(
36
+ {
37
+ "charger_id": charger.charger_id,
38
+ "connector_id": charger.connector_id,
39
+ "require_rfid": charger.require_rfid,
40
+ }
41
+ )
42
+
43
+ for tx in qs:
44
+ data["transactions"].append(
45
+ {
46
+ "charger": tx.charger.charger_id if tx.charger else None,
47
+ "account": tx.account_id,
48
+ "rfid": tx.rfid,
49
+ "vid": tx.vehicle_identifier,
50
+ "vin": tx.vin,
51
+ "meter_start": tx.meter_start,
52
+ "meter_stop": tx.meter_stop,
53
+ "voltage_start": tx.voltage_start,
54
+ "voltage_stop": tx.voltage_stop,
55
+ "current_import_start": tx.current_import_start,
56
+ "current_import_stop": tx.current_import_stop,
57
+ "current_offered_start": tx.current_offered_start,
58
+ "current_offered_stop": tx.current_offered_stop,
59
+ "temperature_start": tx.temperature_start,
60
+ "temperature_stop": tx.temperature_stop,
61
+ "soc_start": tx.soc_start,
62
+ "soc_stop": tx.soc_stop,
63
+ "start_time": tx.start_time.isoformat(),
64
+ "stop_time": tx.stop_time.isoformat() if tx.stop_time else None,
65
+ "received_start_time": tx.received_start_time.isoformat()
66
+ if tx.received_start_time
67
+ else None,
68
+ "received_stop_time": tx.received_stop_time.isoformat()
69
+ if tx.received_stop_time
70
+ else None,
71
+ "meter_values": [
72
+ {
73
+ "connector_id": mv.connector_id,
74
+ "timestamp": mv.timestamp.isoformat(),
75
+ "context": mv.context,
76
+ "energy": str(mv.energy) if mv.energy is not None else None,
77
+ "voltage": str(mv.voltage) if mv.voltage is not None else None,
78
+ "current_import": (
79
+ str(mv.current_import)
80
+ if mv.current_import is not None
81
+ else None
82
+ ),
83
+ "current_offered": (
84
+ str(mv.current_offered)
85
+ if mv.current_offered is not None
86
+ else None
87
+ ),
88
+ "temperature": (
89
+ str(mv.temperature) if mv.temperature is not None else None
90
+ ),
91
+ "soc": str(mv.soc) if mv.soc is not None else None,
92
+ }
93
+ for mv in tx.meter_values.all()
94
+ ],
95
+ }
96
+ )
97
+ return data
98
+
99
+
100
+ def _parse_dt(value: str | None) -> datetime | None:
101
+ if value is None:
102
+ return None
103
+ dt = parse_datetime(value)
104
+ if dt is None:
105
+ raise ValueError(f"Invalid datetime: {value}")
106
+ if timezone.is_naive(dt):
107
+ dt = timezone.make_aware(dt)
108
+ return dt
109
+
110
+
111
+ def import_transactions(data: dict) -> int:
112
+ """Import transactions from export data.
113
+
114
+ Returns number of imported transactions.
115
+ """
116
+ charger_map: dict[str, Charger] = {}
117
+ for item in data.get("chargers", []):
118
+ try:
119
+ serial = Charger.validate_serial(item.get("charger_id"))
120
+ except ValidationError:
121
+ continue
122
+ connector_value = item.get("connector_id", None)
123
+ if connector_value in ("", None):
124
+ connector_value = None
125
+ elif isinstance(connector_value, str):
126
+ connector_value = int(connector_value)
127
+ charger, _ = Charger.objects.get_or_create(
128
+ charger_id=serial,
129
+ defaults={
130
+ "connector_id": connector_value,
131
+ "require_rfid": item.get("require_rfid", False),
132
+ },
133
+ )
134
+ charger_map[serial] = charger
135
+
136
+ imported = 0
137
+ for tx in data.get("transactions", []):
138
+ serial = Charger.normalize_serial(tx.get("charger"))
139
+ if not serial or Charger.is_placeholder_serial(serial):
140
+ continue
141
+ charger = charger_map.get(serial)
142
+ if charger is None:
143
+ try:
144
+ charger, _ = Charger.objects.get_or_create(charger_id=serial)
145
+ except ValidationError:
146
+ continue
147
+ charger_map[serial] = charger
148
+ vid_value = tx.get("vid")
149
+ vin_value = tx.get("vin")
150
+ vid_text = str(vid_value).strip() if vid_value is not None else ""
151
+ vin_text = str(vin_value).strip() if vin_value is not None else ""
152
+ if not vid_text and vin_text:
153
+ vid_text = vin_text
154
+ transaction = Transaction.objects.create(
155
+ charger=charger,
156
+ account_id=tx.get("account"),
157
+ rfid=tx.get("rfid", ""),
158
+ vid=vid_text,
159
+ vin=vin_text,
160
+ meter_start=tx.get("meter_start"),
161
+ meter_stop=tx.get("meter_stop"),
162
+ voltage_start=tx.get("voltage_start"),
163
+ voltage_stop=tx.get("voltage_stop"),
164
+ current_import_start=tx.get("current_import_start"),
165
+ current_import_stop=tx.get("current_import_stop"),
166
+ current_offered_start=tx.get("current_offered_start"),
167
+ current_offered_stop=tx.get("current_offered_stop"),
168
+ temperature_start=tx.get("temperature_start"),
169
+ temperature_stop=tx.get("temperature_stop"),
170
+ soc_start=tx.get("soc_start"),
171
+ soc_stop=tx.get("soc_stop"),
172
+ start_time=_parse_dt(tx.get("start_time")),
173
+ stop_time=_parse_dt(tx.get("stop_time")),
174
+ received_start_time=_parse_dt(tx.get("received_start_time"))
175
+ or _parse_dt(tx.get("start_time")),
176
+ received_stop_time=_parse_dt(tx.get("received_stop_time"))
177
+ or _parse_dt(tx.get("stop_time")),
178
+ )
179
+ for mv in tx.get("meter_values", []):
180
+ connector_id = mv.get("connector_id")
181
+ if isinstance(connector_id, str):
182
+ connector_id = int(connector_id)
183
+ MeterValue.objects.create(
184
+ charger=charger,
185
+ transaction=transaction,
186
+ connector_id=connector_id,
187
+ timestamp=_parse_dt(mv.get("timestamp")),
188
+ context=mv.get("context", ""),
189
+ energy=mv.get("energy"),
190
+ voltage=mv.get("voltage"),
191
+ current_import=mv.get("current_import"),
192
+ current_offered=mv.get("current_offered"),
193
+ temperature=mv.get("temperature"),
194
+ soc=mv.get("soc"),
195
+ )
196
+ imported += 1
197
+ return imported
ocpp/urls.py CHANGED
@@ -1,50 +1,50 @@
1
- from django.urls import include, path
2
-
3
- from . import views
4
-
5
- urlpatterns = [
6
- path("", views.dashboard, name="ocpp-dashboard"),
7
- path("simulator/", views.cp_simulator, name="cp-simulator"),
8
- path("chargers/", views.charger_list, name="charger-list"),
9
- path("chargers/<str:cid>/", views.charger_detail, name="charger-detail"),
10
- path(
11
- "chargers/<str:cid>/connector/<slug:connector>/",
12
- views.charger_detail,
13
- name="charger-detail-connector",
14
- ),
15
- path("chargers/<str:cid>/action/", views.dispatch_action, name="charger-action"),
16
- path(
17
- "chargers/<str:cid>/connector/<slug:connector>/action/",
18
- views.dispatch_action,
19
- name="charger-action-connector",
20
- ),
21
- path("c/<str:cid>/", views.charger_page, name="charger-page"),
22
- path(
23
- "c/<str:cid>/connector/<slug:connector>/",
24
- views.charger_page,
25
- name="charger-page-connector",
26
- ),
27
- path(
28
- "c/<str:cid>/sessions/",
29
- views.charger_session_search,
30
- name="charger-session-search",
31
- ),
32
- path(
33
- "c/<str:cid>/connector/<slug:connector>/sessions/",
34
- views.charger_session_search,
35
- name="charger-session-search-connector",
36
- ),
37
- path("log/<str:cid>/", views.charger_log_page, name="charger-log"),
38
- path(
39
- "log/<str:cid>/connector/<slug:connector>/",
40
- views.charger_log_page,
41
- name="charger-log-connector",
42
- ),
43
- path("c/<str:cid>/status/", views.charger_status, name="charger-status"),
44
- path(
45
- "c/<str:cid>/connector/<slug:connector>/status/",
46
- views.charger_status,
47
- name="charger-status-connector",
48
- ),
49
- path("rfid/", include("ocpp.rfid.urls")),
50
- ]
1
+ from django.urls import include, path
2
+
3
+ from . import views
4
+
5
+ urlpatterns = [
6
+ path("cpms/dashboard/", views.dashboard, name="ocpp-dashboard"),
7
+ path("evcs/simulator/", views.cp_simulator, name="cp-simulator"),
8
+ path("chargers/", views.charger_list, name="charger-list"),
9
+ path("chargers/<str:cid>/", views.charger_detail, name="charger-detail"),
10
+ path(
11
+ "chargers/<str:cid>/connector/<slug:connector>/",
12
+ views.charger_detail,
13
+ name="charger-detail-connector",
14
+ ),
15
+ path("chargers/<str:cid>/action/", views.dispatch_action, name="charger-action"),
16
+ path(
17
+ "chargers/<str:cid>/connector/<slug:connector>/action/",
18
+ views.dispatch_action,
19
+ name="charger-action-connector",
20
+ ),
21
+ path("c/<str:cid>/", views.charger_page, name="charger-page"),
22
+ path(
23
+ "c/<str:cid>/connector/<slug:connector>/",
24
+ views.charger_page,
25
+ name="charger-page-connector",
26
+ ),
27
+ path(
28
+ "c/<str:cid>/sessions/",
29
+ views.charger_session_search,
30
+ name="charger-session-search",
31
+ ),
32
+ path(
33
+ "c/<str:cid>/connector/<slug:connector>/sessions/",
34
+ views.charger_session_search,
35
+ name="charger-session-search-connector",
36
+ ),
37
+ path("log/<str:cid>/", views.charger_log_page, name="charger-log"),
38
+ path(
39
+ "log/<str:cid>/connector/<slug:connector>/",
40
+ views.charger_log_page,
41
+ name="charger-log-connector",
42
+ ),
43
+ path("c/<str:cid>/status/", views.charger_status, name="charger-status"),
44
+ path(
45
+ "c/<str:cid>/connector/<slug:connector>/status/",
46
+ views.charger_status,
47
+ name="charger-status-connector",
48
+ ),
49
+ path("rfid/validator/", include("ocpp.rfid.urls")),
50
+ ]