Creating a custom user model in Django 1.6 - Part 5

Published byGoogle on

Hello everyone and welcome back to the fifth part of this tutorial (part of the Babbler tutorial series). If you haven't read the first four parts I'd encourage you to do so now:

This week we will cover user registration with e-mail validation as well as the "lost / change password" workflow.
As we did two weeks ago we will be using (generic) class-based views to build our forms and Crispy forms to render them. We will also be using Templated emails to handle our email needs and we will create our first management command.

A long long time ago in a galaxy far away, registration forms used to be "open" meaning that one could create an account on almost any website without any credentials check. Spambots and abuse have made this impossible nowadays.

One of the most common methods to fight those problems is the use of an activation link sent by e-mail to the newly registered user. This is the technique that we will be covering today. Another way of checking credentials is to rely on another service to authenticate users but this will be the subject of a whole other tutorial. 

In order to help us in our registration process we will need an activation key. While we could store the key inside the user model, this information is temporary and is meant for a single use so we are going to be using a separate model instead.

As this tutorial is database-agnostic, we will be using python's uuid lib to generated the key which will be stored in a CharField. If you are using PostgreSQL as a database backend, you might want to check out  its built-in UUID field type as well as the Django library allowing you to use it: djorm-ext-pguuid.

(Before getting started, make sure python-dateutil is installed inside your virtualenv.)

.Here is our new model:

# Make sure you have those two lines at the top of myauth/models.py
import uuid
from dateutil.relativedelta import relativedelta

.
.
.

def two_days_from_now():
  return timezone.now() + relativedelta(days=2)

def get_uuid_str():
  return uuid.uuid4().__str__()

class Registration(models.Model):

  uuid = models.CharField(max_length=36, default=get_uuid_str)
  user = models.OneToOneField(User, related_name='registration')
  expires = models.DateTimeField(default=two_days_from_now)

Note that inside the module which defines my user model, I have to refer directly to it by name and cannot use django.contrib.auth.get_user_model

Now that our model is created, don't forget to migrate your database.

We don't need to define any form or admin for this new model since we won't be doing anything with it directly, instead we will create a management task to cleanup expired registrations. You will have to add it to a cron job on your live server.
Management commands are quite trivial to create, they are, by default, located inside <module_name>/management/commands/<command>.py. Note that both management and commands directories require an (empty) __init__.py file.
So let's create a myauth/management/commands/purgeregistrations.py which will look like this:

from django.core.management.base import BaseCommand
from django.utils import timezone

from myauth.models import Registration

class Command(BaseCommand):
  args = None
  help = 'Purges expired registrations'

  def handle(self, *args, **kwargs):
    User.objects.filter(registration__expires__lte=timezone.now()).delete()

Note how we run our search directly on the User model. Registration.user being a OneToOneField, it has it's DELETE_CASCADE setting to on by default. So when we delete the User object associated with the Registration object, both objects get deleted.
As we call delete directly on the QuerySet, the deletion of every expired registration and their associated user is executed in a single query.

Now, if your run python manage.py, you should see your task, you may also try to launch it

 $ python manage.py 
Usage: manage.py subcommand [options] [args]
.
.
.
[myauth]
    purgeregistrations
.
.
.
$ python manage.py purgeregistrations

Now, to get on with our registration process we need to be able to send some e-mails. Django is natively able to send e-mail but I usually like to use Django-templated-email because it allows you to build templated e-mails using Django's templating language. So pip install it into your virtualenv (don't forget to --freeze your environment afterwards) and add it to your settings.py

$ pip install django-templated-email
INSTALLED_APPS = (
## Default configuration
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
## Additional apps
    'south',
    'crispy_forms',
    'templated_email',
## Home made apps
    'babbler',
    'theme',
    'myauth',
)

Now we are ready to create our form. Once again we already have a UserCreationForm (which is basically the same as a registration form) so let's inherit from it and make it suitable to be used with Crispy forms.
Add this class to your myauth/forms.py:

