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.
Files changed (83) hide show
  1. django_spire/auth/group/forms.py +3 -4
  2. django_spire/auth/group/utils.py +1 -2
  3. django_spire/auth/group/views/form_views.py +22 -14
  4. django_spire/auth/group/views/page_views.py +1 -1
  5. django_spire/auth/mfa/utils.py +1 -1
  6. django_spire/auth/permissions/consts.py +2 -1
  7. django_spire/auth/permissions/decorators.py +1 -2
  8. django_spire/auth/permissions/permissions.py +1 -3
  9. django_spire/comment/factories.py +1 -1
  10. django_spire/comment/mixins.py +1 -1
  11. django_spire/comment/views.py +1 -1
  12. django_spire/consts.py +1 -1
  13. django_spire/contrib/breadcrumb/breadcrumbs.py +1 -1
  14. django_spire/contrib/form/confirmation_forms.py +1 -1
  15. django_spire/contrib/form/utils.py +26 -16
  16. django_spire/contrib/generic_views/modal_views.py +1 -1
  17. django_spire/contrib/generic_views/portal_views.py +18 -55
  18. django_spire/contrib/ordering/validators.py +4 -8
  19. django_spire/core/context_processors.py +1 -1
  20. django_spire/core/management/commands/spire_startapp_pkg/user_input.py +1 -1
  21. django_spire/core/middleware/maintenance.py +2 -1
  22. django_spire/core/middleware.py +2 -1
  23. django_spire/core/redirect/generic_redirect.py +1 -1
  24. django_spire/core/redirect/safe_redirect.py +2 -1
  25. django_spire/core/shortcuts.py +4 -2
  26. django_spire/core/table/__init__.py +0 -0
  27. django_spire/core/table/enums.py +18 -0
  28. django_spire/core/templates/django_spire/card/infinite_scroll_card.html +3 -137
  29. django_spire/core/templates/django_spire/container/infinite_scroll_container.html +64 -0
  30. django_spire/core/templates/django_spire/infinite_scroll/base.html +348 -0
  31. django_spire/core/templates/django_spire/infinite_scroll/element/footer.html +11 -0
  32. django_spire/core/templates/django_spire/infinite_scroll/scroll.html +152 -0
  33. django_spire/core/templates/django_spire/item/infinite_scroll_item.html +33 -0
  34. django_spire/core/templates/django_spire/lazy_tab/element/lazy_tab_section_element.html +19 -0
  35. django_spire/core/templates/django_spire/lazy_tab/element/lazy_tab_trigger_element.html +15 -0
  36. django_spire/core/templates/django_spire/lazy_tab/lazy_tab.html +157 -0
  37. django_spire/core/templates/django_spire/page/infinite_scroll_list_page.html +7 -0
  38. django_spire/core/templates/django_spire/table/base.html +185 -373
  39. django_spire/core/templates/django_spire/table/element/footer.html +7 -15
  40. django_spire/core/templates/django_spire/table/element/header.html +1 -1
  41. django_spire/core/templates/django_spire/table/element/row.html +15 -7
  42. django_spire/core/templatetags/spire_core_tags.py +1 -2
  43. django_spire/file/fields.py +1 -1
  44. django_spire/file/interfaces.py +1 -1
  45. django_spire/file/views.py +1 -1
  46. django_spire/history/activity/utils.py +1 -1
  47. django_spire/history/mixins.py +0 -4
  48. django_spire/knowledge/static/django_spire/knowledge/css/editor.css +93 -0
  49. django_spire/knowledge/templates/django_spire/knowledge/entry/version/page/editor_page.html +4 -0
  50. django_spire/notification/app/context_data.py +3 -1
  51. django_spire/notification/app/views/json_views.py +1 -1
  52. django_spire/notification/app/views/page_views.py +2 -1
  53. django_spire/notification/app/views/template_views.py +2 -2
  54. django_spire/profiling/middleware/profiling.py +2 -2
  55. django_spire/profiling/panel.py +2 -2
  56. django_spire/testing/__init__.py +0 -0
  57. django_spire/testing/playwright/__init__.py +64 -0
  58. django_spire/testing/playwright/components/__init__.py +45 -0
  59. django_spire/testing/playwright/components/accordion.py +55 -0
  60. django_spire/testing/playwright/components/attribute_element.py +73 -0
  61. django_spire/testing/playwright/components/base_session_filter_form.py +57 -0
  62. django_spire/testing/playwright/components/breadcrumb_element.py +56 -0
  63. django_spire/testing/playwright/components/card.py +102 -0
  64. django_spire/testing/playwright/components/dropdown.py +87 -0
  65. django_spire/testing/playwright/components/infinite_scroll.py +158 -0
  66. django_spire/testing/playwright/components/lazy_tab.py +92 -0
  67. django_spire/testing/playwright/components/modal.py +101 -0
  68. django_spire/testing/playwright/components/navigation.py +119 -0
  69. django_spire/testing/playwright/components/notification_bell.py +59 -0
  70. django_spire/testing/playwright/components/theme_selector.py +46 -0
  71. django_spire/testing/playwright/components/toast.py +72 -0
  72. django_spire/testing/playwright/fixtures.py +54 -0
  73. django_spire/testing/playwright/pages/__init__.py +6 -0
  74. django_spire/testing/playwright/pages/base.py +24 -0
  75. django_spire/theme/models.py +1 -1
  76. django_spire/theme/tests/test_context_processor.py +0 -1
  77. django_spire/theme/views/json_views.py +1 -1
  78. django_spire/theme/views/page_views.py +1 -1
  79. {django_spire-0.22.3.dist-info → django_spire-0.23.0.dist-info}/METADATA +4 -1
  80. {django_spire-0.22.3.dist-info → django_spire-0.23.0.dist-info}/RECORD +83 -52
  81. {django_spire-0.22.3.dist-info → django_spire-0.23.0.dist-info}/WHEEL +0 -0
  82. {django_spire-0.22.3.dist-info → django_spire-0.23.0.dist-info}/licenses/LICENSE.md +0 -0
  83. {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,6 @@
1
+ from django_spire.testing.playwright.pages.base import BasePage
2
+
3
+
4
+ __all__ = [
5
+ 'BasePage',
6
+ ]
@@ -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)
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass
4
- from typing_extensions import ClassVar
4
+ from typing import ClassVar
5
5
 
6
6
  from django_spire.theme.enums import ThemeFamily, ThemeMode
7
7
 
@@ -1,7 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from unittest.mock import patch
4
- from typing_extensions import Any
5
4
 
6
5
  from django.test import TestCase, RequestFactory
7
6
 
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  import json
4
4
 
5
5
  from http import HTTPStatus
6
- from typing_extensions import TYPE_CHECKING
6
+ from typing import TYPE_CHECKING
7
7
 
8
8
  from django.http import JsonResponse
9
9
  from django.views.decorators.cache import cache_page
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing_extensions import TYPE_CHECKING
3
+ from typing import TYPE_CHECKING
4
4
 
5
5
  from django.contrib.auth.decorators import login_required
6
6
  from django.urls import reverse
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-spire
3
- Version: 0.22.3
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