Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit e1e8f32

Browse files
fix: Allow lazy wizard initialization (#8266)
* fix: Allow lazy wizard initialization (#8265) * fix: Allow lazy wizard initialization * Update cms/cms_toolbars.py Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Update cms/cms_config.py Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> --------- Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * feat: add "clear_wizard_cache" * fix: Wizard pool depr message * Update wizard_base.py * fix linting --------- Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
1 parent f60830e commit e1e8f32

File tree

9 files changed

+104
-82
lines changed

9 files changed

+104
-82
lines changed

cms/cms_config.py

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from collections.abc import Iterable
2+
from functools import cached_property
23
from logging import getLogger
34

45
from django.core.exceptions import ImproperlyConfigured
@@ -19,9 +20,8 @@ class CMSCoreConfig(CMSAppConfig):
1920

2021

2122
class CMSCoreExtensions(CMSAppExtension):
22-
2323
def __init__(self):
24-
self.wizards = {}
24+
self.lazy_wizards = []
2525
self.toolbar_enabled_models = {}
2626
self.model_groupers = {}
2727
self.toolbar_mixins = []
@@ -33,17 +33,30 @@ def configure_wizards(self, cms_config):
3333
"""
3434
if not isinstance(cms_config.cms_wizards, Iterable):
3535
raise ImproperlyConfigured("cms_wizards must be iterable")
36-
for wizard in cms_config.cms_wizards:
37-
if not isinstance(wizard, Wizard):
36+
37+
self.lazy_wizards.append(cms_config.cms_wizards)
38+
39+
@cached_property
40+
def wizards(self) -> dict[str, Wizard]:
41+
"""
42+
Returns a dictionary of wizard instances keyed by their unique IDs.
43+
Iterates over all iterables in `self.lazy_wizards`, filters out objects that are instances
44+
of the `Wizard` class, and constructs a dictionary where each key is the wizard's `id`
45+
and the value is the corresponding `Wizard` instance.
46+
Returns:
47+
dict: A dictionary mapping wizard IDs to `Wizard` instances.
48+
"""
49+
50+
wizards = {}
51+
for iterable in self.lazy_wizards:
52+
new_wizards = {wizard.id: wizard for wizard in iterable}
53+
if wizard := next((wizard for wizard in new_wizards.values() if not isinstance(wizard, Wizard)), None):
54+
# If any wizard in the iterable is not an instance of Wizard, raise an exception
3855
raise ImproperlyConfigured(
39-
"All wizards defined in cms_wizards must inherit "
40-
"from cms.wizards.wizard_base.Wizard"
56+
f"cms_wizards must be iterable of Wizard instances, got {type(wizard)}"
4157
)
42-
elif wizard.id in self.wizards:
43-
msg = f"Wizard for model {wizard.get_model()} has already been registered"
44-
logger.warning(msg)
45-
else:
46-
self.wizards[wizard.id] = wizard
58+
wizards |= new_wizards
59+
return wizards
4760

4861
def configure_toolbar_enabled_models(self, cms_config):
4962
if not isinstance(cms_config.cms_toolbar_enabled_models, Iterable):

cms/cms_toolbars.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -85,13 +85,9 @@ def add_wizard_button(self):
8585
from cms.wizards.wizard_pool import entry_choices
8686
title = _("Create")
8787

88-
if self.page:
89-
user = self.request.user
90-
page_pk = self.page.pk
91-
disabled = len(list(entry_choices(user, self.page))) == 0
92-
else:
93-
page_pk = ''
94-
disabled = True
88+
user = self.request.user
89+
page_pk = self.page.pk if self.page else ""
90+
disabled = not list(entry_choices(user, self.page))
9591

9692
url = '{url}?page={page}&language={lang}&edit'.format(
9793
url=admin_reverse("cms_wizard_create"),

cms/tests/test_cms_config_wizards.py

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,10 @@ def test_raises_exception_if_doesnt_inherit_from_wizard_class(self):
4949
wizard = Mock(id=3, spec=object)
5050
cms_config = Mock(
5151
cms_enabled=True, cms_wizards=[wizard])
52+
extensions.configure_wizards(cms_config)
5253
with self.assertRaises(ImproperlyConfigured):
53-
extensions.configure_wizards(cms_config)
54+
extensions.wizards
55+
5456

5557
def test_raises_exception_if_not_iterable(self):
5658
"""
@@ -63,22 +65,6 @@ def test_raises_exception_if_not_iterable(self):
6365
with self.assertRaises(ImproperlyConfigured):
6466
extensions.configure_wizards(cms_config)
6567

66-
@patch('cms.cms_config.logger.warning')
67-
def test_warning_if_registering_the_same_wizard_twice(self, mocked_logger):
68-
"""
69-
If a wizard is already added to the dict log a warning.
70-
"""
71-
extensions = CMSCoreExtensions()
72-
wizard = Mock(id=81, spec=Wizard)
73-
cms_config = Mock(
74-
cms_enabled=True, cms_wizards=[wizard, wizard])
75-
extensions.configure_wizards(cms_config)
76-
warning_msg = f"Wizard for model {wizard.get_model()} has already been registered"
77-
# Warning message displayed
78-
mocked_logger.assert_called_once_with(warning_msg)
79-
# wizards dict is still what we expect it to be
80-
self.assertDictEqual(extensions.wizards, {81: wizard})
81-
8268

8369
class ConfigureWizardsIntegrationTestCase(CMSTestCase):
8470

cms/tests/test_wizards.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,9 @@ def tearDown(self):
219219
# Clean up in case anything has been removed or added to the
220220
# registered wizards, so other tests don't have problems
221221
extension = apps.get_app_config('cms').cms_extension
222-
extension.wizards = {}
222+
# Reset the cached property 'wizards'
223+
if hasattr(extension, 'wizards'):
224+
del extension.wizards
223225
configs_with_wizards = [
224226
app.cms_config for app in app_registration.get_cms_config_apps()
225227
if hasattr(app.cms_config, 'cms_wizards')
@@ -235,6 +237,9 @@ def test_is_registered_for_registered_wizard(self):
235237
Test for backwards compatibility of is_registered when checking
236238
a registered wizard.
237239
"""
240+
from django.apps import apps
241+
242+
self.assertTrue(apps.get_app_config("cms").cms_extension.wizards)
238243
is_registered = wizard_pool.is_registered(cms_page_wizard)
239244
self.assertTrue(is_registered)
240245

cms/wizards/helpers.py

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,12 @@
1-
from django.apps import apps
1+
import warnings
22

3+
from cms.utils.compat.warnings import RemovedInDjangoCMS60Warning
34

4-
def get_entries():
5-
"""
6-
Returns a list of (wizard.id, wizard) tuples (for all registered
7-
wizards) ordered by weight
5+
from .wizard_base import get_entries, get_entry # noqa: F401
86

9-
``get_entries()`` is useful if it is required to have a list of all registered
10-
wizards. Typically, this is used to iterate over them all. Note that they will
11-
be returned in the order of their ``weight``: smallest numbers for weight are
12-
returned first.::
13-
14-
for wizard_id, wizard in get_entries():
15-
# do something with a wizard...
16-
"""
17-
wizards = apps.get_app_config('cms').cms_extension.wizards
18-
return [value for (key, value) in sorted(
19-
wizards.items(), key=lambda e: e[1].weight)]
20-
21-
22-
def get_entry(entry_key):
23-
"""
24-
Returns a wizard object based on its :attr:`~.cms.wizards.wizard_base.Wizard.id`.
25-
"""
26-
return apps.get_app_config('cms').cms_extension.wizards[entry_key]
7+
warnings.warn(
8+
"The cms.wizards.helpers module is deprecated and will be removed in django CMS 5.1. "
9+
"Use cms.wizards.wizard_base.get_entries and cms.wizards.wizard_pool.get_entry instead.",
10+
RemovedInDjangoCMS60Warning,
11+
stacklevel=2,
12+
)

cms/wizards/views.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from cms.utils.i18n import get_site_language_from_request
1616

1717
from .forms import WizardStep1Form, WizardStep2BaseForm, step2_form_factory
18-
from .wizard_pool import wizard_pool
18+
from .wizard_base import get_entry
1919

2020

2121
class WizardCreateView(SessionWizardView):
@@ -150,7 +150,7 @@ def done(self, form_list, **kwargs):
150150

151151
def get_selected_entry(self):
152152
data = self.get_cleaned_data_for_step('0')
153-
return wizard_pool.get_entry(data['entry'])
153+
return get_entry(data['entry'])
154154

155155
def get_origin_page(self):
156156
data = self.get_cleaned_data_for_step('0')

cms/wizards/wizard_base.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,49 @@
1313
)
1414

1515

16+
def get_entries():
17+
"""
18+
Returns a list of Wizard objects (for all registered wizards) ordered by weight
19+
20+
``get_entries()`` is useful if it is required to have a list of all registered
21+
wizards. Typically, this is used to iterate over them all. Note that they will
22+
be returned in the order of their ``weight``: smallest numbers for weight are
23+
returned first.::
24+
25+
for wizard in get_entries():
26+
# do something with a wizard...
27+
"""
28+
wizards = apps.get_app_config('cms').cms_extension.wizards
29+
return sorted(wizards.values(), key=lambda e: e.weight)
30+
31+
32+
def get_entry(entry_key):
33+
"""
34+
Returns a wizard object based on its :attr:`~.cms.wizards.wizard_base.Wizard.id`.
35+
"""
36+
return apps.get_app_config('cms').cms_extension.wizards[entry_key]
37+
38+
39+
def entry_choices(user, page):
40+
"""
41+
Yields a list of wizard entry tuples of the form (wizard.id, wizard.title) that
42+
the current user can use based on their permission to add instances of the
43+
underlying model objects.
44+
"""
45+
for entry in get_entries():
46+
if entry.user_has_add_permission(user, page=page):
47+
yield (entry.id, entry.title)
48+
49+
50+
def clear_wizard_cache():
51+
"""
52+
Clears the wizard cache. This is useful if you have added or removed wizards
53+
and want to ensure that the changes are reflected immediately.
54+
"""
55+
del apps.get_app_config('cms').cms_extension.wizards
56+
57+
58+
1659
class WizardBase:
1760
"""
1861

cms/wizards/wizard_pool.py

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,15 @@
11
from django.apps import apps
22
from django.utils.translation import gettext as _
33

4-
from cms.wizards.helpers import get_entries, get_entry
5-
from cms.wizards.wizard_base import Wizard
4+
from cms.utils.compat.warnings import RemovedInDjangoCMS60Warning
5+
from cms.wizards.helpers import get_entries, get_entry # noqa: F401
6+
from cms.wizards.wizard_base import Wizard, entry_choices # noqa: F401
67

78

89
class AlreadyRegisteredException(Exception):
910
pass
1011

1112

12-
def entry_choices(user, page):
13-
"""
14-
Yields a list of wizard entries that the current user can use based on their
15-
permission to add instances of the underlying model objects.
16-
"""
17-
for entry in get_entries():
18-
if entry.user_has_add_permission(user, page=page):
19-
yield (entry.id, entry.title)
20-
21-
2213
class WizardPool:
2314
"""
2415
.. deprecated:: 4.0
@@ -49,7 +40,13 @@ def register(self, entry):
4940
name. In this case, the register method will raise a
5041
``cms.wizards.wizard_pool.AlreadyRegisteredException``.
5142
"""
52-
# TODO: Add deprecation warning
43+
import warnings
44+
45+
warnings.warn(
46+
"Using wizard_pool is deprecated. Use the cms_config instead.",
47+
RemovedInDjangoCMS60Warning,
48+
stacklevel=2,
49+
)
5350
assert isinstance(entry, Wizard), "entry must be an instance of Wizard"
5451
if self.is_registered(entry, passive=True):
5552
model = entry.get_model()

docs/reference/wizards.rst

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ When instantiating a Wizard object, use the keywords:
3939
CMS will create one from the pattern:
4040
"Create a new «model.verbose_name» instance."
4141
:edit_mode_on_success: Whether the user will get redirected to object edit url after a
42-
successful creation or not. This only works if the object is registered
42+
successful creation or not. This only works if the object is registered
4343
for toolbar enabled models.
4444

4545

@@ -78,17 +78,13 @@ Wizard class
7878
:members:
7979
:inherited-members:
8080

81-
82-
*******
83-
Helpers
84-
*******
85-
86-
.. module:: cms.wizards.helpers
87-
8881
.. autofunction:: get_entry
8982

9083
.. autofunction:: get_entries
9184

85+
.. autofunction:: entry_choices
86+
87+
9288

9389
***********
9490
wizard_pool

0 commit comments

Comments
 (0)