From 3dc568bb3a31b769e983fd9186f0f9763d559220 Mon Sep 17 00:00:00 2001 From: Dustin Lacewell Date: Wed, 16 Mar 2011 13:22:22 -0500 Subject: [PATCH 1/4] Added UniqueSessionFormWizard and UniqueSessionFormWizardProvider --- formwizard/forms.py | 91 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/formwizard/forms.py b/formwizard/forms.py index be11fe7..0fc3167 100644 --- a/formwizard/forms.py +++ b/formwizard/forms.py @@ -1,3 +1,5 @@ +import uuid + from django.utils.datastructures import SortedDict from django.shortcuts import render_to_response from django.template import RequestContext @@ -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 = '' + +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) + + + From 9f0b65050b28af2b375e401e71408aaca76f06a6 Mon Sep 17 00:00:00 2001 From: Dustin Lacewell Date: Wed, 16 Mar 2011 13:35:39 -0500 Subject: [PATCH 2/4] Added documentation for UniqueSessionFormWizard --- docs/index.rst | 1 + docs/uniquesessionformwizard.rst | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 docs/uniquesessionformwizard.rst diff --git a/docs/index.rst b/docs/index.rst index f6082f3..7a7009d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -21,6 +21,7 @@ Contents: gettingstarted namedformwizard + uniquesessionformwizard apireference Indices and tables diff --git a/docs/uniquesessionformwizard.rst b/docs/uniquesessionformwizard.rst new file mode 100644 index 0000000..b848696 --- /dev/null +++ b/docs/uniquesessionformwizard.rst @@ -0,0 +1,29 @@ +=============================== +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: + +.. code-block:: console + 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: + +.. code-block:: console +
+ {% 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. + From c5ad2f0b69afd32797adc2e3d457f51904939486 Mon Sep 17 00:00:00 2001 From: Dustin Lacewell Date: Wed, 16 Mar 2011 13:39:49 -0500 Subject: [PATCH 3/4] Fixed rest documentation syntax. --- docs/uniquesessionformwizard.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/uniquesessionformwizard.rst b/docs/uniquesessionformwizard.rst index b848696..88f3279 100644 --- a/docs/uniquesessionformwizard.rst +++ b/docs/uniquesessionformwizard.rst @@ -8,7 +8,8 @@ UniqueSessionFormWizard and UniqueSessionFormWizardProvider solve this by provid The URL configuration is very similar but note the difference: -.. code-block:: console +code-block:: + from formwizard import forms provider_instance = forms.UniqueSessionFormWizardProvider( UniqueSessionFormWizard, @@ -19,7 +20,7 @@ The URL configuration is very similar but note the difference: 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: -.. code-block:: console +code-block:: {% csrf_token %} {{ formwizard_uid|safe }} From bcf912f0c230ad413ee5ec4e4bda01852d4d79c9 Mon Sep 17 00:00:00 2001 From: Dustin Lacewell Date: Wed, 16 Mar 2011 13:41:03 -0500 Subject: [PATCH 4/4] Fixed rest documentation syntax. --- docs/uniquesessionformwizard.rst | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/docs/uniquesessionformwizard.rst b/docs/uniquesessionformwizard.rst index 88f3279..5fdaa59 100644 --- a/docs/uniquesessionformwizard.rst +++ b/docs/uniquesessionformwizard.rst @@ -6,9 +6,7 @@ A problem with the stock SessionFormWizard is that a user may only engage a sing 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: - -code-block:: +The URL configuration is very similar but note the difference:: from formwizard import forms provider_instance = forms.UniqueSessionFormWizardProvider( @@ -18,9 +16,8 @@ code-block:: 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: +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:: -code-block:: {% csrf_token %} {{ formwizard_uid|safe }}