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 }