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