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
core/widgets.py CHANGED
@@ -1,51 +1,213 @@
1
- from django import forms
2
- import json
3
-
4
-
5
- class CopyColorWidget(forms.TextInput):
6
- input_type = "color"
7
- template_name = "widgets/copy_color.html"
8
-
9
- class Media:
10
- js = ["core/copy_color.js"]
11
-
12
-
13
- class CodeEditorWidget(forms.Textarea):
14
- """Simple code editor widget for editing recipes."""
15
-
16
- def __init__(self, attrs=None):
17
- default_attrs = {"class": "code-editor"}
18
- if attrs:
19
- default_attrs.update(attrs)
20
- super().__init__(attrs=default_attrs)
21
-
22
- class Media:
23
- css = {"all": ["core/code_editor.css"]}
24
- js = ["core/code_editor.js"]
25
-
26
-
27
- class OdooProductWidget(forms.Select):
28
- """Widget for selecting an Odoo product."""
29
-
30
- template_name = "widgets/odoo_product.html"
31
-
32
- class Media:
33
- js = ["core/odoo_product.js"]
34
-
35
- def get_context(self, name, value, attrs):
36
- attrs = attrs or {}
37
- if isinstance(value, dict):
38
- attrs["data-current-id"] = str(value.get("id", ""))
39
- value = json.dumps(value)
40
- elif not value:
41
- value = ""
42
- return super().get_context(name, value, attrs)
43
-
44
- def value_from_datadict(self, data, files, name):
45
- raw = data.get(name)
46
- if not raw:
47
- return {}
48
- try:
49
- return json.loads(raw)
50
- except Exception:
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())