class UserRegistrationForm(UserCreationForm):

  class Meta:
    # Since we are overriding Meta we have to re-tell it the model also
    model = User
    exclude = ['password', 'last_login', 'date_joined']

  def __init__(self, *args, **kwargs):
    super(UserRegistrationForm, self).__init__(*args, **kwargs)
    self.helper = FormHelper()
    self.helper.form_method = 'post'
    self.helper.form_action = 'register'
    self.helper.form_class = 'form-horizontal'
    self.helper.label_class = 'col-md-5'
    self.helper.field_class = 'col-md-6'
    self.helper.layout = Layout(
      # password1 and password2 are the fields defined in django.contrib.auth.forms.UserCreationForm
      'short_name', 'full_name', 'email', 'username', 'password1', 'password2',
      HTML('<div class="form-group"><div class="col-md-5"> </div><div class="col-md-6">'), 
      Submit('submit', 'Register'), 
      HTML('</div></div>'),
    )

Now, on to the views!
For our registration process we need 3 different views:

  1. a FormView to display and validate the form. This view also sends an e-mail when a valid form is submitted
  2. a TemplateView on sucessfull form submition that just displays a message saying an e-mail has been sent
  3. a RedirectView wich activates the User and redirects to the login page when given the correct activation key (raises an Http404 otherwise)

Add this to your myauth/views:

#Add these 3 lines to your imports
from django.conf import settings
from django.http import Http404

from templated_email import send_templated_mail

#update these 2 lines
from myauth.forms import LoginForm, UserRegistrationForm
from django.views.generic.base import TemplateView, RedirectView

#and add this one
from myauth.models import Registration, User

.
.
.

class RegisterView(FormView):

  template_name = 'myauth/register.html'
  form_class = UserRegistrationForm

  def get_success_url(self):
    return reverse('confirmation_mail_sent')
  
  def form_valid(self, form):
    user = form.save()
    registration = Registration.objects.create(user=user)
    messages.info(self.request, 'Registration successfull')
    send_templated_mail(
      template_name='registration',
      # substitute your e-mail adress
      from_email='noreply@babbler.com',
      recipient_list=[form.cleaned_data['email'],],
      context={
        'url_name': 'activation',
        'url_param': 'key',
        'registration': registration,
        # make sure SITE_URL is defined in your settings.py
        'base_url': settings.SITE_URL,
      },
    )
    return super(RegisterView, self).form_valid(form)


class EmailSentView(TemplateView):

  template_name = 'myauth/email_sent.html'


class ActivationView(RedirectView):

  permanent = False

  def get_redirect_url(self):
    return reverse('login')

  def get(self, request, *args, **kwargs):
    uuid = request.GET.get('key', None)
    if uuid is None:
      raise Http404
    try:
      user = User.objects.get(registration__uuid=uuid)
      user.is_active = True
      user.save()
      user.registration.delete()
      messages.info(self.request, 'User activation successfull')
    except:
      raise Http404
    return super(ActivationView, self).get(request, *args, **kwargs)

There isn't anything new in this code compared to what we did last week, except for the sending of the e-mail which is pretty straight forward.
Regarding this e-mail, as I told you Django-templated-email allows you to use Django's templating language inside e-mail templates. Those templates should have a .email extension and be located inside <app_name>/templates/templated_email/.
They implicitly inherit from a base email template which has blocks you can override. Namely subject, plain and html.
In this tutorial we will only be overriding the subject and html blocks.
Here is what your myauth/templates/templated_email/registration.email should look like:

{% block subject %}Email verification for {{registration.user.full_name}} on Babbler.com{% endblock %}

{% block html %}
<p>Hi {{registration.user.short_name}},</p>

<p>You just signed up for Babbler.com with the username {{registration.user.username}}.<br/>
In order for us to activate your account, please confirm your email address by going to <a href="{{base_url}}{% url url_name %}?{{url_param}}={{registration.uuid}}">{{base_url}}{% url url_name %}?{{url_param}}={{registration.uuid}}</a></p>
{% endblock %}

