django-cms-qe 4.1.0__py3-none-any.whl → 4.2.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.
- cms_qe/cms4_menus.py +132 -0
- cms_qe/cms5_menus.py +87 -0
- cms_qe/cms_menus.py +6 -133
- cms_qe/cms_menus_mixin.py +10 -0
- cms_qe/templates/menu/menu.html +12 -0
- {django_cms_qe-4.1.0.dist-info → django_cms_qe-4.2.0.dist-info}/METADATA +24 -22
- {django_cms_qe-4.1.0.dist-info → django_cms_qe-4.2.0.dist-info}/RECORD +10 -19
- {django_cms_qe-4.1.0.dist-info → django_cms_qe-4.2.0.dist-info}/top_level.txt +0 -1
- test_selenium/__init__.py +0 -0
- test_selenium/browser.py +0 -47
- test_selenium/cms_qe/__init__.py +0 -0
- test_selenium/cms_qe/test_base.py +0 -2
- test_selenium/cms_qe/test_errors.py +0 -14
- test_selenium/conftest.py +0 -6
- test_selenium/fixtures/__init__.py +0 -0
- test_selenium/fixtures/base.py +0 -75
- test_selenium/pages/__init__.py +0 -0
- test_selenium/pages/cms/__init__.py +0 -5
- test_selenium/pages/cms/login.py +0 -17
- test_selenium/pages/cms/page.py +0 -16
- test_selenium/pages/cms/wizard.py +0 -17
- {django_cms_qe-4.1.0.dist-info → django_cms_qe-4.2.0.dist-info}/WHEEL +0 -0
- {django_cms_qe-4.1.0.dist-info → django_cms_qe-4.2.0.dist-info}/licenses/LICENSE +0 -0
cms_qe/cms4_menus.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
from cms.cms_menus import get_menu_node_for_page, get_visible_nodes
|
|
2
|
+
from cms.models import EmptyPageContent, PageContent, PageUrl
|
|
3
|
+
from cms.toolbar.utils import get_toolbar_from_request
|
|
4
|
+
from cms.utils.i18n import get_fallback_languages, get_public_languages, hide_untranslated, is_valid_site_language
|
|
5
|
+
from cms.utils.page import get_page_queryset
|
|
6
|
+
from django.db.models.query import Prefetch, prefetch_related_objects
|
|
7
|
+
from menus.base import Menu
|
|
8
|
+
|
|
9
|
+
from .cms_menus_mixin import CMSMenuMixin
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# This class i a copy of https://github.com/django-cms/django-cms/blob/4.1.7/cms/cms_menus.py#L199
|
|
13
|
+
# with extra function custom_menu_node.
|
|
14
|
+
class CMSMenu(CMSMenuMixin, Menu):
|
|
15
|
+
"""Subclass of :class:`menus.base.Menu`. Its :meth:`~menus.base.Menu.get_nodes()` creates
|
|
16
|
+
a list of NavigationNodes based on a site's :class:`cms.models.pagemodel.Page` objects.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def get_nodes(self, request):
|
|
20
|
+
site = self.renderer.site
|
|
21
|
+
lang = self.renderer.request_language
|
|
22
|
+
toolbar = get_toolbar_from_request(request)
|
|
23
|
+
|
|
24
|
+
pages = get_page_queryset(site)
|
|
25
|
+
|
|
26
|
+
if is_valid_site_language(lang, site_id=site.pk):
|
|
27
|
+
_valid_language = True
|
|
28
|
+
_hide_untranslated = hide_untranslated(lang, site.pk)
|
|
29
|
+
else:
|
|
30
|
+
_valid_language = False
|
|
31
|
+
_hide_untranslated = False
|
|
32
|
+
|
|
33
|
+
if _valid_language:
|
|
34
|
+
# The request language has been explicitly configured
|
|
35
|
+
# for the current site.
|
|
36
|
+
if _hide_untranslated:
|
|
37
|
+
fallbacks = []
|
|
38
|
+
else:
|
|
39
|
+
fallbacks = get_fallback_languages(lang, site_id=site.pk)
|
|
40
|
+
languages = [lang] + [_lang for _lang in fallbacks if _lang != lang]
|
|
41
|
+
else:
|
|
42
|
+
# The request language is not configured for the current site.
|
|
43
|
+
# Fallback to all configured public languages for the current site.
|
|
44
|
+
languages = get_public_languages(site.pk)
|
|
45
|
+
fallbacks = languages
|
|
46
|
+
|
|
47
|
+
pages = (
|
|
48
|
+
pages.filter(pagecontent_set__language__in=languages)
|
|
49
|
+
.select_related("node")
|
|
50
|
+
.order_by("node__path")
|
|
51
|
+
.distinct()
|
|
52
|
+
)
|
|
53
|
+
pages = get_visible_nodes(request, pages, site)
|
|
54
|
+
|
|
55
|
+
if not pages:
|
|
56
|
+
return []
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
homepage = [page for page in pages if page.is_home][0]
|
|
60
|
+
except IndexError:
|
|
61
|
+
homepage = None
|
|
62
|
+
|
|
63
|
+
urls_lookup = Prefetch(
|
|
64
|
+
"urls",
|
|
65
|
+
to_attr="filtered_urls",
|
|
66
|
+
queryset=PageUrl.objects.filter(language__in=languages),
|
|
67
|
+
)
|
|
68
|
+
if toolbar.edit_mode_active or toolbar.preview_mode_active:
|
|
69
|
+
# Get all translations visible in the admin for the current page
|
|
70
|
+
translations_qs = PageContent.admin_manager.current_content(language__in=languages)
|
|
71
|
+
else:
|
|
72
|
+
# Only get public translations
|
|
73
|
+
translations_qs = PageContent.objects.filter(language__in=languages)
|
|
74
|
+
translations_lookup = Prefetch(
|
|
75
|
+
"pagecontent_set",
|
|
76
|
+
to_attr="filtered_translations",
|
|
77
|
+
queryset=translations_qs,
|
|
78
|
+
)
|
|
79
|
+
prefetch_related_objects(pages, urls_lookup, translations_lookup)
|
|
80
|
+
# Build the blank title instances only once
|
|
81
|
+
blank_page_content_cache = {language: EmptyPageContent(language=language) for language in languages}
|
|
82
|
+
|
|
83
|
+
# Maps a node id to its page id
|
|
84
|
+
node_id_to_page: dict[int, int] = {}
|
|
85
|
+
|
|
86
|
+
def _page_to_node(page):
|
|
87
|
+
# EmptyPageContent is used to prevent the cms from trying
|
|
88
|
+
# to find a translation in the database
|
|
89
|
+
page.page_content_cache = blank_page_content_cache.copy()
|
|
90
|
+
|
|
91
|
+
for page_url in page.filtered_urls:
|
|
92
|
+
page.urls_cache[page_url.language] = page_url
|
|
93
|
+
|
|
94
|
+
for trans in page.filtered_translations:
|
|
95
|
+
page.page_content_cache[trans.language] = trans
|
|
96
|
+
|
|
97
|
+
menu_node = get_menu_node_for_page(
|
|
98
|
+
self.renderer,
|
|
99
|
+
page,
|
|
100
|
+
language=lang,
|
|
101
|
+
fallbacks=fallbacks,
|
|
102
|
+
endpoint=toolbar.preview_mode_active or toolbar.edit_mode_active,
|
|
103
|
+
)
|
|
104
|
+
return menu_node
|
|
105
|
+
|
|
106
|
+
menu_nodes = []
|
|
107
|
+
|
|
108
|
+
for page in pages:
|
|
109
|
+
node = page.node
|
|
110
|
+
parent_id = node_id_to_page.get(node.parent_id)
|
|
111
|
+
|
|
112
|
+
if node.parent_id and not parent_id:
|
|
113
|
+
# If the parent page is not available (unpublished, etc..)
|
|
114
|
+
# don't bother creating menu nodes for its descendants.
|
|
115
|
+
continue
|
|
116
|
+
|
|
117
|
+
menu_node = _page_to_node(page)
|
|
118
|
+
if menu_node:
|
|
119
|
+
# Only add pages with at least one page content
|
|
120
|
+
cut_homepage = homepage and not homepage.get_in_navigation(lang)
|
|
121
|
+
|
|
122
|
+
if cut_homepage and parent_id == homepage.pk:
|
|
123
|
+
# When the homepage is hidden from navigation,
|
|
124
|
+
# we need to cut all its direct children from it.
|
|
125
|
+
menu_node.parent_id = None
|
|
126
|
+
else:
|
|
127
|
+
menu_node.parent_id = parent_id
|
|
128
|
+
# This is the reason for the class copy. This line is added.
|
|
129
|
+
self.extend_node_attr(menu_node.attr, page)
|
|
130
|
+
node_id_to_page[node.pk] = page.pk
|
|
131
|
+
menu_nodes.append(menu_node)
|
|
132
|
+
return menu_nodes
|
cms_qe/cms5_menus.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from cms.cms_menus import (VISIBLE_FOR_ANONYMOUS, VISIBLE_FOR_AUTHENTICATED, CMSMenu as CMS5Menu, CMSNavigationNode,
|
|
5
|
+
apphook_pool)
|
|
6
|
+
from cms.models import PageContent
|
|
7
|
+
|
|
8
|
+
from .cms_menus_mixin import CMSMenuMixin
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CMSMenu(CMSMenuMixin, CMS5Menu):
|
|
12
|
+
|
|
13
|
+
# This class i a copy of https://github.com/django-cms/django-cms/blob/5.0.4/cms/cms_menus.py#L199
|
|
14
|
+
# see line above comment: This line is added.
|
|
15
|
+
def get_menu_node_for_page_content(
|
|
16
|
+
self,
|
|
17
|
+
page_content: PageContent,
|
|
18
|
+
preview_url: Optional[str] = None,
|
|
19
|
+
cut: bool = False,
|
|
20
|
+
) -> CMSNavigationNode:
|
|
21
|
+
"""
|
|
22
|
+
Transform a CMS page content object into a navigation node.
|
|
23
|
+
|
|
24
|
+
:param page: The page to transform.
|
|
25
|
+
:param languages: The list of the current language plus fallbacks used to render the menu.
|
|
26
|
+
:param preview_url: If given, serves as a "pattern" for a preview url with the assumption that "/0/"
|
|
27
|
+
is replaced by the actual page content pk. Default is None.
|
|
28
|
+
:param cut: If True the parent_id is set to None. Default is False.
|
|
29
|
+
:returns: A CMSNavigationNode instance.
|
|
30
|
+
"""
|
|
31
|
+
page = page_content.page
|
|
32
|
+
|
|
33
|
+
# These are simple to port over, since they are not calculated.
|
|
34
|
+
# Other attributes will be added conditionally later.
|
|
35
|
+
visibility = page_content.limit_visibility_in_menu
|
|
36
|
+
attr = {
|
|
37
|
+
"is_page": True,
|
|
38
|
+
"soft_root": page_content.soft_root,
|
|
39
|
+
"auth_required": page.login_required,
|
|
40
|
+
"reverse_id": page.reverse_id,
|
|
41
|
+
"is_home": page.is_home,
|
|
42
|
+
"visible_for_authenticated": visibility in VISIBLE_FOR_AUTHENTICATED,
|
|
43
|
+
"visible_for_anonymous": visibility in VISIBLE_FOR_ANONYMOUS,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
extenders = []
|
|
47
|
+
if page.navigation_extenders:
|
|
48
|
+
if page.navigation_extenders in self.renderer.menus:
|
|
49
|
+
extenders.append(page.navigation_extenders)
|
|
50
|
+
elif f"{page.navigation_extenders}:{page.pk}" in self.renderer.menus:
|
|
51
|
+
extenders.append(f"{page.navigation_extenders}:{page.pk}")
|
|
52
|
+
# Is this page an apphook? If so, we need to handle the apphooks's nodes
|
|
53
|
+
# Only run this if we have a translation in the requested language for this
|
|
54
|
+
# object. The page content cache should have been prepopulated in CMSMenu.get_nodes
|
|
55
|
+
# but otherwise, just request the title normally
|
|
56
|
+
if page.application_urls and page_content.language == self.languages[0]:
|
|
57
|
+
# it means it is an apphook
|
|
58
|
+
app = apphook_pool.get_apphook(page.application_urls)
|
|
59
|
+
if app:
|
|
60
|
+
extenders.extend(app.get_menus(page, self.languages[0]))
|
|
61
|
+
# CMSAattachMenus are treated a bit differently to allow them to be
|
|
62
|
+
# able to be attached to multiple points in the navigation.
|
|
63
|
+
attr["navigation_extenders"] = [
|
|
64
|
+
f"{ext.__name__}:{page.pk}" if hasattr(ext, "get_instances") else getattr(ext, "__name__", ext)
|
|
65
|
+
for ext in extenders
|
|
66
|
+
]
|
|
67
|
+
# This is the reason for the class copy. This line is added.
|
|
68
|
+
self.extend_node_attr(attr, page)
|
|
69
|
+
|
|
70
|
+
# Now finally, build the NavigationNode object and return it.
|
|
71
|
+
# The parent_id is manually set by the menu get_nodes method.
|
|
72
|
+
if preview_url:
|
|
73
|
+
# Build preview url by replacing "/0/" in the url template by the actual pk of the page content object
|
|
74
|
+
# Hacky, but faster than calling `admin_reverse` for each page content object
|
|
75
|
+
url = re.sub("(/0/)", f"/{page_content.pk}/", preview_url)
|
|
76
|
+
else:
|
|
77
|
+
url = page.get_absolute_url(language=page_content.language)
|
|
78
|
+
|
|
79
|
+
return CMSNavigationNode(
|
|
80
|
+
title=page_content.menu_title or page_content.title,
|
|
81
|
+
url=url,
|
|
82
|
+
id=page.pk,
|
|
83
|
+
parent_id=None if cut else page_content.page.parent_id,
|
|
84
|
+
attr=attr,
|
|
85
|
+
visible=page_content.in_navigation,
|
|
86
|
+
language=(page_content.language if page_content.language != self.languages[0] else None),
|
|
87
|
+
)
|
cms_qe/cms_menus.py
CHANGED
|
@@ -1,134 +1,7 @@
|
|
|
1
|
-
from cms
|
|
2
|
-
from cms.models import EmptyPageContent, Page, PageContent, PageUrl
|
|
3
|
-
from cms.toolbar.utils import get_toolbar_from_request
|
|
4
|
-
from cms.utils.i18n import get_fallback_languages, get_public_languages, hide_untranslated, is_valid_site_language
|
|
5
|
-
from cms.utils.page import get_page_queryset
|
|
6
|
-
from django.db.models.query import Prefetch, prefetch_related_objects
|
|
7
|
-
from django.http import HttpRequest
|
|
8
|
-
from menus.base import Menu
|
|
1
|
+
from cms import __version__
|
|
9
2
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
based on a site's :class:`cms.models.pagemodel.Page` objects.
|
|
16
|
-
"""
|
|
17
|
-
|
|
18
|
-
def get_nodes(self, request):
|
|
19
|
-
site = self.renderer.site
|
|
20
|
-
lang = self.renderer.request_language
|
|
21
|
-
toolbar = get_toolbar_from_request(request)
|
|
22
|
-
|
|
23
|
-
pages = get_page_queryset(site)
|
|
24
|
-
|
|
25
|
-
if is_valid_site_language(lang, site_id=site.pk):
|
|
26
|
-
_valid_language = True
|
|
27
|
-
_hide_untranslated = hide_untranslated(lang, site.pk)
|
|
28
|
-
else:
|
|
29
|
-
_valid_language = False
|
|
30
|
-
_hide_untranslated = False
|
|
31
|
-
|
|
32
|
-
if _valid_language:
|
|
33
|
-
# The request language has been explicitly configured
|
|
34
|
-
# for the current site.
|
|
35
|
-
if _hide_untranslated:
|
|
36
|
-
fallbacks = []
|
|
37
|
-
else:
|
|
38
|
-
fallbacks = get_fallback_languages(lang, site_id=site.pk)
|
|
39
|
-
languages = [lang] + [_lang for _lang in fallbacks if _lang != lang]
|
|
40
|
-
else:
|
|
41
|
-
# The request language is not configured for the current site.
|
|
42
|
-
# Fallback to all configured public languages for the current site.
|
|
43
|
-
languages = get_public_languages(site.pk)
|
|
44
|
-
fallbacks = languages
|
|
45
|
-
|
|
46
|
-
pages = (
|
|
47
|
-
pages.filter(pagecontent_set__language__in=languages)
|
|
48
|
-
.select_related("node")
|
|
49
|
-
.order_by("node__path")
|
|
50
|
-
.distinct()
|
|
51
|
-
)
|
|
52
|
-
pages = get_visible_nodes(request, pages, site)
|
|
53
|
-
|
|
54
|
-
if not pages:
|
|
55
|
-
return []
|
|
56
|
-
|
|
57
|
-
try:
|
|
58
|
-
homepage = [page for page in pages if page.is_home][0]
|
|
59
|
-
except IndexError:
|
|
60
|
-
homepage = None
|
|
61
|
-
|
|
62
|
-
urls_lookup = Prefetch(
|
|
63
|
-
"urls",
|
|
64
|
-
to_attr="filtered_urls",
|
|
65
|
-
queryset=PageUrl.objects.filter(language__in=languages),
|
|
66
|
-
)
|
|
67
|
-
if toolbar.edit_mode_active or toolbar.preview_mode_active:
|
|
68
|
-
# Get all translations visible in the admin for the current page
|
|
69
|
-
translations_qs = PageContent.admin_manager.current_content(language__in=languages)
|
|
70
|
-
else:
|
|
71
|
-
# Only get public translations
|
|
72
|
-
translations_qs = PageContent.objects.filter(language__in=languages)
|
|
73
|
-
translations_lookup = Prefetch(
|
|
74
|
-
"pagecontent_set",
|
|
75
|
-
to_attr="filtered_translations",
|
|
76
|
-
queryset=translations_qs,
|
|
77
|
-
)
|
|
78
|
-
prefetch_related_objects(pages, urls_lookup, translations_lookup)
|
|
79
|
-
# Build the blank title instances only once
|
|
80
|
-
blank_page_content_cache = {language: EmptyPageContent(language=language) for language in languages}
|
|
81
|
-
|
|
82
|
-
# Maps a node id to its page id
|
|
83
|
-
node_id_to_page: dict[int, int] = {}
|
|
84
|
-
|
|
85
|
-
def _page_to_node(page):
|
|
86
|
-
# EmptyPageContent is used to prevent the cms from trying
|
|
87
|
-
# to find a translation in the database
|
|
88
|
-
page.page_content_cache = blank_page_content_cache.copy()
|
|
89
|
-
|
|
90
|
-
for page_url in page.filtered_urls:
|
|
91
|
-
page.urls_cache[page_url.language] = page_url
|
|
92
|
-
|
|
93
|
-
for trans in page.filtered_translations:
|
|
94
|
-
page.page_content_cache[trans.language] = trans
|
|
95
|
-
|
|
96
|
-
menu_node = get_menu_node_for_page(
|
|
97
|
-
self.renderer,
|
|
98
|
-
page,
|
|
99
|
-
language=lang,
|
|
100
|
-
fallbacks=fallbacks,
|
|
101
|
-
endpoint=toolbar.preview_mode_active or toolbar.edit_mode_active,
|
|
102
|
-
)
|
|
103
|
-
return menu_node
|
|
104
|
-
|
|
105
|
-
menu_nodes = []
|
|
106
|
-
|
|
107
|
-
for page in pages:
|
|
108
|
-
node = page.node
|
|
109
|
-
parent_id = node_id_to_page.get(node.parent_id)
|
|
110
|
-
|
|
111
|
-
if node.parent_id and not parent_id:
|
|
112
|
-
# If the parent page is not available (unpublished, etc..)
|
|
113
|
-
# don't bother creating menu nodes for its descendants.
|
|
114
|
-
continue
|
|
115
|
-
|
|
116
|
-
menu_node = _page_to_node(page)
|
|
117
|
-
if menu_node:
|
|
118
|
-
# Only add pages with at least one page content
|
|
119
|
-
cut_homepage = homepage and not homepage.get_in_navigation(lang)
|
|
120
|
-
|
|
121
|
-
if cut_homepage and parent_id == homepage.pk:
|
|
122
|
-
# When the homepage is hidden from navigation,
|
|
123
|
-
# we need to cut all its direct children from it.
|
|
124
|
-
menu_node.parent_id = None
|
|
125
|
-
else:
|
|
126
|
-
menu_node.parent_id = parent_id
|
|
127
|
-
menu_node = self.custom_menu_node(request, page, menu_node)
|
|
128
|
-
node_id_to_page[node.pk] = page.pk
|
|
129
|
-
menu_nodes.append(menu_node)
|
|
130
|
-
return menu_nodes
|
|
131
|
-
|
|
132
|
-
def custom_menu_node(self, request: HttpRequest, page: Page, menu_node: CMSNavigationNode) -> CMSNavigationNode:
|
|
133
|
-
menu_node.attr["page_description"] = page.get_meta_description()
|
|
134
|
-
return menu_node
|
|
3
|
+
# pylint: skip-file
|
|
4
|
+
if __version__.split(".")[0] == "4":
|
|
5
|
+
from .cms4_menus import CMSMenu # noqa: F401
|
|
6
|
+
else:
|
|
7
|
+
from .cms5_menus import CMSMenu # noqa: F401
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{% load menu_tags %}
|
|
2
|
+
|
|
3
|
+
{% for child in children %}
|
|
4
|
+
<li class="child{% if child.selected %} selected{% endif %}{% if child.ancestor %} ancestor{% endif %}{% if child.sibling %} sibling{% endif %}{% if child.descendant %} descendant{% endif %}">
|
|
5
|
+
<a href="{{ child.attr.redirect_url|default:child.get_absolute_url }}"{% if child.attr.page_description %} title="{{ child.attr.page_description }}"{% endif %}>{{ child.get_menu_title }}</a>
|
|
6
|
+
{% if child.children %}
|
|
7
|
+
<ul>
|
|
8
|
+
{% show_menu from_level to_level extra_inactive extra_active template "" "" child %}
|
|
9
|
+
</ul>
|
|
10
|
+
{% endif %}
|
|
11
|
+
</li>
|
|
12
|
+
{% endfor %}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: django-cms-qe
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.2.0
|
|
4
4
|
Summary: Django CMS Quick & Easy provides all important modules to run new page withouta lot of coding. Aims to do it very easily and securely.
|
|
5
5
|
Home-page: https://websites.pages.nic.cz/django-cms-qe
|
|
6
6
|
Author: CZ.NIC, z.s.p.o.
|
|
@@ -26,15 +26,15 @@ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
|
26
26
|
Requires-Python: >=3.10
|
|
27
27
|
Description-Content-Type: text/markdown
|
|
28
28
|
License-File: LICENSE
|
|
29
|
-
Requires-Dist: django-cms
|
|
29
|
+
Requires-Dist: django-cms<6,>=4.1
|
|
30
30
|
Requires-Dist: django-filer~=3.3
|
|
31
31
|
Requires-Dist: djangocms-admin-style~=3.3
|
|
32
|
-
Requires-Dist: djangocms-alias
|
|
33
|
-
Requires-Dist: djangocms-frontend~=2.
|
|
34
|
-
Requires-Dist: djangocms-text~=0.
|
|
35
|
-
Requires-Dist: djangocms-versioning~=2.
|
|
32
|
+
Requires-Dist: djangocms-alias<4,>=2.0
|
|
33
|
+
Requires-Dist: djangocms-frontend~=2.2
|
|
34
|
+
Requires-Dist: djangocms-text~=0.9
|
|
35
|
+
Requires-Dist: djangocms-versioning~=2.4
|
|
36
36
|
Requires-Dist: easy-thumbnails[svg]~=2.10
|
|
37
|
-
Requires-Dist: argon2-cffi~=
|
|
37
|
+
Requires-Dist: argon2-cffi~=25.1
|
|
38
38
|
Requires-Dist: django-axes~=8.0
|
|
39
39
|
Requires-Dist: django-constance~=4.3
|
|
40
40
|
Requires-Dist: django-csp~=4.0
|
|
@@ -43,40 +43,40 @@ Requires-Dist: django-tablib~=3.2
|
|
|
43
43
|
Requires-Dist: djangocms-file~=3.0
|
|
44
44
|
Requires-Dist: djangocms-googlemap~=2.2
|
|
45
45
|
Requires-Dist: djangocms-icon~=2.1
|
|
46
|
-
Requires-Dist: djangocms-link~=5.
|
|
46
|
+
Requires-Dist: djangocms-link~=5.1
|
|
47
47
|
Requires-Dist: djangocms-picture~=4.1
|
|
48
48
|
Requires-Dist: python-environ~=0.4
|
|
49
49
|
Requires-Dist: django-haystack~=3.3
|
|
50
|
-
Requires-Dist: djangocms-aldryn-forms[captcha]~=8.
|
|
51
|
-
Requires-Dist: djangocms-aldryn-search~=3.
|
|
50
|
+
Requires-Dist: djangocms-aldryn-forms[captcha]~=8.3
|
|
51
|
+
Requires-Dist: djangocms-aldryn-search~=3.1
|
|
52
52
|
Requires-Dist: mailchimp3~=3.0
|
|
53
53
|
Requires-Dist: whoosh~=2.7
|
|
54
|
-
Requires-Dist: Markdown~=3.
|
|
55
|
-
Requires-Dist: django-filter~=25.
|
|
54
|
+
Requires-Dist: Markdown~=3.9
|
|
55
|
+
Requires-Dist: django-filter~=25.2
|
|
56
56
|
Requires-Dist: django-rest-knox~=5.0
|
|
57
57
|
Requires-Dist: djangorestframework~=3.16
|
|
58
58
|
Requires-Dist: drf-spectacular~=0.28
|
|
59
59
|
Provides-Extra: dev
|
|
60
|
-
Requires-Dist: django-simple-captcha~=0.
|
|
61
|
-
Requires-Dist: django-debug-toolbar~=
|
|
62
|
-
Requires-Dist: django-extensions~=
|
|
60
|
+
Requires-Dist: django-simple-captcha~=0.6; extra == "dev"
|
|
61
|
+
Requires-Dist: django-debug-toolbar~=6.0; extra == "dev"
|
|
62
|
+
Requires-Dist: django-extensions~=4.1; extra == "dev"
|
|
63
63
|
Provides-Extra: test
|
|
64
64
|
Requires-Dist: flake8; extra == "test"
|
|
65
65
|
Requires-Dist: isort; extra == "test"
|
|
66
66
|
Requires-Dist: mypy; extra == "test"
|
|
67
67
|
Requires-Dist: pylint; extra == "test"
|
|
68
68
|
Requires-Dist: pylint-django; extra == "test"
|
|
69
|
-
Requires-Dist: pytest~=
|
|
70
|
-
Requires-Dist: pytest-cov~=
|
|
69
|
+
Requires-Dist: pytest~=8.4; extra == "test"
|
|
70
|
+
Requires-Dist: pytest-cov~=7.0; extra == "test"
|
|
71
71
|
Requires-Dist: pytest-data~=0.4; extra == "test"
|
|
72
|
-
Requires-Dist: pytest-django~=
|
|
73
|
-
Requires-Dist: pytest-env~=
|
|
72
|
+
Requires-Dist: pytest-django~=4.11; extra == "test"
|
|
73
|
+
Requires-Dist: pytest-env~=1.2; extra == "test"
|
|
74
74
|
Requires-Dist: pytest-pythonpath~=0.7; extra == "test"
|
|
75
|
-
Requires-Dist: pytest-sugar~=
|
|
75
|
+
Requires-Dist: pytest-sugar~=1.1; extra == "test"
|
|
76
76
|
Requires-Dist: pytest-watch~=4.2; extra == "test"
|
|
77
|
-
Requires-Dist: PyVirtualDisplay~=
|
|
77
|
+
Requires-Dist: PyVirtualDisplay~=3.0; extra == "test"
|
|
78
78
|
Requires-Dist: webdriverwrapper~=2.8; extra == "test"
|
|
79
|
-
Requires-Dist: django-simple-captcha~=0.
|
|
79
|
+
Requires-Dist: django-simple-captcha~=0.6; extra == "test"
|
|
80
80
|
Requires-Dist: testfixtures; extra == "test"
|
|
81
81
|
Requires-Dist: tzdata; extra == "test"
|
|
82
82
|
Provides-Extra: build
|
|
@@ -88,6 +88,8 @@ Provides-Extra: mysql
|
|
|
88
88
|
Requires-Dist: mysqlclient~=2.2; extra == "mysql"
|
|
89
89
|
Provides-Extra: newsblog
|
|
90
90
|
Requires-Dist: djangocms-aldryn-newsblog~=4.0; extra == "newsblog"
|
|
91
|
+
Provides-Extra: cms4
|
|
92
|
+
Requires-Dist: django-cms~=4.1; extra == "cms4"
|
|
91
93
|
Dynamic: author
|
|
92
94
|
Dynamic: author-email
|
|
93
95
|
Dynamic: classifier
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
cms_qe/__init__.py,sha256=do1c4s8BgjukMZMMMhBHs_lG9j8ncnAjR3oTICWEq5w,684
|
|
2
2
|
cms_qe/admin.py,sha256=eLqAF3UIDWWyA0xE0Ft5WP5_3HImSWk3EYL2cRfL_8A,171
|
|
3
3
|
cms_qe/apps.py,sha256=AeRcBWwGs7rKLlzHhnV8M_2BEnkoO9959VwesxfHaio,338
|
|
4
|
-
cms_qe/
|
|
4
|
+
cms_qe/cms4_menus.py,sha256=gfiA1EMoVzl2ZtDIZ0xIwFCotT7c-H7jswVkSZbtooo,5311
|
|
5
|
+
cms_qe/cms5_menus.py,sha256=m1U3ez7CLLSneQLTTy6S0dTylZeLVoqRd_HZOBgiCqo,4124
|
|
6
|
+
cms_qe/cms_menus.py,sha256=D9sG4BhVsQzTOkVL0_-3JHBQpWoMq66g-IWW-DbDKgE,192
|
|
7
|
+
cms_qe/cms_menus_mixin.py,sha256=1i0t2PQoabzffuOZKIhneiJJ9K8Nz70dDS6DqWlJMRg,245
|
|
5
8
|
cms_qe/constants.py,sha256=YWUWCIabSwcamGZynvkJ9i8OWGtfHf-wFirm8GtqQpI,90
|
|
6
9
|
cms_qe/export.py,sha256=3MflO_EmaCrlqqa-cTMOiRirsp3r4mxdQNt-Zh5FtzY,8242
|
|
7
10
|
cms_qe/fixtures.py,sha256=cq_wnZnqBwPBOHpp_0bHk424iCXKvwmN6ZaKwDvguXk,755
|
|
@@ -90,6 +93,7 @@ cms_qe/templates/cms_qe/include/search_form.html,sha256=Wo1AZMWmdE9dgvdWlUZtmZ0V
|
|
|
90
93
|
cms_qe/templates/cms_qe/tests/test_email.txt,sha256=i0XFIXcYFTx2IaXQaaRLd6lK8Mv2Gd0OxVrwKt8uUBQ,14
|
|
91
94
|
cms_qe/templates/cmsplugin_filer_folder/plugins/folder/gallery.html,sha256=TKCWjgBLZZOZtPvg3EAcg8sXta2rj4y6aJpvlA7eZag,246
|
|
92
95
|
cms_qe/templates/cmsplugin_filer_folder/plugins/folder/main.html,sha256=8IiTXNU4K4hKQL6vwqRBbK7TzpeNxEEopq9NG6RZO0A,166
|
|
96
|
+
cms_qe/templates/menu/menu.html,sha256=ZZIhe6acgxwiF9Bgb8yH7XKpI6zpQ0RCDdG8nmJTL1E,592
|
|
93
97
|
cms_qe/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
94
98
|
cms_qe/templatetags/cms_qe_filters.py,sha256=ciYCXLuMVde-f_-B_7a5mHfAJPWPMxYEUMgCHpualRY,784
|
|
95
99
|
cms_qe/templatetags/kwacros.py,sha256=r2oHLltu8BgKKlrpgzXgvLjbIqwcrH13Lww3zTF-nr8,6275
|
|
@@ -3970,21 +3974,8 @@ cms_qe_video/templates/cms_qe/video/video_source_file.html,sha256=QJF5fs88s9Fznp
|
|
|
3970
3974
|
cms_qe_video/templates/cms_qe/video/video_widget.html,sha256=Yumciq6bGlAYI1lYx5j9V6IF8QYrncNYygPTkXEz6Wk,925
|
|
3971
3975
|
cms_qe_video/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3972
3976
|
cms_qe_video/templatetags/cms_qe_video.py,sha256=NR_mGv91J0rEreZrQjCzaaXSrZsKvrSas12wMJ-Dg24,1168
|
|
3973
|
-
django_cms_qe-4.
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
test_selenium/cms_qe/test_base.py,sha256=RbJQLvSvrskOibwY7CmR2XrYCWHBk2UTVyvJwZuDXpE,36
|
|
3979
|
-
test_selenium/cms_qe/test_errors.py,sha256=oGwegQ6hmS-VgnuYKRFOSaVpfNXX5tDdNemGSJoDNpw,378
|
|
3980
|
-
test_selenium/fixtures/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3981
|
-
test_selenium/fixtures/base.py,sha256=c5oqqqvtCRUmVQdvlu6kPBypGz8gdIPJP0Gcvl6fBGs,1788
|
|
3982
|
-
test_selenium/pages/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3983
|
-
test_selenium/pages/cms/__init__.py,sha256=_qe4YZYaQbrXp7Szmmeo4TUSkXlE5Rozu8E3tthA6-E,150
|
|
3984
|
-
test_selenium/pages/cms/login.py,sha256=UPzJQcYff8NUAT4nvmfQoJQxzOJyPrJ_cKtH35NVfNg,521
|
|
3985
|
-
test_selenium/pages/cms/page.py,sha256=YQnpZkopfVnhoyQKpRDGqjNeV6xUl-pEHjEcZ9HRiPk,489
|
|
3986
|
-
test_selenium/pages/cms/wizard.py,sha256=yatbXH-rf1ap4O1hY0I13WikM3zkm_NrAiSK6bqENIU,545
|
|
3987
|
-
django_cms_qe-4.1.0.dist-info/METADATA,sha256=Jh_kHri1S4t76H1AAFtTMjvzx56EFXELL2HgwBEtzNI,6234
|
|
3988
|
-
django_cms_qe-4.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
3989
|
-
django_cms_qe-4.1.0.dist-info/top_level.txt,sha256=fQYSfQoprw1NXhYY-I8AzsPk-Rgst1REh3iOUvwAbkM,164
|
|
3990
|
-
django_cms_qe-4.1.0.dist-info/RECORD,,
|
|
3977
|
+
django_cms_qe-4.2.0.dist-info/licenses/LICENSE,sha256=5wLaeUil0gfU9p8C4zn2Yu_PvZBNieUoYl0z9FcFWdA,1521
|
|
3978
|
+
django_cms_qe-4.2.0.dist-info/METADATA,sha256=8KG2Og6RSiKpsypY_x35P3vKw4FdNsEznKd-XaXsyKE,6310
|
|
3979
|
+
django_cms_qe-4.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
3980
|
+
django_cms_qe-4.2.0.dist-info/top_level.txt,sha256=u9pqfoJfXwh29tMO7lnphA5yn7ChYZquFe7_sNtEiW8,150
|
|
3981
|
+
django_cms_qe-4.2.0.dist-info/RECORD,,
|
test_selenium/__init__.py
DELETED
|
File without changes
|
test_selenium/browser.py
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
from selenium.common.exceptions import NoSuchElementException
|
|
2
|
-
from webdriverwrapper import Chrome as _Chrome
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class ChromeBrowser(_Chrome):
|
|
6
|
-
def get_error_page(self):
|
|
7
|
-
# Django error page.
|
|
8
|
-
try:
|
|
9
|
-
error_page = self.get_elm('summary')
|
|
10
|
-
except NoSuchElementException:
|
|
11
|
-
pass
|
|
12
|
-
else:
|
|
13
|
-
header = error_page.get_elm(tag_name='h1')
|
|
14
|
-
return header.text
|
|
15
|
-
|
|
16
|
-
# Generic error page.
|
|
17
|
-
try:
|
|
18
|
-
error_page = self.get_elm('error-page')
|
|
19
|
-
except NoSuchElementException:
|
|
20
|
-
pass
|
|
21
|
-
else:
|
|
22
|
-
header = error_page.get_elm(tag_name='h1')
|
|
23
|
-
return header.text
|
|
24
|
-
|
|
25
|
-
def get_error_traceback(self):
|
|
26
|
-
try:
|
|
27
|
-
traceback = self.get_elm('traceback_area')
|
|
28
|
-
except NoSuchElementException:
|
|
29
|
-
pass
|
|
30
|
-
else:
|
|
31
|
-
return traceback.text
|
|
32
|
-
|
|
33
|
-
def get_error_messages(self):
|
|
34
|
-
try:
|
|
35
|
-
error_elms = self.get_elms(xpath='//*[contains(@class, "alert-danger")]')
|
|
36
|
-
except NoSuchElementException:
|
|
37
|
-
return []
|
|
38
|
-
else:
|
|
39
|
-
return [error_elm.text for error_elm in error_elms]
|
|
40
|
-
|
|
41
|
-
def get_info_messages(self):
|
|
42
|
-
try:
|
|
43
|
-
info_elms = self.get_elms(xpath='//*[contains(@class, "alert-success")]')
|
|
44
|
-
except NoSuchElementException:
|
|
45
|
-
return []
|
|
46
|
-
else:
|
|
47
|
-
return [info_elm.get_attribute('info') for info_elm in info_elms]
|
test_selenium/cms_qe/__init__.py
DELETED
|
File without changes
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
from webdriverwrapper.decorators import expected_error_page
|
|
2
|
-
|
|
3
|
-
from ..pages.cms import CreatePagePage
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
@expected_error_page(None)
|
|
7
|
-
def test_page_not_found(driver):
|
|
8
|
-
driver.go_to('/404')
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
# No error page should be detected.
|
|
12
|
-
def test_page_not_found_custom_by_cms(driver):
|
|
13
|
-
CreatePagePage(driver).open().create_page('Page not found', slug='error404')
|
|
14
|
-
driver.go_to('/404')
|
test_selenium/conftest.py
DELETED
|
File without changes
|
test_selenium/fixtures/base.py
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
|
|
3
|
-
import pytest
|
|
4
|
-
from selenium import webdriver
|
|
5
|
-
from webdriverwrapper.pytest import * # noqa: F403,F401
|
|
6
|
-
|
|
7
|
-
from ..browser import ChromeBrowser
|
|
8
|
-
from ..pages.cms import LoginPage, WizardPage
|
|
9
|
-
|
|
10
|
-
__all__ = [
|
|
11
|
-
'display',
|
|
12
|
-
'session_driver',
|
|
13
|
-
'_driver',
|
|
14
|
-
'homepage_url',
|
|
15
|
-
'admin_username',
|
|
16
|
-
]
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
@pytest.fixture(scope='session', autouse=True)
|
|
20
|
-
def display(request):
|
|
21
|
-
no_display = request.config.getoption('--no-display')
|
|
22
|
-
|
|
23
|
-
if no_display:
|
|
24
|
-
yield
|
|
25
|
-
else:
|
|
26
|
-
from pyvirtualdisplay import Display
|
|
27
|
-
|
|
28
|
-
display = Display(visible=0, size=(1200, 2000))
|
|
29
|
-
display.start()
|
|
30
|
-
yield
|
|
31
|
-
display.stop()
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
@pytest.yield_fixture(scope='session')
|
|
35
|
-
def session_driver(request, homepage_url, admin_username):
|
|
36
|
-
no_display = request.config.getoption('--no-display')
|
|
37
|
-
|
|
38
|
-
driver = open_browser(homepage_url)
|
|
39
|
-
try:
|
|
40
|
-
LoginPage(driver).open().login(admin_username)
|
|
41
|
-
WizardPage(driver).open().create_home_page()
|
|
42
|
-
yield driver
|
|
43
|
-
finally:
|
|
44
|
-
if not no_display:
|
|
45
|
-
driver.quit()
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def open_browser(homepage_url):
|
|
49
|
-
"""
|
|
50
|
-
Open browser a type URL `homepage_url`.
|
|
51
|
-
"""
|
|
52
|
-
chrome_options = webdriver.ChromeOptions()
|
|
53
|
-
chrome_options.add_argument('--incognito')
|
|
54
|
-
chrome_options.add_argument('--no-sandbox') # So it will work in GitLab CI.
|
|
55
|
-
|
|
56
|
-
driver = ChromeBrowser(chrome_options=chrome_options)
|
|
57
|
-
driver.get(homepage_url)
|
|
58
|
-
return driver
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
@pytest.fixture
|
|
62
|
-
def _driver(session_driver, homepage_url):
|
|
63
|
-
session_driver.get(homepage_url)
|
|
64
|
-
return session_driver
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
@pytest.fixture(scope='session')
|
|
68
|
-
def homepage_url():
|
|
69
|
-
port = os.environ.get('WEB_PORT', '8000')
|
|
70
|
-
return f'http://localhost:{port}'
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
@pytest.fixture(scope='session')
|
|
74
|
-
def admin_username():
|
|
75
|
-
return os.environ.get('ADMIN_USER', 'admin')
|
test_selenium/pages/__init__.py
DELETED
|
File without changes
|
test_selenium/pages/cms/login.py
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
class LoginPage:
|
|
2
|
-
def __init__(self, driver):
|
|
3
|
-
self._driver = driver
|
|
4
|
-
|
|
5
|
-
def open(self):
|
|
6
|
-
self._driver.go_to('/admin')
|
|
7
|
-
return self
|
|
8
|
-
|
|
9
|
-
def login(self, username, password=None):
|
|
10
|
-
"""
|
|
11
|
-
Log in to Django CMS application as `username` with `password`.
|
|
12
|
-
If `password` is empty, then is used same as `username`.
|
|
13
|
-
"""
|
|
14
|
-
self._driver.get_elm('login-form').fill_out_and_submit({
|
|
15
|
-
'username': username,
|
|
16
|
-
'password': password or username,
|
|
17
|
-
})
|
test_selenium/pages/cms/page.py
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
class CreatePagePage:
|
|
2
|
-
def __init__(self, driver):
|
|
3
|
-
self._driver = driver
|
|
4
|
-
|
|
5
|
-
def open(self):
|
|
6
|
-
self._driver.go_to('/admin/cms/page/add/')
|
|
7
|
-
return self
|
|
8
|
-
|
|
9
|
-
def create_page(self, title, slug=''):
|
|
10
|
-
# Title will automatically set slug which we want to force.
|
|
11
|
-
self._driver.get_elm(tag_name='form').fill_out({
|
|
12
|
-
'title': title,
|
|
13
|
-
})
|
|
14
|
-
self._driver.get_elm(tag_name='form').fill_out_and_submit({
|
|
15
|
-
'slug': slug,
|
|
16
|
-
})
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
class WizardPage:
|
|
2
|
-
def __init__(self, driver):
|
|
3
|
-
self._driver = driver
|
|
4
|
-
|
|
5
|
-
def open(self):
|
|
6
|
-
self._driver.go_to('/cms_wizard/create/?language=en')
|
|
7
|
-
return self
|
|
8
|
-
|
|
9
|
-
def create_home_page(self):
|
|
10
|
-
"""
|
|
11
|
-
Creates empty homepage for Django CMS. Usuful to create some
|
|
12
|
-
at the beggining of tests so any test can start somewhere.
|
|
13
|
-
"""
|
|
14
|
-
self._driver.get_elm(tag_name='form').submit()
|
|
15
|
-
self._driver.get_elm(tag_name='form').fill_out_and_submit({
|
|
16
|
-
'1-title': 'homepage',
|
|
17
|
-
})
|
|
File without changes
|
|
File without changes
|