arthexis 0.1.13__py3-none-any.whl → 0.1.14__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 (107) hide show
  1. {arthexis-0.1.13.dist-info → arthexis-0.1.14.dist-info}/METADATA +222 -221
  2. arthexis-0.1.14.dist-info/RECORD +109 -0
  3. {arthexis-0.1.13.dist-info → arthexis-0.1.14.dist-info}/licenses/LICENSE +674 -674
  4. config/__init__.py +5 -5
  5. config/active_app.py +15 -15
  6. config/asgi.py +43 -43
  7. config/auth_app.py +7 -7
  8. config/celery.py +32 -32
  9. config/context_processors.py +67 -69
  10. config/horologia_app.py +7 -7
  11. config/loadenv.py +11 -11
  12. config/logging.py +59 -48
  13. config/middleware.py +25 -25
  14. config/offline.py +49 -49
  15. config/settings.py +691 -682
  16. config/settings_helpers.py +109 -109
  17. config/urls.py +171 -166
  18. config/wsgi.py +17 -17
  19. core/admin.py +3771 -2809
  20. core/admin_history.py +50 -50
  21. core/admindocs.py +151 -151
  22. core/apps.py +356 -272
  23. core/auto_upgrade.py +57 -57
  24. core/backends.py +265 -236
  25. core/changelog.py +342 -0
  26. core/entity.py +133 -133
  27. core/environment.py +61 -61
  28. core/fields.py +168 -168
  29. core/form_fields.py +75 -75
  30. core/github_helper.py +188 -25
  31. core/github_issues.py +178 -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 +100 -100
  36. core/mailer.py +85 -85
  37. core/middleware.py +91 -91
  38. core/models.py +3609 -2795
  39. core/notifications.py +105 -105
  40. core/public_wifi.py +267 -227
  41. core/reference_utils.py +108 -108
  42. core/release.py +721 -368
  43. core/rfid_import_export.py +113 -0
  44. core/sigil_builder.py +149 -149
  45. core/sigil_context.py +20 -20
  46. core/sigil_resolver.py +315 -315
  47. core/system.py +752 -493
  48. core/tasks.py +408 -394
  49. core/temp_passwords.py +181 -181
  50. core/test_system_info.py +186 -139
  51. core/tests.py +2095 -1521
  52. core/tests_liveupdate.py +17 -17
  53. core/urls.py +11 -11
  54. core/user_data.py +641 -633
  55. core/views.py +2175 -1417
  56. core/widgets.py +213 -94
  57. core/workgroup_urls.py +17 -17
  58. core/workgroup_views.py +94 -94
  59. nodes/admin.py +1720 -1161
  60. nodes/apps.py +87 -85
  61. nodes/backends.py +160 -160
  62. nodes/dns.py +203 -203
  63. nodes/feature_checks.py +133 -133
  64. nodes/lcd.py +165 -165
  65. nodes/models.py +1737 -1597
  66. nodes/reports.py +411 -411
  67. nodes/rfid_sync.py +195 -0
  68. nodes/signals.py +18 -0
  69. nodes/tasks.py +46 -46
  70. nodes/tests.py +3810 -3116
  71. nodes/urls.py +15 -14
  72. nodes/utils.py +121 -105
  73. nodes/views.py +683 -619
  74. ocpp/admin.py +948 -948
  75. ocpp/apps.py +25 -25
  76. ocpp/consumers.py +1565 -1459
  77. ocpp/evcs.py +844 -844
  78. ocpp/evcs_discovery.py +158 -158
  79. ocpp/models.py +917 -917
  80. ocpp/reference_utils.py +42 -42
  81. ocpp/routing.py +11 -11
  82. ocpp/simulator.py +745 -745
  83. ocpp/status_display.py +26 -26
  84. ocpp/store.py +601 -541
  85. ocpp/tasks.py +31 -31
  86. ocpp/test_export_import.py +130 -130
  87. ocpp/test_rfid.py +913 -702
  88. ocpp/tests.py +4445 -4094
  89. ocpp/transactions_io.py +189 -189
  90. ocpp/urls.py +50 -50
  91. ocpp/views.py +1479 -1251
  92. pages/admin.py +708 -539
  93. pages/apps.py +10 -10
  94. pages/checks.py +40 -40
  95. pages/context_processors.py +127 -119
  96. pages/defaults.py +13 -13
  97. pages/forms.py +198 -198
  98. pages/middleware.py +205 -153
  99. pages/models.py +607 -426
  100. pages/tests.py +2612 -2200
  101. pages/urls.py +25 -25
  102. pages/utils.py +12 -12
  103. pages/views.py +1165 -1128
  104. arthexis-0.1.13.dist-info/RECORD +0 -105
  105. nodes/actions.py +0 -70
  106. {arthexis-0.1.13.dist-info → arthexis-0.1.14.dist-info}/WHEEL +0 -0
  107. {arthexis-0.1.13.dist-info → arthexis-0.1.14.dist-info}/top_level.txt +0 -0
