Complex Form Preview

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
preview.py
==========

"""Complex form preview app"""

# python imports
import cPickle as pickle

# django imports
from django.forms import Form
from django.forms.formsets import BaseFormSet
from django.conf import settings
from django.utils.hashcompat import md5_constructor
from django.http import Http404
from django.shortcuts import render_to_response
from django.template.context import RequestContext


class ComplexFormPreview(object):
    """Like a form preview view, but for many forms and formsets"""
    # override these - they don't actually work!
    preview_template = 'formtools/preview.html'
    form_template = 'formtools/form.html'

    def __init__(self):
        self.state = {}

    def __call__(self, request, *args, **kwargs):
        # because we use prefixes for all forms, we can avoid doing all
        # of the unused_name junk
        stage = {'1': 'preview', '2': 'post'}.get(
                request.POST.get('stage'), 'preview')
        self.parse_params(*args, **kwargs)
        try:
            method = getattr(self, stage + '_' + request.method.lower())
        except AttributeError:
            raise Http404
        return method(request)

    def init_forms(self, **kwargs):
        """Dynamic form initilization

        Save all forms to the self.state dictionary - this will become the
        context for the templates.

        """
        raise NotImplementedError(
                'You must define an init_forms() method on your %s subclass.' \
                        % self.__class__.__name__)

    def all_valid(self):
        """Check that all forms and formsets are valid"""
        # do not short circut the evaluations so that all forms are
        # evaluated and all errors will be shown
        flag = True
        keys = sorted(self.state.keys())
        for key in keys:
            value = self.state[key]
            if isinstance(value, Form):
                form = value
                flag = flag and form.is_valid()
            elif isinstance(value, BaseFormSet):
                formset = value
                flag = flag and formset.is_valid()
        return flag

    def preview_get(self, request):
        """Display the form"""
        self.init_forms()
        self.state['stage_field'] = 'stage'
        return render_to_response(self.form_template, self.state,
                context_instance=RequestContext(request))

    def preview_post(self, request):
        """Redisplay the forms with errors, or show a preview.

        When the form is POSTed, bind the data and validate all forms.
        If valid, display the preview page, else redisplay the forms.

        """
        self.init_forms(data=request.POST)
        self.state['stage_field'] = 'stage'
        if self.all_valid():
            self.state['hash_field'] = 'hash'
            self.state['hash_value'] = self.security_hash()
            return render_to_response(self.preview_template, self.state,
                    context_instance=RequestContext(request))
        else:
            return render_to_response(self.form_template, self.state,
                    context_instance=RequestContext(request))

    def post_post(self, request):
        """Validate the form and call done or redisplay if invalid"""
        self.init_forms(data=request.POST)
        if self.all_valid():
            if self.security_hash() != request.POST.get('hash'):
                return self.preview_post(request)
            return self.done(request)
        else:
            # there were errors on the modified form
            self.state['stage_field'] = 'stage'
            return render_to_response(self.form_template, self.state,
                    context_instance=RequestContext(request))

    def parse_params(self, *args, **kwargs):
        """Handle captured args/kwargs from the URLconf

        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):
        """Calculate an md5 hash for all of form(set)s"""
        data = [settings.SECRET_KEY]
        # ensure that we always process self.state in the same order
        keys = sorted(self.state.keys())
        for key in keys:
            value = self.state[key]
            if isinstance(value, Form):
                # for each form in self.state add (field, value, )
                # tuples to data
                form = value
                data.extend([(bound_field.name,
                        bound_field.field.clean(bound_field.data) or '', )
                        for bound_field in form])
            elif isinstance(value, BaseFormSet):
                # for each formset in self.state, for each form in a
                # formset, add (field, value, ) tuples to data
                formset = value
                for form in formset.forms:
                    data.extend([(bound_field.name,
                            bound_field.field.clean(bound_field.data) or '')
                            for bound_field in form])
        # pickle the data and hash it to get a shared secret
        pickled = pickle.dumps(data, pickle.HIGHEST_PROTOCOL)
        return md5_constructor(pickled).hexdigest()

    def done(self, request):
        """Save the results of the form and return an HttpResponseRedirect"""
        raise NotImplementedError(
                'You must define a done() method on your %s subclass.' % \
                self.__class__.__name__)


views.py
========

class HIFormPreview(ComplexFormPreview):
    """View with preview capabilities for the HI/sequence form"""
    preview_template = 'sequence/hi_form_preview.html'
    form_template = 'sequence/hi_form.html'

    def parse_params(self, *args, **kwargs):
        """Handle captured args/kwargs from the URLconf"""
        # get the selected HI test
        try:
            self.state['hi_test'] = HITest.objects.get(id=kwargs['test_id'])
        except HITest.DoesNotExist:
            raise Http404("Invalid HI test id: '%s'" % test_id)

        # get a list of segments that this form will be used for (A or B)
        subtypes = self.state['hi_test'].subtype.all()
        if len(subtypes) != 1:
            raise Http404('This form cannot be used for hi_tests of type %s' %
                    hi_test.get_subtype)
        self.state['subtype'] = subtypes[0]

    def init_forms(self, **kwargs):
        """Dynamic form init"""
        hi_test = self.state['hi_test']
        subtype = self.state['subtype']
        ss_formset = SequenceHITestFormSet(
                subtype=subtype,
                hi_test=hi_test,
                prefix='seq_specimen',
                **kwargs)
        gene_form = SequenceGeneForm(subtype=subtype, prefix='gene', **kwargs)
        self.state['ss_formset'] = ss_formset
        self.state['gene_form'] = gene_form

    def done(self, request):
        """save the results of the form and return an HttpResponseRedirect"""
        self.state['ss_formset'].save(self.state['gene_form'])
        return HttpResponseRedirect('/flu/sequence/pending/')


@login_required
def hi_form(request, *args, **kwargs):
    """A thin wrapper for an HIFormPreview instance"""
    # The wrapper is necessary to allow the entire class-based view
    # (derived from ComplexFormPreview) to be wrapped in a
    # login_required.  It would be possible to decorate individual bound
    # view functions of the class, but would end up being more work than
    # using a function and decorator
    view = HIFormPreview()
    return view(request, *args, **kwargs)

More like this

  1. Complex Formsets, Redux by smagala 4 years, 1 month ago
  2. Complex Formsets by smagala 5 years, 3 months ago
  3. Ajax form with jQuery by Donn 5 years, 8 months ago
  4. SectionedForm by marinho 5 years, 10 months ago
  5. FieldAccessForm (per-field user access for forms derived from models) by Killarny 5 years, 6 months ago

Comments

(Forgotten your password?)