Now, in our new views, we mentioned 2 other new templates, here they are:

# myauth/templates/myauth/email_sent.html

{% extends 'base.html'%}
{% load crispy_forms_tags %}
{% block content %}
  <h1>Registration</h1>
  <p>An confirmation e-mail with an activation link has been sent to you.<br/>Please check your e-mail and follow the link to continue the registration process.</p>
{% endblock %}


# myauth/templates/myauth/register.html

{% extends 'base.html'%}
{% load crispy_forms_tags %}
{% block content %}
  <h1>Registration</h1>
  {% crispy form %}
{% endblock %}

All we have left to do to wrap up the registration process is register some urls in urls.py and provide a registration link inside mybaseproject/templates/base.html.

## Update this line in your imports
from myauth.views import LoginView, LogoutView, RegisterView, EmailSentView, ActivationView

.
.
.

    # user authentication and registration
    url(r'^login\.html$', LoginView.as_view(), name='login'),
    url(r'^logout\.html$', LogoutView.as_view(), name='logout'),
    ## these lines are new
    url(r'^register\.html$', RegisterView.as_view(), name='register'),
    url(r'^registration_email_sent\.html', EmailSentView.as_view(), name='confirmation_mail_sent'),
    url(r'^user_activation\.html', ActivationView.as_view(), name='activation'),

.
.
.
{% load staticfiles %}
<html>
  <head> 
    <link href="{% url 'themed-bootstrap' 'my-theme' %}" rel="stylesheet" type="text/css">       
  </head>
  <body>
    <div class="container">
      <header class="navbar navbar-top" role="banner">
        <a href="/" class="navbar-brand">BaBbLeR</a>
        <!-- update this section /-->
        <div class="navbar-right">
          {% if user.is_authenticated %}
            Welcome {{user.get_short_name}} - <a href="{% url 'logout' %}">Log out</a>        
          {% else %} 
            <a href="{% url 'login' %}">Log in</a> or <a href="{% url 'register' %}">Register</a>
          {% endif %}
        </div>
        <!-- end of new code -->
        <div class="clearfix"></div>
        <ul class="nav nav-justified">

And there we have a registration process. As you can see it is a bit tedious but there isn't really anything complicated here and Crispy forms really makes it easy on the templating side.

Now, of course, when you'll have users registered on your site it won't take long before one of them looses their password...

As we have already spoken of the (highly unlikely) case where a user's username would be the same as another user's e-mail address, we will only ask for a user's e-mail address on the "lost password" form.
Once again, the most common behaviour for resetting a lost password is to send an e-mail to the user with a link (containing a unique key) to a "change password" form.
As we already have a Registration class providing a unique key, we will be doing a bit of refactoring to use that same class to store keys related to lost passwords, think of it as a "lost password registration" (no, seruiously, I should have named my class something else).

# myauth/models.py

.
.
.

class Registration(models.Model):

  uuid = models.CharField(max_length=36, default=get_uuid_str)
  # it might be possible the same user requests for a new password twice in a row so let's make this a ForeignKey
  user = models.ForeignKey(User, related_name='registration')
  expires = models.DateTimeField(default=two_days_from_now)
  # add this field
  type = models.CharField(max_length=10, choices=(
      ('register', 'register'),
      ('lostpass', 'lostpass'),
    ), default = 'register')


#myauth/views.py

.
.
.

class ActivationView(RedirectView):

  .
  .
  .

  def get(self, request, *args, **kwargs):
    uuid = request.GET.get('key', None)
    if uuid is None:
      raise Http404
    try:
      # This line changed
      user = User.objects.get(registration__uuid=uuid, type='register')
      user.is_active = True
      user.save()
      user.registration.delete()
      messages.info(self.request, 'User activation successfull')
    except:
      raise Http404
    return super(ActivationView, self).get(request, *args, **kwargs)


# myauth/management/commands/purgeregistrations.py

