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
@@ -0,0 +1,109 @@
1
+ """Utility helpers shared by :mod:`config.settings` and related tests."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import contextlib
6
+ import ipaddress
7
+ import os
8
+ from pathlib import Path
9
+ from typing import Mapping, MutableMapping
10
+
11
+ from django.core.management.utils import get_random_secret_key
12
+ from django.http import request as http_request
13
+ from django.http.request import split_domain_port
14
+
15
+
16
+ __all__ = [
17
+ "extract_ip_from_host",
18
+ "install_validate_host_with_subnets",
19
+ "load_secret_key",
20
+ "strip_ipv6_brackets",
21
+ "validate_host_with_subnets",
22
+ ]
23
+
24
+
25
+ def strip_ipv6_brackets(host: str) -> str:
26
+ """Return ``host`` without IPv6 URL literal brackets."""
27
+
28
+ if host.startswith("[") and host.endswith("]"):
29
+ return host[1:-1]
30
+ return host
31
+
32
+
33
+ def extract_ip_from_host(host: str):
34
+ """Return an :mod:`ipaddress` object for ``host`` when possible."""
35
+
36
+ candidate = strip_ipv6_brackets(host)
37
+ try:
38
+ return ipaddress.ip_address(candidate)
39
+ except ValueError:
40
+ domain, _port = split_domain_port(host)
41
+ if domain and domain != host:
42
+ candidate = strip_ipv6_brackets(domain)
43
+ try:
44
+ return ipaddress.ip_address(candidate)
45
+ except ValueError:
46
+ return None
47
+ return None
48
+
49
+
50
+ def validate_host_with_subnets(host, allowed_hosts, original_validate=None):
51
+ """Extend Django's host validation to honor subnet CIDR notation."""
52
+
53
+ if original_validate is None:
54
+ original_validate = http_request.validate_host
55
+
56
+ ip = extract_ip_from_host(host)
57
+ if ip is None:
58
+ return original_validate(host, allowed_hosts)
59
+
60
+ for pattern in allowed_hosts:
61
+ try:
62
+ network = ipaddress.ip_network(pattern)
63
+ except ValueError:
64
+ continue
65
+ if ip in network:
66
+ return True
67
+ return original_validate(host, allowed_hosts)
68
+
69
+
70
+ def install_validate_host_with_subnets() -> None:
71
+ """Monkeypatch Django's host validator to recognize subnet patterns."""
72
+
73
+ original_validate = http_request.validate_host
74
+
75
+ def _patched(host, allowed_hosts):
76
+ return validate_host_with_subnets(host, allowed_hosts, original_validate)
77
+
78
+ http_request.validate_host = _patched
79
+
80
+
81
+ def load_secret_key(
82
+ base_dir: Path,
83
+ env: Mapping[str, str] | MutableMapping[str, str] | None = None,
84
+ secret_file: Path | None = None,
85
+ ) -> str:
86
+ """Load the Django secret key from the environment or a persisted file."""
87
+
88
+ if env is None:
89
+ env = os.environ
90
+
91
+ for env_var in ("DJANGO_SECRET_KEY", "SECRET_KEY"):
92
+ value = env.get(env_var)
93
+ if value:
94
+ return value
95
+
96
+ if secret_file is None:
97
+ secret_file = base_dir / "locks" / "django-secret.key"
98
+
99
+ with contextlib.suppress(OSError):
100
+ stored_key = secret_file.read_text(encoding="utf-8").strip()
101
+ if stored_key:
102
+ return stored_key
103
+
104
+ generated_key = get_random_secret_key()
105
+ with contextlib.suppress(OSError):
106
+ secret_file.parent.mkdir(parents=True, exist_ok=True)
107
+ secret_file.write_text(generated_key, encoding="utf-8")
108
+
109
+ return generated_key
config/urls.py CHANGED
@@ -1,159 +1,228 @@
1
- """Project URL configuration with automatic app discovery.
2
-
3
- This module includes URL patterns from any installed application that exposes
4
- an internal ``urls`` module. This allows new apps with URL configurations to be
5
- added without editing this file, except for top-level routes such as the admin
6
- interface or the main pages.
7
- """
8
-
9
- from importlib import import_module
10
- from pathlib import Path
11
-
12
- from django.apps import apps
13
- from django.conf import settings
14
- from django.conf.urls.static import static
15
- from django.contrib import admin
16
- from django.urls import include, path
17
- import teams.admin # noqa: F401
18
- from django.views.decorators.csrf import csrf_exempt
19
- from django.views.generic import RedirectView
20
- from django.views.i18n import set_language
21
- from django.utils.translation import gettext_lazy as _
22
- from core import views as core_views
23
- from core.admindocs import (
24
- CommandsView,
25
- ModelGraphIndexView,
26
- OrderedModelIndexView,
27
- )
28
- from man import views as man_views
29
- from pages import views as pages_views
30
-
31
- admin.site.site_header = _("Constellation")
32
- admin.site.site_title = _("Constellation")
33
-
34
- # Apps that require a custom prefix for their URLs
35
- URL_PREFIX_OVERRIDES = {"core": "api/rfid"}
36
-
37
-
38
- def autodiscovered_urlpatterns():
39
- """Collect URL patterns from project apps automatically.
40
-
41
- Scans all installed apps located inside the project directory. If an app
42
- exposes a ``urls`` module, it is included under ``/<app_label>/`` unless a
43
- custom prefix is defined in :data:`URL_PREFIX_OVERRIDES`.
44
- """
45
-
46
- patterns = []
47
- base_dir = Path(settings.BASE_DIR).resolve()
48
- for app_config in apps.get_app_configs():
49
- app_path = Path(app_config.path).resolve()
50
- try:
51
- app_path.relative_to(base_dir)
52
- except ValueError:
53
- # Skip third-party apps outside of the project
54
- continue
55
-
56
- if app_config.label == "pages":
57
- # Root pages URLs are handled explicitly below
58
- continue
59
-
60
- module_name = f"{app_config.name}.urls"
61
- try:
62
- import_module(module_name)
63
- except ModuleNotFoundError:
64
- continue
65
-
66
- prefix = URL_PREFIX_OVERRIDES.get(app_config.label, app_config.label)
67
- patterns.append(path(f"{prefix}/", include(module_name)))
68
-
69
- return patterns
70
-
71
-
72
- urlpatterns = [
73
- path(
74
- "admin/doc/manuals/",
75
- man_views.admin_manual_list,
76
- name="django-admindocs-manuals",
77
- ),
78
- path(
79
- "admin/doc/manuals/<slug:slug>/",
80
- man_views.admin_manual_detail,
81
- name="django-admindocs-manual-detail",
82
- ),
83
- path(
84
- "admin/doc/manuals/<slug:slug>/pdf/",
85
- man_views.manual_pdf,
86
- name="django-admindocs-manual-pdf",
87
- ),
88
- path(
89
- "admin/doc/commands/",
90
- CommandsView.as_view(),
91
- name="django-admindocs-commands",
92
- ),
93
- path(
94
- "admin/doc/commands/",
95
- RedirectView.as_view(pattern_name="django-admindocs-commands"),
96
- ),
97
- path(
98
- "admin/doc/model-graphs/",
99
- ModelGraphIndexView.as_view(),
100
- name="django-admindocs-model-graphs",
101
- ),
102
- path(
103
- "admindocs/model-graphs/",
104
- RedirectView.as_view(pattern_name="django-admindocs-model-graphs"),
105
- ),
106
- path(
107
- "admindocs/models/",
108
- OrderedModelIndexView.as_view(),
109
- name="django-admindocs-models-index",
110
- ),
111
- path("admindocs/", include("django.contrib.admindocs.urls")),
112
- path(
113
- "admin/doc/",
114
- RedirectView.as_view(pattern_name="django-admindocs-docroot"),
115
- ),
116
- path(
117
- "admin/model-graph/<str:app_label>/",
118
- admin.site.admin_view(pages_views.admin_model_graph),
119
- name="admin-model-graph",
120
- ),
121
- path(
122
- "admin/core/releases/<int:pk>/<str:action>/",
123
- core_views.release_progress,
124
- name="release-progress",
125
- ),
126
- path(
127
- "admin/core/todos/<int:pk>/done/",
128
- core_views.todo_done,
129
- name="todo-done",
130
- ),
131
- path(
132
- "admin/core/odoo-products/",
133
- core_views.odoo_products,
134
- name="odoo-products",
135
- ),
136
- path("admin/", admin.site.urls),
137
- path("i18n/setlang/", csrf_exempt(set_language), name="set_language"),
138
- path("api/", include("core.workgroup_urls")),
139
- path("", include("pages.urls")),
140
- ]
141
-
142
- urlpatterns += autodiscovered_urlpatterns()
143
-
144
- if settings.DEBUG:
145
- try:
146
- import debug_toolbar
147
- except ModuleNotFoundError: # pragma: no cover - optional dependency
148
- pass
149
- else:
150
- urlpatterns = [
151
- path(
152
- "__debug__/",
153
- include(
154
- ("debug_toolbar.urls", "debug_toolbar"), namespace="debug_toolbar"
155
- ),
156
- )
157
- ] + urlpatterns
158
-
159
- urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
1
+ """Project URL configuration with automatic app discovery.
2
+
3
+ This module includes URL patterns from any installed application that exposes
4
+ an internal ``urls`` module. This allows new apps with URL configurations to be
5
+ added without editing this file, except for top-level routes such as the admin
6
+ interface or the main pages.
7
+ """
8
+
9
+ from importlib import import_module
10
+ from pathlib import Path
11
+
12
+ from django.apps import apps
13
+ from django.conf import settings
14
+ from django.conf.urls.static import static
15
+ from django.contrib.staticfiles.urls import staticfiles_urlpatterns
16
+ from django.contrib import admin
17
+ from django.core.exceptions import AppRegistryNotReady, ImproperlyConfigured
18
+ from django.db.utils import DatabaseError, OperationalError, ProgrammingError
19
+ from django.urls import include, path
20
+ import teams.admin # noqa: F401
21
+ from django.views.decorators.csrf import csrf_exempt
22
+ from django.views.generic import RedirectView
23
+ from django.views.i18n import set_language
24
+ from django.utils.translation import gettext_lazy as _
25
+ from core import views as core_views
26
+ from core.admindocs import (
27
+ CommandsView,
28
+ ModelGraphIndexView,
29
+ OrderedModelIndexView,
30
+ )
31
+ from pages import views as pages_views
32
+
33
+ try: # Gate optional GraphQL dependency for roles that do not install it
34
+ from api.views import EnergyGraphQLView
35
+ except ModuleNotFoundError as exc: # pragma: no cover - dependency intentionally optional
36
+ if exc.name in {"graphene_django", "graphene"}:
37
+ EnergyGraphQLView = None # type: ignore[assignment]
38
+ else: # pragma: no cover - unrelated import error
39
+ raise
40
+
41
+
42
+ def _graphql_feature_enabled() -> bool:
43
+ """Return ``True`` when the GraphQL endpoint should be exposed."""
44
+
45
+ if EnergyGraphQLView is None:
46
+ return False
47
+
48
+ try:
49
+ from nodes.models import Node, NodeFeature
50
+ except (ModuleNotFoundError, AppRegistryNotReady, ImproperlyConfigured):
51
+ return True
52
+
53
+ try:
54
+ feature = NodeFeature.objects.filter(slug="graphql").first()
55
+ except (DatabaseError, OperationalError, ProgrammingError):
56
+ return True
57
+
58
+ if feature is None:
59
+ return True
60
+
61
+ try:
62
+ node = Node.get_local()
63
+ except (DatabaseError, OperationalError, ProgrammingError):
64
+ return True
65
+
66
+ if node and not node.has_feature("graphql"):
67
+ return False
68
+
69
+ return True
70
+
71
+ admin.site.site_header = _("Constellation")
72
+ admin.site.site_title = _("Constellation")
73
+
74
+ # Apps that require a custom prefix for their URLs
75
+ URL_PREFIX_OVERRIDES = {"core": "api/rfid"}
76
+
77
+
78
+ def autodiscovered_urlpatterns():
79
+ """Collect URL patterns from project apps automatically.
80
+
81
+ Scans all installed apps located inside the project directory. If an app
82
+ exposes a ``urls`` module, it is included under ``/<app_label>/`` unless a
83
+ custom prefix is defined in :data:`URL_PREFIX_OVERRIDES`.
84
+ """
85
+
86
+ patterns = []
87
+ base_dir = Path(settings.BASE_DIR).resolve()
88
+ for app_config in apps.get_app_configs():
89
+ app_path = Path(app_config.path).resolve()
90
+ try:
91
+ app_path.relative_to(base_dir)
92
+ except ValueError:
93
+ # Skip third-party apps outside of the project
94
+ continue
95
+
96
+ if app_config.label == "pages":
97
+ # Root pages URLs are handled explicitly below
98
+ continue
99
+
100
+ module_name = f"{app_config.name}.urls"
101
+ try:
102
+ import_module(module_name)
103
+ except ModuleNotFoundError:
104
+ continue
105
+
106
+ prefix = URL_PREFIX_OVERRIDES.get(app_config.label, app_config.label)
107
+ patterns.append(path(f"{prefix}/", include(module_name)))
108
+
109
+ return patterns
110
+
111
+
112
+ urlpatterns = [
113
+ path(
114
+ "admin/doc/manuals/",
115
+ pages_views.admin_manual_list,
116
+ name="django-admindocs-manuals",
117
+ ),
118
+ path(
119
+ "admin/doc/manuals/<slug:slug>/",
120
+ pages_views.admin_manual_detail,
121
+ name="django-admindocs-manual-detail",
122
+ ),
123
+ path(
124
+ "admin/doc/manuals/<slug:slug>/pdf/",
125
+ pages_views.manual_pdf,
126
+ name="django-admindocs-manual-pdf",
127
+ ),
128
+ path(
129
+ "admin/doc/commands/",
130
+ CommandsView.as_view(),
131
+ name="django-admindocs-commands",
132
+ ),
133
+ path(
134
+ "admin/doc/commands/",
135
+ RedirectView.as_view(pattern_name="django-admindocs-commands"),
136
+ ),
137
+ path(
138
+ "admin/doc/model-graphs/",
139
+ ModelGraphIndexView.as_view(),
140
+ name="django-admindocs-model-graphs",
141
+ ),
142
+ path(
143
+ "admindocs/model-graphs/",
144
+ RedirectView.as_view(pattern_name="django-admindocs-model-graphs"),
145
+ ),
146
+ path(
147
+ "admindocs/models/",
148
+ OrderedModelIndexView.as_view(),
149
+ name="django-admindocs-models-index",
150
+ ),
151
+ path("admindocs/", include("django.contrib.admindocs.urls")),
152
+ path(
153
+ "admin/doc/",
154
+ RedirectView.as_view(pattern_name="django-admindocs-docroot"),
155
+ ),
156
+ path(
157
+ "admin/model-graph/<str:app_label>/",
158
+ admin.site.admin_view(pages_views.admin_model_graph),
159
+ name="admin-model-graph",
160
+ ),
161
+ path("version/", core_views.version_info, name="version-info"),
162
+ path(
163
+ "admin/core/releases/<int:pk>/<str:action>/",
164
+ core_views.release_progress,
165
+ name="release-progress",
166
+ ),
167
+ path(
168
+ "admin/core/todos/<int:pk>/focus/",
169
+ core_views.todo_focus,
170
+ name="todo-focus",
171
+ ),
172
+ path(
173
+ "admin/core/todos/<int:pk>/done/",
174
+ core_views.todo_done,
175
+ name="todo-done",
176
+ ),
177
+ path(
178
+ "admin/core/todos/<int:pk>/delete/",
179
+ core_views.todo_delete,
180
+ name="todo-delete",
181
+ ),
182
+ path(
183
+ "admin/core/todos/<int:pk>/snapshot/",
184
+ core_views.todo_snapshot,
185
+ name="todo-snapshot",
186
+ ),
187
+ path(
188
+ "admin/core/odoo-products/",
189
+ core_views.odoo_products,
190
+ name="odoo-products",
191
+ ),
192
+ path(
193
+ "admin/core/odoo-quote-report/",
194
+ core_views.odoo_quote_report,
195
+ name="odoo-quote-report",
196
+ ),
197
+ path(
198
+ "admin/request-temp-password/",
199
+ core_views.request_temp_password,
200
+ name="admin-request-temp-password",
201
+ ),
202
+ path("admin/", admin.site.urls),
203
+ path("i18n/setlang/", csrf_exempt(set_language), name="set_language"),
204
+ path("", include("pages.urls")),
205
+ ]
206
+
207
+ if _graphql_feature_enabled():
208
+ urlpatterns.append(path("graphql/", EnergyGraphQLView.as_view(), name="graphql"))
209
+
210
+ urlpatterns += autodiscovered_urlpatterns()
211
+
212
+ if settings.DEBUG:
213
+ try:
214
+ import debug_toolbar
215
+ except ModuleNotFoundError: # pragma: no cover - optional dependency
216
+ pass
217
+ else:
218
+ urlpatterns = [
219
+ path(
220
+ "__debug__/",
221
+ include(
222
+ ("debug_toolbar.urls", "debug_toolbar"), namespace="debug_toolbar"
223
+ ),
224
+ )
225
+ ] + urlpatterns
226
+
227
+ urlpatterns += staticfiles_urlpatterns()
228
+ urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
config/wsgi.py CHANGED
@@ -1,17 +1,17 @@
1
- """
2
- WSGI config for config project.
3
-
4
- It exposes the WSGI callable as a module-level variable named ``application``.
5
-
6
- For more information on this file, see
7
- https://docs.djangoproject.com/en/5.2/howto/deployment/wsgi/
8
- """
9
-
10
- import os
11
- from config.loadenv import loadenv
12
- from django.core.wsgi import get_wsgi_application
13
-
14
- loadenv()
15
- os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
16
-
17
- application = get_wsgi_application()
1
+ """
2
+ WSGI config for config project.
3
+
4
+ It exposes the WSGI callable as a module-level variable named ``application``.
5
+
6
+ For more information on this file, see
7
+ https://docs.djangoproject.com/en/5.2/howto/deployment/wsgi/
8
+ """
9
+
10
+ import os
11
+ from config.loadenv import loadenv
12
+ from django.core.wsgi import get_wsgi_application
13
+
14
+ loadenv()
15
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
16
+
17
+ application = get_wsgi_application()