arthexis 0.1.19__py3-none-any.whl → 0.1.21__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.
- {arthexis-0.1.19.dist-info → arthexis-0.1.21.dist-info}/METADATA +5 -6
- {arthexis-0.1.19.dist-info → arthexis-0.1.21.dist-info}/RECORD +42 -44
- config/asgi.py +1 -15
- config/settings.py +0 -26
- config/urls.py +0 -1
- core/admin.py +143 -234
- core/apps.py +0 -6
- core/backends.py +8 -2
- core/environment.py +240 -4
- core/models.py +132 -102
- core/notifications.py +1 -1
- core/reference_utils.py +10 -11
- core/sigil_builder.py +2 -2
- core/tasks.py +24 -1
- core/tests.py +2 -7
- core/views.py +70 -132
- nodes/admin.py +162 -7
- nodes/models.py +294 -48
- nodes/rfid_sync.py +1 -1
- nodes/tasks.py +100 -2
- nodes/tests.py +581 -15
- nodes/urls.py +4 -0
- nodes/views.py +560 -96
- ocpp/admin.py +144 -4
- ocpp/consumers.py +106 -9
- ocpp/models.py +131 -1
- ocpp/tasks.py +4 -0
- ocpp/test_export_import.py +1 -0
- ocpp/test_rfid.py +3 -1
- ocpp/tests.py +183 -9
- ocpp/transactions_io.py +9 -1
- ocpp/urls.py +3 -3
- ocpp/views.py +186 -31
- pages/context_processors.py +15 -21
- pages/defaults.py +1 -1
- pages/module_defaults.py +5 -5
- pages/tests.py +110 -79
- pages/urls.py +1 -1
- pages/views.py +108 -13
- core/workgroup_urls.py +0 -17
- core/workgroup_views.py +0 -94
- {arthexis-0.1.19.dist-info → arthexis-0.1.21.dist-info}/WHEEL +0 -0
- {arthexis-0.1.19.dist-info → arthexis-0.1.21.dist-info}/licenses/LICENSE +0 -0
- {arthexis-0.1.19.dist-info → arthexis-0.1.21.dist-info}/top_level.txt +0 -0
pages/context_processors.py
CHANGED
|
@@ -2,7 +2,6 @@ from utils.sites import get_site
|
|
|
2
2
|
from django.urls import Resolver404, resolve
|
|
3
3
|
from django.conf import settings
|
|
4
4
|
from pathlib import Path
|
|
5
|
-
from types import SimpleNamespace
|
|
6
5
|
from nodes.models import Node
|
|
7
6
|
from core.models import Reference
|
|
8
7
|
from core.reference_utils import filter_visible_references
|
|
@@ -11,7 +10,8 @@ from .models import Module
|
|
|
11
10
|
_FAVICON_DIR = Path(settings.BASE_DIR) / "pages" / "fixtures" / "data"
|
|
12
11
|
_FAVICON_FILENAMES = {
|
|
13
12
|
"default": "favicon.txt",
|
|
14
|
-
"
|
|
13
|
+
"Watchtower": "favicon_watchtower.txt",
|
|
14
|
+
"Constellation": "favicon_watchtower.txt",
|
|
15
15
|
"Control": "favicon_control.txt",
|
|
16
16
|
"Satellite": "favicon_satellite.txt",
|
|
17
17
|
}
|
|
@@ -48,7 +48,6 @@ def nav_links(request):
|
|
|
48
48
|
modules = []
|
|
49
49
|
|
|
50
50
|
valid_modules = []
|
|
51
|
-
datasette_enabled = False
|
|
52
51
|
current_module = None
|
|
53
52
|
user = getattr(request, "user", None)
|
|
54
53
|
user_is_authenticated = getattr(user, "is_authenticated", False)
|
|
@@ -72,19 +71,24 @@ def nav_links(request):
|
|
|
72
71
|
required_groups = getattr(
|
|
73
72
|
view_func, "required_security_groups", frozenset()
|
|
74
73
|
)
|
|
74
|
+
blocked_reason = None
|
|
75
75
|
if required_groups:
|
|
76
76
|
requires_login = True
|
|
77
|
-
setattr(landing, "requires_login", True)
|
|
78
77
|
if not user_is_authenticated:
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
blocked_reason = "login"
|
|
79
|
+
elif not user_is_superuser and not (
|
|
81
80
|
user_group_names & set(required_groups)
|
|
82
81
|
):
|
|
83
|
-
|
|
82
|
+
blocked_reason = "permission"
|
|
84
83
|
elif requires_login and not user_is_authenticated:
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
84
|
+
blocked_reason = "login"
|
|
85
|
+
|
|
86
|
+
if staff_only and not getattr(request.user, "is_staff", False):
|
|
87
|
+
if blocked_reason != "login":
|
|
88
|
+
blocked_reason = "permission"
|
|
89
|
+
|
|
90
|
+
landing.nav_is_locked = bool(blocked_reason)
|
|
91
|
+
landing.nav_lock_reason = blocked_reason
|
|
88
92
|
landings.append(landing)
|
|
89
93
|
if landings:
|
|
90
94
|
normalized_module_path = module.path.rstrip("/") or "/"
|
|
@@ -100,7 +104,7 @@ def nav_links(request):
|
|
|
100
104
|
landings = [landings[0]]
|
|
101
105
|
app_name = getattr(module.application, "name", "").lower()
|
|
102
106
|
if app_name == "awg":
|
|
103
|
-
module.menu = "
|
|
107
|
+
module.menu = "Calculators"
|
|
104
108
|
elif module.path.rstrip("/").lower() == "/man":
|
|
105
109
|
module.menu = "Manual"
|
|
106
110
|
module.enabled_landings = landings
|
|
@@ -111,15 +115,6 @@ def nav_links(request):
|
|
|
111
115
|
):
|
|
112
116
|
current_module = module
|
|
113
117
|
|
|
114
|
-
datasette_lock = Path(settings.BASE_DIR) / "locks" / "datasette.lck"
|
|
115
|
-
if datasette_lock.exists():
|
|
116
|
-
datasette_enabled = True
|
|
117
|
-
datasette_module = SimpleNamespace(
|
|
118
|
-
menu_label="Data",
|
|
119
|
-
path="/data/",
|
|
120
|
-
enabled_landings=[SimpleNamespace(path="/data/", label="Datasette")],
|
|
121
|
-
)
|
|
122
|
-
valid_modules.append(datasette_module)
|
|
123
118
|
|
|
124
119
|
valid_modules.sort(key=lambda m: m.menu_label.lower())
|
|
125
120
|
|
|
@@ -153,5 +148,4 @@ def nav_links(request):
|
|
|
153
148
|
"nav_modules": valid_modules,
|
|
154
149
|
"favicon_url": favicon_url,
|
|
155
150
|
"header_references": header_references,
|
|
156
|
-
"datasette_enabled": datasette_enabled,
|
|
157
151
|
}
|
pages/defaults.py
CHANGED
|
@@ -7,7 +7,7 @@ DEFAULT_APPLICATION_DESCRIPTIONS: Dict[str, str] = {
|
|
|
7
7
|
"awg": "Power, Energy and Cost calculations.",
|
|
8
8
|
"core": "Support for Business Processes and monetization.",
|
|
9
9
|
"ocpp": "Compatibility with Standards and Good Practices.",
|
|
10
|
-
"nodes": "System and Node-level operations
|
|
10
|
+
"nodes": "System and Node-level operations.",
|
|
11
11
|
"pages": "User QA, Continuity Design and Chaos Testing.",
|
|
12
12
|
"teams": "Identity, Entitlements and Access Controls.",
|
|
13
13
|
}
|
pages/module_defaults.py
CHANGED
|
@@ -10,15 +10,15 @@ LandingDefinition = tuple[str, str]
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
ROLE_MODULE_DEFAULTS: Mapping[str, tuple[ModuleDefinition, ...]] = {
|
|
13
|
-
"
|
|
13
|
+
"Watchtower": (
|
|
14
14
|
{
|
|
15
15
|
"application": "ocpp",
|
|
16
16
|
"path": "/ocpp/",
|
|
17
17
|
"menu": "Chargers",
|
|
18
18
|
"landings": (
|
|
19
|
-
("/ocpp/", "CPMS Online Dashboard"),
|
|
20
|
-
("/ocpp/simulator/", "Charge Point Simulator"),
|
|
21
|
-
("/ocpp/rfid/", "RFID Tag Validator"),
|
|
19
|
+
("/ocpp/cpms/dashboard/", "CPMS Online Dashboard"),
|
|
20
|
+
("/ocpp/evcs/simulator/", "Charge Point Simulator"),
|
|
21
|
+
("/ocpp/rfid/validator/", "RFID Tag Validator"),
|
|
22
22
|
),
|
|
23
23
|
},
|
|
24
24
|
{
|
|
@@ -26,7 +26,7 @@ ROLE_MODULE_DEFAULTS: Mapping[str, tuple[ModuleDefinition, ...]] = {
|
|
|
26
26
|
"path": "/awg/",
|
|
27
27
|
"menu": "",
|
|
28
28
|
"landings": (
|
|
29
|
-
("/awg/", "AWG Calculator"),
|
|
29
|
+
("/awg/", "AWG Cable Calculator"),
|
|
30
30
|
("/awg/energy-tariff/", "Energy Tariff Calculator"),
|
|
31
31
|
),
|
|
32
32
|
},
|
pages/tests.py
CHANGED
|
@@ -3,6 +3,7 @@ import os
|
|
|
3
3
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
|
|
4
4
|
|
|
5
5
|
import django
|
|
6
|
+
import pytest
|
|
6
7
|
|
|
7
8
|
django.setup()
|
|
8
9
|
|
|
@@ -508,6 +509,7 @@ class InvitationTests(TestCase):
|
|
|
508
509
|
lead = InviteLead.objects.get()
|
|
509
510
|
self.assertEqual(lead.mac_address, "aa:bb:cc:dd:ee:ff")
|
|
510
511
|
|
|
512
|
+
@pytest.mark.feature("ap-router")
|
|
511
513
|
@patch("pages.views.public_wifi.grant_public_access")
|
|
512
514
|
@patch(
|
|
513
515
|
"pages.views.public_wifi.resolve_mac_address",
|
|
@@ -1360,6 +1362,7 @@ class SiteAdminRegisterCurrentTests(TestCase):
|
|
|
1360
1362
|
self.assertEqual(site.name, "")
|
|
1361
1363
|
|
|
1362
1364
|
|
|
1365
|
+
@pytest.mark.feature("screenshot-poll")
|
|
1363
1366
|
class SiteAdminScreenshotTests(TestCase):
|
|
1364
1367
|
def setUp(self):
|
|
1365
1368
|
self.client = Client()
|
|
@@ -1479,17 +1482,17 @@ class NavAppsTests(TestCase):
|
|
|
1479
1482
|
)
|
|
1480
1483
|
app = Application.objects.create(name="Readme")
|
|
1481
1484
|
Module.objects.create(
|
|
1482
|
-
node_role=role, application=app, path="/", is_default=True, menu="
|
|
1485
|
+
node_role=role, application=app, path="/", is_default=True, menu="Cookbooks"
|
|
1483
1486
|
)
|
|
1484
1487
|
|
|
1485
1488
|
def test_nav_pill_renders(self):
|
|
1486
1489
|
resp = self.client.get(reverse("pages:index"))
|
|
1487
|
-
self.assertContains(resp, "
|
|
1490
|
+
self.assertContains(resp, "COOKBOOKS")
|
|
1488
1491
|
self.assertContains(resp, "badge rounded-pill")
|
|
1489
1492
|
|
|
1490
1493
|
def test_nav_pill_renders_with_port(self):
|
|
1491
1494
|
resp = self.client.get(reverse("pages:index"), HTTP_HOST="127.0.0.1:8000")
|
|
1492
|
-
self.assertContains(resp, "
|
|
1495
|
+
self.assertContains(resp, "COOKBOOKS")
|
|
1493
1496
|
|
|
1494
1497
|
def test_nav_pill_uses_menu_field(self):
|
|
1495
1498
|
site_app = Module.objects.get()
|
|
@@ -1497,7 +1500,7 @@ class NavAppsTests(TestCase):
|
|
|
1497
1500
|
site_app.save()
|
|
1498
1501
|
resp = self.client.get(reverse("pages:index"))
|
|
1499
1502
|
self.assertContains(resp, 'badge rounded-pill text-bg-secondary">DOCS')
|
|
1500
|
-
self.assertNotContains(resp, 'badge rounded-pill text-bg-secondary">
|
|
1503
|
+
self.assertNotContains(resp, 'badge rounded-pill text-bg-secondary">COOKBOOKS')
|
|
1501
1504
|
|
|
1502
1505
|
def test_app_without_root_url_excluded(self):
|
|
1503
1506
|
role = NodeRole.objects.get(name="Terminal")
|
|
@@ -1562,20 +1565,22 @@ class RoleLandingRedirectTests(TestCase):
|
|
|
1562
1565
|
|
|
1563
1566
|
def test_satellite_redirects_to_dashboard(self):
|
|
1564
1567
|
target = self._configure_role_landing(
|
|
1565
|
-
"Satellite", "/ocpp/", "CPMS Online Dashboard"
|
|
1568
|
+
"Satellite", "/ocpp/cpms/dashboard/", "CPMS Online Dashboard"
|
|
1566
1569
|
)
|
|
1567
1570
|
resp = self.client.get(reverse("pages:index"))
|
|
1568
1571
|
self.assertRedirects(resp, target, fetch_redirect_response=False)
|
|
1569
1572
|
|
|
1570
1573
|
def test_control_redirects_to_rfid(self):
|
|
1571
1574
|
target = self._configure_role_landing(
|
|
1572
|
-
"Control", "/ocpp/rfid/", "RFID Tag Validator"
|
|
1575
|
+
"Control", "/ocpp/rfid/validator/", "RFID Tag Validator"
|
|
1573
1576
|
)
|
|
1574
1577
|
resp = self.client.get(reverse("pages:index"))
|
|
1575
1578
|
self.assertRedirects(resp, target, fetch_redirect_response=False)
|
|
1576
1579
|
|
|
1577
1580
|
def test_security_group_redirect_takes_priority(self):
|
|
1578
|
-
self._configure_role_landing(
|
|
1581
|
+
self._configure_role_landing(
|
|
1582
|
+
"Control", "/ocpp/rfid/validator/", "RFID Tag Validator"
|
|
1583
|
+
)
|
|
1579
1584
|
role = self.node.role
|
|
1580
1585
|
group = SecurityGroup.objects.create(name="Operators")
|
|
1581
1586
|
group_landing = self._ensure_landing(role, "/ocpp/group/", "Group Landing")
|
|
@@ -1592,7 +1597,9 @@ class RoleLandingRedirectTests(TestCase):
|
|
|
1592
1597
|
)
|
|
1593
1598
|
|
|
1594
1599
|
def test_user_redirect_overrides_group_with_higher_priority(self):
|
|
1595
|
-
self._configure_role_landing(
|
|
1600
|
+
self._configure_role_landing(
|
|
1601
|
+
"Control", "/ocpp/rfid/validator/", "RFID Tag Validator"
|
|
1602
|
+
)
|
|
1596
1603
|
role = self.node.role
|
|
1597
1604
|
group = SecurityGroup.objects.create(name="Operators")
|
|
1598
1605
|
group_landing = self._ensure_landing(role, "/ocpp/group/", "Group Landing")
|
|
@@ -1614,10 +1621,10 @@ class RoleLandingRedirectTests(TestCase):
|
|
|
1614
1621
|
)
|
|
1615
1622
|
|
|
1616
1623
|
|
|
1617
|
-
class
|
|
1624
|
+
class WatchtowerNavTests(TestCase):
|
|
1618
1625
|
def setUp(self):
|
|
1619
1626
|
self.client = Client()
|
|
1620
|
-
role, _ = NodeRole.objects.get_or_create(name="
|
|
1627
|
+
role, _ = NodeRole.objects.get_or_create(name="Watchtower")
|
|
1621
1628
|
Node.objects.update_or_create(
|
|
1622
1629
|
mac_address=Node.get_current_mac(),
|
|
1623
1630
|
defaults={
|
|
@@ -1627,38 +1634,56 @@ class ConstellationNavTests(TestCase):
|
|
|
1627
1634
|
},
|
|
1628
1635
|
)
|
|
1629
1636
|
Site.objects.update_or_create(
|
|
1630
|
-
id=1, defaults={"domain": "
|
|
1637
|
+
id=1, defaults={"domain": "arthexis.com", "name": "Arthexis"}
|
|
1631
1638
|
)
|
|
1632
1639
|
fixtures = [
|
|
1633
1640
|
Path(
|
|
1634
1641
|
settings.BASE_DIR,
|
|
1635
1642
|
"pages",
|
|
1636
1643
|
"fixtures",
|
|
1637
|
-
"
|
|
1644
|
+
"default__application_pages.json",
|
|
1645
|
+
),
|
|
1646
|
+
Path(
|
|
1647
|
+
settings.BASE_DIR,
|
|
1648
|
+
"pages",
|
|
1649
|
+
"fixtures",
|
|
1650
|
+
"watchtower__application_ocpp.json",
|
|
1651
|
+
),
|
|
1652
|
+
Path(
|
|
1653
|
+
settings.BASE_DIR,
|
|
1654
|
+
"pages",
|
|
1655
|
+
"fixtures",
|
|
1656
|
+
"watchtower__module_ocpp.json",
|
|
1638
1657
|
),
|
|
1639
1658
|
Path(
|
|
1640
1659
|
settings.BASE_DIR,
|
|
1641
1660
|
"pages",
|
|
1642
1661
|
"fixtures",
|
|
1643
|
-
"
|
|
1662
|
+
"watchtower__landing_ocpp_dashboard.json",
|
|
1644
1663
|
),
|
|
1645
1664
|
Path(
|
|
1646
1665
|
settings.BASE_DIR,
|
|
1647
1666
|
"pages",
|
|
1648
1667
|
"fixtures",
|
|
1649
|
-
"
|
|
1668
|
+
"watchtower__landing_ocpp_cp_simulator.json",
|
|
1650
1669
|
),
|
|
1651
1670
|
Path(
|
|
1652
1671
|
settings.BASE_DIR,
|
|
1653
1672
|
"pages",
|
|
1654
1673
|
"fixtures",
|
|
1655
|
-
"
|
|
1674
|
+
"watchtower__landing_ocpp_rfid.json",
|
|
1656
1675
|
),
|
|
1657
1676
|
Path(
|
|
1658
1677
|
settings.BASE_DIR,
|
|
1659
1678
|
"pages",
|
|
1660
1679
|
"fixtures",
|
|
1661
|
-
"
|
|
1680
|
+
"watchtower__module_readme.json",
|
|
1681
|
+
),
|
|
1682
|
+
Path(
|
|
1683
|
+
settings.BASE_DIR,
|
|
1684
|
+
"pages",
|
|
1685
|
+
"fixtures",
|
|
1686
|
+
"watchtower__landing_readme.json",
|
|
1662
1687
|
),
|
|
1663
1688
|
]
|
|
1664
1689
|
call_command("loaddata", *map(str, fixtures))
|
|
@@ -1671,13 +1696,13 @@ class ConstellationNavTests(TestCase):
|
|
|
1671
1696
|
self.assertNotIn("RFID", nav_labels)
|
|
1672
1697
|
self.assertTrue(
|
|
1673
1698
|
Module.objects.filter(
|
|
1674
|
-
path="/ocpp/", node_role__name="
|
|
1699
|
+
path="/ocpp/", node_role__name="Watchtower"
|
|
1675
1700
|
).exists()
|
|
1676
1701
|
)
|
|
1677
1702
|
self.assertFalse(
|
|
1678
1703
|
Module.objects.filter(
|
|
1679
1704
|
path="/ocpp/rfid/",
|
|
1680
|
-
node_role__name="
|
|
1705
|
+
node_role__name="Watchtower",
|
|
1681
1706
|
is_deleted=False,
|
|
1682
1707
|
).exists()
|
|
1683
1708
|
)
|
|
@@ -1689,9 +1714,16 @@ class ConstellationNavTests(TestCase):
|
|
|
1689
1714
|
landing_labels = [landing.label for landing in ocpp_module.enabled_landings]
|
|
1690
1715
|
self.assertIn("RFID Tag Validator", landing_labels)
|
|
1691
1716
|
|
|
1717
|
+
@override_settings(ALLOWED_HOSTS=["testserver", "arthexis.com"])
|
|
1718
|
+
def test_cookbooks_pill_visible_for_arthexis(self):
|
|
1719
|
+
resp = self.client.get(
|
|
1720
|
+
reverse("pages:index"), HTTP_HOST="arthexis.com"
|
|
1721
|
+
)
|
|
1722
|
+
self.assertContains(resp, 'badge rounded-pill text-bg-secondary">COOKBOOKS')
|
|
1723
|
+
|
|
1692
1724
|
def test_ocpp_dashboard_visible(self):
|
|
1693
1725
|
resp = self.client.get(reverse("pages:index"))
|
|
1694
|
-
self.assertContains(resp, 'href="/ocpp/"')
|
|
1726
|
+
self.assertContains(resp, 'href="/ocpp/cpms/dashboard/"')
|
|
1695
1727
|
|
|
1696
1728
|
|
|
1697
1729
|
class ReleaseModuleNavTests(TestCase):
|
|
@@ -1833,7 +1865,7 @@ class ControlNavTests(TestCase):
|
|
|
1833
1865
|
self.client.force_login(user)
|
|
1834
1866
|
resp = self.client.get(reverse("pages:index"))
|
|
1835
1867
|
self.assertEqual(resp.status_code, 200)
|
|
1836
|
-
self.assertContains(resp, 'href="/ocpp/"')
|
|
1868
|
+
self.assertContains(resp, 'href="/ocpp/cpms/dashboard/"')
|
|
1837
1869
|
self.assertContains(
|
|
1838
1870
|
resp, 'badge rounded-pill text-bg-secondary">CHARGERS'
|
|
1839
1871
|
)
|
|
@@ -1865,10 +1897,27 @@ class ControlNavTests(TestCase):
|
|
|
1865
1897
|
self.assertFalse(resp.context["header_references"])
|
|
1866
1898
|
self.assertNotContains(resp, "https://example.com/hidden")
|
|
1867
1899
|
|
|
1900
|
+
def test_header_link_hidden_when_only_site_matches(self):
|
|
1901
|
+
terminal_role, _ = NodeRole.objects.get_or_create(name="Terminal")
|
|
1902
|
+
site = Site.objects.get(domain="testserver")
|
|
1903
|
+
reference = Reference.objects.create(
|
|
1904
|
+
alt_text="Restricted",
|
|
1905
|
+
value="https://example.com/restricted",
|
|
1906
|
+
show_in_header=True,
|
|
1907
|
+
)
|
|
1908
|
+
reference.roles.add(terminal_role)
|
|
1909
|
+
reference.sites.add(site)
|
|
1910
|
+
|
|
1911
|
+
resp = self.client.get(reverse("pages:index"))
|
|
1912
|
+
|
|
1913
|
+
self.assertIn("header_references", resp.context)
|
|
1914
|
+
self.assertFalse(resp.context["header_references"])
|
|
1915
|
+
self.assertNotContains(resp, "https://example.com/restricted")
|
|
1916
|
+
|
|
1868
1917
|
def test_readme_pill_visible(self):
|
|
1869
1918
|
resp = self.client.get(reverse("pages:readme"))
|
|
1870
1919
|
self.assertContains(resp, 'href="/read/"')
|
|
1871
|
-
self.assertContains(resp, 'badge rounded-pill text-bg-secondary">
|
|
1920
|
+
self.assertContains(resp, 'badge rounded-pill text-bg-secondary">COOKBOOKS')
|
|
1872
1921
|
|
|
1873
1922
|
def test_cookbook_pill_has_no_dropdown(self):
|
|
1874
1923
|
module = Module.objects.get(node_role__name="Control", path="/read/")
|
|
@@ -1883,7 +1932,7 @@ class ControlNavTests(TestCase):
|
|
|
1883
1932
|
|
|
1884
1933
|
self.assertContains(
|
|
1885
1934
|
resp,
|
|
1886
|
-
'<a class="nav-link" href="/read/"><span class="badge rounded-pill text-bg-secondary">
|
|
1935
|
+
'<a class="nav-link" href="/read/"><span class="badge rounded-pill text-bg-secondary">COOKBOOKS</span></a>',
|
|
1887
1936
|
html=True,
|
|
1888
1937
|
)
|
|
1889
1938
|
self.assertNotContains(resp, 'dropdown-item" href="/man/"')
|
|
@@ -1990,7 +2039,7 @@ class SatelliteNavTests(TestCase):
|
|
|
1990
2039
|
def test_readme_pill_visible(self):
|
|
1991
2040
|
resp = self.client.get(reverse("pages:readme"))
|
|
1992
2041
|
self.assertContains(resp, 'href="/read/"')
|
|
1993
|
-
self.assertContains(resp, 'badge rounded-pill text-bg-secondary">
|
|
2042
|
+
self.assertContains(resp, 'badge rounded-pill text-bg-secondary">COOKBOOKS')
|
|
1994
2043
|
|
|
1995
2044
|
|
|
1996
2045
|
class PowerNavTests(TestCase):
|
|
@@ -2025,9 +2074,9 @@ class PowerNavTests(TestCase):
|
|
|
2025
2074
|
power_module = module
|
|
2026
2075
|
break
|
|
2027
2076
|
self.assertIsNotNone(power_module)
|
|
2028
|
-
self.assertEqual(power_module.menu_label.upper(), "
|
|
2077
|
+
self.assertEqual(power_module.menu_label.upper(), "CALCULATORS")
|
|
2029
2078
|
landing_labels = {landing.label for landing in power_module.enabled_landings}
|
|
2030
|
-
self.assertIn("AWG Calculator", landing_labels)
|
|
2079
|
+
self.assertIn("AWG Cable Calculator", landing_labels)
|
|
2031
2080
|
|
|
2032
2081
|
def test_manual_pill_label(self):
|
|
2033
2082
|
resp = self.client.get(reverse("pages:index"))
|
|
@@ -2051,9 +2100,26 @@ class PowerNavTests(TestCase):
|
|
|
2051
2100
|
break
|
|
2052
2101
|
self.assertIsNotNone(power_module)
|
|
2053
2102
|
landing_labels = {landing.label for landing in power_module.enabled_landings}
|
|
2054
|
-
self.assertIn("AWG Calculator", landing_labels)
|
|
2103
|
+
self.assertIn("AWG Cable Calculator", landing_labels)
|
|
2055
2104
|
self.assertIn("Energy Tariff Calculator", landing_labels)
|
|
2056
2105
|
|
|
2106
|
+
def test_locked_landing_shows_lock_icon(self):
|
|
2107
|
+
resp = self.client.get(reverse("pages:index"))
|
|
2108
|
+
html = resp.content.decode()
|
|
2109
|
+
energy_index = html.find("Energy Tariff Calculator")
|
|
2110
|
+
self.assertGreaterEqual(energy_index, 0)
|
|
2111
|
+
icon_index = html.find("dropdown-lock-icon", energy_index, energy_index + 300)
|
|
2112
|
+
self.assertGreaterEqual(icon_index, 0)
|
|
2113
|
+
|
|
2114
|
+
def test_lock_icon_disappears_after_login(self):
|
|
2115
|
+
self.client.force_login(self.user)
|
|
2116
|
+
resp = self.client.get(reverse("pages:index"))
|
|
2117
|
+
html = resp.content.decode()
|
|
2118
|
+
energy_index = html.find("Energy Tariff Calculator")
|
|
2119
|
+
self.assertGreaterEqual(energy_index, 0)
|
|
2120
|
+
icon_index = html.find("dropdown-lock-icon", energy_index, energy_index + 300)
|
|
2121
|
+
self.assertEqual(icon_index, -1)
|
|
2122
|
+
|
|
2057
2123
|
|
|
2058
2124
|
class StaffNavVisibilityTests(TestCase):
|
|
2059
2125
|
def setUp(self):
|
|
@@ -2075,12 +2141,12 @@ class StaffNavVisibilityTests(TestCase):
|
|
|
2075
2141
|
def test_nonstaff_pill_hidden(self):
|
|
2076
2142
|
self.client.login(username="user", password="pw")
|
|
2077
2143
|
resp = self.client.get(reverse("pages:index"))
|
|
2078
|
-
self.assertContains(resp, 'href="/ocpp/"')
|
|
2144
|
+
self.assertContains(resp, 'href="/ocpp/cpms/dashboard/"')
|
|
2079
2145
|
|
|
2080
2146
|
def test_staff_sees_pill(self):
|
|
2081
2147
|
self.client.login(username="staff", password="pw")
|
|
2082
2148
|
resp = self.client.get(reverse("pages:index"))
|
|
2083
|
-
self.assertContains(resp, 'href="/ocpp/"')
|
|
2149
|
+
self.assertContains(resp, 'href="/ocpp/cpms/dashboard/"')
|
|
2084
2150
|
|
|
2085
2151
|
|
|
2086
2152
|
class ModuleAdminReloadActionTests(TestCase):
|
|
@@ -2093,7 +2159,7 @@ class ModuleAdminReloadActionTests(TestCase):
|
|
|
2093
2159
|
password="pw",
|
|
2094
2160
|
)
|
|
2095
2161
|
self.client.force_login(self.superuser)
|
|
2096
|
-
self.role, _ = NodeRole.objects.get_or_create(name="
|
|
2162
|
+
self.role, _ = NodeRole.objects.get_or_create(name="Watchtower")
|
|
2097
2163
|
Application.objects.get_or_create(name="ocpp")
|
|
2098
2164
|
Application.objects.get_or_create(name="awg")
|
|
2099
2165
|
Site.objects.update_or_create(
|
|
@@ -2131,7 +2197,11 @@ class ModuleAdminReloadActionTests(TestCase):
|
|
|
2131
2197
|
)
|
|
2132
2198
|
self.assertSetEqual(
|
|
2133
2199
|
charger_landings,
|
|
2134
|
-
{
|
|
2200
|
+
{
|
|
2201
|
+
"/ocpp/cpms/dashboard/",
|
|
2202
|
+
"/ocpp/evcs/simulator/",
|
|
2203
|
+
"/ocpp/rfid/validator/",
|
|
2204
|
+
},
|
|
2135
2205
|
)
|
|
2136
2206
|
|
|
2137
2207
|
calculator_landings = set(
|
|
@@ -2331,12 +2401,12 @@ class LandingCreationTests(TestCase):
|
|
|
2331
2401
|
|
|
2332
2402
|
|
|
2333
2403
|
class LandingFixtureTests(TestCase):
|
|
2334
|
-
def
|
|
2404
|
+
def test_watchtower_fixture_loads_without_duplicates(self):
|
|
2335
2405
|
from glob import glob
|
|
2336
2406
|
|
|
2337
|
-
NodeRole.objects.get_or_create(name="
|
|
2407
|
+
NodeRole.objects.get_or_create(name="Watchtower")
|
|
2338
2408
|
fixtures = glob(
|
|
2339
|
-
str(Path(settings.BASE_DIR, "pages", "fixtures", "
|
|
2409
|
+
str(Path(settings.BASE_DIR, "pages", "fixtures", "watchtower__*.json"))
|
|
2340
2410
|
)
|
|
2341
2411
|
fixtures = sorted(
|
|
2342
2412
|
fixtures,
|
|
@@ -2346,9 +2416,11 @@ class LandingFixtureTests(TestCase):
|
|
|
2346
2416
|
)
|
|
2347
2417
|
call_command("loaddata", *fixtures)
|
|
2348
2418
|
call_command("loaddata", *fixtures)
|
|
2349
|
-
module = Module.objects.get(path="/ocpp/", node_role__name="
|
|
2419
|
+
module = Module.objects.get(path="/ocpp/", node_role__name="Watchtower")
|
|
2350
2420
|
module.create_landings()
|
|
2351
|
-
self.assertEqual(
|
|
2421
|
+
self.assertEqual(
|
|
2422
|
+
module.landings.filter(path="/ocpp/rfid/validator/").count(), 1
|
|
2423
|
+
)
|
|
2352
2424
|
|
|
2353
2425
|
|
|
2354
2426
|
class AllowedHostSubnetTests(TestCase):
|
|
@@ -2501,9 +2573,9 @@ class FaviconTests(TestCase):
|
|
|
2501
2573
|
)
|
|
2502
2574
|
self.assertContains(resp, b64)
|
|
2503
2575
|
|
|
2504
|
-
def
|
|
2576
|
+
def test_watchtower_nodes_use_goldenrod_favicon(self):
|
|
2505
2577
|
with override_settings(MEDIA_ROOT=self.tmpdir):
|
|
2506
|
-
role, _ = NodeRole.objects.get_or_create(name="
|
|
2578
|
+
role, _ = NodeRole.objects.get_or_create(name="Watchtower")
|
|
2507
2579
|
Node.objects.update_or_create(
|
|
2508
2580
|
mac_address=Node.get_current_mac(),
|
|
2509
2581
|
defaults={
|
|
@@ -2518,7 +2590,7 @@ class FaviconTests(TestCase):
|
|
|
2518
2590
|
resp = self.client.get(reverse("pages:index"))
|
|
2519
2591
|
b64 = (
|
|
2520
2592
|
Path(settings.BASE_DIR)
|
|
2521
|
-
.joinpath("pages", "fixtures", "data", "
|
|
2593
|
+
.joinpath("pages", "fixtures", "data", "favicon_watchtower.txt")
|
|
2522
2594
|
.read_text()
|
|
2523
2595
|
.strip()
|
|
2524
2596
|
)
|
|
@@ -3149,47 +3221,6 @@ class AdminModelGraphViewTests(TestCase):
|
|
|
3149
3221
|
self.assertEqual(kwargs.get("format"), "pdf")
|
|
3150
3222
|
|
|
3151
3223
|
|
|
3152
|
-
class DatasetteTests(TestCase):
|
|
3153
|
-
def setUp(self):
|
|
3154
|
-
self.client = Client()
|
|
3155
|
-
User = get_user_model()
|
|
3156
|
-
self.user = User.objects.create_user(username="ds", password="pwd")
|
|
3157
|
-
Site.objects.update_or_create(id=1, defaults={"name": "Terminal"})
|
|
3158
|
-
|
|
3159
|
-
def test_datasette_auth_endpoint(self):
|
|
3160
|
-
resp = self.client.get(reverse("pages:datasette-auth"))
|
|
3161
|
-
self.assertEqual(resp.status_code, 401)
|
|
3162
|
-
self.client.force_login(self.user)
|
|
3163
|
-
resp = self.client.get(reverse("pages:datasette-auth"))
|
|
3164
|
-
self.assertEqual(resp.status_code, 200)
|
|
3165
|
-
|
|
3166
|
-
def test_navbar_includes_datasette_when_enabled(self):
|
|
3167
|
-
lock_dir = Path(settings.BASE_DIR) / "locks"
|
|
3168
|
-
lock_dir.mkdir(exist_ok=True)
|
|
3169
|
-
lock_file = lock_dir / "datasette.lck"
|
|
3170
|
-
try:
|
|
3171
|
-
lock_file.touch()
|
|
3172
|
-
resp = self.client.get(reverse("pages:index"))
|
|
3173
|
-
self.assertContains(resp, 'href="/data/"')
|
|
3174
|
-
finally:
|
|
3175
|
-
lock_file.unlink(missing_ok=True)
|
|
3176
|
-
|
|
3177
|
-
def test_admin_home_includes_datasette_button_when_enabled(self):
|
|
3178
|
-
lock_dir = Path(settings.BASE_DIR) / "locks"
|
|
3179
|
-
lock_dir.mkdir(exist_ok=True)
|
|
3180
|
-
lock_file = lock_dir / "datasette.lck"
|
|
3181
|
-
try:
|
|
3182
|
-
lock_file.touch()
|
|
3183
|
-
self.user.is_staff = True
|
|
3184
|
-
self.user.is_superuser = True
|
|
3185
|
-
self.user.save()
|
|
3186
|
-
self.client.force_login(self.user)
|
|
3187
|
-
resp = self.client.get(reverse("admin:index"))
|
|
3188
|
-
self.assertContains(resp, 'href="/data/"')
|
|
3189
|
-
self.assertContains(resp, ">Datasette<")
|
|
3190
|
-
finally:
|
|
3191
|
-
lock_file.unlink(missing_ok=True)
|
|
3192
|
-
|
|
3193
3224
|
|
|
3194
3225
|
class UserStorySubmissionTests(TestCase):
|
|
3195
3226
|
def setUp(self):
|
pages/urls.py
CHANGED
|
@@ -6,6 +6,7 @@ app_name = "pages"
|
|
|
6
6
|
|
|
7
7
|
urlpatterns = [
|
|
8
8
|
path("", views.index, name="index"),
|
|
9
|
+
path("read/<path:doc>/edit/", views.readme_edit, name="readme-edit"),
|
|
9
10
|
path("read/", views.readme, name="readme"),
|
|
10
11
|
path("read/<path:doc>", views.readme, name="readme-document"),
|
|
11
12
|
path("sitemap.xml", views.sitemap, name="pages-sitemap"),
|
|
@@ -20,7 +21,6 @@ urlpatterns = [
|
|
|
20
21
|
views.invitation_login,
|
|
21
22
|
name="invitation-login",
|
|
22
23
|
),
|
|
23
|
-
path("datasette-auth/", views.datasette_auth, name="datasette-auth"),
|
|
24
24
|
path("man/", views.manual_list, name="manual-list"),
|
|
25
25
|
path("man/<slug:slug>/", views.manual_detail, name="manual-detail"),
|
|
26
26
|
path("man/<slug:slug>/pdf/", views.manual_pdf, name="manual-pdf"),
|