Getting started with Django 3.0 | 2020

In this tutorial i will share with you how to build real world application with Django.

What is Django ?

Django is a high level web framework built in top of Python.

Who use Django ?

Django is used by lot of companies such as Facebook, Google, Pinterest, Mozila etc...

What we will build ?

We go to build a simple phone-book application, but the ideas in this tutorial is applicable in any application.

Let's getting started :

Django installation

To install django in our machine we need to follow theses steps :

  • Install Python with PIP
  • Create a virtual environment
  • Install Django on it

Follow these instructions to install Python and PIP. I will create a folder for this project and create an envirotnemnet on it :

$ mkdir django-3-0
$ cd django-3-0
$ python3 -m venv ./my-env

After that we need to activate it

$ source my-env/bin/activate
(my-env)

We will use Django CLI, it comes with very useful commands.

(my-env) $ django-admin startproject phonebook && cd phonebook

To to our application we need to run django server

(my-env) $ python manage.py runserver

The server will start on http://localhost:8000/ Open your browser and go to http://localhost:8000/

Let’s have a look at what startproject created:

├── manage.py
├── phonebook
│ ├── asgi.py
│ ├── **init**.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── Pipfile
└── Pipfile.lock

These files are:

  • The outer phonebook/ root directory is just a container for your project. Its name doesn’t matter to Django; you can rename it to anything you like.
  • manage.py: A command-line utility that lets you interact with this Django project in various ways. You can read all the details about manage.py in django-admin and manage.py.
  • The inner phonebook/ directory is the actual Python package for your project. Its name is the Python package name you’ll need to use to import anything inside it (e.g. phonebook.urls).
  • phonebook/init.py: An empty file that tells Python that this directory should be considered a Python package. If you’re a Python beginner, read more about packages in the official Python docs.
  • phonebook/settings.py: Settings/configuration for this Django project. Django settings will tell you all about how settings work.
  • phonebook/urls.py: The URL declarations for this Django project; a “table of contents” of your Django-powered site. You can read more about URLs in URL dispatcher.
  • phonebook/wsgi.py: An entry-point for WSGI-compatible web servers to serve your project. See How to deploy with WSGI for more details.

Contact application

In order to create our first application we need to run :

(my-env) $ python manage.py startapp contact

Our application looks like this

├── contact
│ ├── admin.py
│ ├── apps.py
│ ├── **init**.py
│ ├── migrations
│ │ └── **init**.py
│ ├── models.py
│ ├── tests.py
│ └── views.py
├── db.sqlite3
├── manage.py
├── phonebook
│ ├── asgi.py
│ ├── **init**.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── Pipfile
└── Pipfile.lock

we will explain later the details of files . Don't forget to add contact to your settings :

INSTALLED_APPS = [
   'django.contrib.admin',
   'django.contrib.auth',
   'django.contrib.contenttypes',
   'django.contrib.sessions',
   'django.contrib.messages',
   'django.contrib.staticfiles',
   'contact' # add this
]

Now inside contact folder let's create our database schema, every contact we need to save :

-First Name -Last Name -Email -Phone Number -The use who created it -And the timestamp

Open models.py and write :

from django.db import models
from django.contrib.auth.models import User

# Create your models here.


class Contact(models.Model):
   first_name = models.CharField(max_length=150)
   last_name = models.CharField(max_length=150)
   phone = models.CharField(max_length=150)
   email = models.CharField(max_length=150, blank=True)
   created_by = models.ForeignKey(User, on_delete=models.CASCADE)
   created_at = models.DateTimeField(auto_now_add=True)

   def __str__(self):
       return self.phone

Here we import the User table from django.contrib and create a Python class, this represent our database schema. By convention we use underscore (_) to separate words. Before going any further we need to migrate our database. In your terminal stop the server and run :

(my-env) $ python manage.py makemigrations

Migrations for 'contact':
 contact/migrations/0001_initial.py
   - Create model Contact

(my-env) $ python manage.py migrate

Operations to perform:
 Apply all migrations: admin, auth, contact, contenttypes, sessions
Running migrations:
 Applying contenttypes.0001_initial... OK
 Applying auth.0001_initial... OK
 Applying admin.0001_initial... OK
 Applying admin.0002_logentry_remove_auto_add... OK
 Applying admin.0003_logentry_add_action_flag_choices... OK
 Applying contenttypes.0002_remove_content_type_name... OK
 Applying auth.0002_alter_permission_name_max_length... OK
 Applying auth.0003_alter_user_email_max_length... OK
 Applying auth.0004_alter_user_username_opts... OK
 Applying auth.0005_alter_user_last_login_null... OK
 Applying auth.0006_require_contenttypes_0002... OK
 Applying auth.0007_alter_validators_add_error_messages... OK
 Applying auth.0008_alter_user_username_max_length... OK
 Applying auth.0009_alter_user_last_name_max_length... OK
 Applying auth.0010_alter_group_name_max_length... OK
 Applying auth.0011_update_proxy_permissions... OK
 Applying contact.0001_initial... OK
 Applying sessions.0001_initial... OK

