User registration and user profiles

Existing users can now log in, log out, change their password, and reset the password if they forgot it. Now, we need to build a view to allow visitors to create a user account.

User registration

Let's create a simple view to allow user registration in our website. Initially, we have to create a form to let the user enter a username, their real name, and a password. Edit the forms.py file located inside the account application directory and add the following code to it:

from django.contrib.auth.models import User

class UserRegistrationForm(forms.ModelForm):
    password = forms.CharField(label='Password', 
                               widget=forms.PasswordInput)
    password2 = forms.CharField(label='Repeat password', 
                                widget=forms.PasswordInput)

    class Meta:
        model = User
        fields = ('username', 'first_name', 'email')

    def clean_password2(self):
        cd = self.cleaned_data
        if cd['password'] != cd['password2']:
            raise forms.ValidationError('Passwords don't match.')
        return cd['password2']

We have created a model form for the User model. In our form we include only the username, first_name, and email fields of the model. These fields will be validated based on their corresponding model fields. For example, if the user chooses a username that already exists, their will get a validation error. We have added two additional fields password and password2 for the user to set his or her password and confirm it. We have defined a clean_password2() method to check the second password against the first one and not let the form validate if the passwords don't match. This check is done when we validate the form calling its is_valid() method. You can provide a clean_<fieldname>() method to any of your form fields in order to clean the value or raise form validation errors for the specific field. Forms also include a general clean() method to validate the entire form, which is useful to validate fields that depend on each other.

Django also provides a UserCreationForm form that you can use, which resides in django.contrib.auth.forms and is very similar to the one we have created.

Edit the views.py file of the account application and add the following code to it:

from .forms import LoginForm, UserRegistrationForm

def register(request):
    if request.method == 'POST':
        user_form = UserRegistrationForm(request.POST)
        if user_form.is_valid():
            # Create a new user object but avoid saving it yet
            new_user = user_form.save(commit=False)
            # Set the chosen password                 
            new_user.set_password(
                user_form.cleaned_data['password'])
            # Save the User object
            new_user.save()
            return render(request,
                          'account/register_done.html',
                          {'new_user': new_user})
    else:
        user_form = UserRegistrationForm()
    return render(request,
                  'account/register.html',
                  {'user_form': user_form})

The view for creating user accounts is quite simple. Instead of saving the raw password entered by the user, we use the set_password() method of the User model that handles encryption to save for safety.

Now, edit the urls.py file of your account application and add the following URL pattern:

url(r'^register/$', views.register, name='register'),

Finally, create a new template in the account/ template directory, name it register.html, and make it look as follows:

{% extends "base.html" %}

{% block title %}Create an account{% endblock %}

{% block content %}
  <h1>Create an account</h1>
  <p>Please, sign up using the following form:</p>
  <form action="." method="post">
    {{ user_form.as_p }}
    {% csrf_token %}
    <p><input type="submit" value="Create my account"></p>
  </form>
{% endblock %}

Add a template file in the same directory and name it register_done.html. Add the following code to it:

{% extends "base.html" %}

{% block title %}Welcome{% endblock %}

{% block content %}
  <h1>Welcome {{ new_user.first_name }}!</h1>
  <p>Your account has been successfully created. Now you can <a href="{% url "login" %}">log in</a>.</p>
{% endblock %}

Now, open http://127.0.0.1:8000/account/register/ in your browser. You will see the registration page you created:

User registration

Fill in the details for a new user and click the Create my account button. If all fields are valid, the user will be created and you will get the following success message:

User registration

Click the log in link and enter your username and password to verify that you can access your account.

Now, you can also add a link for registration to your login template. Edit the registration/login.html template and replace this line:

<p>Please, use the following form to log-in:</p>

...with this one:

<p>Please, use the following form to log-in. If you don't have an account <a href="{% url "register" %}">register here</a></p>

We made the sign up page accessible from the login page.

Extending the User model

When you have to deal with user accounts, you will find out that the User model of the Django authentication framework is suitable for common cases. However, the User model comes with very basic fields. You may wish to extend the User model to include additional data. The best way to do this is by creating a profile model that contains all additional fields and a one-to-one relationship with the Django User model.

Edit the models.py file of your account application and add the following code to it:

from django.db import models
from django.conf import settings

class Profile(models.Model):
    user = models.OneToOneField(settings.AUTH_USER_MODEL)
    date_of_birth = models.DateField(blank=True, null=True)
    photo = models.ImageField(upload_to='users/%Y/%m/%d', 
                              blank=True)
    
    def __str__(self):
        return 'Profile for user {}'.format(self.user.username)

Note

In order to keep your code generic, use the get_user_model() method to retrieve the user model and the AUTH_USER_MODEL setting to refer to it when defining model's relations to the user model, instead of referring to the auth User model directly.

The user one-to-one field allows us to associate profiles with users. The photo field is an ImageField field. You will need to install one of the Python packages to manage images, which are PIL (Python Imaging Library) or Pillow, which is a PIL fork. Install Pillow by running the following command in your shell:

pip install Pillow==2.9.0

For Django to serve media files uploaded by users with the development server, add the following settings to the settings.py file of your project:

MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')

MEDIA_URL is the base URL to serve the media files uploaded by users, and MEDIA_ROOT is the local path where they reside. We build the path dynamically relative to our project path to make our code more generic.

Now, edit the main urls.py file of the bookmarks project and modify the code as follows:

from django.conf.urls import include, url
from django.contrib import admin
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    url(r'^admin/', include(admin.site.urls)),
    url(r'^account/', include('account.urls')),
]

