forked from ungleich-public/dynamicweb
205 lines
7.4 KiB
Python
205 lines
7.4 KiB
Python
import os
|
|
|
|
from django.conf import settings
|
|
from django.core.exceptions import PermissionDenied
|
|
from django.core.files.storage import FileSystemStorage
|
|
from django.forms import Form
|
|
from django.http import HttpResponse
|
|
from django.urls import NoReverseMatch
|
|
from django.utils.html import escape
|
|
from formtools.wizard.views import SessionWizardView
|
|
|
|
from cms.constants import MODAL_HTML_REDIRECT
|
|
from cms.models import Page
|
|
from cms.utils import get_current_site
|
|
from cms.utils.i18n import get_site_language_from_request
|
|
|
|
from .forms import WizardStep1Form, WizardStep2BaseForm, step2_form_factory
|
|
from .wizard_base import get_entry
|
|
|
|
|
|
class WizardCreateView(SessionWizardView):
|
|
template_name = 'cms/wizards/start.html'
|
|
file_storage = FileSystemStorage(
|
|
location=os.path.join(settings.MEDIA_ROOT, 'wizard_tmp_files'))
|
|
|
|
form_list = [
|
|
('0', WizardStep1Form),
|
|
# Form is used as a placeholder form.
|
|
# the real form will be loaded after step 0
|
|
('1', Form),
|
|
]
|
|
|
|
def dispatch(self, *args, **kwargs):
|
|
user = self.request.user
|
|
|
|
if not user.is_active or not user.is_staff:
|
|
raise PermissionDenied
|
|
self.site = get_current_site(self.request)
|
|
return super().dispatch(*args, **kwargs)
|
|
|
|
def get_current_step(self):
|
|
"""Returns the current step, if possible, else None."""
|
|
try:
|
|
return self.steps.current
|
|
except AttributeError:
|
|
return None
|
|
|
|
def is_first_step(self, step=None):
|
|
step = step or self.get_current_step()
|
|
return step == '0'
|
|
|
|
def is_second_step(self, step=None):
|
|
step = step or self.get_current_step()
|
|
return step == '1'
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
|
|
# The wizard is always shown inside the CMS modal. Surface ``is_popup`` to the
|
|
# template context so that ``admin/base.html`` natively omits the header.
|
|
context['is_popup'] = True
|
|
|
|
if self.is_second_step():
|
|
context['wizard_entry'] = self.get_selected_entry()
|
|
return context
|
|
|
|
def get_form_list(self):
|
|
"""
|
|
Resolve the wizard's form list.
|
|
|
|
Step ``'1'`` is declared with a placeholder ``Form`` because the real
|
|
form depends on the entry chosen in step ``'0'``. Once step 0 has been
|
|
completed, the real form is built and substituted here. Doing the
|
|
substitution in ``get_form_list`` -- instead of mutating the shared
|
|
``self.form_list`` -- keeps the swap visible to django-formtools 2.6+,
|
|
which caches ``get_form_list`` results.
|
|
"""
|
|
form_list = super().get_form_list()
|
|
# ``_building_step_2`` guards against infinite recursion: building the
|
|
# step-2 form needs step 0's cleaned data, which itself resolves the
|
|
# form list again.
|
|
if '1' in form_list and not getattr(self, '_building_step_2', False):
|
|
self._building_step_2 = True
|
|
try:
|
|
if self.get_cleaned_data_for_step('0') is not None:
|
|
form_list['1'] = self.get_step_2_form()
|
|
finally:
|
|
self._building_step_2 = False
|
|
return form_list
|
|
|
|
def get_form(self, step=None, data=None, files=None):
|
|
if step is None:
|
|
step = self.steps.current
|
|
|
|
# We need to grab the page from pre-validated data so that the wizard
|
|
# has it to prepare the list of valid entries.
|
|
#
|
|
# ``get_form`` is re-entered while a form is built: resolving the step-2
|
|
# form list (``get_form_list``) calls ``get_cleaned_data_for_step('0')``,
|
|
# which rebuilds the step-0 form -- and, with empty storage, would reset
|
|
# ``page_pk`` to ``None``. Save and restore it so the inner call can't
|
|
# clobber the value the outer call still needs in ``get_form_kwargs``.
|
|
previous_page_pk = getattr(self, 'page_pk', None)
|
|
if data:
|
|
page_key = f"{step}-page"
|
|
self.page_pk = data.get(page_key, None)
|
|
else:
|
|
self.page_pk = None
|
|
try:
|
|
return super().get_form(step, data, files)
|
|
finally:
|
|
self.page_pk = previous_page_pk
|
|
|
|
def get_form_kwargs(self, step=None):
|
|
"""This is called by self.get_form()"""
|
|
kwargs = super().get_form_kwargs()
|
|
kwargs['wizard_request'] = self.request
|
|
kwargs['wizard_site'] = self.site
|
|
if self.is_second_step(step):
|
|
kwargs['wizard_page'] = self.get_origin_page()
|
|
kwargs['wizard_language'] = self.get_origin_language()
|
|
else:
|
|
page_pk = self.page_pk or self.request.GET.get('page', None)
|
|
if page_pk and page_pk != 'None':
|
|
kwargs['wizard_page'] = Page.objects.filter(pk=page_pk, site=self.site).first()
|
|
else:
|
|
kwargs['wizard_page'] = None
|
|
kwargs['wizard_language'] = get_site_language_from_request(
|
|
self.request,
|
|
site_id=self.site.pk,
|
|
)
|
|
return kwargs
|
|
|
|
def get_form_initial(self, step):
|
|
"""This is called by self.get_form()"""
|
|
initial = super().get_form_initial(step)
|
|
if self.is_first_step(step):
|
|
initial['page'] = self.request.GET.get('page')
|
|
initial['language'] = self.request.GET.get('language')
|
|
return initial
|
|
|
|
def get_step_2_form(self, step=None, data=None, files=None):
|
|
entry_form_class = self.get_selected_entry().form
|
|
step_2_base_form = self.get_step_2_base_form()
|
|
|
|
form = step2_form_factory(
|
|
mixin_cls=step_2_base_form,
|
|
entry_form_class=entry_form_class,
|
|
)
|
|
return form
|
|
|
|
def get_step_2_base_form(self):
|
|
"""
|
|
Returns the base form to be used for step 2.
|
|
This form is sub classed dynamically by the form defined per module.
|
|
"""
|
|
return WizardStep2BaseForm
|
|
|
|
def get_template_names(self):
|
|
if self.is_first_step():
|
|
template_name = self.template_name
|
|
else:
|
|
template_name = self.get_selected_entry().template_name
|
|
return template_name
|
|
|
|
def done(self, form_list, **kwargs):
|
|
"""
|
|
This step only runs if all forms are valid. Simply emits a simple
|
|
template that uses JS to redirect to the newly created object.
|
|
"""
|
|
form_one, form_two = list(form_list)
|
|
instance = form_two.save()
|
|
url = self.get_success_url(instance)
|
|
language = form_one.cleaned_data['language']
|
|
if not url:
|
|
page = self.get_origin_page()
|
|
if page:
|
|
try:
|
|
url = page.get_absolute_url(language)
|
|
except NoReverseMatch:
|
|
url = '/'
|
|
else:
|
|
url = '/'
|
|
return HttpResponse(MODAL_HTML_REDIRECT.format(url=escape(url)))
|
|
|
|
def get_selected_entry(self):
|
|
data = self.get_cleaned_data_for_step('0')
|
|
return get_entry(data['entry'])
|
|
|
|
def get_origin_page(self):
|
|
data = self.get_cleaned_data_for_step('0')
|
|
return data.get('page')
|
|
|
|
def get_origin_language(self):
|
|
data = self.get_cleaned_data_for_step('0')
|
|
return data.get('language')
|
|
|
|
def get_success_url(self, instance):
|
|
entry = self.get_selected_entry()
|
|
language = self.get_origin_language()
|
|
success_url = entry.get_success_url(
|
|
obj=instance,
|
|
language=language,
|
|
)
|
|
return success_url
|