Skip to content
This repository has been archived by the owner on Dec 23, 2018. It is now read-only.

Multiple formwizard instances per session #10

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Contents:

gettingstarted
namedformwizard
uniquesessionformwizard
apireference

Indices and tables
Expand Down
27 changes: 27 additions & 0 deletions docs/uniquesessionformwizard.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
===============================
django-formwizard UniqueSessionFormWizard Documentation
===============================

A problem with the stock SessionFormWizard is that a user may only engage a single wizard at anyone time. If during the processes of filling out a SessionFormWizard the user opens a secondary browser-tab or window and accesses the same form, the state of the first form is destroyed.

UniqueSessionFormWizard and UniqueSessionFormWizardProvider solve this by providing each new form a unique identifier. Instead of associating a URL with a single *FormWizard we instead point it as UniqueSessionFormWizardProvider. The provider will maintain a registry of active forms and assuming each POST request contains the corresponding UID the user will now be able to engage multiple forms simultaneously.

The URL configuration is very similar but note the difference::

from formwizard import forms
provider_instance = forms.UniqueSessionFormWizardProvider(
UniqueSessionFormWizard,
[Form1, Form2, ModelForm1])

urlpatterns = patterns('',
url(r'^$', provider_instance),)

First we initialize a provider with the FormWizard class we wish to use and of course the list of forms the wizard will use. We point the URL at the provider. Any GET request to this URL will initialize a new unique form wizard. Subsequent POST requests should include the session key which is provided to the template::

<form action="." method="post">
{% csrf_token %}
{{ formwizard_uid|safe }}
...

And that's pretty much all there is to it. Currently there is no automation of session cleanup so make sure to clean the sessions from time to time.

91 changes: 91 additions & 0 deletions formwizard/forms.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import uuid

from django.utils.datastructures import SortedDict
from django.shortcuts import render_to_response
from django.template import RequestContext
Expand Down Expand Up @@ -640,3 +642,92 @@ class NamedUrlCookieFormWizard(NamedUrlFormWizard):
def __init__(self, *args, **kwargs):
super(NamedUrlCookieFormWizard, self).__init__(
'formwizard.storage.cookie.CookieStorage', *args, **kwargs)

HIDDEN_SESSION_ID = '<input type="hidden" name="formwizard_uid" value="%s" />'

class UniqueSessionFormWizard(SessionFormWizard):
"""
A FormWizard that takes a name for providing unique
storage state. Provide this or a subclass to a
UniqueSessionFormWizardProvider to serve unique form
wizards.
"""
def __init__(self, name, *args, **kwargs):
self._name = name
super(UniqueSessionFormWizard, self).__init__(*args, **kwargs)

def get_wizard_name(self):
return self._name

def get_template_context(self, request, storage, form):
"""
Update the template context with the unique session key.
"""
# render hidden input to track session uid
hidden_id = HIDDEN_SESSION_ID % self.get_wizard_name()
context = super(UniqueSessionFormWizard, self).get_template_context(
request, storage, form)
context.update({
'formwizard_uid': hidden_id})
return context

class UniqueSessionFormWizardProvider(object):
"""
This class provides unique session-based FormWizards. Each
time a GET request is made a new UniqueSessionFormWizard is
created and a unique session-id is generated. The client
must render {{ formwizard_uid|safe }} inside of the form to
be used. This allows a single user to engage multiple wizards
at the same time.

The provided class must be, or be a subclass of,
UniqueSessionFormWizard. All other arguments are passed
directly to the wizard class upon instantiation.

Example:

from formwizard import forms
provider_instance = forms.UniqueSessionFormWizardProvider(
UniqueSessionFormWizard,
[Form1, Form2, ModelForm1])

urlpatterns = patterns('',
url(r'^$', provider_instance),)

"""
def __init__(self, wizclass, *args, **kwargs):
self._class = wizclass
self._args = args
self._kwargs = kwargs
self._registry = {}

def __call__(self, request, *args, **kwargs):
return self.process_request(request, *args, **kwargs)

def process_request(self, request, *args, **kwargs):
if request.method == "GET":
return self.process_get_request(request, *args, **kwargs)
else:
return self.process_post_request(request, *args, **kwargs)

def process_get_request(self, request, *args, **kwargs):
# generate unique session-id
new_id = str(uuid.uuid4())
# instantiate the FormWizard
new_wizard = self._class(new_id, *self._args, **self._kwargs)
self._registry[new_id] = new_wizard
# return the wizard's view result
return new_wizard(request, *args, **kwargs)

def process_post_request(self, request, *args, **kwargs):
# get the unique session id
uid = request.POST.get('formwizard_uid', None)
if uid:
# lookup associated NamedSessionFormWizard
wizard = self._registry.get(uid, None)
if wizard:
# return the wizard's view result
return wizard(request, *args, **kwargs)