if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL,
                          document_root=settings.MEDIA_ROOT)

In this way, the Django development server will be in charge of serving the media files during development.

The static() helper function is suitable for development but not for production use. Never serve your static files with Django in a production environment.

Open the shell and run the following command to create the database migration for the new model:

python manage.py makemigrations

You will get this output:

Migrations for 'account':
  0001_initial.py:
    - Create model Profile

Next, sync the database with the following command:

python manage.py migrate

You will see an output that includes the following line:

Applying account.0001_initial... OK

Edit the admin.py file of the account application and register the Profile model in the administration site, like this:

from django.contrib import admin
from .models import Profile

class ProfileAdmin(admin.ModelAdmin):
    list_display = ['user', 'date_of_birth', 'photo']

admin.site.register(Profile, ProfileAdmin)

Run the development server again using the python manage.py runserver command. Now, you should be able to see the Profile model in the administration site of your project, as follows:

Extending the User model

Now, we are going to let users edit their profile in the website. Add the following model forms to the forms.py file of the account application:

from .models import Profile

class UserEditForm(forms.ModelForm):
    class Meta:
        model = User
        fields = ('first_name', 'last_name', 'email')

class ProfileEditForm(forms.ModelForm):
    class Meta:
        model = Profile
        fields = ('date_of_birth', 'photo')

These forms are as follows:

  • UserEditForm: Will allow users to edit their first name, last name, and e-mail, which are stored in the built-in User model.
  • ProfileEditForm: Will allow users to edit the extra data we save in the custom Profile model. Users will be able to edit their date of birth and upload a picture for their profile.

Edit the views.py file of the account application and import the Profile model like this:

from .models import Profile

And add the following lines to the register view below new_user.save():

# Create the user profile
profile = Profile.objects.create(user=new_user)

When users register in our site, we create an empty profile associated to them. You should create a Profile object manually using the administration site for the users you created before.

Now, we are going to let users edit their profile. Add the following code to the same file:

from .forms import LoginForm, UserRegistrationForm, 
                   UserEditForm, ProfileEditForm

@login_required
def edit(request):
    if request.method == 'POST':
        user_form = UserEditForm(instance=request.user,
                                 data=request.POST)
        profile_form = ProfileEditForm(
                                    instance=request.user.profile,
                                    data=request.POST,
                                    files=request.FILES)
        if user_form.is_valid() and profile_form.is_valid():
            user_form.save()
            profile_form.save()
    else:
        user_form = UserEditForm(instance=request.user)
        profile_form = ProfileEditForm(
                                    instance=request.user.profile)
    return render(request,
                  'account/edit.html',
                  {'user_form': user_form,
                   'profile_form': profile_form})

