summaryrefslogtreecommitdiffstats
path: root/google_appengine/lib/django/django/contrib/formtools/preview.py
diff options
context:
space:
mode:
Diffstat (limited to 'google_appengine/lib/django/django/contrib/formtools/preview.py')
-rwxr-xr-xgoogle_appengine/lib/django/django/contrib/formtools/preview.py165
1 files changed, 165 insertions, 0 deletions
diff --git a/google_appengine/lib/django/django/contrib/formtools/preview.py b/google_appengine/lib/django/django/contrib/formtools/preview.py
new file mode 100755
index 0000000..daecba7
--- /dev/null
+++ b/google_appengine/lib/django/django/contrib/formtools/preview.py
@@ -0,0 +1,165 @@
+"""
+Formtools Preview application.
+
+This is an abstraction of the following workflow:
+
+ "Display an HTML form, force a preview, then do something with the submission."
+
+Given a django.newforms.Form object that you define, this takes care of the
+following:
+
+ * Displays the form as HTML on a Web page.
+ * Validates the form data once it's submitted via POST.
+ * If it's valid, displays a preview page.
+ * If it's not valid, redisplays the form with error messages.
+ * At the preview page, if the preview confirmation button is pressed, calls
+ a hook that you define -- a done() method.
+
+The framework enforces the required preview by passing a shared-secret hash to
+the preview page. If somebody tweaks the form parameters on the preview page,
+the form submission will fail the hash comparison test.
+
+Usage
+=====
+
+Subclass FormPreview and define a done() method:
+
+ def done(self, request, clean_data):
+ # ...
+
+This method takes an HttpRequest object and a dictionary of the form data after
+it has been validated and cleaned. It should return an HttpResponseRedirect.
+
+Then, just instantiate your FormPreview subclass by passing it a Form class,
+and pass that to your URLconf, like so:
+
+ (r'^post/$', MyFormPreview(MyForm)),
+
+The FormPreview class has a few other hooks. See the docstrings in the source
+code below.
+
+The framework also uses two templates: 'formtools/preview.html' and
+'formtools/form.html'. You can override these by setting 'preview_template' and
+'form_template' attributes on your FormPreview subclass. See
+django/contrib/formtools/templates for the default templates.
+"""
+
+from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
+from django.http import Http404
+from django.shortcuts import render_to_response
+from django.template.context import RequestContext
+import cPickle as pickle
+import md5
+
+AUTO_ID = 'formtools_%s' # Each form here uses this as its auto_id parameter.
+
+class FormPreview(object):
+ preview_template = 'formtools/preview.html'
+ form_template = 'formtools/form.html'
+
+ # METHODS SUBCLASSES SHOULDN'T OVERRIDE ###################################
+
+ def __init__(self, form):
+ # form should be a Form class, not an instance.
+ self.form, self.state = form, {}
+
+ def __call__(self, request, *args, **kwargs):
+ stage = {'1': 'preview', '2': 'post'}.get(request.POST.get(self.unused_name('stage')), 'preview')
+ self.parse_params(*args, **kwargs)
+ try:
+ method = getattr(self, stage + '_' + request.method.lower())
+ except AttributeError:
+ raise Http404
+ return method(request)
+
+ def unused_name(self, name):
+ """
+ Given a first-choice name, adds an underscore to the name until it
+ reaches a name that isn't claimed by any field in the form.
+
+ This is calculated rather than being hard-coded so that no field names
+ are off-limits for use in the form.
+ """
+ while 1:
+ try:
+ f = self.form.fields[name]
+ except KeyError:
+ break # This field name isn't being used by the form.
+ name += '_'
+ return name
+
+ def preview_get(self, request):
+ "Displays the form"
+ f = self.form(auto_id=AUTO_ID)
+ return render_to_response(self.form_template,
+ {'form': f, 'stage_field': self.unused_name('stage'), 'state': self.state},
+ context_instance=RequestContext(request))
+
+ def preview_post(self, request):
+ "Validates the POST data. If valid, displays the preview page. Else, redisplays form."
+ f = self.form(request.POST, auto_id=AUTO_ID)
+ context = {'form': f, 'stage_field': self.unused_name('stage'), 'state': self.state}
+ if f.is_valid():
+ context['hash_field'] = self.unused_name('hash')
+ context['hash_value'] = self.security_hash(request, f)
+ return render_to_response(self.preview_template, context, context_instance=RequestContext(request))
+ else:
+ return render_to_response(self.form_template, context, context_instance=RequestContext(request))
+
+ def post_post(self, request):
+ "Validates the POST data. If valid, calls done(). Else, redisplays form."
+ f = self.form(request.POST, auto_id=AUTO_ID)
+ if f.is_valid():
+ if self.security_hash(request, f) != request.POST.get(self.unused_name('hash')):
+ return self.failed_hash(request) # Security hash failed.
+ return self.done(request, f.clean_data)
+ else:
+ return render_to_response(self.form_template,
+ {'form': f, 'stage_field': self.unused_name('stage'), 'state': self.state},
+ context_instance=RequestContext(request))
+
+ # METHODS SUBCLASSES MIGHT OVERRIDE IF APPROPRIATE ########################
+
+ def parse_params(self, *args, **kwargs):
+ """
+ Given captured args and kwargs from the URLconf, saves something in
+ self.state and/or raises Http404 if necessary.
+
+ For example, this URLconf captures a user_id variable:
+
+ (r'^contact/(?P<user_id>\d{1,6})/$', MyFormPreview(MyForm)),
+
+ In this case, the kwargs variable in parse_params would be
+ {'user_id': 32} for a request to '/contact/32/'. You can use that
+ user_id to make sure it's a valid user and/or save it for later, for
+ use in done().
+ """
+ pass
+
+ def security_hash(self, request, form):
+ """
+ Calculates the security hash for the given Form instance.
+
+ This creates a list of the form field names/values in a deterministic
+ order, pickles the result with the SECRET_KEY setting and takes an md5
+ hash of that.
+
+ Subclasses may want to take into account request-specific information
+ such as the IP address.
+ """
+ data = [(bf.name, bf.data) for bf in form] + [settings.SECRET_KEY]
+ # Use HIGHEST_PROTOCOL because it's the most efficient. It requires
+ # Python 2.3, but Django requires 2.3 anyway, so that's OK.
+ pickled = pickle.dumps(data, protocol=pickle.HIGHEST_PROTOCOL)
+ return md5.new(pickled).hexdigest()
+
+ def failed_hash(self, request):
+ "Returns an HttpResponse in the case of an invalid security hash."
+ return self.preview_post(request)
+
+ # METHODS SUBCLASSES MUST OVERRIDE ########################################
+
+ def done(self, request, clean_data):
+ "Does something with the clean_data and returns an HttpResponseRedirect."
+ raise NotImplementedError('You must define a done() method on your %s subclass.' % self.__class__.__name__)