Let's create a superuser:

(my-env) $ python manage.py createsuperuser

Username (leave blank to use 'hackerpro'): admin
Email address: admin@gmail.com
Password:
Password (again):
Superuser created successfully.

Open your browser and go to http://localhost:8000/admin What you see ? Now we need to add our contact application to the admin. Open admin.py file and write :

# admin.py
from django.contrib import admin
from .models import Contact
# Register your models here.
admin.site.register(Contact)
# views.py
from django.shortcuts import render

def index(request):
    return render(request, "index.html")

Write this in views.py this is a function and it return a template called index.html

We need to define our urls to map this views. Let's create a urls.py file in your main urls.py file add this :

from django.contrib import admin
from django.urls import path, include # add this

urlpatterns = [
    path('admin/', admin.site.urls),
    path("", include("contact.urls")) # add this
]

Now our application looks like this :

├── contact
│   ├── admin.py
│   ├── apps.py
│   ├── __init__.py
│   ├── migrations
│   │   ├── 0001_initial.py
│   │   └── __init__.py
│   ├── models.py
│   ├── tests.py
│   ├── urls.py #new
│   └── views.py
├── db.sqlite3
├── manage.py
├── phonebook
│   ├── asgi.py
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── Pipfile
└── Pipfile.lock

In contact folder go to urls.py and put :

from django.urls import path
from .views import index

urlpatterns = [
    path("", index, name="home")
]

Open your browser and referech the page, what you see ? You see an error like this TemplateDoesNotExist at / To solve this add this code in your settings.py

ROOT_URLCONF = 'phonebook.urls'
TEMPLATE_DIR = os.path.join(BASE_DIR, "templates")  # add this

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [TEMPLATE_DIR], # add this
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

Now we've this structure :

├── contact
│   ├── admin.py
│   ├── apps.py
│   ├── __init__.py
│   ├── migrations
│   │   ├── 0001_initial.py
│   │   └── __init__.py
│   ├── models.py
│   ├── tests.py
│   ├── urls.py
│   └── views.py
├── db.sqlite3
├── manage.py
├── phonebook
│   ├── asgi.py
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── Pipfile
├── Pipfile.lock
└── templates # new
    └── index.html # new
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Home page</title>
  </head>
  <body>
    <h2>Home page</h2>
  </body>
</html>

Display the list of contacts

open views.py and put :

from django.shortcuts import render
from .models import Contact  # add this


def index(request):
    return render(request, "index.html")

# add this


def contact_list(request):
    contacts = Contact.objects.all()
    return render(request, "contact_list.html", {"contacts": contacts})

and in urls.py

from django.urls import path
from .views import index, contact_list  # add this

urlpatterns = [
    path("", index, name="home"),
    path("contacts/", contact_list, name="contacts")  # add this
]

In our template contact_list.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Contacts</title>
  </head>
  <body>
    <h2>Contacts</h2>
    <!-- Check if we've contacts -->
    {% if contacts %}
    <!-- iterate over contact  -->
    {% for contact in contacts %}
    <p>
      {{ contact.first_name }}
    </p>
    {% endfor %}

    {% else %}
    <p>No contact</p>
    {% endif %}
  </body>
</html>

What ? Why you repeat your self ? Do you know DRY concept ? in order to not repeat our self we need to create a base template : in /templates create a file and call it base.html I use Bootstrap starter template

<!DOCTYPE html>
<html lang="en">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1, shrink-to-fit=no"
    />

    <!-- Bootstrap CSS -->
    <link
      rel="stylesheet"
      href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
      integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh"
      crossorigin="anonymous"
    />

    <title>Phonebook!</title>
  </head>
  <body>
    <h2 class="text-center">
      {% block page_title %}

      {% endblock %}
    </h2>
    <div class="container">
      {% block content %}

      {% endblock %}
    </div>

    <!-- Optional JavaScript -->
    <!-- jQuery first, then Popper.js, then Bootstrap JS -->
    <script
      src="https://code.jquery.com/jquery-3.4.1.slim.min.js"
      integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n"
      crossorigin="anonymous"
    ></script>
    <script
      src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"
      integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"
      crossorigin="anonymous"
    ></script>
    <script
      src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"
      integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6"
      crossorigin="anonymous"
    ></script>
  </body>
