import random
from django import forms
from django.db import models
from generic_confirmation.models import DeferredAction
from generic_confirmation.main import LONG
from generic_confirmation import signals


class DeferredFormMixIn(object):
    """
    This is a MixIn class, so that you can also build deferred forms
    from already existing modified ModelForm classes.
    If you build your form from scratch you want to use the ``DeferredForm``
    class.

    """

    token_format = LONG

    def _gen_token(self, format=None, step=0):
        """
        generates a unique (in terms of DeferredAction objects) token based
        on the format tuple in the form of (alphabet, length).
        """
        if format is None:
            format = self.token_format
        chars, length = format
        token = u''.join([random.choice(chars) for i in range(length)])
        try:
            DeferredAction.objects.get(token=token)
        except DeferredAction.DoesNotExist:
            return token
        if step > 9:
            raise Exception("10 attempts to generate a unique token failed.")
        return self._gen_token(format=format, step=step+1)

    def save(self, user=None, valid_until=None, description=None, **kwargs):
        """
        Replaces the ModelForm save method with our own to defer the action
        by storing the data in the db.
        Returns a unique token which is needed to confirm the action and
        resume it.

        Parameters:

        * user (User object): the user requesting the change
        * valid_until (datetime): until when the action can be confirmed
        * description (string): saved with the action object

        """
        if not self.is_valid():
            raise Exception("only call save() on a form after calling is_valid().")

        form_class_name = u"%s.%s" % (self.__class__.__module__,
                                      self.__class__.__name__)

        # we save the uncleaned data here, because form.full_clean() will
        # alter the data in cleaned_data and a second run with cleaned_data as
        # form input will fail for foreignkeys and manytomany fields.
        # additionally, storing the original input is a bit safer, because
        # this is only data which was transfered over http, so we won't
        # get any pickle errors here
        data = {'form_class':form_class_name, 'form_input':self.data,
                'token':self._gen_token(), 'form_prefix': self.prefix,
                'valid_until': valid_until, 'description': description,
                'user': user}

        defer = DeferredAction.objects.create(**data)

        if self.instance is not None:
            # this extra step makes sure that ModelForms for editing and for
            # creating objects both work.
            defer.instance_object = self.instance
            defer.save()

        # inform anyone else that confirmation is requested
        signals.confirmation_required.send(sender=self._meta.model,
                                        instance=defer, user=user)

        if hasattr(self, 'send_notification') and callable(self.send_notification):
            self.send_notification(user, instance=defer)

        return defer.token

    def save_original(self, *args, **kwargs):
        """
        triggr the original ModelForm save
        """
        return forms.ModelForm.save(self, *args, **kwargs)


class DeferredForm(DeferredFormMixIn, forms.ModelForm):
    """
    Inherit from this form to get a ModelForm which will
    automatically defer it's save method until the action
    is confirmed.

    """
    pass


class ConfirmationForm(forms.Form):
    """
    Form to use in views to confirm an action with a token.
    Makes sure the token exists and on calling ``save()``
    will resume the defered action.

    """
    token = forms.CharField(required=True)

    def clean_token(self):
        try:
            obj = DeferredAction.objects.get(token=self.cleaned_data['token'])
            return self.cleaned_data['token']
        except DeferredAction.DoesNotExist:
            raise forms.ValidationError(u"wrong token") #FIXME: i18n

    def save(self):
        return DeferredAction.objects.confirm(token=self.cleaned_data['token'])

import random
from django import forms
from django.db import models
from generic_confirmation.models import DeferredAction
from generic_confirmation.main import LONG
from generic_confirmation import signals