from django.core.management.base import BaseCommand
from django.utils import timezone 

# This line has been updated
from myauth.models import User, Registration

class Command(BaseCommand):
  args = None
  help = 'Purges expired registrations'

  def handle(self, *args, **kwargs):
    #We only remove users related to expired registration processes
    User.objects.filter(registration__expires__lte=timezone.now()).filter(registration__type='register').delete()
    # We cleanup any other expired requests
    Registration.objects.filter(expires_lte=timezone.now()).delete()

Now that our Registration class is ready to manage lost password key generation as well (don't forget to migrate your database), let's create the forms we'll need. We are going to need 2 forms:

  1. A form to request the password change
  2. A form to do the actual password change

Let's get started with the form and views which will request for a new password:

# myauth/forms.py

.
.
.

class LostPasswordForm(forms.Form):

  email = forms.EmailField(label='E-mail address')

  def __init__(self, *args, **kwargs):
    super(LostPasswordForm, self).__init__(*args, **kwargs)
    self.helper = FormHelper()
    self.helper.form_method = 'post'
    self.helper.form_action = 'lost_password'
    self.helper.form_class = 'form-horizontal'
    self.helper.label_class = 'col-md-5'
    self.helper.field_class = 'col-md-6'
    self.helper.layout = Layout(
      'email', 
      HTML('<div class="form-group"><div class="col-md-5"> </div><div class="col-md-6">'), 
      Submit('submit', 'Request new password'), HTML('</div></div>'),
    ) 
    
  def clean(self):
    cleaned_data = super(LostPasswordForm, self).clean()
    try:
      user = User.objects.get(email=cleaned_data['email'], is_active=True)
    except User.DoesNotExist:
      raise forms.ValidationError("We don't know of any user with that e-mail address")
    return cleaned_data


# myauth/views.py

#update this line in your imports
from myauth.forms import LoginForm, UserRegistrationForm, LostPasswordForm

.
.
.

class LostPasswordView(FormView):

  template_name = 'myauth/lost_password.html'
  form_class = LostPasswordForm

  def get_success_url(self):
    return reverse('lost_password_mail_sent')

  def form_valid(self, form):
    user = form.user
    registration = Registration.objects.create(user=user, type='lostpass')
    send_templated_mail(
      template_name='lost_password',
      # substitute your e-mail adress
      from_email='noreply@babbler.com',
      recipient_list=[form.cleaned_data['email'],],
      context={
        'url_name': 'reset_password',
        'url_param': 'key',
        'registration': registration,
        # make sure SITE_URL is defined in your settings.py
        'base_url': settings.SITE_URL,
      },
    ) 
    return super(LostPasswordView, self).form_valid(form)
    
    
class LostPasswordEmailSentView(TemplateView):

  template_name = 'myauth/lost_password_email_sent.html'

 And now the associated templates:

# myauth/templates/myauth/lost_password.html

{% extends 'base.html'%}
{% load crispy_forms_tags %}
{% block content %}
  <h1>Lost password</h1>
  {% crispy form %}
{% endblock %}


# myauth/templates/myauth/lost_password_email_sent.html

{% extends 'base.html'%}
{% load crispy_forms_tags %}
{% block content %}
  <h1>Lost password</h1>
  <p>An e-mail with a link to the password change form has been sent to you.<br/>Please check your e-mail and follow the link to get a new password.</p>
{% endblock %}


# myauth/templates/templated_email/lost_password.email

{% block subject %}Lost password for {{registration.user.full_name}} on Babbler.com{% endblock %}
{% block html %}
<p>Hi {{registration.user.short_name}},</p>

<p>You requested a new password for {{registration.user.username}} on Babbler.com.<br/>
In order for us to change your password, please go here:<a href="{{base_url}}{% url url_name %}?{{url_param}}={{registration.uuid}}">{{base_url}}{% url url_name %}?{{url_param}}={{registration.uuid}}</a></p>
{% endblock %}

Now when it comes to the actual password change, we might be tempted to re-use UserChangeForm which is designed to update a User object. But that would be a bad idea. Such a form would expose the User.id field (either as a hidden HTML input or inside the url) and would make it possible for anyone registered on the site to change anyone else's password provided they know their user id.
Instead we will be using a ModelForm based on the Registration model with two extra password fields.
Also, since we are using a ModelForm which refers to an existing object, we could extend UpdateView for our view. But UpdateView expects a slug or a pk to be passed as url kwargs. Since we have neither of those (we have a uuid), we will be subclassing FormView instead which will be easier.

Here is what our form and view classes will look like:

# myauth/forms.py

#update this line
from myauth.models import User, Registration

.
.
.

class LostPasswordChangeForm(forms.ModelForm):

  id = forms.IntegerField(widget=forms.HiddenInput())
  password1 = forms.CharField(label="Password", min_length=8, widget=forms.PasswordInput())
  password2 = forms.CharField(label="Password confirmation", widget=forms.PasswordInput())

  class Meta:
    model = Registration
    fields = ['id',]
    
  def __init__(self, *args, **kwargs):
    super(LostPasswordChangeForm, self).__init__(*args, **kwargs)
    # Just in case someone else uses our form
    if self.instance is None:
      raise Registration.DoesNotExist
    self.fields['id'].initial = self.instance.pk
    self.helper = FormHelper()
    self.helper.form_method = 'post'
    self.helper.form_action = 'reset_password'
    self.helper.form_class = 'form-horizontal'
    self.helper.label_class = 'col-md-5'
    self.helper.field_class = 'col-md-6'
    self.helper.layout = Layout(
      'id', 'password1', 'password2',
      HTML('<div class="form-group"><div class="col-md-5"> </div><div class="col-md-6">'), 
      Submit('submit', 'Change password'), HTML('</div></div>'),
    ) 

  def clean_password2(self):
    cleaned_data = super(LostPasswordChangeForm, self).clean()
    self.password = cleaned_data.get('password1')
    if self.password != cleaned_data.get('password2'):
      raise forms.ValidationError('Password do not match')
    self.user = self.instance.user
    return self.password


# myauth/views.py

# update this line
from myauth.forms import LoginForm, UserRegistrationForm, LostPasswordForm, LostPasswordChangeForm

.
.
.

class LostPasswordChangeView(FormView):

  template_name = 'myauth/lost_password_change.html'
  form_class = LostPasswordChangeForm

  def get_success_url(self):
    return reverse('login')
  
  def get_form_kwargs(self):
    kwargs = super(LostPasswordChangeView, self).get_form_kwargs()
    if self.request.method == 'GET':
      key = self.request.GET.get('key')
      try:
        registration = Registration.objects.get(uuid=key, type='lostpass')
      except:
        raise Http404
    else:
      try: 
        registration = Registration.objects.get(pk=kwargs['data'].get('id'), type='lostpass')
      except:
        raise Http404
    kwargs['instance'] = registration
    return kwargs

  def form_valid(self, form):
    form.user.set_password(form.password)
    form.user.save()
    form.instance.delete()
    messages.info(self.request, 'Your password has been successfully updated')
    return super(LostPasswordChangeView, self).form_valid(form)

Note how we have overridden get_form_kwargs to pass the instance to the form. We are never dealing with a User, always with a Registration. This limits the risk of someone changing someone else's password. The only, very unlikely, case where it would be possible is if someone asks to reset another user's password (they have to know the other user's e-mail address), the targeted user doesn't do anything about the "reset password" email they receive and the attacker is able to bruteforce the uuid associated with that request in less than two days. As I said, very unlikely.

We will also need a template for this new form, here it is:

# myauth/templates/myauth/lost_password_change.html

{% extends 'base.html'%}
{% load crispy_forms_tags %}
{% block content %}
  <h1>Change password</h1>
  {% crispy form %}
{% endblock %}

Of course, we also need to create urls for the new view, so update your urls.py:

## Update this line
from myauth.views import LoginView, LogoutView, RegisterView, EmailSentView, ActivationView, LostPasswordView, LostPasswordEmailSentView, LostPasswordChangeView

.
.
.

## Add those lines to your urlpatterns
    url(r'^lost_password\.html', LostPasswordView.as_view(), name='lost_password'),
    url(r'^lost_password_mail_sent\.html', LostPasswordEmailSentView.as_view(), name='lost_password_mail_sent'),
    url(r'^reset_password\.html', LostPasswordChangeView.as_view(), name='reset_password'), 

...

And finally, we also have to provide a link to the "lost password" view, preferably from the login form so edit your myauth/templates/myauth/login.html to look like this:

{% extends 'base.html'%}
{% load crispy_forms_tags %}
{% block content %}
  <h1>Login</h1>
  {% crispy form %}
  <p><small><a href="{% url 'lost_password' %}">Lost your password?</a> - <a href="{% url 'register' %}">Create a new account</a></small></p>
{% endblock %}

Tada! That's it, you have yourself a complete and working "Lost password" workflow!

This wraps it up for this week's post. Since this entire tutorial is something that you are likely to have to do for a lot of projects, next week, we will start another tutorial about making an application re-usable.
In the mean time, have a great week everyone and happy coding Smile

As always, you can browse the code for this tutorial on http://vc.lasolution.be/projects/babbler-tutorial/repository.
The code is available for checkout on http://code.lasolution.be/babbler.
The branch associated with this tutorial is custom-user-model

Class-based views Crispy forms Templated e-mails Tutorial Django Custom user model

Comments

Thanks for the good understanding blog. this post is very user for writing the custom model in Django. The previous posts (part 1 to part 4) links are not working, please give the correct link

By Arul - 3 years, 5 months ago Reply 

Thank you :-)

What problem are you experiencing with the links? You can't click on them or you end-up on a 404 page? I don't seem to have a problem with them.

You can access them though the main Blog menu also.

By Emmanuelle Delescolle - 3 years, 5 months ago Reply 

Thanks for the quick reply and other parts links, it is working fine.
From this page i got "502 Bad Gateway" error, but now it is working from this page.

By Arul - 3 years, 5 months ago Reply 

Oh, you probably tried while I was ugrading the website this morning

By Emmanuelle Delescolle - 3 years, 5 months ago Reply 

oh, OK . Do you have any python tutorials?

By Arul - 3 years, 5 months ago Reply 

So far only Django tutorials but maybe some day ;-)