</html>

Change the index.html and contact_list.html

{% extends 'base.html' %}
{% block page_title %}
Home page
{% endblock %}

{% block content %}
  <div class="row">
    <div class="col-12">
        Home page
    </div>
  </div>
{% endblock %}
{% extends 'base.html' %}
{% block page_title %}
Contacts
{% endblock %}
<!-- Check if we've contacts -->
{% if contacts %}
<!-- iterate over contact  -->
{% for contact in contacts %}

<p>
  {{ contact.first_name }}
</p>
{% endfor %}
{% else %}
<p>No contact</p>
{% endif %}

Read more about Django template syntax here!

In this tutorial we will finish our application, at the end of this tutorial you'll have a fully functional Django application. We will implement :

  • Forms(Login and registration)
  • Template
  • Full CRUD functionalities

Let's write our first form, to do so we need to create a new application and called it accounts

Run

(my-env) $ python manage.py startapp accounts

Inside accounts app create a file and call it forms.py

Now we've this tree of files :

├── accounts # new
│   ├── admin.py
│   ├── apps.py
│   ├── forms.py # new
│   ├── __init__.py
│   ├── migrations
│   │   └── __init__.py
│   ├── models.py
│   ├── tests.py
│   └── views.py
├── contact
│   ├── admin.py
│   ├── apps.py
│   ├── __init__.py
│   ├── migrations
│   │   ├── 0001_initial.py
│   │   └── __init__.py
│   ├── models.py
│   ├── tests.py
│   ├── urls.py
│   └── views.py
├── db.sqlite3
├── manage.py
├── phonebook
│   ├── asgi.py
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── Pipfile
├── Pipfile.lock
└── templates
   ├── base.html
   ├── contact
   │   ├── contact_details.html
   │   ├── contact_list.html

in accounts/forms.py add

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


class LoginForm(forms.Form):
   username = forms.CharField(
       label="Username",
       widget=forms.TextInput(
           attrs={
               "class": "form-control"
           }
       ))
   password = forms.CharField(
       label="password",
       widget=forms.PasswordInput(
           attrs={
               "class": "form-control"
           }
       ))


class SignUpForm(UserCreationForm):
   username = forms.CharField(
       label="Username",
       widget=forms.TextInput(
           attrs={
               "class": "form-control"
           }
       ))
   email = forms.EmailField(
       label="Email ",
       widget=forms.EmailInput(
           attrs={
               "class": "form-control"
           }
       ))
   password1 = forms.CharField(
       label="password",
       widget=forms.PasswordInput(
           attrs={
               "class": "form-control"
           }
       ))
   password2 = forms.CharField(
       label="re-enter your password",
       widget=forms.PasswordInput(
           attrs={
               "class": "form-control"
           }
       ))

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

in accounts/views.py add

from django.shortcuts import render

# Create your views here.
from django.shortcuts import render, redirect
from django.contrib.auth import authenticate, login
from django.contrib.auth.models import User
from .forms import LoginForm, SignUpForm


def login_view(request):
    form = LoginForm(request.POST or None)
    if form.is_valid():
        username = form.cleaned_data.get("username")
        password = form.cleaned_data.get("password")
        user = authenticate(username=username, password=password)
        if user is not None:
            login(request, user)
            return redirect("/contacts/")

    return render(request, "accounts/login.html", {"form": form})


def register_user(request):
    if request.method == "POST":
        form = SignUpForm(request.POST)
        if form.is_valid():
            form.save()
            username = form.cleaned_data.get("username")
            raw_password = form.cleaned_data.get("password1")
            user = authenticate(username=username, password=raw_password)
            return redirect("/login/")
    else:
        form = SignUpForm()
    return render(request, "accounts/register.html", {"form": form})

Create urls.py and add

from django.urls import path
from .views import login_view, register_user
from django.contrib.auth.views import LogoutView

urlpatterns = [
   path('login/', login_view, name="login"),
   path('register/', register_user, name="register"),
   path("logout/", LogoutView.as_view(), name="logout")
]

Don't forget to update your main urls.py

from django.contrib import admin
from django.urls import path, include  # add this

urlpatterns = [
   path('admin/', admin.site.urls),
   path("", include("contact.urls")),  # add this
   path("", include("accounts.urls"))  # add this
]

Now you understand how Django works, here is the full application on Github

Read more about Django Here!

Thanks for reading!

© 2021 Ousseynou • Crafted with ❤️ Let's talk