class DeferredFormMixIn(object):
    """
    This is a MixIn class, so that you can also build deferred forms
    from already existing modified ModelForm classes.
    If you build your form from scratch you want to use the ``DeferredForm``
    class.

    """

    token_format = LONG

    def _gen_token(self, format=None, step=0):
        """
        generates a unique (in terms of DeferredAction objects) token based
        on the format tuple in the form of (alphabet, length).
        """
        if format is None:
            format = self.token_format
        chars, length = format
        token = u''.join([random.choice(chars) for i in range(length)])
        try:
            DeferredAction.objects.get(token=token)
        except DeferredAction.DoesNotExist:
            return token
        if step > 9:
            raise Exception("10 attempts to generate a unique token failed.")
        return self._gen_token(format=format, step=step+1)

    def save(self, user=None, valid_until=None, description=None, **kwargs):
        """
        Replaces the ModelForm save method with our own to defer the action
        by storing the data in the db.
        Returns a unique token which is needed to confirm the action and
        resume it.

        Parameters:

        * user (User object): the user requesting the change
        * valid_until (datetime): until when the action can be confirmed
        * description (string): saved with the action object

        """
        if not self.is_valid():
            raise Exception("only call save() on a form after calling is_valid().")

        form_class_name = u"%s.%s" % (self.__class__.__module__,
                                      self.__class__.__name__)

        # we save the uncleaned data here, because form.full_clean() will
        # alter the data in cleaned_data and a second run with cleaned_data as
        # form input will fail for foreignkeys and manytomany fields.
        # additionally, storing the original input is a bit safer, because
        # this is only data which was transfered over http, so we won't
        # get any pickle errors here
        data = {'form_class':form_class_name, 'form_input':self.data,
                'token':self._gen_token(), 'form_prefix': self.prefix,
                'valid_until': valid_until, 'description': description,
                'user': user}

        defer = DeferredAction.objects.create(**data)

        if self.instance is not None:
            # this extra step makes sure that ModelForms for editing and for
            # creating objects both work.
            defer.instance_object = self.instance
            defer.save()

        # inform anyone else that confirmation is requested
        signals.confirmation_required.send(sender=self._meta.model,
                                        instance=defer, user=user)

        if hasattr(self, 'send_notification') and callable(self.send_notification):
            self.send_notification(user, instance=defer)

        return defer.token

    def save_original(self, *args, **kwargs):
        """
        triggr the original ModelForm save
        """
        return forms.ModelForm.save(self, *args, **kwargs)


class DeferredForm(DeferredFormMixIn, forms.ModelForm):
    """
    Inherit from this form to get a ModelForm which will
    automatically defer it's save method until the action
    is confirmed.

    """
    pass


class ConfirmationForm(forms.Form):
    """
    Form to use in views to confirm an action with a token.
    Makes sure the token exists and on calling ``save()``
    will resume the defered action.

    """
    token = forms.CharField(required=True)

    def clean_token(self):
        try:
            obj = DeferredAction.objects.get(token=self.cleaned_data['token'])
            return self.cleaned_data['token']
        except DeferredAction.DoesNotExist:
            raise forms.ValidationError(u"wrong token") #FIXME: i18n

    def save(self):
        return DeferredAction.objects.confirm(token=self.cleaned_data['token'])

This is a MixIn class, so that you can also build deferred forms     from already existing modified ModelForm classes.     If you build your form from scratch you want to use the ``DeferredForm``     class. generates a unique (in terms of DeferredAction objects) token based         on the format tuple in the form of (alphabet, length). Replaces the ModelForm save method with our own to defer the action         by storing the data in the db.         Returns a unique token which is needed to confirm the action and         resume it.          Parameters:          * user (User object): the user requesting the change         * valid_until (datetime): until when the action can be confirmed         * description (string): saved with the action object # we save the uncleaned data here, because form.full_clean() will # alter the data in cleaned_data and a second run with cleaned_data as # form input will fail for foreignkeys and manytomany fields. # additionally, storing the original input is a bit safer, because # this is only data which was transfered over http, so we won't # get any pickle errors here # this extra step makes sure that ModelForms for editing and for # creating objects both work. # inform anyone else that confirmation is requested triggr the original ModelForm save Inherit from this form to get a ModelForm which will     automatically defer it's save method until the action     is confirmed. Form to use in views to confirm an action with a token.     Makes sure the token exists and on calling ``save()``     will resume the defered action. #FIXME: i18n