By Emmanuelle Delescolle - 3 years, 5 months ago Reply 

OK cool and thanks for expecting that day :-)

By Arul - 3 years, 5 months ago Reply 

No problem ;-)

By Emmanuelle Delescolle - 3 years, 5 months ago Reply 

Hello !

Thanks for the good tutorial !

In the lost password change form, it could be better to use the uuid instead of the id.
If you change the id directly in the html, it could be possible to manage to change another password recovery. It is less likely to find another uuid.

What do you think ?

Regards

By Jrlaforge - 3 years, 2 months ago Reply 

Hi, you are welcome :-)

Yes you are right, one could submit two lost password forms in a row for 2 different users (one whose e-mail they have access to) and therefore guess the id.

Therefore using the uuid instead of (or with) the id would be a more secure solution.

Thanks for the tip.

Best regards

By Emmanuelle Delescolle - 3 years, 2 months ago Reply 

I think you forgot to import TemplateView in views.py at some point.

I followed your tutorial but for some reason after implementing all this stuff at once I broke my application. Now I can't log in anymore, nor register. When I type in valid username / password I just stay on the login.html page for some reason. No idea how I broke it by just adding this Registration stuff. Maybe it would be great if you would split it up into smaller chunks which can be tested in between (registration, then test it, then implement password recovery).

