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
pages/middleware.py CHANGED
@@ -1,153 +1,213 @@
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
+ from .utils import cache_original_referer, get_original_referer, landing_leads_supported
14
+
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class ViewHistoryMiddleware:
20
+ """Persist public site visits for analytics."""
21
+
22
+ _EXCLUDED_PREFIXES = ("/admin", "/__debug__", "/healthz", "/status")
23
+
24
+ def __init__(self, get_response):
25
+ self.get_response = get_response
26
+ static_url = getattr(settings, "STATIC_URL", "") or ""
27
+ media_url = getattr(settings, "MEDIA_URL", "") or ""
28
+ self._skipped_prefixes = tuple(
29
+ prefix.rstrip("/") for prefix in (static_url, media_url) if prefix
30
+ )
31
+
32
+ def __call__(self, request):
33
+ cache_original_referer(request)
34
+ should_track = self._should_track(request)
35
+ if not should_track:
36
+ return self.get_response(request)
37
+
38
+ error_message = ""
39
+ try:
40
+ response = self.get_response(request)
41
+ except Exception as exc: # pragma: no cover - re-raised for Django
42
+ status_code = getattr(exc, "status_code", 500) or 500
43
+ error_message = str(exc)
44
+ self._record_visit(request, status_code, error_message)
45
+ raise
46
+ else:
47
+ status_code = getattr(response, "status_code", 0) or 0
48
+ self._record_visit(request, status_code, error_message)
49
+ return response
50
+
51
+ def _should_track(self, request) -> bool:
52
+ method = request.method.upper()
53
+ if method not in {"GET", "HEAD"}:
54
+ return False
55
+
56
+ path = request.path
57
+ if any(path.startswith(prefix) for prefix in self._EXCLUDED_PREFIXES):
58
+ return False
59
+
60
+ if any(path.startswith(prefix) for prefix in self._skipped_prefixes):
61
+ return False
62
+
63
+ if path.startswith("/favicon") or path.startswith("/robots.txt"):
64
+ return False
65
+
66
+ if "djdt" in request.GET:
67
+ return False
68
+
69
+ return True
70
+
71
+ def _record_visit(self, request, status_code: int, error_message: str) -> None:
72
+ try:
73
+ status = HTTPStatus(status_code)
74
+ status_text = status.phrase
75
+ except ValueError:
76
+ status_text = ""
77
+
78
+ view_name = self._resolve_view_name(request)
79
+ full_path = request.get_full_path()
80
+ if not error_message and status_code >= HTTPStatus.BAD_REQUEST:
81
+ error_message = status_text or f"HTTP {status_code}"
82
+
83
+ landing = None
84
+ if status_code < HTTPStatus.BAD_REQUEST:
85
+ landing = self._resolve_landing(request)
86
+
87
+ try:
88
+ ViewHistory.objects.create(
89
+ path=full_path,
90
+ method=request.method,
91
+ status_code=status_code,
92
+ status_text=status_text,
93
+ error_message=(error_message or "")[:1000],
94
+ view_name=view_name,
95
+ )
96
+ except Exception: # pragma: no cover - best effort logging
97
+ logger.debug(
98
+ "Failed to record ViewHistory for %s", full_path, exc_info=True
99
+ )
100
+ else:
101
+ self._update_user_last_visit_ip(request)
102
+
103
+ if landing is not None:
104
+ self._record_landing_lead(request, landing)
105
+
106
+ def _resolve_landing(self, request):
107
+ path = request.path
108
+ if not path:
109
+ return None
110
+ try:
111
+ return (
112
+ Landing.objects.filter(
113
+ path=path,
114
+ enabled=True,
115
+ is_deleted=False,
116
+ )
117
+ .select_related("module", "module__application", "module__node_role")
118
+ .first()
119
+ )
120
+ except Exception: # pragma: no cover - best effort logging
121
+ logger.debug(
122
+ "Failed to resolve Landing for %s", path, exc_info=True
123
+ )
124
+ return None
125
+
126
+ def _record_landing_lead(self, request, landing):
127
+ if request.method.upper() != "GET":
128
+ return
129
+
130
+ if not getattr(landing, "track_leads", False):
131
+ return
132
+
133
+ if not landing_leads_supported():
134
+ return
135
+
136
+ referer = get_original_referer(request)
137
+ user_agent = request.META.get("HTTP_USER_AGENT", "") or ""
138
+ ip_address = self._extract_client_ip(request) or None
139
+ user = getattr(request, "user", None)
140
+ if not getattr(user, "is_authenticated", False):
141
+ user = None
142
+
143
+ try:
144
+ LandingLead.objects.create(
145
+ landing=landing,
146
+ user=user,
147
+ path=request.get_full_path(),
148
+ referer=referer,
149
+ user_agent=user_agent,
150
+ ip_address=ip_address,
151
+ )
152
+ except Exception: # pragma: no cover - best effort logging
153
+ logger.debug(
154
+ "Failed to record LandingLead for %s", landing.path, exc_info=True
155
+ )
156
+
157
+ def _resolve_view_name(self, request) -> str:
158
+ match = getattr(request, "resolver_match", None)
159
+ if match is None:
160
+ try:
161
+ match = resolve(request.path_info)
162
+ except Resolver404:
163
+ return ""
164
+
165
+ if getattr(match, "view_name", ""):
166
+ return match.view_name
167
+
168
+ func = getattr(match, "func", None)
169
+ if func is None:
170
+ return ""
171
+
172
+ module = getattr(func, "__module__", "")
173
+ name = getattr(func, "__name__", "")
174
+ if module and name:
175
+ return f"{module}.{name}"
176
+ return name or module or ""
177
+
178
+ def _extract_client_ip(self, request) -> str:
179
+ forwarded = request.META.get("HTTP_X_FORWARDED_FOR", "")
180
+ candidates = []
181
+ if forwarded:
182
+ candidates.extend(part.strip() for part in forwarded.split(","))
183
+ remote = request.META.get("REMOTE_ADDR", "").strip()
184
+ if remote:
185
+ candidates.append(remote)
186
+
187
+ for candidate in candidates:
188
+ if not candidate:
189
+ continue
190
+ try:
191
+ ipaddress.ip_address(candidate)
192
+ except ValueError:
193
+ continue
194
+ return candidate
195
+ return ""
196
+
197
+ def _update_user_last_visit_ip(self, request) -> None:
198
+ user = getattr(request, "user", None)
199
+ if not getattr(user, "is_authenticated", False) or not getattr(user, "pk", None):
200
+ return
201
+
202
+ ip_address = self._extract_client_ip(request)
203
+ if not ip_address or getattr(user, "last_visit_ip_address", None) == ip_address:
204
+ return
205
+
206
+ try:
207
+ user.last_visit_ip_address = ip_address
208
+ user.save(update_fields=["last_visit_ip_address"])
209
+ except Exception: # pragma: no cover - best effort logging
210
+ logger.debug(
211
+ "Failed to update last_visit_ip_address for user %s", user.pk,
212
+ exc_info=True,
213
+ )