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.
- arthexis-0.1.26.dist-info/METADATA +272 -0
- arthexis-0.1.26.dist-info/RECORD +111 -0
- {arthexis-0.1.9.dist-info → arthexis-0.1.26.dist-info}/licenses/LICENSE +674 -674
- config/__init__.py +5 -5
- config/active_app.py +15 -15
- config/asgi.py +29 -29
- config/auth_app.py +7 -7
- config/celery.py +32 -25
- config/context_processors.py +67 -68
- config/horologia_app.py +7 -7
- config/loadenv.py +11 -11
- config/logging.py +59 -48
- config/middleware.py +71 -25
- config/offline.py +49 -49
- config/settings.py +676 -492
- config/settings_helpers.py +109 -0
- config/urls.py +228 -159
- config/wsgi.py +17 -17
- core/admin.py +4052 -2066
- core/admin_history.py +50 -50
- core/admindocs.py +192 -151
- core/apps.py +350 -223
- core/auto_upgrade.py +72 -0
- core/backends.py +311 -124
- core/changelog.py +403 -0
- core/entity.py +149 -133
- core/environment.py +60 -43
- core/fields.py +168 -75
- core/form_fields.py +75 -0
- core/github_helper.py +188 -25
- core/github_issues.py +183 -172
- core/github_repos.py +72 -0
- core/lcd_screen.py +78 -78
- core/liveupdate.py +25 -25
- core/log_paths.py +114 -100
- core/mailer.py +89 -83
- core/middleware.py +91 -91
- core/models.py +5041 -2195
- core/notifications.py +105 -105
- core/public_wifi.py +267 -227
- core/reference_utils.py +107 -0
- core/release.py +940 -346
- core/rfid_import_export.py +113 -0
- core/sigil_builder.py +149 -131
- core/sigil_context.py +20 -20
- core/sigil_resolver.py +250 -284
- core/system.py +1425 -230
- core/tasks.py +538 -199
- core/temp_passwords.py +181 -0
- core/test_system_info.py +202 -43
- core/tests.py +2673 -1069
- core/tests_liveupdate.py +17 -17
- core/urls.py +11 -11
- core/user_data.py +681 -495
- core/views.py +2484 -789
- core/widgets.py +213 -51
- nodes/admin.py +2236 -445
- nodes/apps.py +98 -70
- nodes/backends.py +160 -53
- nodes/dns.py +203 -0
- nodes/feature_checks.py +133 -0
- nodes/lcd.py +165 -165
- nodes/models.py +2375 -870
- nodes/reports.py +411 -0
- nodes/rfid_sync.py +210 -0
- nodes/signals.py +18 -0
- nodes/tasks.py +141 -46
- nodes/tests.py +5045 -1489
- nodes/urls.py +29 -13
- nodes/utils.py +172 -73
- nodes/views.py +1768 -304
- ocpp/admin.py +1775 -481
- ocpp/apps.py +25 -25
- ocpp/consumers.py +1843 -630
- ocpp/evcs.py +844 -928
- ocpp/evcs_discovery.py +158 -0
- ocpp/models.py +1417 -640
- ocpp/network.py +398 -0
- ocpp/reference_utils.py +42 -0
- ocpp/routing.py +11 -9
- ocpp/simulator.py +745 -368
- ocpp/status_display.py +26 -0
- ocpp/store.py +603 -403
- ocpp/tasks.py +479 -31
- ocpp/test_export_import.py +131 -130
- ocpp/test_rfid.py +1072 -540
- ocpp/tests.py +5494 -2296
- ocpp/transactions_io.py +197 -165
- ocpp/urls.py +50 -50
- ocpp/views.py +2024 -912
- pages/admin.py +1123 -396
- pages/apps.py +45 -10
- pages/checks.py +40 -40
- pages/context_processors.py +151 -85
- pages/defaults.py +13 -0
- pages/forms.py +221 -0
- pages/middleware.py +213 -153
- pages/models.py +720 -252
- pages/module_defaults.py +156 -0
- pages/site_config.py +137 -0
- pages/tasks.py +74 -0
- pages/tests.py +4009 -1389
- pages/urls.py +38 -20
- pages/utils.py +93 -12
- pages/views.py +1736 -762
- arthexis-0.1.9.dist-info/METADATA +0 -168
- arthexis-0.1.9.dist-info/RECORD +0 -92
- core/workgroup_urls.py +0 -17
- core/workgroup_views.py +0 -94
- nodes/actions.py +0 -70
- {arthexis-0.1.9.dist-info → arthexis-0.1.26.dist-info}/WHEEL +0 -0
- {arthexis-0.1.9.dist-info → arthexis-0.1.26.dist-info}/top_level.txt +0 -0
core/widgets.py
CHANGED
|
@@ -1,51 +1,213 @@
|
|
|
1
|
-
from
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
return {}
|
|
1
|
+
from collections import OrderedDict
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from django import forms
|
|
5
|
+
from django.forms.widgets import ClearableFileInput
|
|
6
|
+
import json
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CopyColorWidget(forms.TextInput):
|
|
10
|
+
input_type = "color"
|
|
11
|
+
template_name = "widgets/copy_color.html"
|
|
12
|
+
|
|
13
|
+
class Media:
|
|
14
|
+
js = ["core/copy_color.js"]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class CodeEditorWidget(forms.Textarea):
|
|
18
|
+
"""Simple code editor widget for editing recipes."""
|
|
19
|
+
|
|
20
|
+
def __init__(self, attrs=None):
|
|
21
|
+
default_attrs = {"class": "code-editor"}
|
|
22
|
+
if attrs:
|
|
23
|
+
default_attrs.update(attrs)
|
|
24
|
+
super().__init__(attrs=default_attrs)
|
|
25
|
+
|
|
26
|
+
class Media:
|
|
27
|
+
css = {"all": ["core/code_editor.css"]}
|
|
28
|
+
js = ["core/code_editor.js"]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class OdooProductWidget(forms.Select):
|
|
32
|
+
"""Widget for selecting an Odoo product."""
|
|
33
|
+
|
|
34
|
+
template_name = "widgets/odoo_product.html"
|
|
35
|
+
|
|
36
|
+
class Media:
|
|
37
|
+
js = ["core/odoo_product.js"]
|
|
38
|
+
|
|
39
|
+
def get_context(self, name, value, attrs):
|
|
40
|
+
attrs = attrs or {}
|
|
41
|
+
if isinstance(value, dict):
|
|
42
|
+
attrs["data-current-id"] = str(value.get("id", ""))
|
|
43
|
+
value = json.dumps(value)
|
|
44
|
+
elif not value:
|
|
45
|
+
value = ""
|
|
46
|
+
return super().get_context(name, value, attrs)
|
|
47
|
+
|
|
48
|
+
def value_from_datadict(self, data, files, name):
|
|
49
|
+
raw = data.get(name)
|
|
50
|
+
if not raw:
|
|
51
|
+
return {}
|
|
52
|
+
try:
|
|
53
|
+
return json.loads(raw)
|
|
54
|
+
except Exception:
|
|
55
|
+
return {}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class AdminBase64FileWidget(ClearableFileInput):
|
|
59
|
+
"""Clearable file input that exposes base64 data for downloads."""
|
|
60
|
+
|
|
61
|
+
template_name = "widgets/admin_base64_file.html"
|
|
62
|
+
|
|
63
|
+
def __init__(
|
|
64
|
+
self,
|
|
65
|
+
*,
|
|
66
|
+
download_name: str | None = None,
|
|
67
|
+
content_type: str = "application/octet-stream",
|
|
68
|
+
**kwargs,
|
|
69
|
+
) -> None:
|
|
70
|
+
self.download_name = download_name
|
|
71
|
+
self.content_type = content_type
|
|
72
|
+
super().__init__(**kwargs)
|
|
73
|
+
|
|
74
|
+
def is_initial(self, value):
|
|
75
|
+
if isinstance(value, str):
|
|
76
|
+
return bool(value)
|
|
77
|
+
return super().is_initial(value)
|
|
78
|
+
|
|
79
|
+
def format_value(self, value):
|
|
80
|
+
if isinstance(value, str):
|
|
81
|
+
return value
|
|
82
|
+
return super().format_value(value)
|
|
83
|
+
|
|
84
|
+
def get_context(self, name, value, attrs):
|
|
85
|
+
if isinstance(value, str):
|
|
86
|
+
base64_value = value.strip()
|
|
87
|
+
rendered_value = None
|
|
88
|
+
else:
|
|
89
|
+
base64_value = None
|
|
90
|
+
rendered_value = value
|
|
91
|
+
context = super().get_context(name, rendered_value, attrs)
|
|
92
|
+
widget_context = context["widget"]
|
|
93
|
+
widget_context["is_initial"] = bool(base64_value)
|
|
94
|
+
widget_context["base64_value"] = base64_value
|
|
95
|
+
widget_context["download_name"] = self.download_name or f"{name}.bin"
|
|
96
|
+
widget_context["content_type"] = self.content_type
|
|
97
|
+
return context
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class RFIDDataWidget(forms.Textarea):
|
|
101
|
+
"""Render RFID block dumps as a readable grid while keeping raw JSON editable."""
|
|
102
|
+
|
|
103
|
+
template_name = "admin/core/widgets/rfid_data_widget.html"
|
|
104
|
+
|
|
105
|
+
def __init__(self, attrs: dict[str, Any] | None = None) -> None:
|
|
106
|
+
default_attrs = {
|
|
107
|
+
"class": "vLargeTextField rfid-data-widget__input",
|
|
108
|
+
"rows": 8,
|
|
109
|
+
}
|
|
110
|
+
if attrs:
|
|
111
|
+
default_attrs.update(attrs)
|
|
112
|
+
super().__init__(attrs=default_attrs)
|
|
113
|
+
|
|
114
|
+
class Media:
|
|
115
|
+
css = {"all": ["core/rfid_data_widget.css"]}
|
|
116
|
+
js = ["core/rfid_data_widget.js"]
|
|
117
|
+
|
|
118
|
+
def format_value(self, value): # noqa: D401 - inherits docs
|
|
119
|
+
if value in ({}, []):
|
|
120
|
+
return "[]"
|
|
121
|
+
if value is None:
|
|
122
|
+
return "[]"
|
|
123
|
+
if isinstance(value, str):
|
|
124
|
+
return value
|
|
125
|
+
try:
|
|
126
|
+
return json.dumps(value, indent=2, sort_keys=True)
|
|
127
|
+
except (TypeError, ValueError):
|
|
128
|
+
try:
|
|
129
|
+
return json.dumps(list(value), indent=2, sort_keys=True)
|
|
130
|
+
except Exception:
|
|
131
|
+
return "[]"
|
|
132
|
+
|
|
133
|
+
def get_context(self, name, value, attrs):
|
|
134
|
+
context = super().get_context(name, value, attrs)
|
|
135
|
+
parsed_entries = self._parse_entries(value)
|
|
136
|
+
context["widget"]["sectors"] = self._build_sectors(parsed_entries)
|
|
137
|
+
context["widget"]["has_parse_error"] = bool(
|
|
138
|
+
context["widget"].get("value") and not parsed_entries
|
|
139
|
+
)
|
|
140
|
+
context["widget"]["byte_headers"] = [f"{index:02X}" for index in range(16)]
|
|
141
|
+
return context
|
|
142
|
+
|
|
143
|
+
def _parse_entries(self, value: Any) -> list[dict[str, Any]]:
|
|
144
|
+
if isinstance(value, str):
|
|
145
|
+
try:
|
|
146
|
+
value = json.loads(value)
|
|
147
|
+
except Exception:
|
|
148
|
+
return []
|
|
149
|
+
if not isinstance(value, list):
|
|
150
|
+
return []
|
|
151
|
+
|
|
152
|
+
entries: list[dict[str, Any]] = []
|
|
153
|
+
for entry in value:
|
|
154
|
+
if not isinstance(entry, dict):
|
|
155
|
+
continue
|
|
156
|
+
block = entry.get("block")
|
|
157
|
+
data = entry.get("data")
|
|
158
|
+
if not isinstance(block, int) or not isinstance(data, (list, tuple)):
|
|
159
|
+
continue
|
|
160
|
+
|
|
161
|
+
bytes_: list[str] = []
|
|
162
|
+
raw_bytes: list[int] = []
|
|
163
|
+
valid = True
|
|
164
|
+
for raw in list(data)[:16]:
|
|
165
|
+
try:
|
|
166
|
+
byte_value = int(raw)
|
|
167
|
+
except (TypeError, ValueError):
|
|
168
|
+
valid = False
|
|
169
|
+
break
|
|
170
|
+
byte_value = max(0, min(255, byte_value))
|
|
171
|
+
raw_bytes.append(byte_value)
|
|
172
|
+
bytes_.append(f"{byte_value:02X}")
|
|
173
|
+
if not valid:
|
|
174
|
+
continue
|
|
175
|
+
|
|
176
|
+
if len(bytes_) < 16:
|
|
177
|
+
bytes_.extend(["--"] * (16 - len(bytes_)))
|
|
178
|
+
raw_bytes.extend([0] * (16 - len(raw_bytes)))
|
|
179
|
+
|
|
180
|
+
text_chars: list[str] = []
|
|
181
|
+
for byte_value in raw_bytes:
|
|
182
|
+
if 32 <= byte_value <= 126:
|
|
183
|
+
text_chars.append(chr(byte_value))
|
|
184
|
+
else:
|
|
185
|
+
text_chars.append("·")
|
|
186
|
+
text_value = "".join(text_chars)
|
|
187
|
+
|
|
188
|
+
entries.append(
|
|
189
|
+
{
|
|
190
|
+
"block": block,
|
|
191
|
+
"sector": block // 4,
|
|
192
|
+
"offset": block % 4,
|
|
193
|
+
"key": entry.get("key"),
|
|
194
|
+
"bytes": bytes_,
|
|
195
|
+
"is_trailer": block % 4 == 3,
|
|
196
|
+
"text_value": text_value,
|
|
197
|
+
}
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
return sorted(entries, key=lambda item: item["block"])
|
|
201
|
+
|
|
202
|
+
def _build_sectors(self, entries: list[dict[str, Any]]):
|
|
203
|
+
sectors: OrderedDict[int, dict[str, Any]] = OrderedDict()
|
|
204
|
+
for entry in entries:
|
|
205
|
+
sector_key = entry["sector"]
|
|
206
|
+
sector = sectors.setdefault(
|
|
207
|
+
sector_key,
|
|
208
|
+
{"sector": sector_key, "blocks": []},
|
|
209
|
+
)
|
|
210
|
+
sector["blocks"].append(entry)
|
|
211
|
+
for sector in sectors.values():
|
|
212
|
+
sector["blocks"].sort(key=lambda block: block["offset"])
|
|
213
|
+
return list(sectors.values())
|