pages/middleware.py CHANGED
@@ -1,153 +1,205 @@
1
- """Middleware helpers for the pages application."""
2
-
3
- from __future__ import annotations
4
-
5
- import ipaddress
6
- import logging
7
- from http import HTTPStatus
8
-
9
- from django.conf import settings
10
- from django.urls import Resolver404, resolve
11
-
12
- from .models import ViewHistory
13
-
14
-
15
- logger = logging.getLogger(__name__)
16
-
17
-
18
- class ViewHistoryMiddleware:
19
- """Persist public site visits for analytics."""
20
-
21
- _EXCLUDED_PREFIXES = ("/admin", "/__debug__", "/healthz", "/status")
22
-
23
- def __init__(self, get_response):
24
- self.get_response = get_response
25
- static_url = getattr(settings, "STATIC_URL", "") or ""
26
- media_url = getattr(settings, "MEDIA_URL", "") or ""
27
- self._skipped_prefixes = tuple(
28
- prefix.rstrip("/") for prefix in (static_url, media_url) if prefix
29
- )
30
-
31
- def __call__(self, request):
32
- should_track = self._should_track(request)
33
- if not should_track:
34
- return self.get_response(request)
35
-
36
- error_message = ""
37
- try:
38
- response = self.get_response(request)
39
- except Exception as exc: # pragma: no cover - re-raised for Django
40
- status_code = getattr(exc, "status_code", 500) or 500
41
- error_message = str(exc)
42
- self._record_visit(request, status_code, error_message)
43
- raise
44
- else:
45
- status_code = getattr(response, "status_code", 0) or 0
46
- self._record_visit(request, status_code, error_message)
47
- return response
48
-
49
- def _should_track(self, request) -> bool:
50
- method = request.method.upper()
51
- if method not in {"GET", "HEAD"}:
52
- return False
53
-
54
- path = request.path
55
- if any(path.startswith(prefix) for prefix in self._EXCLUDED_PREFIXES):
56
- return False
57
-
58
- if any(path.startswith(prefix) for prefix in self._skipped_prefixes):
59
- return False
60
-
61
- if path.startswith("/favicon") or path.startswith("/robots.txt"):
62
- return False
63
-
64
- if "djdt" in request.GET:
65
- return False
66
-
67
- return True
68
-
69
- def _record_visit(self, request, status_code: int, error_message: str) -> None:
70
- try:
71
- status = HTTPStatus(status_code)
72
- status_text = status.phrase
73
- except ValueError:
74
- status_text = ""
75
-
76
- view_name = self._resolve_view_name(request)
77
- full_path = request.get_full_path()
78
- if not error_message and status_code >= HTTPStatus.BAD_REQUEST:
79
- error_message = status_text or f"HTTP {status_code}"
80
-
81
- try:
82
- ViewHistory.objects.create(
83
- path=full_path,
84
- method=request.method,
85
- status_code=status_code,
86
- status_text=status_text,
87
- error_message=(error_message or "")[:1000],
88
- view_name=view_name,
89
- )
90
- except Exception: # pragma: no cover - best effort logging
91
- logger.debug(
92
- "Failed to record ViewHistory for %s", full_path, exc_info=True
93
- )
94
- else:
95
- self._update_user_last_visit_ip(request)
96
-
97
- def _resolve_view_name(self, request) -> str:
98
- match = getattr(request, "resolver_match", None)
99
- if match is None:
100
- try:
101
- match = resolve(request.path_info)
102
- except Resolver404:
103
- return ""
104
-
105
- if getattr(match, "view_name", ""):
106
- return match.view_name
107
-
108
- func = getattr(match, "func", None)
109
- if func is None:
110
- return ""
111
-
112
- module = getattr(func, "__module__", "")
113
- name = getattr(func, "__name__", "")
114
- if module and name:
115
- return f"{module}.{name}"
116
- return name or module or ""
117
-
118
- def _extract_client_ip(self, request) -> str:
119
- forwarded = request.META.get("HTTP_X_FORWARDED_FOR", "")
120
- candidates = []
121
- if forwarded:
122
- candidates.extend(part.strip() for part in forwarded.split(","))
123
- remote = request.META.get("REMOTE_ADDR", "").strip()
124
- if remote:
125
- candidates.append(remote)
126
-
127
- for candidate in candidates:
128
- if not candidate:
129
- continue
130
- try:
131
- ipaddress.ip_address(candidate)
132
- except ValueError:
133
- continue
134
- return candidate
135
- return ""
136
-
137
- def _update_user_last_visit_ip(self, request) -> None:
138
- user = getattr(request, "user", None)
139
- if not getattr(user, "is_authenticated", False) or not getattr(user, "pk", None):
140
- return
141
-
142
- ip_address = self._extract_client_ip(request)
143
- if not ip_address or getattr(user, "last_visit_ip_address", None) == ip_address:
144
- return
145
-
146
- try:
147
- user.last_visit_ip_address = ip_address
148
- user.save(update_fields=["last_visit_ip_address"])
149
- except Exception: # pragma: no cover - best effort logging
150
- logger.debug(
151
- "Failed to update last_visit_ip_address for user %s", user.pk,
152
- exc_info=True,
153
- )
1
+ """Middleware helpers for the pages application."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import ipaddress
6
+ import logging
7
+ from http import HTTPStatus
8
+
9
+ from django.conf import settings
10
+ from django.urls import Resolver404, resolve
11
+
12
+ from .models import Landing, LandingLead, ViewHistory
13
+
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class ViewHistoryMiddleware:
19
+ """Persist public site visits for analytics."""
20
+
21
+ _EXCLUDED_PREFIXES = ("/admin", "/__debug__", "/healthz", "/status")
22
+
23
+ def __init__(self, get_response):
24
+ self.get_response = get_response
25
+ static_url = getattr(settings, "STATIC_URL", "") or ""
26
+ media_url = getattr(settings, "MEDIA_URL", "") or ""
27
+ self._skipped_prefixes = tuple(
28
+ prefix.rstrip("/") for prefix in (static_url, media_url) if prefix
29
+ )
30
+
31
+ def __call__(self, request):
32
+ should_track = self._should_track(request)
33
+ if not should_track:
34
+ return self.get_response(request)
35
+
36
+ error_message = ""
37
+ try:
38
+ response = self.get_response(request)
39
+ except Exception as exc: # pragma: no cover - re-raised for Django
40
+ status_code = getattr(exc, "status_code", 500) or 500
41
+ error_message = str(exc)
42
+ self._record_visit(request, status_code, error_message)
43
+ raise
44
+ else:
45
+ status_code = getattr(response, "status_code", 0) or 0
46
+ self._record_visit(request, status_code, error_message)
47
+ return response
48
+
49
+ def _should_track(self, request) -> bool:
50
+ method = request.method.upper()
51
+ if method not in {"GET", "HEAD"}:
52
+ return False
53
+
54
+ path = request.path
55
+ if any(path.startswith(prefix) for prefix in self._EXCLUDED_PREFIXES):
56
+ return False
57
+
58
+ if any(path.startswith(prefix) for prefix in self._skipped_prefixes):
59
+ return False
60
+
61
+ if path.startswith("/favicon") or path.startswith("/robots.txt"):
62
+ return False
63
+
64
+ if "djdt" in request.GET:
65
+ return False
66
+
67
+ return True
68
+
69
+ def _record_visit(self, request, status_code: int, error_message: str) -> None:
70
+ try:
71
+ status = HTTPStatus(status_code)
72
+ status_text = status.phrase
73
+ except ValueError:
74
+ status_text = ""
75
+
76
+ view_name = self._resolve_view_name(request)
77
+ full_path = request.get_full_path()
78
+ if not error_message and status_code >= HTTPStatus.BAD_REQUEST:
79
+ error_message = status_text or f"HTTP {status_code}"
80
+
81
+ landing = None
82
+ if status_code < HTTPStatus.BAD_REQUEST:
83
+ landing = self._resolve_landing(request)
84
+
85
+ try:
86
+ ViewHistory.objects.create(
87
+ path=full_path,
88
+ method=request.method,
89
+ status_code=status_code,
90
+ status_text=status_text,
91
+ error_message=(error_message or "")[:1000],
92
+ view_name=view_name,
93
+ )
94
+ except Exception: # pragma: no cover - best effort logging
95
+ logger.debug(
96
+ "Failed to record ViewHistory for %s", full_path, exc_info=True
97
+ )
98
+ else:
99
+ self._update_user_last_visit_ip(request)
100
+
101
+ if landing is not None:
102
+ self._record_landing_lead(request, landing)
103
+
104
+ def _resolve_landing(self, request):
105
+ path = request.path
106
+ if not path:
107
+ return None
108
+ try:
109
+ return (
110
+ Landing.objects.filter(
111
+ path=path,
112
+ enabled=True,
113
+ is_deleted=False,
114
+ )
115
+ .select_related("module", "module__application", "module__node_role")
116
+ .first()
117
+ )
118
+ except Exception: # pragma: no cover - best effort logging
119
+ logger.debug(
120
+ "Failed to resolve Landing for %s", path, exc_info=True
121
+ )
122
+ return None
123
+
124
+ def _record_landing_lead(self, request, landing):
125
+ if request.method.upper() != "GET":
126
+ return
127
+
128
+ referer = request.META.get("HTTP_REFERER", "") or ""
129
+ user_agent = request.META.get("HTTP_USER_AGENT", "") or ""
130
+ ip_address = self._extract_client_ip(request) or None
131
+ user = getattr(request, "user", None)
132
+ if not getattr(user, "is_authenticated", False):
133
+ user = None
134
+
135
+ try:
136
+ LandingLead.objects.create(
137
+ landing=landing,
138
+ user=user,
139
+ path=request.get_full_path(),
140
+ referer=referer,
141
+ user_agent=user_agent,
142
+ ip_address=ip_address,
143
+ )
144
+ except Exception: # pragma: no cover - best effort logging
145
+ logger.debug(
146
+ "Failed to record LandingLead for %s", landing.path, exc_info=True
147
+ )
148
+
149
+ def _resolve_view_name(self, request) -> str:
150
+ match = getattr(request, "resolver_match", None)
151
+ if match is None:
152
+ try:
153
+ match = resolve(request.path_info)
154
+ except Resolver404:
155
+ return ""
156
+
157
+ if getattr(match, "view_name", ""):
158
+ return match.view_name
159
+
160
+ func = getattr(match, "func", None)
161
+ if func is None:
162
+ return ""
163
+
164
+ module = getattr(func, "__module__", "")
165
+ name = getattr(func, "__name__", "")
166
+ if module and name:
167
+ return f"{module}.{name}"
168
+ return name or module or ""
169
+
170
+ def _extract_client_ip(self, request) -> str:
171
+ forwarded = request.META.get("HTTP_X_FORWARDED_FOR", "")
172
+ candidates = []
173
+ if forwarded:
174
+ candidates.extend(part.strip() for part in forwarded.split(","))
175
+ remote = request.META.get("REMOTE_ADDR", "").strip()
176
+ if remote:
177
+ candidates.append(remote)
178
+
179
+ for candidate in candidates:
180
+ if not candidate:
181
+ continue
182
+ try:
183
+ ipaddress.ip_address(candidate)
184
+ except ValueError:
185
+ continue
186
+ return candidate
187
+ return ""
188
+
189
+ def _update_user_last_visit_ip(self, request) -> None:
190
+ user = getattr(request, "user", None)
191
+ if not getattr(user, "is_authenticated", False) or not getattr(user, "pk", None):
192
+ return
193
+
194
+ ip_address = self._extract_client_ip(request)
195
+ if not ip_address or getattr(user, "last_visit_ip_address", None) == ip_address:
196
+ return
197
+
198
+ try:
199
+ user.last_visit_ip_address = ip_address
200
+ user.save(update_fields=["last_visit_ip_address"])
201
+ except Exception: # pragma: no cover - best effort logging
202
+ logger.debug(
203
+ "Failed to update last_visit_ip_address for user %s", user.pk,
204
+ exc_info=True,
205
+ )