17
\$\begingroup\$

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:

  1. The user goes to the registration page and fills in the following fields:
  • first_name
  • last_name
  • email
  1. The user submits the form and receives a confirmation email with an URL containing a unique token.

  2. When the user clicks on the received link, he's redirected to a page where he'll set his password.

  3. 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..)
Sᴀᴍ Onᴇᴌᴀ
29.6k16 gold badges45 silver badges203 bronze badges
asked May 17, 2018 at 15:18
\$\endgroup\$
1
  • 5
    \$\begingroup\$ It could be worth putting your code and templates in a public repo for people to clone, to make it easier to review \$\endgroup\$ Commented May 24, 2018 at 6:36

0

Know someone who can answer? Share a link to this question via email, Twitter, or Facebook.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.