By Tobias Dacoir - 3 years ago Reply 

I'm not sure I can really help you regarding you "broke your application" without further details.
This part of the tutorial adds and updates some file which are already present.

TemplateView is indeed a required import, I updated the code here.

As far as splitting this in two distinct part, well I thought this is what I did. The "Lost password" part starts with "Now, of course, when you'll have users registered on your site it won't take long before one of them looses their password..."
If you have suggestions on how to make this clearer, please let me know.

By Emmanuelle Delescolle - 3 years ago Reply 

I ran into a similar issue because I made some mistakes in copying the code.
In my_auth/forms.py the UserRegistrationForm class was wrong and using a ModelForm:
class UserRegistrationForm(forms.ModelForm):

make sure you pass UserCreationForm from django.contrib.auth.forms.UserCreationForm like:

from django.contrib.auth.forms import UserCreationForm
class UserRegistrationForm(UserCreationForm):

Also the registration form should have password1, and password2 fields (I didn't use crispy forms so I missed some nuances that were pretty critical), so my register.html had something like this:

<div class="form-group">
<label for="id_password1">Password</label>
{{ form.password1.errors }}
{{ form.password1 }}<br>
</div>
<div class="form-group">
<label for="id_password2">Password Confirmation</label>
{{ form.password2.errors }}
{{ form.password2 }}
</div>
Someone smarter than myself can comment, but whatever I was doing created a password hash that was too weak to allow me to log into the application so I continually was routed to login.html even though all of my user data was properly stored in the database. Hope that helps someone.

By Dan Coughlin - 2 years, 3 months ago Reply 

Also, I did check out the babbler repo using hg but in mybaseproject/urls.py you include myauth.urls but I don't see any urls.py in myauth folder.

By Tobias Dacoir - 3 years ago Reply 

Looks like you didn't checkout the correct branch and are at a further point in this tutorial series.

To checkout a specific branch with mercurial, use the "-u" flag. In this case for example "hg clone http://code.lasolution.be/babbler -u custom-user-model". Read the mercurial documentation for more info on branches and how to switch between them.

By Emmanuelle Delescolle - 3 years ago Reply 

This tutorial is awesome! Thanks a lot!

I created a new profile view (so the user will be able to change his profile without using the admin console) but it doesn't load the logged in user data. Any suggestions on what I'm missing?

class ProfileView(FormView):
template_name = 'myauth/profile.html'
form_class = UserChangeForm
def form_valid(self, form):
user = form.save()
user.is_active = True
user.save()
messages.info(self.request, 'Profile update successfull')
return super(ProfileView, self).form_valid(form)

By Maik Berchten - 2 years, 8 months ago Reply 

Thanks :-)

