[Users] Check case-insensitive username when registering
[mygpo.git] / mygpo / users / views / registration.py
blob9f713c70876c1c68e8dae0318700e61fcb16ffa7
1 import re
3 from django import forms
4 from django.core.validators import RegexValidator
5 from django.core.exceptions import ValidationError
6 from django.db import IntegrityError, transaction
7 from django.http import HttpResponseRedirect
8 from django.views.generic.edit import FormView
9 from django.utils.translation import ugettext as _
10 from django.template.loader import render_to_string
11 from django.core.urlresolvers import reverse, reverse_lazy
12 from django.views.generic import TemplateView
13 from django.views.generic.base import View
14 from django.contrib import messages
15 from django.contrib.auth import get_user_model
16 from django.contrib.sites.requests import RequestSite
18 from mygpo.utils import random_token
19 from mygpo.users.models import UserProxy
22 USERNAME_MAXLEN = get_user_model()._meta.get_field('username').max_length
24 USERNAME_REGEX = re.compile(r'^\w[\w.+-]*$')
27 class DuplicateUsername(ValidationError):
28 """ The username is already in use """
30 def __init__(self, username):
31 self.username = username
32 super().__init__('The username {0} is already in use.'
33 .format(username))
36 class DuplicateEmail(ValidationError):
37 """ The email address is already in use """
39 def __init__(self, email):
40 self.email = email
41 super().__init__('The email address {0} is already in use.'
42 .format(email_addr))
45 class UsernameValidator(RegexValidator):
46 """ Validates that a username uses only allowed characters """
47 regex = USERNAME_REGEX
48 message = 'Invalid Username'
49 code='invalid-username'
52 class RegistrationForm(forms.Form):
53 """ Form that is used to register a new user """
54 username = forms.CharField(max_length=USERNAME_MAXLEN,
55 validators=[UsernameValidator()],
57 email = forms.EmailField()
58 password1 = forms.CharField(widget=forms.PasswordInput())
59 password2 = forms.CharField(widget=forms.PasswordInput())
61 def clean(self):
62 cleaned_data = super(RegistrationForm, self).clean()
63 password1 = cleaned_data.get('password1')
64 password2 = cleaned_data.get('password2')
66 if not password1 or password1 != password2:
67 raise forms.ValidationError('Passwords do not match')
70 class RegistrationView(FormView):
71 """ View to register a new user """
72 template_name = 'registration/registration_form.html'
73 form_class = RegistrationForm
74 success_url = reverse_lazy('registration-complete')
76 def form_valid(self, form):
77 """ called whene the form was POSTed and its contents were valid """
79 try:
80 user = self.create_user(form)
82 except ValidationError as e:
83 messages.error(self.request, '; '.join(e.messages))
84 return HttpResponseRedirect(reverse('register'))
86 except IntegrityError:
87 messages.error(self.request,
88 _('Username or email address already in use'))
89 return HttpResponseRedirect(reverse('register'))
91 send_activation_email(user, self.request)
92 return super(RegistrationView, self).form_valid(form)
94 @transaction.atomic
95 def create_user(self, form):
96 User = get_user_model()
97 user = User()
98 username = form.cleaned_data['username']
100 self._check_username(username)
101 user.username = username
103 email_addr = form.cleaned_data['email']
104 user.email = email_addr
106 user.set_password(form.cleaned_data['password1'])
107 user.is_active = False
108 user.full_clean()
110 try:
111 user.save()
113 except IntegrityError as e:
114 if 'django_auth_unique_email' in str(e):
115 # this was not caught by the form validation, but now validates
116 # the DB's unique constraint
117 raise DuplicateEmail(email_addr) from e
118 else:
119 raise
121 user.profile.activation_key = random_token()
122 user.profile.save()
124 return user
126 def _check_username(self, username):
127 """ Check if the username is already in use
129 Until there is a case-insensitive constraint on usernames, it is
130 necessary to check for existing usernames manually. This is not a
131 perfect solution, but the chance that two people sign up with the same
132 username at the same time is low enough. """
133 UserModel = get_user_model()
134 users = UserModel.objects.filter(username__iexact=username)
135 if users.exists():
136 raise DuplicateUsername(username)
139 class ActivationView(TemplateView):
140 """ Activates an already registered user """
142 template_name = 'registration/activation_failed.html'
144 def get(self, request, activation_key):
145 User = get_user_model()
147 try:
148 user = UserProxy.objects.get(
149 profile__activation_key=activation_key,
150 is_active=False,
152 except UserProxy.DoesNotExist:
153 messages.error(request, _('The activation link is either not '
154 'valid or has already expired.'))
155 return super(ActivationView, self).get(request, activation_key)
157 user.activate()
158 messages.success(request, _('Your user has been activated. '
159 'You can log in now.'))
160 return HttpResponseRedirect(reverse('login'))
163 class ResendActivationForm(forms.Form):
164 """ Form for resending the activation email """
166 username = forms.CharField(max_length=USERNAME_MAXLEN, required=False)
167 email = forms.EmailField(required=False)
169 def clean(self):
170 cleaned_data = super(ResendActivationForm, self).clean()
171 username = cleaned_data.get('username')
172 email = cleaned_data.get('email')
174 if not username and not email:
175 raise forms.ValidationError(_('Either username or email address '
176 'are required.'))
179 class ResendActivationView(FormView):
180 """ View to resend the activation email """
181 template_name = 'registration/resend_activation.html'
182 form_class = ResendActivationForm
183 success_url = reverse_lazy('resent-activation')
185 def form_valid(self, form):
186 """ called whene the form was POSTed and its contents were valid """
188 try:
189 user = UserProxy.objects.all().by_username_or_email(
190 form.cleaned_data['username'],
191 form.cleaned_data['email'],
194 except UserProxy.DoesNotExist:
195 messages.error(self.request, _('User does not exist.'))
196 return HttpResponseRedirect(reverse('resend-activation'))
198 if user.profile.activation_key is None:
199 messages.success(self.request, _('Your account already has been '
200 'activated. Go ahead and log in.'))
202 send_activation_email(user, self.request)
203 return super(ResendActivationView, self).form_valid(form)
206 class ResentActivationView(TemplateView):
207 template_name = 'registration/resent_activation.html'
210 def send_activation_email(user, request):
211 """ Sends the activation email for the given user """
213 subj = render_to_string('registration/activation_email_subject.txt')
214 # remove trailing newline added by render_to_string
215 subj = subj.strip()
217 msg = render_to_string('registration/activation_email.txt', {
218 'site': RequestSite(request),
219 'activation_key': user.profile.activation_key,
221 user.email_user(subj, msg)