###Short intro
Short intro
###Description
Description
###Code
Code
###Things that I'd like to be reviewed:
Things that I'd like to be reviewed:
###Short intro
###Description
###Code
###Things that I'd like to be reviewed:
Short intro
Description
Code
Things that I'd like to be reviewed:
Let's register that Django user
###Short intro
So, I've been using Django for a while now and thought it would be nice to start a simple application. In an ideal World, each app must have a way of letting its users register and that's what I'm gonna do below.
###Description
I've used the latest Django version for this project -> because of this, I couldn't use django-registration
module as it's incompatible with the above. Apart from this, I've used Python 3.6.5.
What I want to do it's common along other web applications:
- The user goes to the registration page and fills in the following fields:
first_name
last_name
email
The user submits the form and receives a confirmation email with an URL containing a unique token.
When the user clicks on the received link, he's redirected to a page where he'll set his password.
When done, he's logged in to the dashboard page.
###Code
models.py
from django.contrib.auth.models import AbstractBaseUser
from django.contrib.auth.models import BaseUserManager
from django.contrib.auth.models import PermissionsMixin
from django.db import models
from django.utils.translation import ugettext_lazy as _
class UserManager(BaseUserManager):
"""
A custom user manager to deal with emails as unique identifiers for auth
instead of usernames. The default that's used is "UserManager"
"""
def _create_user(self, email, password, **extra_fields):
"""
Creates and saves a User with the given email and password.
"""
if not email:
raise ValueError('The Email must be set')
email = self.normalize_email(email)
user = self.model(email=email, **extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
def create_user(self, email, password=None, **extra_fields):
"""
Create and save a regular User with the given email and password.
"""
extra_fields.setdefault('is_staff', False)
extra_fields.setdefault('is_superuser', False)
return self._create_user(email, password, **extra_fields)
def create_superuser(self, email, password, **extra_fields):
"""
Create and save a SuperUser with the given email and password.
"""
extra_fields.setdefault('is_staff', True)
extra_fields.setdefault('is_superuser', True)
extra_fields.setdefault('is_active', True)
if extra_fields.get('is_staff') is not True:
raise ValueError('Superuser must have is_staff=True.')
if extra_fields.get('is_superuser') is not True:
raise ValueError('Superuser must have is_superuser=True.')
return self._create_user(email, password, **extra_fields)
class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(unique=True, null=True, max_length=64)
first_name = models.CharField(max_length=64)
last_name = models.CharField(max_length=64)
is_staff = models.BooleanField(
_('staff status'),
default=False,
help_text=_('Designates whether the user can log into this site.'),
)
is_active = models.BooleanField(
_('active'),
default=True,
help_text=_(
'Designates whether this user should be treated as active. '
'Unselect this instead of deleting accounts.'
),
)
USERNAME_FIELD = 'email'
objects = UserManager()
def __str__(self):
return self.email
def get_full_name(self):
return self.email
def get_short_name(self):
return self.email
forms.py
from django.contrib.auth import get_user_model
from django.forms import ModelForm
User = get_user_model()
class RegistrationForm(ModelForm):
class Meta:
model = User
fields = ('email', 'first_name', 'last_name')
def save(self, commit=True):
user = super(RegistrationForm, self).save(commit=False)
if commit:
user.save()
return user
views.py
from django.contrib import messages
from django.contrib.auth import update_session_auth_hash, get_user_model, authenticate, login
from django.contrib.auth.forms import AuthenticationForm, SetPasswordForm
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.views import LoginView, LogoutView
from django.contrib.sites.shortcuts import get_current_site
from django.core.mail import EmailMessage
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.template.loader import render_to_string
from django.utils.encoding import force_bytes, force_text
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
from django.views.generic import TemplateView, View
from .forms import RegistrationForm
from .tokens import account_activation_token
User = get_user_model()
class HomeView(LoginRequiredMixin, TemplateView):
template_name = 'base/home.html'
class CustomLoginView(LoginView):
template_name = 'base/login.html'
form_class = AuthenticationForm
class CustomLogoutView(LogoutView):
template_name = 'base/login.html'
class RegistrationView(View):
def get(self, request):
registration_form = RegistrationForm()
return render(request, 'base/register.html', {'form': registration_form})
def post(self, request):
registration_form = RegistrationForm(request.POST)
if registration_form.is_valid():
user = registration_form.save(commit=False)
user.is_active = False
user.set_unusable_password()
user.save()
current_site = get_current_site(request)
mail_subject = 'Activate your account.'
message = render_to_string('base/activation-email.html', {
'user': user,
'domain': current_site.domain,
'uid': urlsafe_base64_encode(force_bytes(user.pk)).decode(),
'token': account_activation_token.make_token(user),
})
to_email = registration_form.cleaned_data.get('email')
email = EmailMessage(
mail_subject, message, to=[to_email]
)
email.send()
return render(request, 'base/confirm-email.html')
class ActivationView(View):
def get(self, request, uidb64, token):
try:
uid = force_text(urlsafe_base64_decode(uidb64))
user = User.objects.get(pk=uid)
except(TypeError, ValueError, OverflowError, User.DoesNotExist):
user = None
if user is not None and account_activation_token.check_token(user, token):
user.is_active = True
user.save()
login(request, user)
form = SetPasswordForm(request.user)
return render(request, 'base/password-set.html', {'form': form})
else:
return render(request, 'base/verification-failed.html')
def post(self, request, uidb64, token):
try:
uid = force_text(urlsafe_base64_decode(uidb64))
user = User.objects.get(pk=uid)
except(TypeError, ValueError, OverflowError, User.DoesNotExist):
user = None
form = SetPasswordForm(user, request.POST)
if form.is_valid():
user = form.save()
update_session_auth_hash(request, user)
user = authenticate(username=user.email, password=form.cleaned_data['new_password1'])
login(request, user)
return HttpResponseRedirect('/')
else:
messages.error(request, form.errors)
return render(request, 'base/password-set.html', {'form': form})
urls.py
from django.conf.urls import url
from django.views.generic import TemplateView
from .views import (
CustomLoginView, CustomLogoutView, HomeView,
RegistrationView, ActivationView
)
app_name = 'apps.base'
urlpatterns = [
url(r'^$', HomeView.as_view(), name='home'),
url(r'^login/$', CustomLoginView.as_view(), name='login'),
url(r'^logout/$', CustomLogoutView.as_view(), name='logout'),
url(r'^register/$', RegistrationView.as_view(), name='register'),
url(r'^confirm_email/$', TemplateView.as_view(template_name='base/confirm-email.html'), name='confirm_email'),
url(r'^activate/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$',
ActivationView.as_view(), name='activate'),
]
tokens.py
from django.contrib.auth.tokens import PasswordResetTokenGenerator
from django.utils import six
class TokenGenerator(PasswordResetTokenGenerator):
def _make_hash_value(self, user, timestamp):
return (
six.text_type(user.pk) + six.text_type(timestamp) +
six.text_type(user.is_active)
)
account_activation_token = TokenGenerator()
###Things that I'd like to be reviewed:
- Django Design Patterns: Did I use the correct views / forms / models in my app? Should some of the logic be written somewhere else etc...
- Security: Is my register system secure? If not, what can I do to make it so?
- General feedback: Anything that comes to your mind is welcome (including criticism, ideas, improvements, cats...aliens..)