The thing is FormView is designed to create or update records. Since you don't specify the user it should load by default it will create a new one.

My advice would be to use an UpdateView instead ( http://ccbv.co.uk/projects/Django/1.7/django.views.generic.edit/UpdateView/ ) to remove the possibility of creating a new record and to override the get_object method to load the current user.

Cheers

By Emmanuelle Delescolle - 2 years, 8 months ago Reply 

Wow amazing!! Thanks a lot for answering my noob question, and so quickly!
Now I just have to figure out why the save fails ;-)

By Maik Berchten - 2 years, 8 months ago Reply 

You're welcome and I hope you do figure out why save fails ;-)

By Emmanuelle Delescolle - 2 years, 8 months ago Reply 

This was very helpful, thank you! I had to make an edit to the ActivationClass in myauth/views.py on line 38:
user = User.objects.get(registration__uuid=uuid, type='register')

I changed the type to reference the registration table:

user = User.objects.get(registration__uuid=uuid, registration__type='register')

Not sure if that was an error on my part or something others may find useful.

By Dan Coughlin - 2 years, 3 months ago Reply 

Thanks for your feed-backs.
This code here says "type" (http://vc.lasolution.be/projects/babbler-tutorial/repository/revisions/custom-user-model/entry/myauth/models.py#L93) but as type is a python keyword, you're probably better off using "registration_type" anywats ;-)

By Emmanuelle Delescolle - 2 years, 3 months ago Reply 

Post a comment