We use the login_required decorator because users have to be authenticated to edit their profile. In this case, we are using two model forms: UserEditForm to store the data of the built-in User model and ProfileEditForm to store the additional profile data. To validate the submitted data, we check that the is_valid() method of both forms returns True. In this case, we save both forms to update the corresponding object in the database.

Add the following URL pattern to the urls.py file of the account application:

url(r'^edit/$', views.edit, name='edit'),

Finally, create a template for this view in templates/account/ and name it edit.html. Add the following code to it:

{% extends "base.html" %}

{% block title %}Edit your account{% endblock %}

{% block content %}
  <h1>Edit your account</h1>
  <p>You can edit your account using the following form:</p>
  <form action="." method="post" enctype="multipart/form-data">
    {{ user_form.as_p }}
    {{ profile_form.as_p }}
    {% csrf_token %}
    <p><input type="submit" value="Save changes"></p>
  </form>
{% endblock %}

Note

We include enctype="multipart/form-data" in our form to enable file uploads. We use one HTML form to submit both the user_form and the profile_form forms.

Register a new user and open http://127.0.0.1:8000/account/edit/. You should see the following page:

Extending the User model

Now, you can also edit the dashboard page and include links to edit profile and change password pages. Open the account/dashboard.html template and replace this line:

<p>Welcome to your dashboard.</p>

...with this one:

<p>Welcome to your dashboard. You can <a href="{% url "edit" %}">edit your profile</a> or <a href="{% url "password_change" %}">change your password</a>.</p>

Users can now access the form to edit their profile from their dashboard.

Using a custom User model

Django also offers a way to substitute the whole User model with your own custom model. Your user class should inherit from Django's AbstractUser class, which provides the full implementation of the default user as an abstract model. You can read more about this method at https://docs.djangoproject.com/en/1.8/topics/auth/customizing/#substituting-a-custom-user-model.

Using a custom user model will give you more flexibility, but it might also result in more difficult integration with pluggable applications that interact with the User model.

Using the messages framework

When dealing with user actions, you might want to inform your users about the result of their actions. Django has a built-in messages framework that allows you to display one-time notifications to your users. The messages framework is located at django.contrib.messages and it is included in the default INSTALLED_APPS list of the settings.py file when you create new projects using python manage.py startproject. You will notice that your settings file contains a middleware named django.contrib.messages.middleware.MessageMiddleware in the list of MIDDLEWARE_CLASSES of your settings. The messages framework provides a simple way to add messages to users. Messages are stored in the database and displayed in the next request the user does. You can use the messages framework in your views by importing the messages module and adding new messages with simple shortcuts like this:

from django.contrib import messages
messages.error(request, 'Something went wrong')

You can create new messages using the add_message() method or any of the following shortcut methods:

  • success(): Success messages to display after an action was successful
  • info(): Informational messages
  • warning(): Something has not yet failed but may fail imminently
  • error(): An action was not successful or something failed
  • debug(): Debug messages that will be removed or ignored in a production environment

Let's display messages to users. Since the messages framework applies globally to the project, we can display messages for the user in our base template. Open the base.html template and add the following code between the <div> element with the id header and the <div> element with the id content:

{% if messages %}
  <ul class="messages">
    {% for message in messages %}
      <li class="{{ message.tags }}">
        {{ message|safe }}
          <a href="#" class="close"></a>
      </li>
    {% endfor %}
  </ul>
{% endif %}

The messages framework includes a context processor that adds a messages variable to the request context. So you can use this variable in your templates to display current messages to the user.

Now, let's modify our edit view to use the messages framework. Edit the views.py file of your application and make the edit view look as follows:

from django.contrib import messages

@login_required
def edit(request):
    if request.method == 'POST':
    # ...
        if user_form.is_valid() and profile_form.is_valid():
            user_form.save()
            profile_form.save()
            messages.success(request, 'Profile updated '
                                      'successfully')
        else:
            messages.error(request, 'Error updating your profile')
    else:
        user_form = UserEditForm(instance=request.user)
        # ...

We add a success message when the user successfully updates their profile. If any of the forms are invalid, we add an error message instead.

Open http://127.0.0.1:8000/account/edit/ in your browser and edit your profile. When the profile is successfully updated, you should see the following message:

Using the messages framework

When the form is not valid, you should see the following message:

Using the messages framework
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset