arthexis 0.1.3__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.3.dist-info/METADATA +126 -0
- arthexis-0.1.3.dist-info/RECORD +73 -0
- arthexis-0.1.3.dist-info/WHEEL +5 -0
- arthexis-0.1.3.dist-info/licenses/LICENSE +21 -0
- arthexis-0.1.3.dist-info/top_level.txt +5 -0
- config/__init__.py +6 -0
- config/active_app.py +15 -0
- config/asgi.py +29 -0
- config/auth_app.py +8 -0
- config/celery.py +19 -0
- config/context_processors.py +68 -0
- config/loadenv.py +11 -0
- config/logging.py +43 -0
- config/middleware.py +25 -0
- config/offline.py +47 -0
- config/settings.py +374 -0
- config/urls.py +91 -0
- config/wsgi.py +17 -0
- core/__init__.py +0 -0
- core/admin.py +830 -0
- core/apps.py +67 -0
- core/backends.py +82 -0
- core/entity.py +97 -0
- core/environment.py +43 -0
- core/fields.py +70 -0
- core/lcd_screen.py +77 -0
- core/middleware.py +34 -0
- core/models.py +1277 -0
- core/notifications.py +95 -0
- core/release.py +451 -0
- core/system.py +111 -0
- core/tasks.py +100 -0
- core/tests.py +483 -0
- core/urls.py +11 -0
- core/user_data.py +333 -0
- core/views.py +431 -0
- nodes/__init__.py +0 -0
- nodes/actions.py +72 -0
- nodes/admin.py +347 -0
- nodes/apps.py +76 -0
- nodes/lcd.py +151 -0
- nodes/models.py +577 -0
- nodes/tasks.py +50 -0
- nodes/tests.py +1072 -0
- nodes/urls.py +13 -0
- nodes/utils.py +62 -0
- nodes/views.py +262 -0
- ocpp/__init__.py +0 -0
- ocpp/admin.py +392 -0
- ocpp/apps.py +24 -0
- ocpp/consumers.py +267 -0
- ocpp/evcs.py +911 -0
- ocpp/models.py +300 -0
- ocpp/routing.py +9 -0
- ocpp/simulator.py +357 -0
- ocpp/store.py +175 -0
- ocpp/tasks.py +27 -0
- ocpp/test_export_import.py +129 -0
- ocpp/test_rfid.py +345 -0
- ocpp/tests.py +1229 -0
- ocpp/transactions_io.py +119 -0
- ocpp/urls.py +17 -0
- ocpp/views.py +359 -0
- pages/__init__.py +0 -0
- pages/admin.py +231 -0
- pages/apps.py +10 -0
- pages/checks.py +41 -0
- pages/context_processors.py +72 -0
- pages/models.py +224 -0
- pages/tests.py +628 -0
- pages/urls.py +17 -0
- pages/utils.py +13 -0
- pages/views.py +191 -0
pages/tests.py
ADDED
|
@@ -0,0 +1,628 @@
|
|
|
1
|
+
from django.test import Client, TestCase, override_settings
|
|
2
|
+
from django.urls import reverse
|
|
3
|
+
from django.contrib.auth import get_user_model
|
|
4
|
+
from django.contrib.sites.models import Site
|
|
5
|
+
from django.contrib import admin
|
|
6
|
+
from django.core.exceptions import DisallowedHost
|
|
7
|
+
import socket
|
|
8
|
+
from pages.models import Application, Module, SiteBadge, Favorite
|
|
9
|
+
from core.user_data import UserDatum
|
|
10
|
+
from pages.admin import ApplicationAdmin
|
|
11
|
+
from django.apps import apps as django_apps
|
|
12
|
+
from django.core.files.uploadedfile import SimpleUploadedFile
|
|
13
|
+
import base64
|
|
14
|
+
import tempfile
|
|
15
|
+
import shutil
|
|
16
|
+
from django.conf import settings
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from unittest.mock import patch
|
|
19
|
+
from django.core import mail
|
|
20
|
+
from django.core.management import call_command
|
|
21
|
+
import re
|
|
22
|
+
from django.contrib.contenttypes.models import ContentType
|
|
23
|
+
|
|
24
|
+
from nodes.models import Node, ContentSample, NodeRole
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class LoginViewTests(TestCase):
|
|
28
|
+
def setUp(self):
|
|
29
|
+
self.client = Client()
|
|
30
|
+
User = get_user_model()
|
|
31
|
+
self.staff = User.objects.create_user(
|
|
32
|
+
username="staff", password="pwd", is_staff=True
|
|
33
|
+
)
|
|
34
|
+
self.user = User.objects.create_user(username="user", password="pwd")
|
|
35
|
+
Site.objects.update_or_create(id=1, defaults={"name": "Terminal"})
|
|
36
|
+
|
|
37
|
+
def test_login_link_in_navbar(self):
|
|
38
|
+
resp = self.client.get(reverse("pages:index"))
|
|
39
|
+
self.assertContains(resp, 'href="/login/"')
|
|
40
|
+
|
|
41
|
+
def test_staff_login_redirects_admin(self):
|
|
42
|
+
resp = self.client.post(
|
|
43
|
+
reverse("pages:login"),
|
|
44
|
+
{"username": "staff", "password": "pwd"},
|
|
45
|
+
)
|
|
46
|
+
self.assertRedirects(resp, reverse("admin:index"))
|
|
47
|
+
|
|
48
|
+
def test_already_logged_in_staff_redirects(self):
|
|
49
|
+
self.client.force_login(self.staff)
|
|
50
|
+
resp = self.client.get(reverse("pages:login"))
|
|
51
|
+
self.assertRedirects(resp, reverse("admin:index"))
|
|
52
|
+
|
|
53
|
+
def test_regular_user_redirects_next(self):
|
|
54
|
+
resp = self.client.post(
|
|
55
|
+
reverse("pages:login") + "?next=/nodes/list/",
|
|
56
|
+
{"username": "user", "password": "pwd"},
|
|
57
|
+
)
|
|
58
|
+
self.assertRedirects(resp, "/nodes/list/")
|
|
59
|
+
|
|
60
|
+
def test_staff_redirects_next_when_specified(self):
|
|
61
|
+
resp = self.client.post(
|
|
62
|
+
reverse("pages:login") + "?next=/nodes/list/",
|
|
63
|
+
{"username": "staff", "password": "pwd"},
|
|
64
|
+
)
|
|
65
|
+
self.assertRedirects(resp, "/nodes/list/")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class InvitationTests(TestCase):
|
|
69
|
+
def setUp(self):
|
|
70
|
+
self.client = Client()
|
|
71
|
+
User = get_user_model()
|
|
72
|
+
self.user = User.objects.create_user(
|
|
73
|
+
username="invited",
|
|
74
|
+
email="invite@example.com",
|
|
75
|
+
is_active=False,
|
|
76
|
+
)
|
|
77
|
+
self.user.set_unusable_password()
|
|
78
|
+
self.user.save()
|
|
79
|
+
Site.objects.update_or_create(id=1, defaults={"name": "Terminal"})
|
|
80
|
+
|
|
81
|
+
def test_login_page_has_request_link(self):
|
|
82
|
+
resp = self.client.get(reverse("pages:login"))
|
|
83
|
+
self.assertContains(resp, reverse("pages:request-invite"))
|
|
84
|
+
|
|
85
|
+
def test_request_invite_sets_csrf_cookie(self):
|
|
86
|
+
resp = self.client.get(reverse("pages:request-invite"))
|
|
87
|
+
self.assertIn("csrftoken", resp.cookies)
|
|
88
|
+
|
|
89
|
+
def test_invitation_flow(self):
|
|
90
|
+
resp = self.client.post(
|
|
91
|
+
reverse("pages:request-invite"), {"email": "invite@example.com"}
|
|
92
|
+
)
|
|
93
|
+
self.assertEqual(resp.status_code, 200)
|
|
94
|
+
self.assertEqual(len(mail.outbox), 1)
|
|
95
|
+
link = re.search(r"http://testserver[\S]+", mail.outbox[0].body).group(0)
|
|
96
|
+
resp = self.client.get(link)
|
|
97
|
+
self.assertEqual(resp.status_code, 200)
|
|
98
|
+
resp = self.client.post(link)
|
|
99
|
+
self.user.refresh_from_db()
|
|
100
|
+
self.assertTrue(self.user.is_active)
|
|
101
|
+
self.assertIn("_auth_user_id", self.client.session)
|
|
102
|
+
|
|
103
|
+
def test_request_invite_handles_email_errors(self):
|
|
104
|
+
with patch("pages.views.send_mail", side_effect=Exception("fail")):
|
|
105
|
+
resp = self.client.post(
|
|
106
|
+
reverse("pages:request-invite"), {"email": "invite@example.com"}
|
|
107
|
+
)
|
|
108
|
+
self.assertEqual(resp.status_code, 200)
|
|
109
|
+
self.assertContains(
|
|
110
|
+
resp, "If the email exists, an invitation has been sent."
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class NavbarBrandTests(TestCase):
|
|
115
|
+
def setUp(self):
|
|
116
|
+
self.client = Client()
|
|
117
|
+
Site.objects.update_or_create(
|
|
118
|
+
id=1, defaults={"name": "Terminal", "domain": "testserver"}
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
def test_site_name_displayed_when_known(self):
|
|
122
|
+
resp = self.client.get(reverse("pages:index"))
|
|
123
|
+
self.assertContains(
|
|
124
|
+
resp, '<a class="navbar-brand" href="/">Terminal</a>'
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
def test_default_brand_when_unknown(self):
|
|
128
|
+
Site.objects.filter(id=1).update(domain="example.com")
|
|
129
|
+
resp = self.client.get(reverse("pages:index"))
|
|
130
|
+
self.assertContains(
|
|
131
|
+
resp, '<a class="navbar-brand" href="/">Arthexis</a>'
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
@override_settings(ALLOWED_HOSTS=["127.0.0.1", "testserver"])
|
|
135
|
+
def test_brand_uses_role_name_when_site_name_blank(self):
|
|
136
|
+
role, _ = NodeRole.objects.get_or_create(name="Terminal")
|
|
137
|
+
Node.objects.update_or_create(
|
|
138
|
+
mac_address=Node.get_current_mac(),
|
|
139
|
+
defaults={
|
|
140
|
+
"hostname": "localhost",
|
|
141
|
+
"address": "127.0.0.1",
|
|
142
|
+
"role": role,
|
|
143
|
+
},
|
|
144
|
+
)
|
|
145
|
+
Site.objects.filter(id=1).update(name="", domain="127.0.0.1")
|
|
146
|
+
resp = self.client.get(reverse("pages:index"), HTTP_HOST="127.0.0.1")
|
|
147
|
+
self.assertEqual(resp.context["badge_site_name"], "Terminal")
|
|
148
|
+
self.assertContains(resp, '<a class="navbar-brand" href="/">Terminal</a>')
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class AdminBadgesTests(TestCase):
|
|
152
|
+
def setUp(self):
|
|
153
|
+
self.client = Client()
|
|
154
|
+
User = get_user_model()
|
|
155
|
+
self.admin = User.objects.create_superuser(
|
|
156
|
+
username="badge-admin", password="pwd", email="admin@example.com"
|
|
157
|
+
)
|
|
158
|
+
self.client.force_login(self.admin)
|
|
159
|
+
Site.objects.update_or_create(
|
|
160
|
+
id=1, defaults={"name": "test", "domain": "testserver"}
|
|
161
|
+
)
|
|
162
|
+
from nodes.models import Node
|
|
163
|
+
|
|
164
|
+
self.node_hostname = "otherhost"
|
|
165
|
+
self.node = Node.objects.create(
|
|
166
|
+
hostname=self.node_hostname,
|
|
167
|
+
address=socket.gethostbyname(socket.gethostname()),
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
def test_badges_show_site_and_node(self):
|
|
171
|
+
resp = self.client.get(reverse("admin:index"))
|
|
172
|
+
self.assertContains(resp, "SITE: test")
|
|
173
|
+
self.assertContains(resp, f"NODE: {self.node_hostname}")
|
|
174
|
+
|
|
175
|
+
def test_badges_show_node_role(self):
|
|
176
|
+
from nodes.models import NodeRole
|
|
177
|
+
|
|
178
|
+
role = NodeRole.objects.create(name="Dev")
|
|
179
|
+
self.node.role = role
|
|
180
|
+
self.node.save()
|
|
181
|
+
resp = self.client.get(reverse("admin:index"))
|
|
182
|
+
self.assertContains(resp, "ROLE: Dev")
|
|
183
|
+
|
|
184
|
+
def test_badges_warn_when_node_missing(self):
|
|
185
|
+
from nodes.models import Node
|
|
186
|
+
|
|
187
|
+
Node.objects.all().delete()
|
|
188
|
+
resp = self.client.get(reverse("admin:index"))
|
|
189
|
+
self.assertContains(resp, "NODE: Unknown")
|
|
190
|
+
self.assertContains(resp, "badge-unknown")
|
|
191
|
+
self.assertContains(resp, "#6c757d")
|
|
192
|
+
|
|
193
|
+
def test_badges_link_to_admin(self):
|
|
194
|
+
resp = self.client.get(reverse("admin:index"))
|
|
195
|
+
site_list = reverse("admin:pages_siteproxy_changelist")
|
|
196
|
+
site_change = reverse("admin:pages_siteproxy_change", args=[1])
|
|
197
|
+
node_list = reverse("admin:nodes_node_changelist")
|
|
198
|
+
node_change = reverse("admin:nodes_node_change", args=[self.node.pk])
|
|
199
|
+
self.assertContains(resp, f'href="{site_list}"')
|
|
200
|
+
self.assertContains(resp, f'href="{site_change}"')
|
|
201
|
+
self.assertContains(resp, f'href="{node_list}"')
|
|
202
|
+
self.assertContains(resp, f'href="{node_change}"')
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
class AdminSidebarTests(TestCase):
|
|
206
|
+
def setUp(self):
|
|
207
|
+
self.client = Client()
|
|
208
|
+
User = get_user_model()
|
|
209
|
+
self.admin = User.objects.create_superuser(
|
|
210
|
+
username="sidebar_admin", password="pwd", email="admin@example.com"
|
|
211
|
+
)
|
|
212
|
+
self.client.force_login(self.admin)
|
|
213
|
+
Site.objects.update_or_create(
|
|
214
|
+
id=1, defaults={"name": "test", "domain": "testserver"}
|
|
215
|
+
)
|
|
216
|
+
from nodes.models import Node
|
|
217
|
+
|
|
218
|
+
Node.objects.create(hostname="testserver", address="127.0.0.1")
|
|
219
|
+
|
|
220
|
+
def test_sidebar_app_groups_collapsible_script_present(self):
|
|
221
|
+
url = reverse("admin:nodes_node_changelist")
|
|
222
|
+
resp = self.client.get(url)
|
|
223
|
+
self.assertContains(resp, 'id="admin-collapsible-apps"')
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class SiteAdminRegisterCurrentTests(TestCase):
|
|
227
|
+
def setUp(self):
|
|
228
|
+
self.client = Client()
|
|
229
|
+
User = get_user_model()
|
|
230
|
+
self.admin = User.objects.create_superuser(
|
|
231
|
+
username="site-admin", password="pwd", email="admin@example.com"
|
|
232
|
+
)
|
|
233
|
+
self.client.force_login(self.admin)
|
|
234
|
+
Site.objects.update_or_create(
|
|
235
|
+
id=1, defaults={"name": "Constellation", "domain": "arthexis.com"}
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
def test_register_current_creates_site(self):
|
|
239
|
+
resp = self.client.get(reverse("admin:pages_siteproxy_changelist"))
|
|
240
|
+
self.assertContains(resp, "Register Current")
|
|
241
|
+
|
|
242
|
+
resp = self.client.get(reverse("admin:pages_siteproxy_register_current"))
|
|
243
|
+
self.assertRedirects(resp, reverse("admin:pages_siteproxy_changelist"))
|
|
244
|
+
self.assertTrue(Site.objects.filter(domain="testserver").exists())
|
|
245
|
+
site = Site.objects.get(domain="testserver")
|
|
246
|
+
self.assertEqual(site.name, "testserver")
|
|
247
|
+
|
|
248
|
+
@override_settings(ALLOWED_HOSTS=["127.0.0.1", "testserver"])
|
|
249
|
+
def test_register_current_ip_sets_pages_name(self):
|
|
250
|
+
resp = self.client.get(
|
|
251
|
+
reverse("admin:pages_siteproxy_register_current"), HTTP_HOST="127.0.0.1"
|
|
252
|
+
)
|
|
253
|
+
self.assertRedirects(resp, reverse("admin:pages_siteproxy_changelist"))
|
|
254
|
+
site = Site.objects.get(domain="127.0.0.1")
|
|
255
|
+
self.assertEqual(site.name, "")
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
class SiteAdminScreenshotTests(TestCase):
|
|
259
|
+
def setUp(self):
|
|
260
|
+
self.client = Client()
|
|
261
|
+
User = get_user_model()
|
|
262
|
+
self.admin = User.objects.create_superuser(
|
|
263
|
+
username="screenshot-admin", password="pwd", email="admin@example.com"
|
|
264
|
+
)
|
|
265
|
+
self.client.force_login(self.admin)
|
|
266
|
+
Site.objects.update_or_create(
|
|
267
|
+
id=1, defaults={"name": "Terminal", "domain": "testserver"}
|
|
268
|
+
)
|
|
269
|
+
self.node = Node.objects.create(
|
|
270
|
+
hostname="localhost",
|
|
271
|
+
address="127.0.0.1",
|
|
272
|
+
port=80,
|
|
273
|
+
mac_address=Node.get_current_mac(),
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
@patch("pages.admin.capture_screenshot")
|
|
277
|
+
def test_capture_screenshot_action(self, mock_capture):
|
|
278
|
+
screenshot_dir = settings.LOG_DIR / "screenshots"
|
|
279
|
+
screenshot_dir.mkdir(parents=True, exist_ok=True)
|
|
280
|
+
file_path = screenshot_dir / "test.png"
|
|
281
|
+
file_path.write_bytes(b"frontpage")
|
|
282
|
+
mock_capture.return_value = Path("screenshots/test.png")
|
|
283
|
+
url = reverse("admin:pages_siteproxy_changelist")
|
|
284
|
+
response = self.client.post(
|
|
285
|
+
url,
|
|
286
|
+
{"action": "capture_screenshot", "_selected_action": [1]},
|
|
287
|
+
follow=True,
|
|
288
|
+
)
|
|
289
|
+
self.assertEqual(response.status_code, 200)
|
|
290
|
+
self.assertEqual(
|
|
291
|
+
ContentSample.objects.filter(kind=ContentSample.IMAGE).count(), 1
|
|
292
|
+
)
|
|
293
|
+
screenshot = ContentSample.objects.filter(kind=ContentSample.IMAGE).first()
|
|
294
|
+
self.assertEqual(screenshot.node, self.node)
|
|
295
|
+
self.assertEqual(screenshot.path, "screenshots/test.png")
|
|
296
|
+
self.assertEqual(screenshot.method, "ADMIN")
|
|
297
|
+
link = reverse("admin:nodes_contentsample_change", args=[screenshot.pk])
|
|
298
|
+
self.assertContains(response, link)
|
|
299
|
+
mock_capture.assert_called_once_with("http://testserver/")
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
class AdminBadgesWebsiteTests(TestCase):
|
|
303
|
+
def setUp(self):
|
|
304
|
+
self.client = Client()
|
|
305
|
+
User = get_user_model()
|
|
306
|
+
self.admin = User.objects.create_superuser(
|
|
307
|
+
username="badge-admin2", password="pwd", email="admin@example.com"
|
|
308
|
+
)
|
|
309
|
+
self.client.force_login(self.admin)
|
|
310
|
+
role, _ = NodeRole.objects.get_or_create(name="Terminal")
|
|
311
|
+
Node.objects.update_or_create(
|
|
312
|
+
mac_address=Node.get_current_mac(),
|
|
313
|
+
defaults={"hostname": "localhost", "address": "127.0.0.1", "role": role},
|
|
314
|
+
)
|
|
315
|
+
Site.objects.update_or_create(
|
|
316
|
+
id=1, defaults={"name": "", "domain": "127.0.0.1"}
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
@override_settings(ALLOWED_HOSTS=["127.0.0.1", "testserver"])
|
|
320
|
+
def test_badge_shows_domain_when_site_name_blank(self):
|
|
321
|
+
resp = self.client.get(reverse("admin:index"), HTTP_HOST="127.0.0.1")
|
|
322
|
+
self.assertContains(resp, "SITE: 127.0.0.1")
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
class NavAppsTests(TestCase):
|
|
326
|
+
def setUp(self):
|
|
327
|
+
self.client = Client()
|
|
328
|
+
role, _ = NodeRole.objects.get_or_create(name="Terminal")
|
|
329
|
+
Node.objects.update_or_create(
|
|
330
|
+
mac_address=Node.get_current_mac(),
|
|
331
|
+
defaults={"hostname": "localhost", "address": "127.0.0.1", "role": role},
|
|
332
|
+
)
|
|
333
|
+
Site.objects.update_or_create(
|
|
334
|
+
id=1, defaults={"domain": "127.0.0.1", "name": ""}
|
|
335
|
+
)
|
|
336
|
+
app = Application.objects.create(name="Readme")
|
|
337
|
+
Module.objects.create(
|
|
338
|
+
node_role=role, application=app, path="/", is_default=True
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
def test_nav_pill_renders(self):
|
|
342
|
+
resp = self.client.get(reverse("pages:index"))
|
|
343
|
+
self.assertContains(resp, "README")
|
|
344
|
+
self.assertContains(resp, "badge rounded-pill")
|
|
345
|
+
|
|
346
|
+
def test_nav_pill_renders_with_port(self):
|
|
347
|
+
resp = self.client.get(reverse("pages:index"), HTTP_HOST="127.0.0.1:8000")
|
|
348
|
+
self.assertContains(resp, "README")
|
|
349
|
+
|
|
350
|
+
def test_nav_pill_uses_menu_field(self):
|
|
351
|
+
site_app = Module.objects.get()
|
|
352
|
+
site_app.menu = "Docs"
|
|
353
|
+
site_app.save()
|
|
354
|
+
resp = self.client.get(reverse("pages:index"))
|
|
355
|
+
self.assertContains(resp, 'badge rounded-pill text-bg-secondary">DOCS')
|
|
356
|
+
self.assertNotContains(resp, 'badge rounded-pill text-bg-secondary">README')
|
|
357
|
+
|
|
358
|
+
def test_app_without_root_url_excluded(self):
|
|
359
|
+
role = NodeRole.objects.get(name="Terminal")
|
|
360
|
+
app = Application.objects.create(name="core")
|
|
361
|
+
Module.objects.create(node_role=role, application=app, path="/core/")
|
|
362
|
+
resp = self.client.get(reverse("pages:index"))
|
|
363
|
+
self.assertNotContains(resp, 'href="/core/"')
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
class StaffNavVisibilityTests(TestCase):
|
|
367
|
+
def setUp(self):
|
|
368
|
+
self.client = Client()
|
|
369
|
+
role, _ = NodeRole.objects.get_or_create(name="Terminal")
|
|
370
|
+
Node.objects.update_or_create(
|
|
371
|
+
mac_address=Node.get_current_mac(),
|
|
372
|
+
defaults={"hostname": "localhost", "address": "127.0.0.1", "role": role},
|
|
373
|
+
)
|
|
374
|
+
Site.objects.update_or_create(
|
|
375
|
+
id=1, defaults={"domain": "testserver", "name": ""}
|
|
376
|
+
)
|
|
377
|
+
app = Application.objects.create(name="ocpp")
|
|
378
|
+
Module.objects.create(node_role=role, application=app, path="/ocpp/")
|
|
379
|
+
User = get_user_model()
|
|
380
|
+
self.user = User.objects.create_user("user", password="pw")
|
|
381
|
+
self.staff = User.objects.create_user("staff", password="pw", is_staff=True)
|
|
382
|
+
|
|
383
|
+
def test_nonstaff_pill_hidden(self):
|
|
384
|
+
self.client.login(username="user", password="pw")
|
|
385
|
+
resp = self.client.get(reverse("pages:index"))
|
|
386
|
+
self.assertContains(resp, 'href="/ocpp/"')
|
|
387
|
+
|
|
388
|
+
def test_staff_sees_pill(self):
|
|
389
|
+
self.client.login(username="staff", password="pw")
|
|
390
|
+
resp = self.client.get(reverse("pages:index"))
|
|
391
|
+
self.assertContains(resp, 'href="/ocpp/"')
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
class ApplicationModelTests(TestCase):
|
|
395
|
+
def test_path_defaults_to_slugified_name(self):
|
|
396
|
+
role, _ = NodeRole.objects.get_or_create(name="Terminal")
|
|
397
|
+
Node.objects.update_or_create(
|
|
398
|
+
mac_address=Node.get_current_mac(),
|
|
399
|
+
defaults={"hostname": "localhost", "address": "127.0.0.1", "role": role},
|
|
400
|
+
)
|
|
401
|
+
Site.objects.update_or_create(
|
|
402
|
+
id=1, defaults={"domain": "testserver", "name": ""}
|
|
403
|
+
)
|
|
404
|
+
app = Application.objects.create(name="core")
|
|
405
|
+
site_app = Module.objects.create(node_role=role, application=app)
|
|
406
|
+
self.assertEqual(site_app.path, "/core/")
|
|
407
|
+
|
|
408
|
+
def test_installed_flag_false_when_missing(self):
|
|
409
|
+
app = Application.objects.create(name="missing")
|
|
410
|
+
self.assertFalse(app.installed)
|
|
411
|
+
|
|
412
|
+
def test_verbose_name_property(self):
|
|
413
|
+
app = Application.objects.create(name="ocpp")
|
|
414
|
+
config = django_apps.get_app_config("ocpp")
|
|
415
|
+
self.assertEqual(app.verbose_name, config.verbose_name)
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
class ApplicationAdminFormTests(TestCase):
|
|
419
|
+
def test_name_field_uses_local_apps(self):
|
|
420
|
+
admin_instance = ApplicationAdmin(Application, admin.site)
|
|
421
|
+
form = admin_instance.get_form(request=None)()
|
|
422
|
+
choices = [choice[0] for choice in form.fields["name"].choices]
|
|
423
|
+
self.assertIn("core", choices)
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
class ApplicationAdminDisplayTests(TestCase):
|
|
427
|
+
def setUp(self):
|
|
428
|
+
User = get_user_model()
|
|
429
|
+
self.admin = User.objects.create_superuser(
|
|
430
|
+
username="app-admin", password="pwd", email="admin@example.com"
|
|
431
|
+
)
|
|
432
|
+
self.client = Client()
|
|
433
|
+
self.client.force_login(self.admin)
|
|
434
|
+
|
|
435
|
+
def test_changelist_shows_verbose_name(self):
|
|
436
|
+
Application.objects.create(name="ocpp")
|
|
437
|
+
resp = self.client.get(reverse("admin:pages_application_changelist"))
|
|
438
|
+
config = django_apps.get_app_config("ocpp")
|
|
439
|
+
self.assertContains(resp, config.verbose_name)
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
class LandingCreationTests(TestCase):
|
|
443
|
+
def setUp(self):
|
|
444
|
+
role, _ = NodeRole.objects.get_or_create(name="Terminal")
|
|
445
|
+
Node.objects.update_or_create(
|
|
446
|
+
mac_address=Node.get_current_mac(),
|
|
447
|
+
defaults={"hostname": "localhost", "address": "127.0.0.1", "role": role},
|
|
448
|
+
)
|
|
449
|
+
self.app, _ = Application.objects.get_or_create(name="pages")
|
|
450
|
+
Site.objects.update_or_create(
|
|
451
|
+
id=1, defaults={"domain": "testserver", "name": ""}
|
|
452
|
+
)
|
|
453
|
+
self.role = role
|
|
454
|
+
|
|
455
|
+
def test_landings_created_on_module_creation(self):
|
|
456
|
+
module = Module.objects.create(node_role=self.role, application=self.app, path="/")
|
|
457
|
+
self.assertTrue(module.landings.filter(path="/").exists())
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
class LandingFixtureTests(TestCase):
|
|
461
|
+
def test_constellation_fixture_loads_without_duplicates(self):
|
|
462
|
+
fixture = Path(settings.BASE_DIR, "pages", "fixtures", "constellation.json")
|
|
463
|
+
call_command("loaddata", str(fixture))
|
|
464
|
+
call_command("loaddata", str(fixture))
|
|
465
|
+
module = Module.objects.get(path="/ocpp/", node_role__name="Constellation")
|
|
466
|
+
self.assertEqual(module.landings.filter(path="/ocpp/rfid/").count(), 1)
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
class AllowedHostSubnetTests(TestCase):
|
|
470
|
+
def setUp(self):
|
|
471
|
+
self.client = Client()
|
|
472
|
+
Site.objects.update_or_create(
|
|
473
|
+
id=1, defaults={"domain": "testserver", "name": "pages"}
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
@override_settings(ALLOWED_HOSTS=["10.42.0.0/16", "192.168.0.0/16"])
|
|
477
|
+
def test_private_network_hosts_allowed(self):
|
|
478
|
+
resp = self.client.get(
|
|
479
|
+
reverse("pages:index"), HTTP_HOST="10.42.1.5"
|
|
480
|
+
)
|
|
481
|
+
self.assertEqual(resp.status_code, 200)
|
|
482
|
+
resp = self.client.get(
|
|
483
|
+
reverse("pages:index"), HTTP_HOST="192.168.2.3"
|
|
484
|
+
)
|
|
485
|
+
self.assertEqual(resp.status_code, 200)
|
|
486
|
+
|
|
487
|
+
@override_settings(ALLOWED_HOSTS=["10.42.0.0/16"])
|
|
488
|
+
def test_host_outside_subnets_disallowed(self):
|
|
489
|
+
resp = self.client.get(
|
|
490
|
+
reverse("pages:index"), HTTP_HOST="11.0.0.1"
|
|
491
|
+
)
|
|
492
|
+
self.assertEqual(resp.status_code, 400)
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
class RFIDPageTests(TestCase):
|
|
496
|
+
def setUp(self):
|
|
497
|
+
self.client = Client()
|
|
498
|
+
Site.objects.update_or_create(
|
|
499
|
+
id=1, defaults={"domain": "testserver", "name": "pages"}
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
def test_page_renders(self):
|
|
503
|
+
resp = self.client.get(reverse("rfid-reader"))
|
|
504
|
+
self.assertContains(resp, "Scanner ready")
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
class FaviconTests(TestCase):
|
|
508
|
+
def setUp(self):
|
|
509
|
+
self.client = Client()
|
|
510
|
+
self.tmpdir = tempfile.mkdtemp()
|
|
511
|
+
self.addCleanup(shutil.rmtree, self.tmpdir)
|
|
512
|
+
|
|
513
|
+
def _png(self, name):
|
|
514
|
+
data = base64.b64decode(
|
|
515
|
+
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg=="
|
|
516
|
+
)
|
|
517
|
+
return SimpleUploadedFile(name, data, content_type="image/png")
|
|
518
|
+
|
|
519
|
+
def test_site_app_favicon_preferred_over_site(self):
|
|
520
|
+
with override_settings(MEDIA_ROOT=self.tmpdir):
|
|
521
|
+
role, _ = NodeRole.objects.get_or_create(name="Terminal")
|
|
522
|
+
Node.objects.update_or_create(
|
|
523
|
+
mac_address=Node.get_current_mac(),
|
|
524
|
+
defaults={"hostname": "localhost", "address": "127.0.0.1", "role": role},
|
|
525
|
+
)
|
|
526
|
+
site, _ = Site.objects.update_or_create(
|
|
527
|
+
id=1, defaults={"domain": "testserver", "name": ""}
|
|
528
|
+
)
|
|
529
|
+
SiteBadge.objects.create(
|
|
530
|
+
site=site, badge_color="#28a745", favicon=self._png("site.png")
|
|
531
|
+
)
|
|
532
|
+
app = Application.objects.create(name="readme")
|
|
533
|
+
Module.objects.create(
|
|
534
|
+
node_role=role,
|
|
535
|
+
application=app,
|
|
536
|
+
path="/",
|
|
537
|
+
is_default=True,
|
|
538
|
+
favicon=self._png("app.png"),
|
|
539
|
+
)
|
|
540
|
+
resp = self.client.get(reverse("pages:index"))
|
|
541
|
+
self.assertContains(resp, "app.png")
|
|
542
|
+
|
|
543
|
+
def test_site_favicon_used_when_app_missing(self):
|
|
544
|
+
with override_settings(MEDIA_ROOT=self.tmpdir):
|
|
545
|
+
role, _ = NodeRole.objects.get_or_create(name="Terminal")
|
|
546
|
+
Node.objects.update_or_create(
|
|
547
|
+
mac_address=Node.get_current_mac(),
|
|
548
|
+
defaults={"hostname": "localhost", "address": "127.0.0.1", "role": role},
|
|
549
|
+
)
|
|
550
|
+
site, _ = Site.objects.update_or_create(
|
|
551
|
+
id=1, defaults={"domain": "testserver", "name": ""}
|
|
552
|
+
)
|
|
553
|
+
SiteBadge.objects.create(
|
|
554
|
+
site=site, badge_color="#28a745", favicon=self._png("site.png")
|
|
555
|
+
)
|
|
556
|
+
app = Application.objects.create(name="readme")
|
|
557
|
+
Module.objects.create(
|
|
558
|
+
node_role=role, application=app, path="/", is_default=True
|
|
559
|
+
)
|
|
560
|
+
resp = self.client.get(reverse("pages:index"))
|
|
561
|
+
self.assertContains(resp, "site.png")
|
|
562
|
+
|
|
563
|
+
def test_default_favicon_used_when_none_defined(self):
|
|
564
|
+
with override_settings(MEDIA_ROOT=self.tmpdir):
|
|
565
|
+
role, _ = NodeRole.objects.get_or_create(name="Terminal")
|
|
566
|
+
Node.objects.update_or_create(
|
|
567
|
+
mac_address=Node.get_current_mac(),
|
|
568
|
+
defaults={"hostname": "localhost", "address": "127.0.0.1", "role": role},
|
|
569
|
+
)
|
|
570
|
+
Site.objects.update_or_create(
|
|
571
|
+
id=1, defaults={"domain": "testserver", "name": ""}
|
|
572
|
+
)
|
|
573
|
+
resp = self.client.get(reverse("pages:index"))
|
|
574
|
+
b64 = (
|
|
575
|
+
Path(settings.BASE_DIR)
|
|
576
|
+
.joinpath("pages", "fixtures", "data", "favicon.txt")
|
|
577
|
+
.read_text()
|
|
578
|
+
.strip()
|
|
579
|
+
)
|
|
580
|
+
self.assertContains(resp, b64)
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
class FavoriteTests(TestCase):
|
|
584
|
+
def setUp(self):
|
|
585
|
+
self.client = Client()
|
|
586
|
+
User = get_user_model()
|
|
587
|
+
self.user = User.objects.create_superuser(
|
|
588
|
+
username="favadmin", password="pwd", email="fav@example.com"
|
|
589
|
+
)
|
|
590
|
+
self.client.force_login(self.user)
|
|
591
|
+
Site.objects.update_or_create(id=1, defaults={"name": "test", "domain": "testserver"})
|
|
592
|
+
|
|
593
|
+
def test_add_favorite(self):
|
|
594
|
+
ct = ContentType.objects.get_by_natural_key("pages", "application")
|
|
595
|
+
url = reverse("admin:favorite_toggle", args=[ct.id])
|
|
596
|
+
resp = self.client.post(url, {"custom_label": "Apps", "user_data": "on"})
|
|
597
|
+
self.assertRedirects(resp, reverse("admin:index"))
|
|
598
|
+
fav = Favorite.objects.get(user=self.user, content_type=ct)
|
|
599
|
+
self.assertEqual(fav.custom_label, "Apps")
|
|
600
|
+
self.assertTrue(fav.user_data)
|
|
601
|
+
|
|
602
|
+
def test_existing_favorite_redirects_to_list(self):
|
|
603
|
+
ct = ContentType.objects.get_by_natural_key("pages", "application")
|
|
604
|
+
Favorite.objects.create(user=self.user, content_type=ct)
|
|
605
|
+
url = reverse("admin:favorite_toggle", args=[ct.id])
|
|
606
|
+
resp = self.client.get(url)
|
|
607
|
+
self.assertRedirects(resp, reverse("admin:favorite_list"))
|
|
608
|
+
resp = self.client.get(reverse("admin:favorite_list"))
|
|
609
|
+
self.assertContains(resp, ct.name)
|
|
610
|
+
|
|
611
|
+
def test_update_user_data_from_list(self):
|
|
612
|
+
ct = ContentType.objects.get_by_natural_key("pages", "application")
|
|
613
|
+
fav = Favorite.objects.create(user=self.user, content_type=ct)
|
|
614
|
+
url = reverse("admin:favorite_list")
|
|
615
|
+
resp = self.client.post(url, {"user_data": [str(fav.pk)]})
|
|
616
|
+
self.assertRedirects(resp, url)
|
|
617
|
+
fav.refresh_from_db()
|
|
618
|
+
self.assertTrue(fav.user_data)
|
|
619
|
+
|
|
620
|
+
def test_dashboard_includes_favorites_and_user_data(self):
|
|
621
|
+
fav_ct = ContentType.objects.get_by_natural_key("pages", "application")
|
|
622
|
+
Favorite.objects.create(user=self.user, content_type=fav_ct, custom_label="Apps")
|
|
623
|
+
role = NodeRole.objects.create(name="DataRole")
|
|
624
|
+
ud_ct = ContentType.objects.get_for_model(NodeRole)
|
|
625
|
+
UserDatum.objects.create(user=self.user, content_type=ud_ct, object_id=role.pk)
|
|
626
|
+
resp = self.client.get(reverse("admin:index"))
|
|
627
|
+
self.assertContains(resp, reverse("admin:pages_application_changelist"))
|
|
628
|
+
self.assertContains(resp, reverse("admin:nodes_noderole_changelist"))
|
pages/urls.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from django.urls import path
|
|
2
|
+
from . import views
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
app_name = "pages"
|
|
6
|
+
|
|
7
|
+
urlpatterns = [
|
|
8
|
+
path("", views.index, name="index"),
|
|
9
|
+
path("sitemap.xml", views.sitemap, name="pages-sitemap"),
|
|
10
|
+
path("login/", views.login_view, name="login"),
|
|
11
|
+
path("request-invite/", views.request_invite, name="request-invite"),
|
|
12
|
+
path(
|
|
13
|
+
"invitation/<uidb64>/<token>/",
|
|
14
|
+
views.invitation_login,
|
|
15
|
+
name="invitation-login",
|
|
16
|
+
),
|
|
17
|
+
]
|
pages/utils.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from django.urls import path as django_path
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def landing(label=None):
|
|
5
|
+
"""Decorator to mark a view as a landing page."""
|
|
6
|
+
|
|
7
|
+
def decorator(view):
|
|
8
|
+
view.landing = True
|
|
9
|
+
view.landing_label = label or view.__name__.replace("_", " ").title()
|
|
10
|
+
return view
|
|
11
|
+
|
|
12
|
+
return decorator
|
|
13
|
+
|