"""Common abstract classes for forms."""
try:
    import cPickle as pickle
except ImportError:
    import pickle

from django import forms
from django.conf import settings
from django.contrib.formtools.wizard import FormWizard
from django.forms.forms import BoundField
from django.forms.formsets import BaseFormSet
from django.utils.hashcompat import md5_constructor

__all__ = ('security_hash', 'BoundFormWizard')

def security_hash(request, form, exclude=None, *args):
    """Calculates a security hash for the given Form/FormSet instance.

    This creates a list of the form field names/values in a deterministic
    order, pickles the result with the SECRET_KEY setting, then takes an md5
    hash of that.
    """

    data = []
    if exclude is None:
        exclude = ()
    if isinstance(form, BaseFormSet):
        for _form in form.forms + [form.management_form]:
            for bf in _form:
                value = bf.field.clean(bf.data) or ''
                if isinstance(value, basestring):
                    value = value.strip()
                data.append((bf.name, value))
    else:
        for bf in form:
            if bf.name in exclude:
                continue
            value = bf.field.clean(bf.data) or ''
            if isinstance(value, basestring):
                value = value.strip()
            data.append((bf.name, value))
    data.extend(args)
    data.append(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, pickle.HIGHEST_PROTOCOL)

    return md5_constructor(pickled).hexdigest()

class BoundFormWizard(FormWizard):
    """Render prev_fields as a list of bound form fields in the template
    context rather than raw html."""
    def security_hash(self, request, form):
        """Calculates the security hash for the given HttpRequest and
        Form/FormSet instances.

        Subclasses may want to take into account request-specific information,
        such as the IP address.
        """
        return security_hash(request, form)

    def render(self, form, request, step, context=None):
        "Renders the given Form object, returning an HttpResponse."
        old_data = request.POST
        prev_fields = []
        if old_data:
            for i in range(step):
                old_form = self.get_form(i, old_data)
                hash_name = 'hash_%s' % i
                if isinstance(old_form, BaseFormSet):
                    for _form in old_form.forms + [old_form.management_form]:
                        prev_fields.extend([bf for bf in _form])
                else:
                    prev_fields.extend([bf for bf in old_form])
                hash_field = forms.Field(initial=old_data.get(hash_name,
                    self.security_hash(request, old_form)))
                bf = BoundField(forms.Form(), hash_field, hash_name)
                prev_fields.append(bf)
        return self.render_template(request, form, prev_fields, step, context)

# vim: ai ts=4 sts=4 et sw=4
