app/django/contrib/formtools/preview.py
changeset 54 03e267d67478
child 323 ff1a9aa48cfd
equal deleted inserted replaced
53:57b4279d8c4e 54:03e267d67478
       
     1 """
       
     2 Formtools Preview application.
       
     3 """
       
     4 
       
     5 from django.conf import settings
       
     6 from django.http import Http404
       
     7 from django.shortcuts import render_to_response
       
     8 from django.template.context import RequestContext
       
     9 import cPickle as pickle
       
    10 import md5
       
    11 
       
    12 AUTO_ID = 'formtools_%s' # Each form here uses this as its auto_id parameter.
       
    13 
       
    14 class FormPreview(object):
       
    15     preview_template = 'formtools/preview.html'
       
    16     form_template = 'formtools/form.html'
       
    17 
       
    18     # METHODS SUBCLASSES SHOULDN'T OVERRIDE ###################################
       
    19 
       
    20     def __init__(self, form):
       
    21         # form should be a Form class, not an instance.
       
    22         self.form, self.state = form, {}
       
    23 
       
    24     def __call__(self, request, *args, **kwargs):
       
    25         stage = {'1': 'preview', '2': 'post'}.get(request.POST.get(self.unused_name('stage')), 'preview')
       
    26         self.parse_params(*args, **kwargs)
       
    27         try:
       
    28             method = getattr(self, stage + '_' + request.method.lower())
       
    29         except AttributeError:
       
    30             raise Http404
       
    31         return method(request)
       
    32 
       
    33     def unused_name(self, name):
       
    34         """
       
    35         Given a first-choice name, adds an underscore to the name until it
       
    36         reaches a name that isn't claimed by any field in the form.
       
    37 
       
    38         This is calculated rather than being hard-coded so that no field names
       
    39         are off-limits for use in the form.
       
    40         """
       
    41         while 1:
       
    42             try:
       
    43                 f = self.form.base_fields[name]
       
    44             except KeyError:
       
    45                 break # This field name isn't being used by the form.
       
    46             name += '_'
       
    47         return name
       
    48 
       
    49     def preview_get(self, request):
       
    50         "Displays the form"
       
    51         f = self.form(auto_id=AUTO_ID)
       
    52         return render_to_response(self.form_template,
       
    53             {'form': f, 'stage_field': self.unused_name('stage'), 'state': self.state},
       
    54             context_instance=RequestContext(request))
       
    55 
       
    56     def preview_post(self, request):
       
    57         "Validates the POST data. If valid, displays the preview page. Else, redisplays form."
       
    58         f = self.form(request.POST, auto_id=AUTO_ID)
       
    59         context = {'form': f, 'stage_field': self.unused_name('stage'), 'state': self.state}
       
    60         if f.is_valid():
       
    61             context['hash_field'] = self.unused_name('hash')
       
    62             context['hash_value'] = self.security_hash(request, f)
       
    63             return render_to_response(self.preview_template, context, context_instance=RequestContext(request))
       
    64         else:
       
    65             return render_to_response(self.form_template, context, context_instance=RequestContext(request))
       
    66 
       
    67     def post_post(self, request):
       
    68         "Validates the POST data. If valid, calls done(). Else, redisplays form."
       
    69         f = self.form(request.POST, auto_id=AUTO_ID)
       
    70         if f.is_valid():
       
    71             if self.security_hash(request, f) != request.POST.get(self.unused_name('hash')):
       
    72                 return self.failed_hash(request) # Security hash failed.
       
    73             return self.done(request, f.cleaned_data)
       
    74         else:
       
    75             return render_to_response(self.form_template,
       
    76                 {'form': f, 'stage_field': self.unused_name('stage'), 'state': self.state},
       
    77                 context_instance=RequestContext(request))
       
    78 
       
    79     # METHODS SUBCLASSES MIGHT OVERRIDE IF APPROPRIATE ########################
       
    80 
       
    81     def parse_params(self, *args, **kwargs):
       
    82         """
       
    83         Given captured args and kwargs from the URLconf, saves something in
       
    84         self.state and/or raises Http404 if necessary.
       
    85 
       
    86         For example, this URLconf captures a user_id variable:
       
    87 
       
    88             (r'^contact/(?P<user_id>\d{1,6})/$', MyFormPreview(MyForm)),
       
    89 
       
    90         In this case, the kwargs variable in parse_params would be
       
    91         {'user_id': 32} for a request to '/contact/32/'. You can use that
       
    92         user_id to make sure it's a valid user and/or save it for later, for
       
    93         use in done().
       
    94         """
       
    95         pass
       
    96 
       
    97     def security_hash(self, request, form):
       
    98         """
       
    99         Calculates the security hash for the given Form instance.
       
   100 
       
   101         This creates a list of the form field names/values in a deterministic
       
   102         order, pickles the result with the SECRET_KEY setting and takes an md5
       
   103         hash of that.
       
   104 
       
   105         Subclasses may want to take into account request-specific information
       
   106         such as the IP address.
       
   107         """
       
   108         data = [(bf.name, bf.data or '') for bf in form] + [settings.SECRET_KEY]
       
   109         # Use HIGHEST_PROTOCOL because it's the most efficient. It requires
       
   110         # Python 2.3, but Django requires 2.3 anyway, so that's OK.
       
   111         pickled = pickle.dumps(data, pickle.HIGHEST_PROTOCOL)
       
   112         return md5.new(pickled).hexdigest()
       
   113 
       
   114     def failed_hash(self, request):
       
   115         "Returns an HttpResponse in the case of an invalid security hash."
       
   116         return self.preview_post(request)
       
   117 
       
   118     # METHODS SUBCLASSES MUST OVERRIDE ########################################
       
   119 
       
   120     def done(self, request, cleaned_data):
       
   121         """
       
   122         Does something with the cleaned_data and returns an
       
   123         HttpResponseRedirect.
       
   124         """
       
   125         raise NotImplementedError('You must define a done() method on your %s subclass.' % self.__class__.__name__)