arthexis 0.1.13__py3-none-any.whl → 0.1.14__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 (107) hide show
  1. {arthexis-0.1.13.dist-info → arthexis-0.1.14.dist-info}/METADATA +222 -221
  2. arthexis-0.1.14.dist-info/RECORD +109 -0
  3. {arthexis-0.1.13.dist-info → arthexis-0.1.14.dist-info}/licenses/LICENSE +674 -674
  4. config/__init__.py +5 -5
  5. config/active_app.py +15 -15
  6. config/asgi.py +43 -43
  7. config/auth_app.py +7 -7
  8. config/celery.py +32 -32
  9. config/context_processors.py +67 -69
  10. config/horologia_app.py +7 -7
  11. config/loadenv.py +11 -11
  12. config/logging.py +59 -48
  13. config/middleware.py +25 -25
  14. config/offline.py +49 -49
  15. config/settings.py +691 -682
  16. config/settings_helpers.py +109 -109
  17. config/urls.py +171 -166
  18. config/wsgi.py +17 -17
  19. core/admin.py +3771 -2809
  20. core/admin_history.py +50 -50
  21. core/admindocs.py +151 -151
  22. core/apps.py +356 -272
  23. core/auto_upgrade.py +57 -57
  24. core/backends.py +265 -236
  25. core/changelog.py +342 -0
  26. core/entity.py +133 -133
  27. core/environment.py +61 -61
  28. core/fields.py +168 -168
  29. core/form_fields.py +75 -75
  30. core/github_helper.py +188 -25
  31. core/github_issues.py +178 -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 +100 -100
  36. core/mailer.py +85 -85
  37. core/middleware.py +91 -91
  38. core/models.py +3609 -2795
  39. core/notifications.py +105 -105
  40. core/public_wifi.py +267 -227
  41. core/reference_utils.py +108 -108
  42. core/release.py +721 -368
  43. core/rfid_import_export.py +113 -0
  44. core/sigil_builder.py +149 -149
  45. core/sigil_context.py +20 -20
  46. core/sigil_resolver.py +315 -315
  47. core/system.py +752 -493
  48. core/tasks.py +408 -394
  49. core/temp_passwords.py +181 -181
  50. core/test_system_info.py +186 -139
  51. core/tests.py +2095 -1521
  52. core/tests_liveupdate.py +17 -17
  53. core/urls.py +11 -11
  54. core/user_data.py +641 -633
  55. core/views.py +2175 -1417
  56. core/widgets.py +213 -94
  57. core/workgroup_urls.py +17 -17
  58. core/workgroup_views.py +94 -94
  59. nodes/admin.py +1720 -1161
  60. nodes/apps.py +87 -85
  61. nodes/backends.py +160 -160
  62. nodes/dns.py +203 -203
  63. nodes/feature_checks.py +133 -133
  64. nodes/lcd.py +165 -165
  65. nodes/models.py +1737 -1597
  66. nodes/reports.py +411 -411
  67. nodes/rfid_sync.py +195 -0
  68. nodes/signals.py +18 -0
  69. nodes/tasks.py +46 -46
  70. nodes/tests.py +3810 -3116
  71. nodes/urls.py +15 -14
  72. nodes/utils.py +121 -105
  73. nodes/views.py +683 -619
  74. ocpp/admin.py +948 -948
  75. ocpp/apps.py +25 -25
  76. ocpp/consumers.py +1565 -1459
  77. ocpp/evcs.py +844 -844
  78. ocpp/evcs_discovery.py +158 -158
  79. ocpp/models.py +917 -917
  80. ocpp/reference_utils.py +42 -42
  81. ocpp/routing.py +11 -11
  82. ocpp/simulator.py +745 -745
  83. ocpp/status_display.py +26 -26
  84. ocpp/store.py +601 -541
  85. ocpp/tasks.py +31 -31
  86. ocpp/test_export_import.py +130 -130
  87. ocpp/test_rfid.py +913 -702
  88. ocpp/tests.py +4445 -4094
  89. ocpp/transactions_io.py +189 -189
  90. ocpp/urls.py +50 -50
  91. ocpp/views.py +1479 -1251
  92. pages/admin.py +708 -539
  93. pages/apps.py +10 -10
  94. pages/checks.py +40 -40
  95. pages/context_processors.py +127 -119
  96. pages/defaults.py +13 -13
  97. pages/forms.py +198 -198
  98. pages/middleware.py +205 -153
  99. pages/models.py +607 -426
  100. pages/tests.py +2612 -2200
  101. pages/urls.py +25 -25
  102. pages/utils.py +12 -12
  103. pages/views.py +1165 -1128
  104. arthexis-0.1.13.dist-info/RECORD +0 -105
  105. nodes/actions.py +0 -70
  106. {arthexis-0.1.13.dist-info → arthexis-0.1.14.dist-info}/WHEEL +0 -0
  107. {arthexis-0.1.13.dist-info → arthexis-0.1.14.dist-info}/top_level.txt +0 -0
