arthexis 0.1.16__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.
- {arthexis-0.1.16.dist-info → arthexis-0.1.26.dist-info}/METADATA +84 -35
- arthexis-0.1.26.dist-info/RECORD +111 -0
- config/asgi.py +1 -15
- config/middleware.py +47 -1
- config/settings.py +15 -30
- config/urls.py +53 -1
- core/admin.py +540 -450
- core/apps.py +0 -6
- core/auto_upgrade.py +19 -4
- core/backends.py +13 -3
- core/changelog.py +66 -5
- core/environment.py +4 -5
- core/models.py +1566 -203
- core/notifications.py +1 -1
- core/reference_utils.py +10 -11
- core/release.py +55 -7
- core/sigil_builder.py +2 -2
- core/sigil_resolver.py +1 -66
- core/system.py +268 -2
- core/tasks.py +174 -48
- core/tests.py +314 -16
- core/user_data.py +42 -2
- core/views.py +278 -183
- nodes/admin.py +557 -65
- nodes/apps.py +11 -0
- nodes/models.py +658 -113
- nodes/rfid_sync.py +1 -1
- nodes/tasks.py +97 -2
- nodes/tests.py +1212 -116
- nodes/urls.py +15 -1
- nodes/utils.py +51 -3
- nodes/views.py +1239 -154
- ocpp/admin.py +979 -152
- ocpp/consumers.py +268 -28
- ocpp/models.py +488 -3
- ocpp/network.py +398 -0
- ocpp/store.py +6 -4
- ocpp/tasks.py +296 -2
- ocpp/test_export_import.py +1 -0
- ocpp/test_rfid.py +121 -4
- ocpp/tests.py +950 -11
- ocpp/transactions_io.py +9 -1
- ocpp/urls.py +3 -3
- ocpp/views.py +596 -51
- pages/admin.py +262 -30
- pages/apps.py +35 -0
- pages/context_processors.py +26 -21
- pages/defaults.py +1 -1
- pages/forms.py +31 -8
- pages/middleware.py +6 -2
- pages/models.py +77 -2
- pages/module_defaults.py +5 -5
- pages/site_config.py +137 -0
- pages/tests.py +885 -109
- pages/urls.py +13 -2
- pages/utils.py +70 -0
- pages/views.py +558 -55
- arthexis-0.1.16.dist-info/RECORD +0 -111
- core/workgroup_urls.py +0 -17
- core/workgroup_views.py +0 -94
- {arthexis-0.1.16.dist-info → arthexis-0.1.26.dist-info}/WHEEL +0 -0
- {arthexis-0.1.16.dist-info → arthexis-0.1.26.dist-info}/licenses/LICENSE +0 -0
- {arthexis-0.1.16.dist-info → arthexis-0.1.26.dist-info}/top_level.txt +0 -0
nodes/urls.py
CHANGED
|
@@ -8,8 +8,22 @@ urlpatterns = [
|
|
|
8
8
|
path("register/", views.register_node, name="register-node"),
|
|
9
9
|
path("screenshot/", views.capture, name="node-screenshot"),
|
|
10
10
|
path("net-message/", views.net_message, name="net-message"),
|
|
11
|
-
path("
|
|
11
|
+
path("net-message/pull/", views.net_message_pull, name="net-message-pull"),
|
|
12
12
|
path("rfid/export/", views.export_rfids, name="node-rfid-export"),
|
|
13
13
|
path("rfid/import/", views.import_rfids, name="node-rfid-import"),
|
|
14
|
+
path("network/chargers/", views.network_chargers, name="node-network-chargers"),
|
|
15
|
+
path(
|
|
16
|
+
"network/chargers/forward/",
|
|
17
|
+
views.forward_chargers,
|
|
18
|
+
name="node-network-forward-chargers",
|
|
19
|
+
),
|
|
20
|
+
path(
|
|
21
|
+
"network/chargers/action/",
|
|
22
|
+
views.network_charger_action,
|
|
23
|
+
name="node-network-charger-action",
|
|
24
|
+
),
|
|
25
|
+
path("proxy/session/", views.proxy_session, name="node-proxy-session"),
|
|
26
|
+
path("proxy/login/<str:token>/", views.proxy_login, name="node-proxy-login"),
|
|
27
|
+
path("proxy/execute/", views.proxy_execute, name="node-proxy-execute"),
|
|
14
28
|
path("<slug:endpoint>/", views.public_node_endpoint, name="node-public-endpoint"),
|
|
15
29
|
]
|
nodes/utils.py
CHANGED
|
@@ -11,6 +11,11 @@ from selenium import webdriver
|
|
|
11
11
|
from selenium.webdriver.firefox.options import Options
|
|
12
12
|
from selenium.common.exceptions import WebDriverException
|
|
13
13
|
|
|
14
|
+
try: # pragma: no cover - optional dependency may be missing
|
|
15
|
+
from geckodriver_autoinstaller import install as install_geckodriver
|
|
16
|
+
except Exception: # pragma: no cover - fallback when installer is unavailable
|
|
17
|
+
install_geckodriver = None
|
|
18
|
+
|
|
14
19
|
from .classifiers import run_default_classifiers, suppress_default_classifiers
|
|
15
20
|
from .models import ContentSample
|
|
16
21
|
|
|
@@ -18,6 +23,29 @@ SCREENSHOT_DIR = settings.LOG_DIR / "screenshots"
|
|
|
18
23
|
CAMERA_DIR = settings.LOG_DIR / "camera"
|
|
19
24
|
logger = logging.getLogger(__name__)
|
|
20
25
|
|
|
26
|
+
_FIREFOX_BINARY_CANDIDATES = ("firefox", "firefox-esr", "firefox-bin")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _find_firefox_binary() -> str | None:
|
|
30
|
+
"""Return the first available Firefox binary path or ``None``."""
|
|
31
|
+
|
|
32
|
+
for candidate in _FIREFOX_BINARY_CANDIDATES:
|
|
33
|
+
path = shutil.which(candidate)
|
|
34
|
+
if path:
|
|
35
|
+
return path
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _ensure_geckodriver() -> None:
|
|
40
|
+
"""Install geckodriver on demand when possible."""
|
|
41
|
+
|
|
42
|
+
if install_geckodriver is None: # pragma: no cover - dependency not installed
|
|
43
|
+
return
|
|
44
|
+
try:
|
|
45
|
+
install_geckodriver()
|
|
46
|
+
except Exception as exc: # pragma: no cover - external failures are rare in tests
|
|
47
|
+
logger.warning("Unable to ensure geckodriver availability: %s", exc)
|
|
48
|
+
|
|
21
49
|
|
|
22
50
|
def capture_screenshot(url: str, cookies=None) -> Path:
|
|
23
51
|
"""Capture a screenshot of ``url`` and save it to :data:`SCREENSHOT_DIR`.
|
|
@@ -25,8 +53,16 @@ def capture_screenshot(url: str, cookies=None) -> Path:
|
|
|
25
53
|
``cookies`` can be an iterable of Selenium cookie mappings which will be
|
|
26
54
|
applied after the initial navigation and before the screenshot is taken.
|
|
27
55
|
"""
|
|
56
|
+
firefox_binary = _find_firefox_binary()
|
|
57
|
+
if not firefox_binary:
|
|
58
|
+
raise RuntimeError(
|
|
59
|
+
"Screenshot capture failed: Firefox is not installed. Install Firefox to enable screenshot capture."
|
|
60
|
+
)
|
|
61
|
+
|
|
28
62
|
options = Options()
|
|
63
|
+
options.binary_location = firefox_binary
|
|
29
64
|
options.add_argument("-headless")
|
|
65
|
+
_ensure_geckodriver()
|
|
30
66
|
try:
|
|
31
67
|
with webdriver.Firefox(options=options) as browser:
|
|
32
68
|
browser.set_window_size(1280, 720)
|
|
@@ -48,7 +84,12 @@ def capture_screenshot(url: str, cookies=None) -> Path:
|
|
|
48
84
|
return filename
|
|
49
85
|
except WebDriverException as exc:
|
|
50
86
|
logger.error("Failed to capture screenshot from %s: %s", url, exc)
|
|
51
|
-
|
|
87
|
+
message = str(exc)
|
|
88
|
+
if "Unable to obtain driver for firefox" in message:
|
|
89
|
+
message = (
|
|
90
|
+
"Firefox WebDriver is unavailable. Install geckodriver or configure the GECKODRIVER environment variable so Selenium can locate it."
|
|
91
|
+
)
|
|
92
|
+
raise RuntimeError(f"Screenshot capture failed: {message}") from exc
|
|
52
93
|
|
|
53
94
|
|
|
54
95
|
def capture_rpi_snapshot(timeout: int = 10) -> Path:
|
|
@@ -90,10 +131,13 @@ def save_screenshot(
|
|
|
90
131
|
*,
|
|
91
132
|
content: str | None = None,
|
|
92
133
|
user=None,
|
|
134
|
+
link_duplicates: bool = False,
|
|
93
135
|
):
|
|
94
136
|
"""Save screenshot file info if not already recorded.
|
|
95
137
|
|
|
96
|
-
Returns the created :class:`ContentSample
|
|
138
|
+
Returns the created :class:`ContentSample`. If ``link_duplicates`` is ``True``
|
|
139
|
+
and a sample with identical content already exists, the existing record is
|
|
140
|
+
returned instead of ``None``.
|
|
97
141
|
"""
|
|
98
142
|
|
|
99
143
|
original = path
|
|
@@ -101,7 +145,11 @@ def save_screenshot(
|
|
|
101
145
|
path = settings.LOG_DIR / path
|
|
102
146
|
with path.open("rb") as fh:
|
|
103
147
|
digest = hashlib.sha256(fh.read()).hexdigest()
|
|
104
|
-
|
|
148
|
+
existing = ContentSample.objects.filter(hash=digest).first()
|
|
149
|
+
if existing:
|
|
150
|
+
if link_duplicates:
|
|
151
|
+
logger.info("Duplicate screenshot content; reusing existing sample")
|
|
152
|
+
return existing
|
|
105
153
|
logger.info("Duplicate screenshot content; record not created")
|
|
106
154
|
return None
|
|
107
155
|
stored_path = (original if not original.is_absolute() else path).as_posix()
|