django-spire 0.22.3__py3-none-any.whl → 0.23.0__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.
- django_spire/auth/group/forms.py +3 -4
- django_spire/auth/group/utils.py +1 -2
- django_spire/auth/group/views/form_views.py +22 -14
- django_spire/auth/group/views/page_views.py +1 -1
- django_spire/auth/mfa/utils.py +1 -1
- django_spire/auth/permissions/consts.py +2 -1
- django_spire/auth/permissions/decorators.py +1 -2
- django_spire/auth/permissions/permissions.py +1 -3
- django_spire/comment/factories.py +1 -1
- django_spire/comment/mixins.py +1 -1
- django_spire/comment/views.py +1 -1
- django_spire/consts.py +1 -1
- django_spire/contrib/breadcrumb/breadcrumbs.py +1 -1
- django_spire/contrib/form/confirmation_forms.py +1 -1
- django_spire/contrib/form/utils.py +26 -16
- django_spire/contrib/generic_views/modal_views.py +1 -1
- django_spire/contrib/generic_views/portal_views.py +18 -55
- django_spire/contrib/ordering/validators.py +4 -8
- django_spire/core/context_processors.py +1 -1
- django_spire/core/management/commands/spire_startapp_pkg/user_input.py +1 -1
- django_spire/core/middleware/maintenance.py +2 -1
- django_spire/core/middleware.py +2 -1
- django_spire/core/redirect/generic_redirect.py +1 -1
- django_spire/core/redirect/safe_redirect.py +2 -1
- django_spire/core/shortcuts.py +4 -2
- django_spire/core/table/__init__.py +0 -0
- django_spire/core/table/enums.py +18 -0
- django_spire/core/templates/django_spire/card/infinite_scroll_card.html +3 -137
- django_spire/core/templates/django_spire/container/infinite_scroll_container.html +64 -0
- django_spire/core/templates/django_spire/infinite_scroll/base.html +348 -0
- django_spire/core/templates/django_spire/infinite_scroll/element/footer.html +11 -0
- django_spire/core/templates/django_spire/infinite_scroll/scroll.html +152 -0
- django_spire/core/templates/django_spire/item/infinite_scroll_item.html +33 -0
- django_spire/core/templates/django_spire/lazy_tab/element/lazy_tab_section_element.html +19 -0
- django_spire/core/templates/django_spire/lazy_tab/element/lazy_tab_trigger_element.html +15 -0
- django_spire/core/templates/django_spire/lazy_tab/lazy_tab.html +157 -0
- django_spire/core/templates/django_spire/page/infinite_scroll_list_page.html +7 -0
- django_spire/core/templates/django_spire/table/base.html +185 -373
- django_spire/core/templates/django_spire/table/element/footer.html +7 -15
- django_spire/core/templates/django_spire/table/element/header.html +1 -1
- django_spire/core/templates/django_spire/table/element/row.html +15 -7
- django_spire/core/templatetags/spire_core_tags.py +1 -2
- django_spire/file/fields.py +1 -1
- django_spire/file/interfaces.py +1 -1
- django_spire/file/views.py +1 -1
- django_spire/history/activity/utils.py +1 -1
- django_spire/history/mixins.py +0 -4
- django_spire/knowledge/static/django_spire/knowledge/css/editor.css +93 -0
- django_spire/knowledge/templates/django_spire/knowledge/entry/version/page/editor_page.html +4 -0
- django_spire/notification/app/context_data.py +3 -1
- django_spire/notification/app/views/json_views.py +1 -1
- django_spire/notification/app/views/page_views.py +2 -1
- django_spire/notification/app/views/template_views.py +2 -2
- django_spire/profiling/middleware/profiling.py +2 -2
- django_spire/profiling/panel.py +2 -2
- django_spire/testing/__init__.py +0 -0
- django_spire/testing/playwright/__init__.py +64 -0
- django_spire/testing/playwright/components/__init__.py +45 -0
- django_spire/testing/playwright/components/accordion.py +55 -0
- django_spire/testing/playwright/components/attribute_element.py +73 -0
- django_spire/testing/playwright/components/base_session_filter_form.py +57 -0
- django_spire/testing/playwright/components/breadcrumb_element.py +56 -0
- django_spire/testing/playwright/components/card.py +102 -0
- django_spire/testing/playwright/components/dropdown.py +87 -0
- django_spire/testing/playwright/components/infinite_scroll.py +158 -0
- django_spire/testing/playwright/components/lazy_tab.py +92 -0
- django_spire/testing/playwright/components/modal.py +101 -0
- django_spire/testing/playwright/components/navigation.py +119 -0
- django_spire/testing/playwright/components/notification_bell.py +59 -0
- django_spire/testing/playwright/components/theme_selector.py +46 -0
- django_spire/testing/playwright/components/toast.py +72 -0
- django_spire/testing/playwright/fixtures.py +54 -0
- django_spire/testing/playwright/pages/__init__.py +6 -0
- django_spire/testing/playwright/pages/base.py +24 -0
- django_spire/theme/models.py +1 -1
- django_spire/theme/tests/test_context_processor.py +0 -1
- django_spire/theme/views/json_views.py +1 -1
- django_spire/theme/views/page_views.py +1 -1
- {django_spire-0.22.3.dist-info → django_spire-0.23.0.dist-info}/METADATA +4 -1
- {django_spire-0.22.3.dist-info → django_spire-0.23.0.dist-info}/RECORD +83 -52
- {django_spire-0.22.3.dist-info → django_spire-0.23.0.dist-info}/WHEEL +0 -0
- {django_spire-0.22.3.dist-info → django_spire-0.23.0.dist-info}/licenses/LICENSE.md +0 -0
- {django_spire-0.22.3.dist-info → django_spire-0.23.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from playwright.sync_api import Locator, Page
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class NotificationBell:
|
|
10
|
+
"""Playwright component for django_spire/notification/app/element/notification_bell.html"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, page: Page) -> None:
|
|
13
|
+
self.page = page
|
|
14
|
+
|
|
15
|
+
@property
|
|
16
|
+
def badge(self) -> Locator:
|
|
17
|
+
return self.bell.locator('.badge, .position-absolute')
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def bell(self) -> Locator:
|
|
21
|
+
return self.page.locator('.bi-bell').first
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def dropdown(self) -> Locator:
|
|
25
|
+
return self.page.locator('.notification-dropdown, [x-show*="notification"]')
|
|
26
|
+
|
|
27
|
+
def click(self) -> None:
|
|
28
|
+
self.bell.click()
|
|
29
|
+
|
|
30
|
+
def get_badge_count(self) -> int:
|
|
31
|
+
if not self.has_badge():
|
|
32
|
+
return 0
|
|
33
|
+
|
|
34
|
+
text = self.badge.inner_text()
|
|
35
|
+
|
|
36
|
+
if text.isdigit():
|
|
37
|
+
return int(text)
|
|
38
|
+
|
|
39
|
+
return 0
|
|
40
|
+
|
|
41
|
+
def has_badge(self) -> bool:
|
|
42
|
+
return self.badge.count() > 0 and self.badge.is_visible()
|
|
43
|
+
|
|
44
|
+
def has_notifications(self) -> bool:
|
|
45
|
+
return self.has_badge() and self.get_badge_count() > 0
|
|
46
|
+
|
|
47
|
+
def is_dropdown_open(self) -> bool:
|
|
48
|
+
return self.dropdown.is_visible()
|
|
49
|
+
|
|
50
|
+
def is_visible(self) -> bool:
|
|
51
|
+
return self.bell.is_visible()
|
|
52
|
+
|
|
53
|
+
def open_dropdown(self) -> None:
|
|
54
|
+
if not self.is_dropdown_open():
|
|
55
|
+
self.click()
|
|
56
|
+
|
|
57
|
+
def close_dropdown(self) -> None:
|
|
58
|
+
if self.is_dropdown_open():
|
|
59
|
+
self.click()
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from playwright.sync_api import Locator, Page
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ThemeSelector:
|
|
10
|
+
"""Playwright component for django_spire/theme/element/theme_selector.html"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, page: Page) -> None:
|
|
13
|
+
self.page = page
|
|
14
|
+
|
|
15
|
+
@property
|
|
16
|
+
def icon(self) -> Locator:
|
|
17
|
+
return self.page.locator('.bi-sun-fill, .bi-moon-fill').first
|
|
18
|
+
|
|
19
|
+
def click(self) -> None:
|
|
20
|
+
self.icon.click()
|
|
21
|
+
|
|
22
|
+
def get_current_mode(self) -> str:
|
|
23
|
+
html = self.page.locator('html')
|
|
24
|
+
return html.get_attribute('data-theme') or 'light'
|
|
25
|
+
|
|
26
|
+
def get_current_theme_family(self) -> str:
|
|
27
|
+
html = self.page.locator('html')
|
|
28
|
+
return html.get_attribute('data-theme-family') or ''
|
|
29
|
+
|
|
30
|
+
def is_dark_mode(self) -> bool:
|
|
31
|
+
return self.get_current_mode() == 'dark'
|
|
32
|
+
|
|
33
|
+
def is_light_mode(self) -> bool:
|
|
34
|
+
return self.get_current_mode() == 'light'
|
|
35
|
+
|
|
36
|
+
def is_visible(self) -> bool:
|
|
37
|
+
return self.icon.is_visible()
|
|
38
|
+
|
|
39
|
+
def toggle(self) -> None:
|
|
40
|
+
self.click()
|
|
41
|
+
|
|
42
|
+
def wait_for_theme_change(self, expected_mode: str, timeout: int = 5000) -> None:
|
|
43
|
+
self.page.wait_for_function(
|
|
44
|
+
f'() => document.documentElement.getAttribute("data-theme") === "{expected_mode}"',
|
|
45
|
+
timeout=timeout
|
|
46
|
+
)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from playwright.sync_api import Locator, Page
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Toast:
|
|
10
|
+
"""Playwright component for django_spire/messages/messages.html"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, page: Page) -> None:
|
|
13
|
+
self.page = page
|
|
14
|
+
|
|
15
|
+
@property
|
|
16
|
+
def container(self) -> Locator:
|
|
17
|
+
return self.page.locator('.fixed-top[\\@notify\\.window]')
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def toasts(self) -> Locator:
|
|
21
|
+
return self.container.locator('[x-data*="show:"]')
|
|
22
|
+
|
|
23
|
+
def get_toast(self, index: int = 0) -> Locator:
|
|
24
|
+
return self.toasts.nth(index)
|
|
25
|
+
|
|
26
|
+
def get_toast_count(self) -> int:
|
|
27
|
+
return self.toasts.count()
|
|
28
|
+
|
|
29
|
+
def get_toast_icon(self, index: int = 0) -> Locator:
|
|
30
|
+
return self.get_toast(index).locator('i.bi').first
|
|
31
|
+
|
|
32
|
+
def get_toast_message(self, index: int = 0) -> str:
|
|
33
|
+
return self.get_toast(index).locator('[x-text="notification.message"]').inner_text()
|
|
34
|
+
|
|
35
|
+
def get_toast_type(self, index: int = 0) -> str:
|
|
36
|
+
toast = self.get_toast(index)
|
|
37
|
+
classes = toast.get_attribute('class') or ''
|
|
38
|
+
|
|
39
|
+
if 'border-app-success' in classes:
|
|
40
|
+
return 'success'
|
|
41
|
+
|
|
42
|
+
if 'border-app-warning' in classes:
|
|
43
|
+
return 'warning'
|
|
44
|
+
|
|
45
|
+
if 'border-app-danger' in classes:
|
|
46
|
+
return 'error'
|
|
47
|
+
|
|
48
|
+
if 'border-app-primary' in classes:
|
|
49
|
+
return 'info'
|
|
50
|
+
|
|
51
|
+
return 'unknown'
|
|
52
|
+
|
|
53
|
+
def close_toast(self, index: int = 0) -> None:
|
|
54
|
+
self.get_toast(index).locator('.bi-x-lg').click()
|
|
55
|
+
|
|
56
|
+
def has_success_toast(self) -> bool:
|
|
57
|
+
return self.page.locator('.border-app-success').count() > 0
|
|
58
|
+
|
|
59
|
+
def has_warning_toast(self) -> bool:
|
|
60
|
+
return self.page.locator('.border-app-warning').count() > 0
|
|
61
|
+
|
|
62
|
+
def has_error_toast(self) -> bool:
|
|
63
|
+
return self.page.locator('.border-app-danger').count() > 0
|
|
64
|
+
|
|
65
|
+
def has_info_toast(self) -> bool:
|
|
66
|
+
return self.page.locator('.border-app-primary').count() > 0
|
|
67
|
+
|
|
68
|
+
def wait_for_toast(self, timeout: int = 5000) -> None:
|
|
69
|
+
self.toasts.first.wait_for(state='visible', timeout=timeout)
|
|
70
|
+
|
|
71
|
+
def wait_for_toast_to_disappear(self, timeout: int = 10000) -> None:
|
|
72
|
+
self.toasts.first.wait_for(state='hidden', timeout=timeout)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from typing import Any, TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
from django.contrib.auth import get_user_model
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from playwright.sync_api import Page
|
|
12
|
+
from pytest_django.plugin import _LiveServer
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
User = get_user_model()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def pytest_configure(config: Any) -> None:
|
|
19
|
+
os.environ['DJANGO_ALLOW_ASYNC_UNSAFE'] = 'true'
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@pytest.fixture(scope='session')
|
|
23
|
+
def browser_context_args(browser_context_args: dict[str, Any]) -> dict[str, Any]:
|
|
24
|
+
return {
|
|
25
|
+
**browser_context_args,
|
|
26
|
+
'no_viewport': True,
|
|
27
|
+
'viewport': None,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@pytest.fixture(scope='session')
|
|
32
|
+
def browser_type_launch_args(browser_type_launch_args: dict[str, Any]) -> dict[str, Any]:
|
|
33
|
+
return {
|
|
34
|
+
**browser_type_launch_args,
|
|
35
|
+
'args': ['--start-maximized'],
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@pytest.fixture
|
|
40
|
+
def authenticated_page(page: Page, live_server: _LiveServer, transactional_db: None) -> Page:
|
|
41
|
+
User.objects.create_user(
|
|
42
|
+
username='testuser',
|
|
43
|
+
password='testpass123',
|
|
44
|
+
is_staff=True,
|
|
45
|
+
is_superuser=True
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
page.goto(f'{live_server.url}/admin/login/')
|
|
49
|
+
page.fill('input[name="username"]', 'testuser')
|
|
50
|
+
page.fill('input[name="password"]', 'testpass123')
|
|
51
|
+
page.click('input[type="submit"]')
|
|
52
|
+
page.wait_for_url(f'{live_server.url}/admin/')
|
|
53
|
+
|
|
54
|
+
return page
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from playwright.sync_api import Page
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BasePage:
|
|
10
|
+
def __init__(self, page: Page, base_url: str) -> None:
|
|
11
|
+
self.base_url = base_url
|
|
12
|
+
self.page = page
|
|
13
|
+
|
|
14
|
+
def goto(self, url: str) -> None:
|
|
15
|
+
self.page.goto(url)
|
|
16
|
+
|
|
17
|
+
def has_text(self, text: str) -> bool:
|
|
18
|
+
return text in self.page.locator('body').inner_text()
|
|
19
|
+
|
|
20
|
+
def wait_for_load_state(self, state: str = 'networkidle') -> None:
|
|
21
|
+
self.page.wait_for_load_state(state)
|
|
22
|
+
|
|
23
|
+
def wait_for_timeout(self, timeout: int) -> None:
|
|
24
|
+
self.page.wait_for_timeout(timeout)
|
django_spire/theme/models.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: django-spire
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.23.0
|
|
4
4
|
Summary: A project for Django Spire
|
|
5
5
|
Author-email: Brayden Carlson <braydenc@stratusadv.com>, Nathan Johnson <nathanj@stratusadv.com>
|
|
6
6
|
License: Copyright (c) 2024 Stratus Advanced Technologies and Contributors.
|
|
@@ -73,9 +73,12 @@ Requires-Dist: django-browser-reload; extra == "development"
|
|
|
73
73
|
Requires-Dist: django-debug-toolbar; extra == "development"
|
|
74
74
|
Requires-Dist: django-watchfiles; extra == "development"
|
|
75
75
|
Requires-Dist: pip; extra == "development"
|
|
76
|
+
Requires-Dist: playwright; extra == "development"
|
|
76
77
|
Requires-Dist: pyinstrument; extra == "development"
|
|
77
78
|
Requires-Dist: pyrefly; extra == "development"
|
|
78
79
|
Requires-Dist: pytest; extra == "development"
|
|
80
|
+
Requires-Dist: pytest-django; extra == "development"
|
|
81
|
+
Requires-Dist: pytest-playwright; extra == "development"
|
|
79
82
|
Requires-Dist: setuptools; extra == "development"
|
|
80
83
|
Requires-Dist: twine; extra == "development"
|
|
81
84
|
Provides-Extra: documentation
|