arthexis 0.1.13__py3-none-any.whl → 0.1.15__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.
Files changed (108) hide show
  1. {arthexis-0.1.13.dist-info → arthexis-0.1.15.dist-info}/METADATA +224 -221
  2. arthexis-0.1.15.dist-info/RECORD +110 -0
  3. {arthexis-0.1.13.dist-info → arthexis-0.1.15.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 +3795 -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 +149 -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 +114 -100
  36. core/mailer.py +85 -85
  37. core/middleware.py +91 -91
  38. core/models.py +3637 -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 +840 -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 +952 -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 +2168 -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 +2201 -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 +1764 -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 +3830 -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 +769 -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 +209 -153
  99. pages/models.py +643 -426
  100. pages/tasks.py +74 -0
  101. pages/tests.py +3025 -2200
  102. pages/urls.py +26 -25
  103. pages/utils.py +23 -12
  104. pages/views.py +1176 -1128
  105. arthexis-0.1.13.dist-info/RECORD +0 -105
  106. nodes/actions.py +0 -70
  107. {arthexis-0.1.13.dist-info → arthexis-0.1.15.dist-info}/WHEEL +0 -0
  108. {arthexis-0.1.13.dist-info → arthexis-0.1.15.dist-info}/top_level.txt +0 -0
core/test_system_info.py CHANGED
@@ -1,139 +1,186 @@
1
- import json
2
- import os
3
- from pathlib import Path
4
- from subprocess import CompletedProcess
5
- from unittest.mock import patch
6
-
7
-
8
- os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
9
-
10
- import django
11
-
12
- django.setup()
13
-
14
- from django.conf import settings
15
- from django.test import SimpleTestCase, override_settings
16
- from nodes.models import Node, NodeFeature, NodeRole
17
- from core.system import _gather_info, get_system_sigil_values
18
-
19
-
20
- class SystemInfoRoleTests(SimpleTestCase):
21
- @override_settings(NODE_ROLE="Terminal")
22
- def test_defaults_to_terminal(self):
23
- info = _gather_info()
24
- self.assertEqual(info["role"], "Terminal")
25
-
26
- @override_settings(NODE_ROLE="Satellite")
27
- def test_uses_settings_role(self):
28
- info = _gather_info()
29
- self.assertEqual(info["role"], "Satellite")
30
-
31
-
32
- class SystemInfoScreenModeTests(SimpleTestCase):
33
- def test_without_lockfile(self):
34
- info = _gather_info()
35
- self.assertEqual(info["screen_mode"], "")
36
-
37
- def test_with_lockfile(self):
38
- lock_dir = Path(settings.BASE_DIR) / "locks"
39
- lock_dir.mkdir(exist_ok=True)
40
- lock_file = lock_dir / "screen_mode.lck"
41
- lock_file.write_text("tft")
42
- try:
43
- info = _gather_info()
44
- self.assertEqual(info["screen_mode"], "tft")
45
- finally:
46
- lock_file.unlink()
47
- if not any(lock_dir.iterdir()):
48
- lock_dir.rmdir()
49
-
50
-
51
- class SystemInfoRevisionTests(SimpleTestCase):
52
- @patch("core.system.revision.get_revision", return_value="abcdef1234567890")
53
- def test_includes_full_revision(self, mock_revision):
54
- info = _gather_info()
55
- self.assertEqual(info["revision"], "abcdef1234567890")
56
- mock_revision.assert_called_once()
57
-
58
-
59
- class SystemInfoDatabaseTests(SimpleTestCase):
60
- def test_collects_database_definitions(self):
61
- info = _gather_info()
62
- self.assertIn("databases", info)
63
- aliases = {entry["alias"] for entry in info["databases"]}
64
- self.assertIn("default", aliases)
65
-
66
- @override_settings(
67
- DATABASES={
68
- "default": {
69
- "ENGINE": "django.db.backends.sqlite3",
70
- "NAME": Path("/tmp/db.sqlite3"),
71
- }
72
- }
73
- )
74
- def test_serializes_path_database_names(self):
75
- info = _gather_info()
76
- databases = info["databases"]
77
- self.assertEqual(databases[0]["name"], "/tmp/db.sqlite3")
78
-
79
-
80
- class SystemInfoRunserverDetectionTests(SimpleTestCase):
81
- @patch("core.system.subprocess.run")
82
- def test_detects_runserver_process_port(self, mock_run):
83
- mock_run.return_value = CompletedProcess(
84
- args=["pgrep"],
85
- returncode=0,
86
- stdout="123 python manage.py runserver 0.0.0.0:8000 --noreload\n",
87
- )
88
-
89
- info = _gather_info()
90
-
91
- self.assertTrue(info["running"])
92
- self.assertEqual(info["port"], 8000)
93
-
94
- @patch("core.system._probe_ports", return_value=(True, 8000))
95
- @patch("core.system.subprocess.run", side_effect=FileNotFoundError)
96
- def test_falls_back_to_port_probe_when_pgrep_missing(self, mock_run, mock_probe):
97
- info = _gather_info()
98
-
99
- self.assertTrue(info["running"])
100
- self.assertEqual(info["port"], 8000)
101
-
102
-
103
- class SystemSigilValueTests(SimpleTestCase):
104
- def test_exports_values_for_sigil_resolution(self):
105
- sample_info = {
106
- "installed": True,
107
- "revision": "abcdef",
108
- "service": "gunicorn",
109
- "mode": "internal",
110
- "port": 8888,
111
- "role": "Terminal",
112
- "screen_mode": "",
113
- "features": [
114
- {"display": "Feature", "expected": True, "actual": False, "slug": "feature"}
115
- ],
116
- "running": True,
117
- "service_status": "active",
118
- "hostname": "example.local",
119
- "ip_addresses": ["127.0.0.1"],
120
- "databases": [
121
- {
122
- "alias": "default",
123
- "engine": "django.db.backends.sqlite3",
124
- "name": "db.sqlite3",
125
- }
126
- ],
127
- }
128
- with patch("core.system._gather_info", return_value=sample_info):
129
- values = get_system_sigil_values()
130
-
131
- self.assertEqual(values["REVISION"], "abcdef")
132
- self.assertEqual(values["RUNNING"], "True")
133
- self.assertEqual(values["NGINX_MODE"], "internal (8888)")
134
- self.assertEqual(values["IP_ADDRESSES"], "127.0.0.1")
135
- features = json.loads(values["FEATURES"])
136
- self.assertEqual(features[0]["display"], "Feature")
137
- databases = json.loads(values["DATABASES"])
138
- self.assertEqual(databases[0]["alias"], "default")
139
-
1
+ import json
2
+ import os
3
+ from datetime import timedelta
4
+ from pathlib import Path
5
+ from subprocess import CompletedProcess
6
+ from tempfile import TemporaryDirectory
7
+ from unittest.mock import Mock, patch
8
+
9
+
10
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
11
+
12
+ import django
13
+
14
+ django.setup()
15
+
16
+ from django.conf import settings
17
+ from django.test import SimpleTestCase, override_settings
18
+ from nodes.models import Node, NodeFeature, NodeRole
19
+ from core.system import (
20
+ _gather_info,
21
+ _load_auto_upgrade_log_entries,
22
+ _read_auto_upgrade_mode,
23
+ get_system_sigil_values,
24
+ )
25
+
26
+
27
+ class SystemInfoRoleTests(SimpleTestCase):
28
+ @override_settings(NODE_ROLE="Terminal")
29
+ def test_defaults_to_terminal(self):
30
+ info = _gather_info()
31
+ self.assertEqual(info["role"], "Terminal")
32
+
33
+ @override_settings(NODE_ROLE="Satellite")
34
+ def test_uses_settings_role(self):
35
+ info = _gather_info()
36
+ self.assertEqual(info["role"], "Satellite")
37
+
38
+
39
+ class SystemInfoScreenModeTests(SimpleTestCase):
40
+ def test_without_lockfile(self):
41
+ info = _gather_info()
42
+ self.assertEqual(info["screen_mode"], "")
43
+
44
+ def test_with_lockfile(self):
45
+ lock_dir = Path(settings.BASE_DIR) / "locks"
46
+ lock_dir.mkdir(exist_ok=True)
47
+ lock_file = lock_dir / "screen_mode.lck"
48
+ lock_file.write_text("tft")
49
+ try:
50
+ info = _gather_info()
51
+ self.assertEqual(info["screen_mode"], "tft")
52
+ finally:
53
+ lock_file.unlink()
54
+ if not any(lock_dir.iterdir()):
55
+ lock_dir.rmdir()
56
+
57
+
58
+ class SystemInfoRevisionTests(SimpleTestCase):
59
+ @patch("core.system.revision.get_revision", return_value="abcdef1234567890")
60
+ def test_includes_full_revision(self, mock_revision):
61
+ info = _gather_info()
62
+ self.assertEqual(info["revision"], "abcdef1234567890")
63
+ mock_revision.assert_called_once()
64
+
65
+
66
+ class SystemInfoDatabaseTests(SimpleTestCase):
67
+ def test_collects_database_definitions(self):
68
+ info = _gather_info()
69
+ self.assertIn("databases", info)
70
+ aliases = {entry["alias"] for entry in info["databases"]}
71
+ self.assertIn("default", aliases)
72
+
73
+ @override_settings(
74
+ DATABASES={
75
+ "default": {
76
+ "ENGINE": "django.db.backends.sqlite3",
77
+ "NAME": Path("/tmp/db.sqlite3"),
78
+ }
79
+ }
80
+ )
81
+ def test_serializes_path_database_names(self):
82
+ info = _gather_info()
83
+ databases = info["databases"]
84
+ self.assertEqual(databases[0]["name"], "/tmp/db.sqlite3")
85
+
86
+
87
+ class AutoUpgradeModeTests(SimpleTestCase):
88
+ def test_lock_file_read_error_marks_enabled(self):
89
+ mock_path = Mock()
90
+ mock_path.exists.return_value = True
91
+ mock_path.read_text.side_effect = OSError
92
+
93
+ with patch("core.system._auto_upgrade_mode_file", return_value=mock_path):
94
+ info = _read_auto_upgrade_mode(Path("/tmp"))
95
+
96
+ self.assertTrue(info["lock_exists"])
97
+ self.assertTrue(info["enabled"])
98
+ self.assertTrue(info["read_error"])
99
+
100
+
101
+ class AutoUpgradeLogParsingTests(SimpleTestCase):
102
+ def test_parses_zulu_timestamp_entries(self):
103
+ with TemporaryDirectory() as tmpdir:
104
+ base_dir = Path(tmpdir)
105
+ log_dir = base_dir / "logs"
106
+ log_dir.mkdir()
107
+ log_path = log_dir / "auto-upgrade.log"
108
+ log_path.write_text("2024-01-01T12:34:56Z Started\n", encoding="utf-8")
109
+
110
+ with patch("core.system._format_timestamp", return_value="formatted") as mock_format:
111
+ result = _load_auto_upgrade_log_entries(base_dir)
112
+
113
+ entries = result["entries"]
114
+ self.assertEqual(len(entries), 1)
115
+ entry = entries[0]
116
+ self.assertEqual(entry["message"], "Started")
117
+ self.assertEqual(entry["timestamp"], "formatted")
118
+
119
+ mock_format.assert_called_once()
120
+ parsed_dt = mock_format.call_args[0][0]
121
+ self.assertEqual(parsed_dt.year, 2024)
122
+ self.assertEqual(parsed_dt.month, 1)
123
+ self.assertEqual(parsed_dt.day, 1)
124
+ self.assertEqual(parsed_dt.utcoffset(), timedelta(0))
125
+
126
+
127
+ class SystemInfoRunserverDetectionTests(SimpleTestCase):
128
+ @patch("core.system.subprocess.run")
129
+ def test_detects_runserver_process_port(self, mock_run):
130
+ mock_run.return_value = CompletedProcess(
131
+ args=["pgrep"],
132
+ returncode=0,
133
+ stdout="123 python manage.py runserver 0.0.0.0:8000 --noreload\n",
134
+ )
135
+
136
+ info = _gather_info()
137
+
138
+ self.assertTrue(info["running"])
139
+ self.assertEqual(info["port"], 8000)
140
+
141
+ @patch("core.system._probe_ports", return_value=(True, 8000))
142
+ @patch("core.system.subprocess.run", side_effect=FileNotFoundError)
143
+ def test_falls_back_to_port_probe_when_pgrep_missing(self, mock_run, mock_probe):
144
+ info = _gather_info()
145
+
146
+ self.assertTrue(info["running"])
147
+ self.assertEqual(info["port"], 8000)
148
+
149
+
150
+ class SystemSigilValueTests(SimpleTestCase):
151
+ def test_exports_values_for_sigil_resolution(self):
152
+ sample_info = {
153
+ "installed": True,
154
+ "revision": "abcdef",
155
+ "service": "gunicorn",
156
+ "mode": "internal",
157
+ "port": 8888,
158
+ "role": "Terminal",
159
+ "screen_mode": "",
160
+ "features": [
161
+ {"display": "Feature", "expected": True, "actual": False, "slug": "feature"}
162
+ ],
163
+ "running": True,
164
+ "service_status": "active",
165
+ "hostname": "example.local",
166
+ "ip_addresses": ["127.0.0.1"],
167
+ "databases": [
168
+ {
169
+ "alias": "default",
170
+ "engine": "django.db.backends.sqlite3",
171
+ "name": "db.sqlite3",
172
+ }
173
+ ],
174
+ }
175
+ with patch("core.system._gather_info", return_value=sample_info):
176
+ values = get_system_sigil_values()
177
+
178
+ self.assertEqual(values["REVISION"], "abcdef")
179
+ self.assertEqual(values["RUNNING"], "True")
180
+ self.assertEqual(values["NGINX_MODE"], "internal (8888)")
181
+ self.assertEqual(values["IP_ADDRESSES"], "127.0.0.1")
182
+ features = json.loads(values["FEATURES"])
183
+ self.assertEqual(features[0]["display"], "Feature")
184
+ databases = json.loads(values["DATABASES"])
185
+ self.assertEqual(databases[0]["alias"], "default")
186
+