简体   繁体   中英

Django SessionWizardView current step missing from form_list after Ajax request

I've got a SessionWizardView process with conditional extra steps which at the end of the first step essentially asks 'do you want to add another person' so my condition is generated by checking the cleaned data for the previous step;

def function_factory(prev_step):
    """ Creates the functions for the condition dict controlling the additional
    entrant steps in the process.

    :param prev_step: step in the signup process to check
    :type prev_step: unicode
    :return: additional_entrant()
    :rtype:
    """
    def additional_entrant(wizard):
        """
        Checks the cleaned_data for the previous step to see if another entrant
        needs to be added
        """
        # try to get the cleaned data of prev_step
        cleaned_data = wizard.get_cleaned_data_for_step(prev_step) or {}

        # check if the field ``add_another_person`` was checked.
        return cleaned_data.get(u'add_another_person', False)

    return additional_entrant

def make_condition_stuff(extra_steps, last_step_before_repeat):
    cond_funcs = {}
    cond_dict = {}
    form_lst = [
        (u"main_entrant", EntrantForm),
    ]

    for x in range(last_step_before_repeat, extra_steps):
        key1 = u"{}_{}".format(ADDITIONAL_STEP_NAME, x)
        if x == 1:
            prev_step = u"main_entrant"
        else:
            prev_step = u"{}_{}".format(ADDITIONAL_STEP_NAME, x-1)
        cond_funcs[key1] = function_factory(prev_step)
        cond_dict[key1] = cond_funcs[key1]
        form_lst.append(
            (key1, AdditionalEntrantForm)
        )

    form_lst.append(
        (u"terms", TermsForm)
    )

    return cond_funcs, cond_dict, form_lst

last_step_before_extras = 1
extra_steps = settings.ADDITIONAL_ENTRANTS

cond_funcs, cond_dict, form_list = make_condition_stuff(
    extra_steps,
    last_step_before_extras
)

I've also got a dictionary which stores step data behind a key accessible via the session cookie, which also holds a list of the people's details entered by a user. After the first form, this list is rendered as a select box & on selection triggers an Ajax call to the SessionWizard with kwargs which triggers the call to a method which returns a JsonResponse ;

class SignupWizard(SessionWizardView):
    template_name = 'entrant/wizard_form.html'
    form_list = form_list
    condition_dict = cond_dict
    model = Entrant
    main_entrant = None
    data_dict = dict()

    def get_data(self, source_step, step):
        session_data_dict = self.get_session_data_dict()
        try:
            data = session_data_dict[source_step].copy()
            data['event'] = self.current_event.id
            for key in data.iterkeys():
                if step not in key:
                    newkey = u'{}-{}'.format(step, key)
                    data[newkey] = data[key]
                    del data[key]
        except (KeyError, RuntimeError):
            data = dict()
            data['error'] = (
                u'There was a problem retrieving the data you requested. '
                u'Please resubmit the form if you would like to try again.'
            )

        response = JsonResponse(data)
        return response

    def dispatch(self, request, *args, **kwargs):
        response = super(SignupWizard, self).dispatch(
            request, *args, **kwargs
        )
        if 'get_data' in kwargs:
            data_id = kwargs['get_data']
            step = kwargs['step']
            response = self.get_data(data_id, step)

        # update the response (e.g. adding cookies)
        self.storage.update_response(response)
        return response

    def process_step(self, form):
        form_data = self.get_form_step_data(form)
        current_step = self.storage.current_step or ''
        session_data_dict = self.get_session_data_dict()

        if current_step in session_data_dict:
            # Always replace the existing data for a step.
            session_data_dict.pop(current_step)

        if not isinstance(form, TermsForm):
            entrant_data = dict()
            fields_to_remove = [
                'email', 'confirm_email', 'password',
                'confirm_password', 'csrfmiddlewaretoken'
            ]
            for k, v in form_data.iteritems():
                entrant_data[k] = v
            for field in fields_to_remove:
                if '{}-{}'.format(current_step, field) in entrant_data:
                    entrant_data.pop('{}-{}'.format(current_step, field))
                if '{}'.format(field) in entrant_data:
                    entrant_data.pop('{}'.format(field))

            for k in entrant_data.iterkeys():
                new_key = re.sub('{}-'.format(current_step), u'', k)
                entrant_data[new_key] = entrant_data.pop(k)

            session_data_dict[current_step] = entrant_data
            done = False
            for i, data in enumerate(session_data_dict['data_list']):
                if data[0] == current_step:
                    session_data_dict['data_list'][i] = (
                        current_step, u'{} {}'.format(
                            entrant_data['first_name'],
                            entrant_data['last_name']
                        )
                    )
                    done = True

            if not done:
                session_data_dict['data_list'].append(
                    (
                        current_step, u'{} {}'.format(
                            entrant_data['first_name'],
                            entrant_data['last_name']
                        )
                    )
                )

        return form_data

If you step through the form without triggering the Ajax call then the form submits and the condition dict behaves as expected. But if the Ajax is triggered and the data is returned to the form, once you submit the form the session data appears to have gone. Is there a way to change the way I've got this setup so that get_data() can return the data to the page, without destroying the session?

I've got the SESSION_ENGINE set to cached_db on a dev server but I've got an issue whereby when you submit the first conditional form and the system calls get_next_step() followed by get_form_list() and the condition check no longer returns that first conditional form so I'm left with my default form list and a ValueError raised because current_step is no longer part of form_list .

So, to recap, I step through my first form, trigger the first conditional form using the 'add_another_person' field which renders the form as expected at which point form_list looks like this;

form_list   
    u'main_entrant' <class 'online_entry.forms.EntrantForm'>
    u'additional_entrant_1' <class 'online_entry.forms.EntrantForm'>
    u'terms' <class 'online_entry.forms.TermsForm'>

But as soon as additional_entrant_1 triggers the Ajax method, then is submitted, the form_list runs through the condition dict & looks like this;

form_list   
    u'main_entrant' <class 'online_entry.forms.EntrantForm'>
    u'terms' <class 'online_entry.forms.TermsForm'>

Could this be an issue with the session storage or the session becoming invalid?

I always overlook the simple explanation.

The get() request of the SessionWizardView resets the storage, and the Ajax call I was making hit the view as a get request, resetting the storage, but also passing back my information.

So with a simple override of the get() method I've resolved this;

def get(self, request, *args, **kwargs):
    if 'get_data' in kwargs:
        data_id = kwargs['get_data']
        step = kwargs['step']
        response = self.get_data(data_id, step)
    else:
        self.storage.reset()

        # reset the current step to the first step.
        self.storage.current_step = self.steps.first
        response = self.render(self.get_form())
    return response

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM