dynamicweb/cms/wizards/wizard_base.py

218 lines
8.4 KiB
Python

import hashlib
from django.apps import apps
from django.core.exceptions import ImproperlyConfigured
from django.forms.models import ModelForm
from django.utils.encoding import force_str
from django.utils.functional import cached_property
from django.utils.translation import gettext as _, override as force_language
from cms.toolbar.utils import (
get_object_edit_url,
get_object_preview_url,
)
def get_entries():
"""
Returns a list of Wizard objects (for all registered wizards) ordered by weight
``get_entries()`` is useful if it is required to have a list of all registered
wizards. Typically, this is used to iterate over them all. Note that they will
be returned in the order of their ``weight``: smallest numbers for weight are
returned first.::
for wizard in get_entries():
# do something with a wizard...
"""
wizards = apps.get_app_config('cms').cms_extension.wizards
return sorted(wizards.values(), key=lambda e: e.weight)
def get_entry(entry_key):
"""
Returns a wizard object based on its :attr:`~.cms.wizards.wizard_base.Wizard.id`.
"""
return apps.get_app_config('cms').cms_extension.wizards[entry_key]
def entry_choices(user, page, site=None):
"""
Yields a list of wizard entry tuples of the form (wizard.id, wizard.title) that
the current user can use based on their permission to add instances of the
underlying model objects.
"""
for entry in get_entries():
if entry.user_has_add_permission(user, page=page, site=site):
yield (entry.id, entry.title)
def clear_wizard_cache():
"""
Clears the wizard cache. This is useful if you have added or removed wizards
and want to ensure that the changes are reflected immediately.
"""
del apps.get_app_config('cms').cms_extension.wizards
class WizardBase:
"""
"""
template_name = None
def __init__(self, title, weight, form, model=None, template_name=None,
description=None, edit_mode_on_success=True):
"""
:param title: The title of the wizard. It will appear in a large font size on the wizard “menu”
:param weight: Used for determining the order of the wizards on the
creation form.
:param form: The form to use for this wizard. This is mandatory, but can
be sub-classed from :class:`django.forms.Form` or clas:`django.forms.ModelForm`.
:param model: Required either here or in the form's Meta class. This is
used to determine uniqueness of the wizards, so, only one
wizard per model.
:param template_name: The full-path to the template to use, if any.
:param description: This is used on the start form. The description is optional, but if it is
not supplied, the CMS will create one from the pattern:
"Create a new «model.verbose_name» instance."
:param edit_mode_on_success: Whether the user will get redirected to object edit url after a
successful creation or not. This only works if the object is registered
for toolbar enabled models.
"""
self.title = title
self.weight = weight
self.form = form
self.model = model
self.description = description
self.edit_mode_on_success = edit_mode_on_success
if template_name is not None:
self.template_name = template_name
class Wizard(WizardBase):
"""
All wizard classes should inherit from ``cms.wizards.wizard_base.Wizard``. This
class implements a number of methods that may be overridden as required.
"""
template_name = 'cms/wizards/create.html'
_hash_cache = None
@property
def id(self):
"""
To construct a unique ID for each wizard, we start with the module and
class name for uniqueness, we hash it because a wizard's ID is displayed
in the form's markup, and we'd rather not expose code paths there.
"""
if not self._hash_cache:
full_path = force_str(
".".join([self.__module__, self.__class__.__name__])
).encode('utf-8')
hash = hashlib.sha1()
hash.update(full_path)
self._hash_cache = hash.hexdigest()
return self._hash_cache
def get_title(self, **kwargs):
"""
Simply returns the ``title`` property assigned during instantiation. Override
this method if this needs to be determined programmatically.
"""
return self.title
def get_weight(self, **kwargs):
"""
Simply returns the ``weight`` property assigned during instantiation. Override
this method if this needs to be determined programmatically.
"""
return self.weight
def get_description(self, **kwargs):
"""
Simply returns the ``description`` property assigned during instantiation or one
derived from the model if description is not provided during instantiation.
Override this method if this needs to be determined programmatically.
"""
if self.description:
return self.description
model = self.get_model()
if model:
model_name = model._meta.verbose_name
return _("Create a new %s instance.") % model_name
return ""
def __str__(self):
return force_str(self.title)
def __repr__(self):
display = f'<{self.__module__}.{self.__class__.__name__} id={self.id} object at {hex(id(self))}>'
return display
def user_has_add_permission(self, user, **kwargs):
"""
Returns boolean reflecting whether the given «user» has permission to
add instances of this wizard's associated model. Can be overridden as
required for more complex situations.
:param user: The current user using the wizard.
:return: True if the user should be able to use this wizard.
"""
model = self.get_model()
app_label = model._meta.app_label
model_name = model.__name__.lower()
return user.has_perm(f"{app_label}.add_{model_name}")
def get_success_url(self, obj, **kwargs):
"""
Once the wizard has completed, the user will be redirected to the URL of the new
object that was created. By default, this is done by return the result of
calling the ``get_absolute_url`` method on the object. If the object is registered
for toolbar enabled models, the object edit url will be returned. This may be modified
to return the preview url instead by setting the wizard property ``edit_mode_on_success``
to False.
In some cases, the created content will not implement ``get_absolute_url`` or
that redirecting the user is undesirable. In these cases, simply override this
method. If ``get_success_url`` returns ``None``, the CMS will just redirect to
the current page after the object is created.
:param object obj: The created object
:param dict kwargs: Arbitrary keyword arguments
"""
extension = apps.get_app_config('cms').cms_extension
if obj.__class__ in extension.toolbar_enabled_models:
if self.edit_mode_on_success:
return get_object_edit_url(obj, language=kwargs.get("language", None))
return get_object_preview_url(obj, language=kwargs.get("language", None))
else:
if "language" in kwargs:
with force_language(language=kwargs["language"]):
return obj.get_absolute_url()
return obj.get_absolute_url()
def get_model(self):
if self.model:
return self.model
if issubclass(self.form, ModelForm):
model = self.form._meta.model
if model:
return model
raise ImproperlyConfigured("Please set entry 'model' attribute or use "
"ModelForm subclass as a form")
@cached_property
def widget_attributes(self):
return {
'description': self.get_description(),
'title': self.get_title(),
'weight': self.get_weight(),
'id': self.id,
'form': self.form,
'model': self.model,
'template_name': self.template_name
}