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.

Files changed (51) hide show
  1. {arthexis-0.1.9.dist-info → arthexis-0.1.11.dist-info}/METADATA +76 -23
  2. arthexis-0.1.11.dist-info/RECORD +99 -0
  3. config/context_processors.py +1 -0
  4. config/settings.py +245 -26
  5. config/urls.py +11 -4
  6. core/admin.py +585 -57
  7. core/apps.py +29 -1
  8. core/auto_upgrade.py +57 -0
  9. core/backends.py +115 -3
  10. core/environment.py +23 -5
  11. core/fields.py +93 -0
  12. core/mailer.py +3 -1
  13. core/models.py +482 -38
  14. core/reference_utils.py +108 -0
  15. core/sigil_builder.py +23 -5
  16. core/sigil_resolver.py +35 -4
  17. core/system.py +400 -140
  18. core/tasks.py +151 -8
  19. core/temp_passwords.py +181 -0
  20. core/test_system_info.py +97 -1
  21. core/tests.py +393 -15
  22. core/user_data.py +154 -16
  23. core/views.py +499 -20
  24. nodes/admin.py +149 -6
  25. nodes/backends.py +125 -18
  26. nodes/dns.py +203 -0
  27. nodes/models.py +498 -9
  28. nodes/tests.py +682 -3
  29. nodes/views.py +154 -7
  30. ocpp/admin.py +63 -3
  31. ocpp/consumers.py +255 -41
  32. ocpp/evcs.py +6 -3
  33. ocpp/models.py +52 -7
  34. ocpp/reference_utils.py +42 -0
  35. ocpp/simulator.py +62 -5
  36. ocpp/store.py +30 -0
  37. ocpp/test_rfid.py +169 -7
  38. ocpp/tests.py +414 -8
  39. ocpp/views.py +109 -76
  40. pages/admin.py +9 -1
  41. pages/context_processors.py +24 -4
  42. pages/defaults.py +14 -0
  43. pages/forms.py +131 -0
  44. pages/models.py +53 -14
  45. pages/tests.py +450 -14
  46. pages/urls.py +4 -0
  47. pages/views.py +419 -110
  48. arthexis-0.1.9.dist-info/RECORD +0 -92
  49. {arthexis-0.1.9.dist-info → arthexis-0.1.11.dist-info}/WHEEL +0 -0
  50. {arthexis-0.1.9.dist-info → arthexis-0.1.11.dist-info}/licenses/LICENSE +0 -0
  51. {arthexis-0.1.9.dist-info → arthexis-0.1.11.dist-info}/top_level.txt +0 -0
@@ -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": _("Environment"),
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
- sigils_text = upload.read().decode("utf-8", errors="ignore")
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
- sigils_text = f"[{single}]" if not single.startswith("[") else single
100
- resolved_text = resolve_sigils_in_text(sigils_text) if sigils_text else ""
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() == "SYS":
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
- return _failed_resolution(original_token)
285
+ return _failed_resolution(original_token)
255
286
 
256
287
 
257
288
  def resolve_sigils(text: str, current: Optional[models.Model] = None) -> str: