arthexis 0.1.9__py3-none-any.whl → 0.1.11__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.9.dist-info → arthexis-0.1.11.dist-info}/METADATA +76 -23
- arthexis-0.1.11.dist-info/RECORD +99 -0
- config/context_processors.py +1 -0
- config/settings.py +245 -26
- config/urls.py +11 -4
- core/admin.py +585 -57
- core/apps.py +29 -1
- core/auto_upgrade.py +57 -0
- core/backends.py +115 -3
- core/environment.py +23 -5
- core/fields.py +93 -0
- core/mailer.py +3 -1
- core/models.py +482 -38
- core/reference_utils.py +108 -0
- core/sigil_builder.py +23 -5
- core/sigil_resolver.py +35 -4
- core/system.py +400 -140
- core/tasks.py +151 -8
- core/temp_passwords.py +181 -0
- core/test_system_info.py +97 -1
- core/tests.py +393 -15
- core/user_data.py +154 -16
- core/views.py +499 -20
- nodes/admin.py +149 -6
- nodes/backends.py +125 -18
- nodes/dns.py +203 -0
- nodes/models.py +498 -9
- nodes/tests.py +682 -3
- nodes/views.py +154 -7
- ocpp/admin.py +63 -3
- ocpp/consumers.py +255 -41
- ocpp/evcs.py +6 -3
- ocpp/models.py +52 -7
- ocpp/reference_utils.py +42 -0
- ocpp/simulator.py +62 -5
- ocpp/store.py +30 -0
- ocpp/test_rfid.py +169 -7
- ocpp/tests.py +414 -8
- ocpp/views.py +109 -76
- pages/admin.py +9 -1
- pages/context_processors.py +24 -4
- pages/defaults.py +14 -0
- pages/forms.py +131 -0
- pages/models.py +53 -14
- pages/tests.py +450 -14
- pages/urls.py +4 -0
- pages/views.py +419 -110
- arthexis-0.1.9.dist-info/RECORD +0 -92
- {arthexis-0.1.9.dist-info → arthexis-0.1.11.dist-info}/WHEEL +0 -0
- {arthexis-0.1.9.dist-info → arthexis-0.1.11.dist-info}/licenses/LICENSE +0 -0
- {arthexis-0.1.9.dist-info → arthexis-0.1.11.dist-info}/top_level.txt +0 -0
core/reference_utils.py
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""Utility helpers for working with Reference objects."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Iterable, TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from django.contrib.sites.models import Site
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING: # pragma: no cover - imported only for type checking
|
|
10
|
+
from django.http import HttpRequest
|
|
11
|
+
from nodes.models import Node
|
|
12
|
+
from .models import Reference
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def filter_visible_references(
|
|
16
|
+
refs: Iterable["Reference"],
|
|
17
|
+
*,
|
|
18
|
+
request: "HttpRequest | None" = None,
|
|
19
|
+
site: Site | None = None,
|
|
20
|
+
node: "Node | None" = None,
|
|
21
|
+
respect_footer_visibility: bool = True,
|
|
22
|
+
) -> list["Reference"]:
|
|
23
|
+
"""Return references visible for the current context."""
|
|
24
|
+
|
|
25
|
+
if site is None and request is not None:
|
|
26
|
+
try:
|
|
27
|
+
host = request.get_host().split(":")[0]
|
|
28
|
+
except Exception:
|
|
29
|
+
host = ""
|
|
30
|
+
if host:
|
|
31
|
+
site = Site.objects.filter(domain__iexact=host).first()
|
|
32
|
+
|
|
33
|
+
site_id = site.pk if site else None
|
|
34
|
+
|
|
35
|
+
if node is None:
|
|
36
|
+
try:
|
|
37
|
+
from nodes.models import Node # imported lazily to avoid circular import
|
|
38
|
+
|
|
39
|
+
node = Node.get_local()
|
|
40
|
+
except Exception:
|
|
41
|
+
node = None
|
|
42
|
+
|
|
43
|
+
node_role_id = getattr(node, "role_id", None)
|
|
44
|
+
node_active_feature_ids: set[int] = set()
|
|
45
|
+
if node is not None:
|
|
46
|
+
assignments_manager = getattr(node, "feature_assignments", None)
|
|
47
|
+
if assignments_manager is not None:
|
|
48
|
+
try:
|
|
49
|
+
assignments = list(
|
|
50
|
+
assignments_manager.filter(is_deleted=False).select_related(
|
|
51
|
+
"feature"
|
|
52
|
+
)
|
|
53
|
+
)
|
|
54
|
+
except Exception:
|
|
55
|
+
assignments = []
|
|
56
|
+
for assignment in assignments:
|
|
57
|
+
feature = getattr(assignment, "feature", None)
|
|
58
|
+
if feature is None or getattr(feature, "is_deleted", False):
|
|
59
|
+
continue
|
|
60
|
+
try:
|
|
61
|
+
if feature.is_enabled:
|
|
62
|
+
node_active_feature_ids.add(feature.pk)
|
|
63
|
+
except Exception:
|
|
64
|
+
continue
|
|
65
|
+
|
|
66
|
+
visible_refs: list["Reference"] = []
|
|
67
|
+
for ref in refs:
|
|
68
|
+
required_roles = {role.pk for role in ref.roles.all()}
|
|
69
|
+
required_features = {feature.pk for feature in ref.features.all()}
|
|
70
|
+
required_sites = {current_site.pk for current_site in ref.sites.all()}
|
|
71
|
+
|
|
72
|
+
if required_roles or required_features or required_sites:
|
|
73
|
+
allowed = False
|
|
74
|
+
if required_roles and node_role_id and node_role_id in required_roles:
|
|
75
|
+
allowed = True
|
|
76
|
+
elif (
|
|
77
|
+
required_features
|
|
78
|
+
and node_active_feature_ids
|
|
79
|
+
and node_active_feature_ids.intersection(required_features)
|
|
80
|
+
):
|
|
81
|
+
allowed = True
|
|
82
|
+
elif required_sites and site_id and site_id in required_sites:
|
|
83
|
+
allowed = True
|
|
84
|
+
|
|
85
|
+
if not allowed:
|
|
86
|
+
continue
|
|
87
|
+
|
|
88
|
+
if respect_footer_visibility:
|
|
89
|
+
if ref.footer_visibility == ref.FOOTER_PUBLIC:
|
|
90
|
+
visible_refs.append(ref)
|
|
91
|
+
elif (
|
|
92
|
+
ref.footer_visibility == ref.FOOTER_PRIVATE
|
|
93
|
+
and request
|
|
94
|
+
and request.user.is_authenticated
|
|
95
|
+
):
|
|
96
|
+
visible_refs.append(ref)
|
|
97
|
+
elif (
|
|
98
|
+
ref.footer_visibility == ref.FOOTER_STAFF
|
|
99
|
+
and request
|
|
100
|
+
and request.user.is_authenticated
|
|
101
|
+
and request.user.is_staff
|
|
102
|
+
):
|
|
103
|
+
visible_refs.append(ref)
|
|
104
|
+
else:
|
|
105
|
+
visible_refs.append(ref)
|
|
106
|
+
|
|
107
|
+
return visible_refs
|
|
108
|
+
|
core/sigil_builder.py
CHANGED
|
@@ -17,7 +17,7 @@ from .sigil_resolver import (
|
|
|
17
17
|
def generate_model_sigils(**kwargs) -> None:
|
|
18
18
|
"""Ensure built-in configuration SigilRoot entries exist."""
|
|
19
19
|
SigilRoot = apps.get_model("core", "SigilRoot")
|
|
20
|
-
for prefix in ["ENV", "SYS"]:
|
|
20
|
+
for prefix in ["ENV", "CONF", "SYS"]:
|
|
21
21
|
# Ensure built-in configuration roots exist without violating the
|
|
22
22
|
# unique ``prefix`` constraint, even if older databases already have
|
|
23
23
|
# entries with a different ``context_type``.
|
|
@@ -40,7 +40,12 @@ def _sigil_builder_view(request):
|
|
|
40
40
|
{
|
|
41
41
|
"prefix": "ENV",
|
|
42
42
|
"url": reverse("admin:environment"),
|
|
43
|
-
"label": _("
|
|
43
|
+
"label": _("Environ"),
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"prefix": "CONF",
|
|
47
|
+
"url": reverse("admin:config"),
|
|
48
|
+
"label": _("Config"),
|
|
44
49
|
},
|
|
45
50
|
{
|
|
46
51
|
"prefix": "SYS",
|
|
@@ -88,16 +93,27 @@ def _sigil_builder_view(request):
|
|
|
88
93
|
|
|
89
94
|
sigils_text = ""
|
|
90
95
|
resolved_text = ""
|
|
96
|
+
show_sigils_input = True
|
|
97
|
+
show_result = False
|
|
91
98
|
if request.method == "POST":
|
|
92
99
|
sigils_text = request.POST.get("sigils_text", "")
|
|
100
|
+
source_text = sigils_text
|
|
93
101
|
upload = request.FILES.get("sigils_file")
|
|
94
102
|
if upload:
|
|
95
|
-
|
|
103
|
+
source_text = upload.read().decode("utf-8", errors="ignore")
|
|
104
|
+
show_sigils_input = False
|
|
96
105
|
else:
|
|
97
106
|
single = request.POST.get("sigil", "")
|
|
98
107
|
if single:
|
|
99
|
-
|
|
100
|
-
|
|
108
|
+
source_text = (
|
|
109
|
+
f"[{single}]" if not single.startswith("[") else single
|
|
110
|
+
)
|
|
111
|
+
sigils_text = source_text
|
|
112
|
+
if source_text:
|
|
113
|
+
resolved_text = resolve_sigils_in_text(source_text)
|
|
114
|
+
show_result = True
|
|
115
|
+
if upload:
|
|
116
|
+
sigils_text = ""
|
|
101
117
|
|
|
102
118
|
context = admin.site.each_context(request)
|
|
103
119
|
context.update(
|
|
@@ -108,6 +124,8 @@ def _sigil_builder_view(request):
|
|
|
108
124
|
"auto_fields": auto_fields,
|
|
109
125
|
"sigils_text": sigils_text,
|
|
110
126
|
"resolved_text": resolved_text,
|
|
127
|
+
"show_sigils_input": show_sigils_input,
|
|
128
|
+
"show_result": show_result,
|
|
111
129
|
}
|
|
112
130
|
)
|
|
113
131
|
return TemplateResponse(request, "admin/sigil_builder.html", context)
|
core/sigil_resolver.py
CHANGED
|
@@ -11,6 +11,7 @@ from django.core import serializers
|
|
|
11
11
|
from django.db import models
|
|
12
12
|
|
|
13
13
|
from .sigil_context import get_context
|
|
14
|
+
from .system import get_system_sigil_values, resolve_system_namespace_value
|
|
14
15
|
|
|
15
16
|
logger = logging.getLogger("core.entity")
|
|
16
17
|
|
|
@@ -150,6 +151,18 @@ def _resolve_token(token: str, current: Optional[models.Model] = None) -> str:
|
|
|
150
151
|
SigilRoot = apps.get_model("core", "SigilRoot")
|
|
151
152
|
try:
|
|
152
153
|
root = SigilRoot.objects.get(prefix__iexact=lookup_root)
|
|
154
|
+
except SigilRoot.DoesNotExist:
|
|
155
|
+
logger.warning("Unknown sigil root [%s]", lookup_root)
|
|
156
|
+
return _failed_resolution(original_token)
|
|
157
|
+
except Exception:
|
|
158
|
+
logger.exception(
|
|
159
|
+
"Error resolving sigil [%s.%s]",
|
|
160
|
+
lookup_root,
|
|
161
|
+
key_upper or normalized_key or raw_key,
|
|
162
|
+
)
|
|
163
|
+
return _failed_resolution(original_token)
|
|
164
|
+
|
|
165
|
+
try:
|
|
153
166
|
if root.context_type == SigilRoot.Context.CONFIG:
|
|
154
167
|
if not normalized_key:
|
|
155
168
|
return ""
|
|
@@ -176,7 +189,7 @@ def _resolve_token(token: str, current: Optional[models.Model] = None) -> str:
|
|
|
176
189
|
key_upper or normalized_key or raw_key or "",
|
|
177
190
|
)
|
|
178
191
|
return _failed_resolution(original_token)
|
|
179
|
-
if root.prefix.upper() == "
|
|
192
|
+
if root.prefix.upper() == "CONF":
|
|
180
193
|
for candidate in [normalized_key, key_upper, key_lower]:
|
|
181
194
|
if not candidate:
|
|
182
195
|
continue
|
|
@@ -188,6 +201,26 @@ def _resolve_token(token: str, current: Optional[models.Model] = None) -> str:
|
|
|
188
201
|
if fallback is not None:
|
|
189
202
|
return fallback
|
|
190
203
|
return ""
|
|
204
|
+
if root.prefix.upper() == "SYS":
|
|
205
|
+
values = get_system_sigil_values()
|
|
206
|
+
candidates = {
|
|
207
|
+
key_upper,
|
|
208
|
+
normalized_key.upper() if normalized_key else None,
|
|
209
|
+
(raw_key or "").upper(),
|
|
210
|
+
}
|
|
211
|
+
for candidate in candidates:
|
|
212
|
+
if not candidate:
|
|
213
|
+
continue
|
|
214
|
+
if candidate in values:
|
|
215
|
+
return values[candidate]
|
|
216
|
+
resolved = resolve_system_namespace_value(candidate)
|
|
217
|
+
if resolved is not None:
|
|
218
|
+
return resolved
|
|
219
|
+
logger.warning(
|
|
220
|
+
"Missing system information for sigil [SYS.%s]",
|
|
221
|
+
key_upper or normalized_key or raw_key or "",
|
|
222
|
+
)
|
|
223
|
+
return _failed_resolution(original_token)
|
|
191
224
|
elif root.context_type == SigilRoot.Context.ENTITY:
|
|
192
225
|
model = root.content_type.model_class() if root.content_type else None
|
|
193
226
|
instance = None
|
|
@@ -243,15 +276,13 @@ def _resolve_token(token: str, current: Optional[models.Model] = None) -> str:
|
|
|
243
276
|
return _failed_resolution(original_token)
|
|
244
277
|
return serializers.serialize("json", [instance])
|
|
245
278
|
return _failed_resolution(original_token)
|
|
246
|
-
except SigilRoot.DoesNotExist:
|
|
247
|
-
logger.warning("Unknown sigil root [%s]", lookup_root)
|
|
248
279
|
except Exception:
|
|
249
280
|
logger.exception(
|
|
250
281
|
"Error resolving sigil [%s.%s]",
|
|
251
282
|
lookup_root,
|
|
252
283
|
key_upper or normalized_key or raw_key,
|
|
253
284
|
)
|
|
254
|
-
|
|
285
|
+
return _failed_resolution(original_token)
|
|
255
286
|
|
|
256
287
|
|
|
257
288
|
def resolve_sigils(text: str, current: Optional[models.Model] = None) -> str:
|