| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343 |
- import re
- from collections import OrderedDict
- from django import forms
- from django.db import models
- from django.template import loader
- try:
- from formtools.wizard.storage import get_storage
- from formtools.wizard.forms import ManagementForm
- from formtools.wizard.views import StepsHelper
- except:
- # work for django<1.8
- from django.contrib.formtools.wizard.storage import get_storage
- from django.contrib.formtools.wizard.forms import ManagementForm
- from django.contrib.formtools.wizard.views import StepsHelper
- from django.utils import six
- from django.utils.encoding import smart_text
- from django.utils.module_loading import import_string
- from django.forms import ValidationError
- from django.forms.models import modelform_factory
- from xadmin.sites import site
- from xadmin.views import BaseAdminPlugin, ModelFormAdminView
- def normalize_name(name):
- new = re.sub('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))', '_\\1', name)
- return new.lower().strip('_')
- class WizardFormPlugin(BaseAdminPlugin):
- wizard_form_list = None
- wizard_for_update = False
- storage_name = 'formtools.wizard.storage.session.SessionStorage'
- form_list = None
- initial_dict = None
- instance_dict = None
- condition_dict = None
- file_storage = None
- def _get_form_prefix(self, step=None):
- if step is None:
- step = self.steps.current
- obj = self.get_form_list().keys()
- if six.PY3:
- obj = [s for s in obj]
- return 'step_%d' % obj.index(step)
- def get_form_list(self):
- if not hasattr(self, '_form_list'):
- init_form_list = OrderedDict()
- assert len(
- self.wizard_form_list) > 0, 'at least one form is needed'
- for i, form in enumerate(self.wizard_form_list):
- init_form_list[smart_text(form[0])] = form[1]
- self._form_list = init_form_list
- return self._form_list
- # Plugin replace methods
- def init_request(self, *args, **kwargs):
- if self.request.is_ajax() or ("_ajax" in self.request.GET) or not hasattr(self.request, 'session') or (args and not self.wizard_for_update):
- # update view
- return False
- return bool(self.wizard_form_list)
- def prepare_form(self, __):
- # init storage and step helper
- self.prefix = normalize_name(self.__class__.__name__)
- self.storage = get_storage(
- self.storage_name, self.prefix, self.request,
- getattr(self, 'file_storage', None))
- self.steps = StepsHelper(self)
- self.wizard_goto_step = False
- if self.request.method == 'GET':
- self.storage.reset()
- self.storage.current_step = self.steps.first
- self.admin_view.model_form = self.get_step_form()
- else:
- # Look for a wizard_goto_step element in the posted data which
- # contains a valid step name. If one was found, render the requested
- # form. (This makes stepping back a lot easier).
- wizard_goto_step = self.request.POST.get('wizard_goto_step', None)
- if wizard_goto_step and int(wizard_goto_step) < len(self.get_form_list()):
- obj = self.get_form_list().keys()
- if six.PY3:
- obj = [s for s in obj]
- self.storage.current_step = obj[int(wizard_goto_step)]
- self.admin_view.model_form = self.get_step_form()
- self.wizard_goto_step = True
- return
- # Check if form was refreshed
- management_form = ManagementForm(
- self.request.POST, prefix=self.prefix)
- if not management_form.is_valid():
- raise ValidationError(
- 'ManagementForm data is missing or has been tampered.')
- form_current_step = management_form.cleaned_data['current_step']
- if (form_current_step != self.steps.current and
- self.storage.current_step is not None):
- # form refreshed, change current step
- self.storage.current_step = form_current_step
- # get the form for the current step
- self.admin_view.model_form = self.get_step_form()
- def get_form_layout(self, __):
- attrs = self.get_form_list()[self.steps.current]
- if type(attrs) is dict and 'layout' in attrs:
- self.admin_view.form_layout = attrs['layout']
- else:
- self.admin_view.form_layout = None
- return __()
- def get_step_form(self, step=None):
- if step is None:
- step = self.steps.current
- attrs = self.get_form_list()[step]
- if type(attrs) in (list, tuple):
- return modelform_factory(self.model, form=forms.ModelForm,
- fields=attrs, formfield_callback=self.admin_view.formfield_for_dbfield)
- elif type(attrs) is dict:
- if attrs.get('fields', None):
- return modelform_factory(self.model, form=forms.ModelForm,
- fields=attrs['fields'], formfield_callback=self.admin_view.formfield_for_dbfield)
- if attrs.get('callback', None):
- callback = attrs['callback']
- if callable(callback):
- return callback(self)
- elif hasattr(self.admin_view, str(callback)):
- return getattr(self.admin_view, str(callback))(self)
- elif issubclass(attrs, forms.BaseForm):
- return attrs
- return None
- def get_step_form_obj(self, step=None):
- if step is None:
- step = self.steps.current
- form = self.get_step_form(step)
- return form(prefix=self._get_form_prefix(step),
- data=self.storage.get_step_data(step),
- files=self.storage.get_step_files(step))
- def get_form_datas(self, datas):
- datas['prefix'] = self._get_form_prefix()
- if self.request.method == 'POST' and self.wizard_goto_step:
- datas.update({
- 'data': self.storage.get_step_data(self.steps.current),
- 'files': self.storage.get_step_files(self.steps.current)
- })
- return datas
- def valid_forms(self, __):
- if self.wizard_goto_step:
- # goto get_response directly
- return False
- return __()
- def _done(self):
- cleaned_data = self.get_all_cleaned_data()
- exclude = self.admin_view.exclude
- opts = self.admin_view.opts
- instance = self.admin_view.org_obj or self.admin_view.model()
- file_field_list = []
- for f in opts.fields:
- if not f.editable or isinstance(f, models.AutoField) \
- or not f.name in cleaned_data:
- continue
- if exclude and f.name in exclude:
- continue
- # Defer saving file-type fields until after the other fields, so a
- # callable upload_to can use the values from other fields.
- if isinstance(f, models.FileField):
- file_field_list.append(f)
- else:
- f.save_form_data(instance, cleaned_data[f.name])
- for f in file_field_list:
- f.save_form_data(instance, cleaned_data[f.name])
- instance.save()
- for f in opts.many_to_many:
- if f.name in cleaned_data:
- f.save_form_data(instance, cleaned_data[f.name])
- self.admin_view.new_obj = instance
- def save_forms(self, __):
- # if the form is valid, store the cleaned data and files.
- form_obj = self.admin_view.form_obj
- self.storage.set_step_data(self.steps.current, form_obj.data)
- self.storage.set_step_files(self.steps.current, form_obj.files)
- # check if the current step is the last step
- if self.steps.current == self.steps.last:
- # no more steps, render done view
- return self._done()
- def save_models(self, __):
- pass
- def save_related(self, __):
- pass
- def get_context(self, context):
- context.update({
- "show_save": False,
- "show_save_as_new": False,
- "show_save_and_add_another": False,
- "show_save_and_continue": False,
- })
- return context
- def get_response(self, response):
- self.storage.update_response(response)
- return response
- def post_response(self, __):
- if self.steps.current == self.steps.last:
- self.storage.reset()
- return __()
- # change the stored current step
- self.storage.current_step = self.steps.next
- self.admin_view.form_obj = self.get_step_form_obj()
- self.admin_view.setup_forms()
- return self.admin_view.get_response()
- def get_all_cleaned_data(self):
- """
- Returns a merged dictionary of all step cleaned_data dictionaries.
- If a step contains a `FormSet`, the key will be prefixed with formset
- and contain a list of the formset cleaned_data dictionaries.
- """
- cleaned_data = {}
- for form_key, attrs in self.get_form_list().items():
- form_obj = self.get_step_form_obj(form_key)
- if form_obj.is_valid():
- if type(attrs) is dict and 'convert' in attrs:
- callback = attrs['convert']
- if callable(callback):
- callback(self, cleaned_data, form_obj)
- elif hasattr(self.admin_view, str(callback)):
- getattr(self.admin_view,
- str(callback))(self, cleaned_data, form_obj)
- elif isinstance(form_obj.cleaned_data, (tuple, list)):
- cleaned_data.update({
- 'formset-%s' % form_key: form_obj.cleaned_data
- })
- else:
- cleaned_data.update(form_obj.cleaned_data)
- return cleaned_data
- def get_cleaned_data_for_step(self, step):
- """
- Returns the cleaned data for a given `step`. Before returning the
- cleaned data, the stored values are being revalidated through the
- form. If the data doesn't validate, None will be returned.
- """
- if step in self.get_form_list():
- form_obj = self.get_step_form_obj(step)
- if form_obj.is_valid():
- return form_obj.cleaned_data
- return None
- def get_next_step(self, step=None):
- """
- Returns the next step after the given `step`. If no more steps are
- available, None will be returned. If the `step` argument is None, the
- current step will be determined automatically.
- """
- if step is None:
- step = self.steps.current
- obj = self.get_form_list().keys()
- if six.PY3:
- obj = [s for s in obj]
- key = obj.index(step) + 1
- if len(obj) > key:
- return obj[key]
- return None
- def get_prev_step(self, step=None):
- """
- Returns the previous step before the given `step`. If there are no
- steps available, None will be returned. If the `step` argument is
- None, the current step will be determined automatically.
- """
- if step is None:
- step = self.steps.current
- obj = self.get_form_list().keys()
- if six.PY3:
- obj = [s for s in obj]
- key = obj.index(step) - 1
- if key >= 0:
- return obj[key]
- return None
- def get_step_index(self, step=None):
- """
- Returns the index for the given `step` name. If no step is given,
- the current step will be used to get the index.
- """
- if step is None:
- step = self.steps.current
- obj = self.get_form_list().keys()
- if six.PY3:
- obj = [s for s in obj]
- return obj.index(step)
- def block_before_fieldsets(self, context, nodes):
- context = context.update(dict(self.storage.extra_data))
- context['wizard'] = {
- 'steps': self.steps,
- 'management_form': ManagementForm(prefix=self.prefix, initial={
- 'current_step': self.steps.current,
- }),
- }
- nodes.append(loader.render_to_string('xadmin/blocks/model_form.before_fieldsets.wizard.html', context))
- def block_submit_line(self, context, nodes):
- context = context.update(dict(self.storage.extra_data))
- context['wizard'] = {
- 'steps': self.steps
- }
- nodes.append(loader.render_to_string('xadmin/blocks/model_form.submit_line.wizard.html', context))
- site.register_plugin(WizardFormPlugin, ModelFormAdminView)
|