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/user_data.py
CHANGED
|
@@ -16,6 +16,7 @@ from django.dispatch import receiver
|
|
|
16
16
|
from django.http import HttpResponse, HttpResponseRedirect
|
|
17
17
|
from django.template.response import TemplateResponse
|
|
18
18
|
from django.urls import path, reverse
|
|
19
|
+
from django.utils.functional import LazyObject
|
|
19
20
|
from django.utils.translation import gettext as _
|
|
20
21
|
|
|
21
22
|
from .entity import Entity
|
|
@@ -39,7 +40,14 @@ def _username_for(user) -> str:
|
|
|
39
40
|
|
|
40
41
|
|
|
41
42
|
def _user_allows_user_data(user) -> bool:
|
|
42
|
-
|
|
43
|
+
if not user:
|
|
44
|
+
return False
|
|
45
|
+
username = _username_for(user)
|
|
46
|
+
UserModel = get_user_model()
|
|
47
|
+
system_username = getattr(UserModel, "SYSTEM_USERNAME", "")
|
|
48
|
+
if system_username and username == system_username:
|
|
49
|
+
return True
|
|
50
|
+
return not getattr(user, "is_profile_restricted", False)
|
|
43
51
|
|
|
44
52
|
|
|
45
53
|
def _data_dir(user) -> Path:
|
|
@@ -93,18 +101,68 @@ def _seed_fixture_path(instance) -> Path | None:
|
|
|
93
101
|
return None
|
|
94
102
|
|
|
95
103
|
|
|
104
|
+
def _coerce_user(candidate, user_model):
|
|
105
|
+
if candidate is None:
|
|
106
|
+
return None
|
|
107
|
+
if isinstance(candidate, user_model):
|
|
108
|
+
return candidate
|
|
109
|
+
if isinstance(candidate, LazyObject):
|
|
110
|
+
try:
|
|
111
|
+
candidate._setup()
|
|
112
|
+
except Exception:
|
|
113
|
+
return None
|
|
114
|
+
return _coerce_user(candidate._wrapped, user_model)
|
|
115
|
+
return None
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _select_fixture_user(candidate, user_model):
|
|
119
|
+
user = _coerce_user(candidate, user_model)
|
|
120
|
+
visited: set[int] = set()
|
|
121
|
+
while user is not None:
|
|
122
|
+
identifier = user.pk or id(user)
|
|
123
|
+
if identifier in visited:
|
|
124
|
+
break
|
|
125
|
+
visited.add(identifier)
|
|
126
|
+
username = _username_for(user)
|
|
127
|
+
admin_username = getattr(user_model, "ADMIN_USERNAME", "")
|
|
128
|
+
if admin_username and username == admin_username:
|
|
129
|
+
try:
|
|
130
|
+
delegate = getattr(user, "operate_as", None)
|
|
131
|
+
except user_model.DoesNotExist:
|
|
132
|
+
delegate = None
|
|
133
|
+
else:
|
|
134
|
+
delegate = _coerce_user(delegate, user_model)
|
|
135
|
+
if delegate is not None and delegate is not user:
|
|
136
|
+
user = delegate
|
|
137
|
+
continue
|
|
138
|
+
if _user_allows_user_data(user):
|
|
139
|
+
return user
|
|
140
|
+
try:
|
|
141
|
+
delegate = getattr(user, "operate_as", None)
|
|
142
|
+
except user_model.DoesNotExist:
|
|
143
|
+
delegate = None
|
|
144
|
+
user = _coerce_user(delegate, user_model)
|
|
145
|
+
return None
|
|
146
|
+
|
|
147
|
+
|
|
96
148
|
def _resolve_fixture_user(instance, fallback=None):
|
|
97
149
|
UserModel = get_user_model()
|
|
98
150
|
owner = getattr(instance, "user", None)
|
|
99
|
-
|
|
100
|
-
|
|
151
|
+
selected = _select_fixture_user(owner, UserModel)
|
|
152
|
+
if selected is not None:
|
|
153
|
+
return selected
|
|
101
154
|
if hasattr(instance, "owner"):
|
|
102
155
|
try:
|
|
103
156
|
owner_value = instance.owner
|
|
104
157
|
except Exception:
|
|
105
158
|
owner_value = None
|
|
106
|
-
|
|
107
|
-
|
|
159
|
+
else:
|
|
160
|
+
selected = _select_fixture_user(owner_value, UserModel)
|
|
161
|
+
if selected is not None:
|
|
162
|
+
return selected
|
|
163
|
+
selected = _select_fixture_user(fallback, UserModel)
|
|
164
|
+
if selected is not None:
|
|
165
|
+
return selected
|
|
108
166
|
return fallback
|
|
109
167
|
|
|
110
168
|
|
|
@@ -180,6 +238,36 @@ def _mark_fixture_user_data(path: Path) -> None:
|
|
|
180
238
|
model.all_objects.filter(pk=pk).update(is_user_data=True)
|
|
181
239
|
|
|
182
240
|
|
|
241
|
+
def _fixture_targets_installed_apps(data) -> bool:
|
|
242
|
+
"""Return ``True`` when *data* only targets installed apps and models."""
|
|
243
|
+
|
|
244
|
+
if not isinstance(data, list):
|
|
245
|
+
return True
|
|
246
|
+
|
|
247
|
+
labels = {
|
|
248
|
+
obj.get("model")
|
|
249
|
+
for obj in data
|
|
250
|
+
if isinstance(obj, dict) and obj.get("model")
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
for label in labels:
|
|
254
|
+
if not isinstance(label, str):
|
|
255
|
+
continue
|
|
256
|
+
if "." not in label:
|
|
257
|
+
continue
|
|
258
|
+
app_label, model_name = label.split(".", 1)
|
|
259
|
+
if not app_label or not model_name:
|
|
260
|
+
continue
|
|
261
|
+
if not apps.is_installed(app_label):
|
|
262
|
+
return False
|
|
263
|
+
try:
|
|
264
|
+
apps.get_model(label)
|
|
265
|
+
except LookupError:
|
|
266
|
+
return False
|
|
267
|
+
|
|
268
|
+
return True
|
|
269
|
+
|
|
270
|
+
|
|
183
271
|
def _load_fixture(path: Path, *, mark_user_data: bool = True) -> bool:
|
|
184
272
|
"""Load a fixture from *path* and optionally flag loaded entities."""
|
|
185
273
|
|
|
@@ -203,9 +291,12 @@ def _load_fixture(path: Path, *, mark_user_data: bool = True) -> bool:
|
|
|
203
291
|
except Exception:
|
|
204
292
|
data = None
|
|
205
293
|
else:
|
|
206
|
-
if isinstance(data, list)
|
|
207
|
-
|
|
208
|
-
|
|
294
|
+
if isinstance(data, list):
|
|
295
|
+
if not data:
|
|
296
|
+
path.unlink(missing_ok=True)
|
|
297
|
+
return False
|
|
298
|
+
if not _fixture_targets_installed_apps(data):
|
|
299
|
+
return False
|
|
209
300
|
|
|
210
301
|
try:
|
|
211
302
|
call_command("loaddata", str(path), ignorenonexistent=True)
|
|
@@ -230,6 +321,30 @@ def _is_user_fixture(path: Path) -> bool:
|
|
|
230
321
|
return len(parts) >= 2 and parts[1].lower() == "user"
|
|
231
322
|
|
|
232
323
|
|
|
324
|
+
def _get_request_ip(request) -> str:
|
|
325
|
+
"""Return the best-effort client IP for ``request``."""
|
|
326
|
+
|
|
327
|
+
if request is None:
|
|
328
|
+
return ""
|
|
329
|
+
|
|
330
|
+
meta = getattr(request, "META", None)
|
|
331
|
+
if not getattr(meta, "get", None):
|
|
332
|
+
return ""
|
|
333
|
+
|
|
334
|
+
forwarded = meta.get("HTTP_X_FORWARDED_FOR")
|
|
335
|
+
if forwarded:
|
|
336
|
+
for value in str(forwarded).split(","):
|
|
337
|
+
candidate = value.strip()
|
|
338
|
+
if candidate:
|
|
339
|
+
return candidate
|
|
340
|
+
|
|
341
|
+
remote = meta.get("REMOTE_ADDR")
|
|
342
|
+
if remote:
|
|
343
|
+
return str(remote).strip()
|
|
344
|
+
|
|
345
|
+
return ""
|
|
346
|
+
|
|
347
|
+
|
|
233
348
|
_shared_fixtures_loaded = False
|
|
234
349
|
|
|
235
350
|
|
|
@@ -260,6 +375,18 @@ def load_user_fixtures(user, *, include_shared: bool = False) -> None:
|
|
|
260
375
|
def _on_login(sender, request, user, **kwargs):
|
|
261
376
|
load_user_fixtures(user, include_shared=not _shared_fixtures_loaded)
|
|
262
377
|
|
|
378
|
+
if not (
|
|
379
|
+
getattr(user, "is_staff", False) or getattr(user, "is_superuser", False)
|
|
380
|
+
):
|
|
381
|
+
return
|
|
382
|
+
|
|
383
|
+
username = _username_for(user) or "unknown"
|
|
384
|
+
ip_address = _get_request_ip(request) or "unknown"
|
|
385
|
+
|
|
386
|
+
from nodes.models import NetMessage
|
|
387
|
+
|
|
388
|
+
NetMessage.broadcast(subject=f"login {username}", body=f"@ {ip_address}")
|
|
389
|
+
|
|
263
390
|
|
|
264
391
|
@receiver(post_save, sender=settings.AUTH_USER_MODEL)
|
|
265
392
|
def _on_user_created(sender, instance, created, **kwargs):
|
|
@@ -274,9 +401,7 @@ class UserDatumAdminMixin(admin.ModelAdmin):
|
|
|
274
401
|
def render_change_form(
|
|
275
402
|
self, request, context, add=False, change=False, form_url="", obj=None
|
|
276
403
|
):
|
|
277
|
-
context["show_user_datum"] = issubclass(
|
|
278
|
-
self.model, Entity
|
|
279
|
-
) and _user_allows_user_data(request.user)
|
|
404
|
+
context["show_user_datum"] = issubclass(self.model, Entity)
|
|
280
405
|
context["show_seed_datum"] = issubclass(self.model, Entity)
|
|
281
406
|
context["show_save_as_copy"] = issubclass(self.model, Entity) or hasattr(
|
|
282
407
|
self.model, "clone"
|
|
@@ -317,6 +442,9 @@ class EntityModelAdmin(UserDatumAdminMixin, admin.ModelAdmin):
|
|
|
317
442
|
)
|
|
318
443
|
if copied:
|
|
319
444
|
return
|
|
445
|
+
if getattr(self, "_skip_entity_user_datum", False):
|
|
446
|
+
return
|
|
447
|
+
|
|
320
448
|
target_user = _resolve_fixture_user(obj, request.user)
|
|
321
449
|
allow_user_data = _user_allows_user_data(target_user)
|
|
322
450
|
if request.POST.get("_user_datum") == "on":
|
|
@@ -389,11 +517,23 @@ def patch_admin_user_datum() -> None:
|
|
|
389
517
|
admin.site._user_datum_patched = True
|
|
390
518
|
|
|
391
519
|
|
|
392
|
-
def
|
|
393
|
-
|
|
520
|
+
def _iter_entity_admin_models():
|
|
521
|
+
"""Yield registered :class:`Entity` admin models without proxy duplicates."""
|
|
522
|
+
|
|
523
|
+
seen: set[type] = set()
|
|
394
524
|
for model, model_admin in admin.site._registry.items():
|
|
395
525
|
if not issubclass(model, Entity):
|
|
396
526
|
continue
|
|
527
|
+
concrete_model = model._meta.concrete_model
|
|
528
|
+
if concrete_model in seen:
|
|
529
|
+
continue
|
|
530
|
+
seen.add(concrete_model)
|
|
531
|
+
yield model, model_admin
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
def _seed_data_view(request):
|
|
535
|
+
sections = []
|
|
536
|
+
for model, model_admin in _iter_entity_admin_models():
|
|
397
537
|
objs = model.objects.filter(is_seed_data=True)
|
|
398
538
|
if not objs.exists():
|
|
399
539
|
continue
|
|
@@ -413,9 +553,7 @@ def _seed_data_view(request):
|
|
|
413
553
|
|
|
414
554
|
def _user_data_view(request):
|
|
415
555
|
sections = []
|
|
416
|
-
for model, model_admin in
|
|
417
|
-
if not issubclass(model, Entity):
|
|
418
|
-
continue
|
|
556
|
+
for model, model_admin in _iter_entity_admin_models():
|
|
419
557
|
objs = model.objects.filter(is_user_data=True)
|
|
420
558
|
if not objs.exists():
|
|
421
559
|
continue
|