django tutorial - Part 2. Database Migrations, User Registration, Login Logout system

Categories:   web development  
Tags:   python   django  

This is part 2 of my “django study Notes”.

You can check the previous post here.

Credits:

I repost content I study, this helps me learn quicker.

This post covers parts 4 ~ 7 in Corey Schafer’s django tutorial.

Side Note

Before we begin, make sure your project is up to date if using git:

git remote update
git status
git pull

Also make sure your python virtual environment is activated.

  • virtualenv:
source .venv/scripts/activate

Also, when you install new packages like we’ll do in this post, it’s a good idea to add it to your requirements.txt file so it’s easier to get started on other machines.

To save all installed packages to the requirements.txt file:

pip freeze > requirements.txt

To install all packages based on the requirements.txt file:

pip install -r requirements.txt

The Admin page

  • migrate the database first with the defaults:
$ python manage.py makemigrations
  • apply the migration:
$ python manage.py migrate

Create a super user

$ python manage.py createsuperuser
  • fill in the required information for the super user.
  • goto: localhost:8000/admin log in with the created account.

Further Database Migrations

  • We’ll create a model for Posts with a one/many relationship with User.
  • blog/models.py:
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User

class Post(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()
    date_posted = models.DateTimeField(default=timezone.now)
    # date_updated = models.DateTimeField(auto_now=True)
    author = models.ForeignKey(User, on_delete=models.CASCADE)
  • apply the migration:
$ python manage.py makemigrations
  • this will create the migration file.

View the sql for the migration for debugging

  • use the app name and migration number found inside the migrations folder:
$ python manage.py sqlmigrate blog 0001
  • we can run the migration:
$ python manage.py migrate

django orm to query the database

  • run the python shell:
$ python manage.py shell
$ from blog.models import Post
$ from django.contrib.auth.models import User
$ User.objects.all()
$ User.objects.first()
$ User.objects.last()
$ User.objects.filter(username='john')
$ User.objects.filter(username='john').first()
$ user = User.objects.filter(username='john').first()
$ user
$ user.id
$ user.pk
$ user = User.objects.get(id=1)
$ user
$ Post.objects.all()
$ post_1 = Post(title='blog 1', content='the first content', author=user)
$ Post.objects.all()
$ post_1.save()
$ Post.objects.all()

Create a __str__ method (dunder, magic, special methods)

  • blog/models.py:
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User

class Post(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()
    date_posted = models.DateTimeField(default=timezone.now)
    # date_updated = models.DateTimeField(auto_now=True)
    author = models.ForeignKey(User, on_delete=models.CASCADE)

    def __str__(self):
        return self.title
  • restart the python shell:
$ exit()
$ python manage.py shell
$ from blog.models import Post
$ from django.contrib.auth.models import User
$ Post.objects.all()
$ user = User.objects.filter(username='john').first()
$ user
$ post_2 = Post(title='blog 2', content='second post content', author_id=user.id)
$ post_2.save()
$ Post.objects.all()
$ post = Post.objects.first()
$ post.content
$ post.date_posted
$ post.author
$ post.author.email

$ user.post_set
$ user.post_set.all()
$ user.post_set.create(title='blog 3', content='third blog content')
$ exit()

Query some post form the database

  • import the Post in blog/views.py:
from .models import Post
  • change the context to fetch the Post model:
def home(request):
    context = {
        'posts': Post.objects.all()
    }
    return render(request, 'blog/home.html', context)
  • run the server and check the site:
$ python manage.py runserver
  • The posts are displaying from the database now but the datetime format includes the time.
  • let’s remove the time by updating a line in blog/templates/home.html:
<small class="text-muted">{{ post.date_posted|date:"F d, Y" }}</small>
  • let’s remove the dummy posts data from blog/views.py, so the file looks like:
from django.shortcuts import render
# from django.http import HttpResponse
from .models import Post

def home(request):
    context = {
        'posts': Post.objects.all()
    }
    return render(request, 'blog/home.html', context)

def about(request):
    return render(request, 'blog/about.html', {'title': 'About'})

Adding the Post model to the admin page

  • import and register the Post model in blog/admin.py:
from django.contrib import admin
from .models import Post

admin.site.register(Post)
  • now in the admin page, you can add and edit the posts.

User Registration

  • Create a new app for new users to register:
$ python manage.py startapp users
  • add this to the INSTALLED_APPS in settings.py :
INSTALLED_APPS = [
    'blog.apps.BlogConfig',
    'users.apps.UsersConfig',
    'crispy_forms',

We’ll install crispy forms for some nice styling:

$ pip install django-crispy-forms
$ pip freeze > requirements.txt
  • make crispy_forms use bootstrap 4 instead of the default bootstrap 2
  • add this line to the end of the settings.py file:
CRISPY_TEMPLATE_PACK = 'bootstrap4'
  • We should add this users app to INSTALLED_APPS in the projects settings.py:
INSTALLED_APPS = [
    'blog.apps.BlogConfig',
    'users.apps.UsersConfig',

We’ll also create a new form that inherits from the UserCreationForm.

create a new file users/forms.py:

from django import forms
from django .contrib.auth.models import User
from django .contrib.auth.forms import UserCreationForm

# extends UserCreationForm and add the email field
class UserRegisterForm(UserCreationForm):
    email = forms.EmailField()

    # configurations 
    class Meta:
        model = User
        fields = ['username', 'email', 'password1', 'password2']

This file extends the UserCreationForm but with the addition of our email field.

The users view

  • create a new view in users/view.py:
from django.shortcuts import render, redirect
# from django.contrib.auth.forms import UserCreationForm
from django.contrib import messages
from .forms import UserRegisterForm

def register(request):
    if request.method == 'POST':
        # form = UserCreationForm(request.POST) # POST => form with the users input
        form = UserRegisterForm(request.POST)
        if form.is_valid():
            form.save() # saves to database
            username = form.cleaned_data.get('username')
            messages.success(request, f'Account created for {username}!')
            return redirect('blog-home')
    else:
        # form = UserCreationForm()
        form = UserRegisterForm()
    return render(request, 'users/register.html', {'form': form})

# types of messages
# messages.debug
# messages.info
# messages.success
# messages.warning
# messages.error
  • the UserCreationForm helps us create the form quickly.
  • If have a conditional for POST request to handle submitted data.
  • We have a conditional to check form validation.
  • We included messages to display a message to the user.
  • The messages block will be added to the base.html file.
  • We will redirect the user after registration is successful.

Add the message block to the main base.html file:

</header>
    <main role="main" class="container">
        <div class="row">
            <div class="col-md-8">
                {% if messages %}
                    {% for message in messages %}
                        <div class="alert alert-{{ message.tags }}">
                            {{ message }}
                        </div>
                    {% endfor %}
                {% endif %}
                {% block content %}{% endblock %}
            </div>

The User templates

  • Create the template folder for the users app:
  • users > templates > users > register.html
  • register.html:
{% extends "blog/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
    <div class="content-section">
        <form method="POST">
            {% csrf_token %}
            <fieldset class="form-group">
                <legend class="border-bottom mb-4">Join Today</legend>
                {{ form|crispy }}
            </fieldset>
            <div class="form-group">
                <button class="btn btn-outline-info" type="submit">Sign Up</button>
            </div>
        </form>
        <div class="border-top pt-3">
            <small class="text-muted">
                Already Have An Account? <a href="#" class="ml-2">Sign In</a>
            </small>
        </div>
    </div>
{% endblock content %}
  • always include the csrf token with the form like above.

Url pattern

  • Create a urlpattern for this form:
  • import the users view as well as creating the url pattern in the projects urls.py file:
from django.contrib import admin
from django.urls import path, include
from users import views as user_views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('register/', user_views.register, name='register'),
    path('', include('blog.urls')),
]
  • run the server the check the register page:
$ python manage.py runserver
  • The register page should be visible now with a clean layout and working validations.

Authentication

  • import the auth_views in the main urls.py file as well as the urlpatterns.
  • Also add the profile view which we’ll use later.
from django.contrib import admin
from django.contrib.auth import views as auth_views
from django.urls import path, include
from users import views as user_views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('register/', user_views.register, name='register'),
    path('profile/', user_views.profile, name='profile'),
    path('login/', auth_views.LoginView.as_view(template_name='users/login.html'), name='login'),
    path('logout/', auth_views.LogoutView.as_view(template_name='users/logout.html'), name='logout'),
    path('', include('blog.urls')),
]
  • We’ll set the template files using the template_name, and won’ use the default locations.

  • let’s create these two files users/login.html & users/logout.html.

  • login.html:

{% extends "blog/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
    <div class="content-section">
        <form method="POST">
            {% csrf_token %}
            <fieldset class="form-group">
                <legend class="border-bottom mb-4">Log In</legend>
                {{ form|crispy }}
            </fieldset>
            <div class="form-group">
                <button class="btn btn-outline-info" type="submit">Login</button>
            </div>
        </form>
        <div class="border-top pt-3">
            <small class="text-muted">
                Need An Account? <a href="{% url 'register' %}" class="ml-2">Sign Up</a>
            </small>
        </div>
    </div>
{% endblock content %}

let’s update our register.html too:

{% extends "blog/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
    <div class="content-section">
        <form method="POST">
            {% csrf_token %}
            <fieldset class="form-group">
                <legend class="border-bottom mb-4">Join Today</legend>
                {{ form|crispy }}
            </fieldset>
            <div class="form-group">
                <button class="btn btn-outline-info" type="submit">Sign Up</button>
            </div>
        </form>
        <div class="border-top pt-3">
            <small class="text-muted">
                Already Have An Account? <a href="{% url 'login' %}" class="ml-2">Sign In</a>
            </small>
        </div>
    </div>
{% endblock content %}
  • we’ll also add a setting to redirect users that log in.
  • Add this to the end of the main settings.py file:
LOGIN_REDIRECT_URL = 'blog-home'
LOGIN_URL = 'login'
  • We also add ‘login’ urlpattern as LOGIN_URL to change the default login route.

  • Let’s also have registered user redirect to the login page in user/views.py:

from django.shortcuts import render, redirect
# from django.contrib.auth.forms import UserCreationForm
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from .forms import UserRegisterForm

def register(request):
    if request.method == 'POST':
        # form = UserCreationForm(request.POST) # POST => form with the users input
        form = UserRegisterForm(request.POST)
        if form.is_valid():
            form.save() # saves to database
            username = form.cleaned_data.get('username')
            messages.success(request, f'Your account has been created! You can now log in.')
            return redirect('login')
    else:
        # form = UserCreationForm()
        form = UserRegisterForm()
    return render(request, 'users/register.html', {'form': form})

@login_required
def profile(request):
    return render(request, 'users/profile.html')
  • We changed the message and redirect them to the login page now.
  • We also added a profile method we’ll use later.
  • We imported a login_required decorator.

  • The users/logout.html:

{% extends "blog/base.html" %}
{% block content %}
    <h2>You have been logged out</h2>
    <div class="border-top pt-3">
        <small class="text-muted">
            <a href="{% url 'login' %}">Log In Again</a>
        </small>
    </div>
{% endblock content %}
  • Fix up the Nav bar based on login / logout status.
  • In the main base.html, change this:
<!-- Navbar Right Side -->
<div class="navbar-nav">
    <a class="nav-item nav-link" href="#">Login</a>
    <a class="nav-item nav-link" href="#">Register</a>
</div>

to this:

<!-- Navbar Right Side -->
<div class="navbar-nav">
    {% if user.is_authenticated %}
        <a class="nav-item nav-link" href="{% url 'profile' %}">Profile</a>
        <a class="nav-item nav-link" href="{% url 'logout' %}">Logout</a>
    {% else %}
        <a class="nav-item nav-link" href="{% url 'login' %}">Login</a>
        <a class="nav-item nav-link" href="{% url 'register' %}">Register</a>                            
    {% endif %}
</div>
  • this will show different links based on whether the user is logged in or not.
  • We also added a link for the profile page that we’ll set up later.

let’s now create a User’s profile page.

  • let’s also create this file, users/profile.html:
{% extends "blog/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
    <h1>{{ user.username }}</h1>
{% endblock content %}
  • The profile page is now visible and only for logged in users.

I think we will end this section here.

Summary

In this article we covered:

  • Database migrations.
  • briefly looked at ORM queries.
  • Added the Post Model to the admin page.
  • User Registration.
  • User Authentication - login/logout.
  • User Authentication - the login_required decorator.

In the next post we’ll cover managing the user profile.

Related Products



Categories:   web development  
Tags:   python   django