ocpp/transactions_io.py CHANGED
@@ -1,189 +1,189 @@
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
- "vin": tx.vin,
50
- "meter_start": tx.meter_start,
51
- "meter_stop": tx.meter_stop,
52
- "voltage_start": tx.voltage_start,
53
- "voltage_stop": tx.voltage_stop,
54
- "current_import_start": tx.current_import_start,
55
- "current_import_stop": tx.current_import_stop,
56
- "current_offered_start": tx.current_offered_start,
57
- "current_offered_stop": tx.current_offered_stop,
58
- "temperature_start": tx.temperature_start,
59
- "temperature_stop": tx.temperature_stop,
60
- "soc_start": tx.soc_start,
61
- "soc_stop": tx.soc_stop,
62
- "start_time": tx.start_time.isoformat(),
63
- "stop_time": tx.stop_time.isoformat() if tx.stop_time else None,
64
- "received_start_time": tx.received_start_time.isoformat()
65
- if tx.received_start_time
66
- else None,
67
- "received_stop_time": tx.received_stop_time.isoformat()
68
- if tx.received_stop_time
69
- else None,
70
- "meter_values": [
71
- {
72
- "connector_id": mv.connector_id,
73
- "timestamp": mv.timestamp.isoformat(),
74
- "context": mv.context,
75
- "energy": str(mv.energy) if mv.energy is not None else None,
76
- "voltage": str(mv.voltage) if mv.voltage is not None else None,
77
- "current_import": (
78
- str(mv.current_import)
79
- if mv.current_import is not None
80
- else None
81
- ),
82
- "current_offered": (
83
- str(mv.current_offered)
84
- if mv.current_offered is not None
85
- else None
86
- ),
87
- "temperature": (
88
- str(mv.temperature) if mv.temperature is not None else None
89
- ),
90
- "soc": str(mv.soc) if mv.soc is not None else None,
91
- }
92
- for mv in tx.meter_values.all()
93
- ],
94
- }
95
- )
96
- return data
97
-
98
-
99
- def _parse_dt(value: str | None) -> datetime | None:
100
- if value is None:
101
- return None
102
- dt = parse_datetime(value)
103
- if dt is None:
104
- raise ValueError(f"Invalid datetime: {value}")
105
- if timezone.is_naive(dt):
106
- dt = timezone.make_aware(dt)
107
- return dt
108
-
109
-
110
- def import_transactions(data: dict) -> int:
111
- """Import transactions from export data.
112
-
113
- Returns number of imported transactions.
114
- """
115
- charger_map: dict[str, Charger] = {}
116
- for item in data.get("chargers", []):
117
- try:
118
- serial = Charger.validate_serial(item.get("charger_id"))
119
- except ValidationError:
120
- continue
121
- connector_value = item.get("connector_id", None)
122
- if connector_value in ("", None):
123
- connector_value = None
124
- elif isinstance(connector_value, str):
125
- connector_value = int(connector_value)
126
- charger, _ = Charger.objects.get_or_create(
127
- charger_id=serial,
128
- defaults={
129
- "connector_id": connector_value,
130
- "require_rfid": item.get("require_rfid", False),
131
- },
132
- )
133
- charger_map[serial] = charger
134
-
135
- imported = 0
136
- for tx in data.get("transactions", []):
137
- serial = Charger.normalize_serial(tx.get("charger"))
138
- if not serial or Charger.is_placeholder_serial(serial):
139
- continue
140
- charger = charger_map.get(serial)
141
- if charger is None:
142
- try:
143
- charger, _ = Charger.objects.get_or_create(charger_id=serial)
144
- except ValidationError:
145
- continue
146
- charger_map[serial] = charger
147
- transaction = Transaction.objects.create(
148
- charger=charger,
149
- account_id=tx.get("account"),
150
- rfid=tx.get("rfid", ""),
151
- vin=tx.get("vin", ""),
152
- meter_start=tx.get("meter_start"),
153
- meter_stop=tx.get("meter_stop"),
154
- voltage_start=tx.get("voltage_start"),
155
- voltage_stop=tx.get("voltage_stop"),
156
- current_import_start=tx.get("current_import_start"),
157
- current_import_stop=tx.get("current_import_stop"),
158
- current_offered_start=tx.get("current_offered_start"),
159
- current_offered_stop=tx.get("current_offered_stop"),
160
- temperature_start=tx.get("temperature_start"),
161
- temperature_stop=tx.get("temperature_stop"),
162
- soc_start=tx.get("soc_start"),
163
- soc_stop=tx.get("soc_stop"),
164
- start_time=_parse_dt(tx.get("start_time")),
165
- stop_time=_parse_dt(tx.get("stop_time")),
166
- received_start_time=_parse_dt(tx.get("received_start_time"))
167
- or _parse_dt(tx.get("start_time")),
168
- received_stop_time=_parse_dt(tx.get("received_stop_time"))
169
- or _parse_dt(tx.get("stop_time")),
170
- )
171
- for mv in tx.get("meter_values", []):
172
- connector_id = mv.get("connector_id")
173
- if isinstance(connector_id, str):
174
- connector_id = int(connector_id)
175
- MeterValue.objects.create(
176
- charger=charger,
177
- transaction=transaction,
178
- connector_id=connector_id,
179
- timestamp=_parse_dt(mv.get("timestamp")),
180
- context=mv.get("context", ""),
181
- energy=mv.get("energy"),
182
- voltage=mv.get("voltage"),
183
- current_import=mv.get("current_import"),
184
- current_offered=mv.get("current_offered"),
185
- temperature=mv.get("temperature"),
186
- soc=mv.get("soc"),
187
- )
188
- imported += 1
189
- 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
+ "vin": tx.vin,
50
+ "meter_start": tx.meter_start,
51
+ "meter_stop": tx.meter_stop,
52
+ "voltage_start": tx.voltage_start,
53
+ "voltage_stop": tx.voltage_stop,
54
+ "current_import_start": tx.current_import_start,
55
+ "current_import_stop": tx.current_import_stop,
56
+ "current_offered_start": tx.current_offered_start,
57
+ "current_offered_stop": tx.current_offered_stop,
58
+ "temperature_start": tx.temperature_start,
59
+ "temperature_stop": tx.temperature_stop,
60
+ "soc_start": tx.soc_start,
61
+ "soc_stop": tx.soc_stop,
62
+ "start_time": tx.start_time.isoformat(),
63
+ "stop_time": tx.stop_time.isoformat() if tx.stop_time else None,
64
+ "received_start_time": tx.received_start_time.isoformat()
65
+ if tx.received_start_time
66
+ else None,
67
+ "received_stop_time": tx.received_stop_time.isoformat()
68
+ if tx.received_stop_time
69
+ else None,
70
+ "meter_values": [
71
+ {
72
+ "connector_id": mv.connector_id,
73
+ "timestamp": mv.timestamp.isoformat(),
74
+ "context": mv.context,
75
+ "energy": str(mv.energy) if mv.energy is not None else None,
76
+ "voltage": str(mv.voltage) if mv.voltage is not None else None,
77
+ "current_import": (
78
+ str(mv.current_import)
79
+ if mv.current_import is not None
80
+ else None
81
+ ),
82
+ "current_offered": (
83
+ str(mv.current_offered)
84
+ if mv.current_offered is not None
85
+ else None
86
+ ),
87
+ "temperature": (
88
+ str(mv.temperature) if mv.temperature is not None else None
89
+ ),
90
+ "soc": str(mv.soc) if mv.soc is not None else None,
91
+ }
92
+ for mv in tx.meter_values.all()
93
+ ],
94
+ }
95
+ )
96
+ return data
97
+
98
+
99
+ def _parse_dt(value: str | None) -> datetime | None:
100
+ if value is None:
101
+ return None
102
+ dt = parse_datetime(value)
103
+ if dt is None:
104
+ raise ValueError(f"Invalid datetime: {value}")
105
+ if timezone.is_naive(dt):
106
+ dt = timezone.make_aware(dt)
107
+ return dt
108
+
109
+
110
+ def import_transactions(data: dict) -> int:
111
+ """Import transactions from export data.
112
+
113
+ Returns number of imported transactions.
114
+ """
115
+ charger_map: dict[str, Charger] = {}
116
+ for item in data.get("chargers", []):
117
+ try:
118
+ serial = Charger.validate_serial(item.get("charger_id"))
119
+ except ValidationError:
120
+ continue
121
+ connector_value = item.get("connector_id", None)
122
+ if connector_value in ("", None):
123
+ connector_value = None
124
+ elif isinstance(connector_value, str):
125
+ connector_value = int(connector_value)
126
+ charger, _ = Charger.objects.get_or_create(
127
+ charger_id=serial,
128
+ defaults={
129
+ "connector_id": connector_value,
130
+ "require_rfid": item.get("require_rfid", False),
131
+ },
132
+ )
133
+ charger_map[serial] = charger
134
+
135
+ imported = 0
136
+ for tx in data.get("transactions", []):
137
+ serial = Charger.normalize_serial(tx.get("charger"))
138
+ if not serial or Charger.is_placeholder_serial(serial):
139
+ continue
140
+ charger = charger_map.get(serial)
141
+ if charger is None:
142
+ try:
143
+ charger, _ = Charger.objects.get_or_create(charger_id=serial)
144
+ except ValidationError:
145
+ continue
146
+ charger_map[serial] = charger
147
+ transaction = Transaction.objects.create(
148
+ charger=charger,
149
+ account_id=tx.get("account"),
150
+ rfid=tx.get("rfid", ""),
151
+ vin=tx.get("vin", ""),
152
+ meter_start=tx.get("meter_start"),
153
+ meter_stop=tx.get("meter_stop"),
154
+ voltage_start=tx.get("voltage_start"),
155
+ voltage_stop=tx.get("voltage_stop"),
156
+ current_import_start=tx.get("current_import_start"),
157
+ current_import_stop=tx.get("current_import_stop"),
158
+ current_offered_start=tx.get("current_offered_start"),
159
+ current_offered_stop=tx.get("current_offered_stop"),
160
+ temperature_start=tx.get("temperature_start"),
161
+ temperature_stop=tx.get("temperature_stop"),
162
+ soc_start=tx.get("soc_start"),
163
+ soc_stop=tx.get("soc_stop"),
164
+ start_time=_parse_dt(tx.get("start_time")),
165
+ stop_time=_parse_dt(tx.get("stop_time")),
166
+ received_start_time=_parse_dt(tx.get("received_start_time"))
167
+ or _parse_dt(tx.get("start_time")),
168
+ received_stop_time=_parse_dt(tx.get("received_stop_time"))
169
+ or _parse_dt(tx.get("stop_time")),
170
+ )
171
+ for mv in tx.get("meter_values", []):
172
+ connector_id = mv.get("connector_id")
173
+ if isinstance(connector_id, str):
174
+ connector_id = int(connector_id)
175
+ MeterValue.objects.create(
176
+ charger=charger,
177
+ transaction=transaction,
178
+ connector_id=connector_id,
179
+ timestamp=_parse_dt(mv.get("timestamp")),
180
+ context=mv.get("context", ""),
181
+ energy=mv.get("energy"),
182
+ voltage=mv.get("voltage"),
183
+ current_import=mv.get("current_import"),
184
+ current_offered=mv.get("current_offered"),
185
+ temperature=mv.get("temperature"),
186
+ soc=mv.get("soc"),
187
+ )
188
+ imported += 1
189
+ 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("", 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
+ ]