Abstracted email resetting during the email change process onto the AccountForm....
[philo.git] / philo / contrib / waldo / forms.py
1 from datetime import date
2
3 from django import forms
4 from django.conf import settings
5 from django.contrib.auth import authenticate
6 from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
7 from django.contrib.auth.models import User
8 from django.core.exceptions import ValidationError
9 from django.utils.translation import ugettext_lazy as _
10
11 from philo.contrib.waldo.tokens import REGISTRATION_TIMEOUT_DAYS
12
13
14 class EmailInput(forms.TextInput):
15         """Displays an HTML5 email input on browsers which support it and a normal text input on other browsers."""
16         input_type = 'email'
17
18
19 class RegistrationForm(UserCreationForm):
20         """
21         Handles user registration. If :mod:`recaptcha_django` is installed on the system and :class:`recaptcha_django.middleware.ReCaptchaMiddleware` is in :setting:`settings.MIDDLEWARE_CLASSES`, then a recaptcha field will automatically be added to the registration form.
22         
23         .. seealso:: `recaptcha-django <http://code.google.com/p/recaptcha-django/>`_
24         
25         """
26         #: An :class:`EmailField` using the :class:`EmailInput` widget.
27         email = forms.EmailField(widget=EmailInput)
28         try:
29                 from recaptcha_django import ReCaptchaField
30         except ImportError:
31                 pass
32         else:
33                 if 'recaptcha_django.middleware.ReCaptchaMiddleware' in settings.MIDDLEWARE_CLASSES:
34                         recaptcha = ReCaptchaField()
35         
36         def clean_username(self):
37                 username = self.cleaned_data['username']
38                 
39                 # Trivial case: if the username doesn't exist, go for it!
40                 try:
41                         user = User.objects.get(username=username)
42                 except User.DoesNotExist:
43                         return username
44                 
45                 if not user.is_active and (date.today() - user.date_joined.date()).days > REGISTRATION_TIMEOUT_DAYS and user.last_login == user.date_joined:
46                         # Then this is a user who has not confirmed their registration and whose time is up. Delete the old user and return the username.
47                         user.delete()
48                         return username
49                 
50                 raise ValidationError(_("A user with that username already exists."))
51         
52         def clean_email(self):
53                 if User.objects.filter(email__iexact=self.cleaned_data['email']):
54                         raise ValidationError(_('This email is already in use. Please supply a different email address'))
55                 return self.cleaned_data['email']
56         
57         def save(self):
58                 username = self.cleaned_data['username']
59                 email = self.cleaned_data['email']
60                 password = self.cleaned_data['password1']
61                 new_user = User.objects.create_user(username, email, password)
62                 new_user.is_active = False
63                 new_user.save()
64                 return new_user
65
66
67 class UserAccountForm(forms.ModelForm):
68         """Handles a user's account - by default, :attr:`auth.User.first_name`, :attr:`auth.User.last_name`, :attr:`auth.User.email`."""
69         first_name = User._meta.get_field('first_name').formfield(required=True)
70         last_name = User._meta.get_field('last_name').formfield(required=True)
71         email = User._meta.get_field('email').formfield(required=True, widget=EmailInput)
72         
73         def __init__(self, user, *args, **kwargs):
74                 kwargs['instance'] = user
75                 super(UserAccountForm, self).__init__(*args, **kwargs)
76         
77         def email_changed(self):
78                 """Returns ``True`` if the email field changed value and ``False`` if it did not, or if there is no email field on the form. This method must be supplied by account forms used with :mod:`~philo.contrib.waldo`."""
79                 return 'email' in self.changed_data
80         
81         def reset_email(self):
82                 """
83                 ModelForms modify their instances in-place during :meth:`_post_clean`; this method resets the email value to its initial state and returns the altered value. This is a method on the form to allow unusual behavior such as storing email on a :class:`UserProfile`.
84                 
85                 """
86                 email = self.instance.email
87                 self.instance.email = self.initial['email']
88                 self.cleaned_data.pop('email')
89                 return email
90         
91         @classmethod
92         def set_email(cls, user, email):
93                 """
94                 Given a valid instance and an email address, correctly set the email address for that instance and save the changes. This is a class method in order to allow unusual behavior such as storing email on a :class:`UserProfile`.
95                 
96                 """
97                 user.email = email
98                 user.save()
99                 
100         
101         class Meta:
102                 model = User
103                 fields = ('first_name', 'last_name', 'email')
104
105
106 class WaldoAuthenticationForm(AuthenticationForm):
107         """Handles user authentication. Checks that the user has not mistakenly entered their email address (like :class:`django.contrib.admin.forms.AdminAuthenticationForm`) but does not require that the user be staff."""
108         ERROR_MESSAGE = _("Please enter a correct username and password. Note that both fields are case-sensitive.")
109         
110         def clean(self):
111                 username = self.cleaned_data.get('username')
112                 password = self.cleaned_data.get('password')
113                 message = self.ERROR_MESSAGE
114                 
115                 if username and password:
116                         self.user_cache = authenticate(username=username, password=password)
117                         if self.user_cache is None:
118                                 if u'@' in username:
119                                         # Maybe they entered their email? Look it up, but still raise a ValidationError.
120                                         try:
121                                                 user = User.objects.get(email=username)
122                                         except (User.DoesNotExist, User.MultipleObjectsReturned):
123                                                 pass
124                                         else:
125                                                 if user.check_password(password):
126                                                         message = _("Your e-mail address is not your username. Try '%s' instead.") % user.username
127                                 raise ValidationError(message)
128                         elif not self.user_cache.is_active:
129                                 raise ValidationError(message)
130                 self.check_for_test_cookie()
131                 return self.cleaned_data