Django 3 Tutorial & CRUD Example with MySQL and Bootstrap

  • Author: Ahmed Bouchefra

Django 3 is released with full async support! In this tutorial, we'll see by example how to create a CRUD application from scratch and step by step. We'll see how to configure a MySQL database, enable the admin interface, and create the django views.

We'll be using Bootstrap 4 for styling.

You'll learn how to:

  • Implement CRUD operations,
  • Configure and access a MySQL database,
  • Create django views, templates and urls,
  • Style the UI with Bootstrap 4

Django 3 Features

Django 3 comes with many new features such as:

  • MariaDB support: Django now officially supports MariaDB 10.1+. You can use MariaDB via the MySQL backend,
  • ASGI support for async programming,
  • Django 3.0 provides support for running as an ASGI application, making Django fully async-capable
  • Exclusion constraints on PostgreSQL: Django 3.0 adds a new ExclusionConstraint class which adds exclusion constraints on PostgreSQL, etc.

Prerequisites

Let's start with the prerequisites for this tutorial. In order to follow the tutorial step by step, you'll need a few requirements, such as:

  • Basic knowledge of Python,
  • Working knowledge of Django (django-admin.py and manage.py),
  • A recent version of Python 3 installed on your system (the latest version is 3.7),
  • MySQL database installed on your system.

We will be using pip and venv which are bundled as modules in recent versions of Python so you don't actually need to install them unless you are working with old versions.

If you are ready, lets go started!

Django 3 Tutorial, Step 1 - Creating a MySQL Database

In this step, we'll create a mysql database for storing our application data.

Open a new command-line interface and run the mysql client as follows:

$ mysql -u root -p

You'll be prompted for your MySQL password, enter it and press Enter.

Next, create a database using the following SQL statement:

mysql> create database mydb;

We now have an empty mysql database!

Django 3 Tutorial, Step 2 - Initializing a New Virtual Environment

In this step, we'll initialize a new virtual environment for installing our project packages in separation of the system-wide packages.

Head back to your command-line interface and run the following command:

$ python3 -m venv .env

Next, activate your virtual environment using the following command:

$ source .env/bin/activate

At this point of our tutorial, we've a mysql database for persisting data and created a virtual environment for installing the project packages.

Django 3 Tutorial, Step 3 - Installing Django and MySQL Client

In this step, we'll install django and mysql client from PyPI using pip in our activated virtual environment.

Head back to your command-line interface and run the following command to install the django package:

$ pip install django

At the time of writing this tutorial, django-3.0.2 is installed.

You will also need to install the mysql client for Python using pip:

$ pip install mysqlclient

Django 3 Tutorial, Step 4 - Initializing a New Project

In this step, we'll initialize a new django project using the django-admin.

Head back to your command-line interface and run the following command:

$ django-admin startproject djangoCrudExample

Next, open the settings.py file and update the database settings to configure the mydb database:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql', 
        'NAME': 'mydb',
        'USER': 'root',
        'PASSWORD': '<YOUR_DB_PASSWORD>',
        'HOST': 'localhost',   
        'PORT': '3306',
    }    
}

Next, migrate the database using the following commands:

$ cd djangoCrudExample
$ python3 manage.py migrate

You'll get a similar output:

Operations to perform:
  Apply all migrations: admin, auth, 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 sessions.0001_initial... OK

This simply applies a set of builtin django migrations to create some necessary database tables or the working of django.

Django 3 Tutorial, Step 5 - Installing django-widget-tweaks

In this step, we'll install django-widget-tweaks in our virtual environment. Head back to your command-line interface and run the following command:

$ pip insll django-widget-tweaks

Next, open the settings.py file and add the application to the installed apps:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'widget_tweaks'
]

Django 3 Tutorial, Step 6 - Creating an Admin User

In this step, we'll create an admin user that will allow us to access the admin interface of our app using the following command:

$ python manage.py createsuperuser

Provide the desired username, email and password when prompted:

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

Django 3 Tutorial, Step 7 - Creating a Django Application

In this step, we'll create a django application.

Head back to your command-line interface, and run the following command:

$ python manage.py startapp crudapp

Next, you need to add it in the settings.py file as follows:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'widget_tweaks',
    'crudapp'
]

Django 3 Tutorial, Step 8 - Creating the Model(s)

In this step. we'll create the database model for storing contacts.

Open the crudapp/models.py file and add the following code:

from django.db import models


class Contact(models.Model):
    firstName = models.CharField("First name", max_length=255, blank = True, null = True)
    lastName = models.CharField("Last name", max_length=255, blank = True, null = True)
    email = models.EmailField()
    phone = models.CharField(max_length=20, blank = True, null = True)
    address = models.TextField(blank=True, null=True)
    description = models.TextField(blank=True, null=True)
    createdAt = models.DateTimeField("Created At", auto_now_add=True)

    def __str__(self):
        return self.firstName

After creating these model, you need to create migrations using the following command:

$ python manage.py makemigrations

You should get a similar output:

  crudapp/migrations/0001_initial.py
    - Create model Contact

Next, you need to migrate your database using the following command:

$ python manage.py migrate

You should get a similar output:

  Applying crudapp.0001_initial... OK

Django 3 Tutorial, Step 9 - Creating a Form

In this step, we'll create a form for creating a contact.

In the crudapp folder, create a forms.py file and add the following code:

from django import forms
from .models import Contact

class ContactForm(forms.ModelForm):
    class Meta:
        model = Contact
        fields = "__all__"

We import the Contact model from the models.py file. We created a class called ContactForm, subclassing Django’s ModelForms from the django.forms package and specifying the model we want to use. We also specified that we will be using all fields in the Contact model. This will make it possible for us to display those fields in our templates.

Django 3 Tutorial, Step 10 - Creating the Views

In this step, we'll create the views for performing the CRUD operations.

Open the crudapp/views.py file and add:

from django.shortcuts import render, redirect, get_object_or_404
from .models import Contact
from .forms import ContactForm
from django.views.generic import ListView, DetailView

Next, add:

class IndexView(ListView):
    template_name = 'crudapp/index.html'
    context_object_name = 'contact_list'

    def get_queryset(self):
        return Contact.objects.all()

class ContactDetailView(DetailView):
    model = Contact
    template_name = 'crudapp/contact-detail.html'

Next, add:

def create(request):
    if request.method == 'POST':
        form = ContactForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect('index')
    form = ContactForm()

    return render(request,'crudapp/create.html',{'form': form})

def edit(request, pk, template_name='crudapp/edit.html'):
    contact = get_object_or_404(Contact, pk=pk)
    form = ContactForm(request.POST or None, instance=post)
    if form.is_valid():
        form.save()
        return redirect('index')
    return render(request, template_name, {'form':form})

def delete(request, pk, template_name='crudapp/confirm_delete.html'):
    contact = get_object_or_404(Contact, pk=pk)
    if request.method=='POST':
        contact.delete()
        return redirect('index')
    return render(request, template_name, {'object':contact})

Django 3 Tutorial, Step 11 - Creating Templates

Open the settings.py file and add os.path.join(BASE_DIR, 'templates') to the TEMPLATES array:

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        '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',
            ],
        },
    },
]

This will tell django to look for the templates in the templates folder.

Next, inside the crudapp folder create a templates folder:

$ mkdir templates

Next, inside the templates folder, create the following files:

  • base.html
  • confirm_delete.html
  • edit.html
  • index.html
  • create.html
  • contact-detail.html

By running the following commands from the root of your project:

$ mkdir templates
$ cd templates
$ mkdir crudapp
$ touch crudapp/base.html
$ touch crudapp/confirm_delete.html
$ touch crudapp/edit.html
$ touch crudapp/index.html
$ touch crudapp/create.html
$ touch crudapp/contact-detail.html

Open the crudapp/templates/base.html file and the add:

<!DOCTYPE html>
<html>
<head>
 <title>Django 3 CRUD Example</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.0/css/bootstrap.min.css">
</head>
<body>
{% block content %}
{% endblock %}
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.0/js/bootstrap.min.js"></script>
</body>
</html>

Next, open the crudapp/templates/index.html file and the add:

{% extends 'crudapp/base.html' %}
{% block content %}
<div class="container-fluid">
    <div class="row">
        <div class="col-md-1 col-xs-1 col-sm-1"></div>
        <div class="col-md-10 col-xs-10 col-sm-10">
            <h3 class="round3" style="text-align:center;">Contacts</h3>
        </div>
        <div class="col-md-1 col-xs-1 col-sm-1"></div>
    </div>
    <div class="row">
        <div class="col-md-10 col-xs-10 col-sm-10"></div>
        <div class="col-md-2 col-xs-1 col-sm-1">
            <br />
            <a href="{% url 'create' %}">
                <button type="button" class="btn btn-success">
                    <span class="glyphicon glyphicon-plus"></span>
                </button>
            </a>
        </div>
    </div>
    <br />
    {% for contact in contact_list %}
    <div class="row">
        <div class="col-md-1 col-xs-1 col-sm-1"></div>
        <div class="col-md-7 col-xs-7 col-sm-7">
            <ul class="list-group">

                <li class="list-group-item ">
                    <a href="{% url 'detail' contact.pk %}"> {{ contact.firstName }} {{contact.lastName}} </a> <span class="badge"></span>
                </li>
            </ul>
            <br>
        </div>
        <div class="col-md-1 col-xs-1 col-sm-1">
            <a href="{% url 'detail' contact.pk %}">
                <button type="button" class="btn btn-info">
                    <span class="glyphicon glyphicon-open"></span>
                </button>
            </a>
        </div>
        <div class="col-md-1">
            <a href="{% url 'edit' contact.pk %}">
                <button type="button" class="btn btn-info">
                    <span class="glyphicon glyphicon-pencil"></span>
                </button>
            </a>
        </div>
        <div class="col-md-1">
            <a href="{% url 'delete' contact.pk %}">
                <button type="button" class="btn btn-danger">
                    <span class="glyphicon glyphicon-trash"></span>
                </button>
            </a>
        </div>
        <div class="col-md-1 col-xs-1 col-sm-1"></div>
    </div>
    {% endfor %}
</div>
{% endblock %}

Next, open the crudapp/templates/create.html file and the add:

{% load widget_tweaks %}
<!DOCTYPE html>
<html>

<head>
    <title>Posts</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css"
        integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
    <style type="text/css">
        <style>
    </style>
    </style>

</head>

<body>

    <div class="container-fluid">
        <div class="row">
            <div class="col-md-1 col-xs-1 col-sm-1"></div>

            <div class="col-md-10 col-xs-10 col-sm-10 ">
                <br />
                <h6 style="text-align:center;">
                    <font color="red"> All fields are required</font>
                </h6>
            </div>
            <div class="col-md-1 col-xs-1 col-sm-1">
            </div>
        </div>
        <div class="row">
            <div class="col-md-1 col-xs-1 col-sm-1"></div>
            <div class="col-md-10 col-xs-10 col-sm-10">
                <form method="post" novalidate>
                    {% csrf_token %}
                    {% for hidden_field in form.hidden_fields %}
                    {{ hidden_field }}
                    {% endfor %}
                    {% for field in form.visible_fields %}
                    <div class="form-group">
                        {{ field.label_tag }}
                        {% render_field field class="form-control" %}
                        {% if field.help_text %}
                        <small class="form-text text-muted">{{ field.help_text }}</small>
                        {% endif %}
                    </div>
                    {% endfor %}
                    <button type="submit" class="btn btn-primary">post</button>
                </form>
                <br>
            </div>
            <div class="col-md-1 col-xs-1 col-sm-1"></div>
        </div>
    </div>
    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
        integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
        crossorigin="anonymous"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js"
        integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49"
        crossorigin="anonymous"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js"
        integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy"
        crossorigin="anonymous"></script>
</body>

</html>

Next, open the crudapp/templates/edit.html file and the add:

{% load widget_tweaks %}
<!DOCTYPE html>
<html>

<head>
    <title>Edit Contact</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css"
        integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
    <style type="text/css">
        <style>
    </style>
    </style>

</head>

<body>
<div class="container-fluid">
    <div class="row">
        <div class="col-md-1 col-xs-1 col-sm-1"></div>
        <div class="col-md-10 col-xs-10 col-sm-10 ">
            <br />
            <h6 style="text-align:center;">
                <font color="red"> All fields are required</font>
            </h6>
        </div>
        <div class="col-md-1 col-xs-1 col-sm-1">
        </div>
    </div>
    <div class="row">
        <div class="col-md-1 col-xs-1 col-sm-1"></div>
        <div class="col-md-10 col-xs-10 col-sm-10">
            <form method="post" novalidate>
                {% csrf_token %}
                {% for hidden_field in form.hidden_fields %}
                {{ hidden_field }}
                {% endfor %}
                {% for field in form.visible_fields %}
                <div class="form-group">
                    {{ field.label_tag }}
                    {% render_field field class="form-control" %}
                    {% if field.help_text %}
                    <small class="form-text text-muted">{{ field.help_text }}</small>
                    {% endif %}
                </div>
                {% endfor %}
                <button type="submit" class="btn btn-primary">submit</button>
            </form>
            <br>
        </div>
        <div class="col-md-1 col-xs-1 col-sm-1"></div>
    </div>
</div>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
</body>
</html>

Next, open the crudapp/templates/confirm_delete.html file and the add:

{% extends 'crudapp/base.html' %}
{% block content %}
<div class="container">
    <div class="row"></div>
    <br />
    <div class="row">
        <div class="col-md-2 col-xs-2 col-sm-2"></div>
        <div class="col-md-10 col-xs-10 col-sm-10">
            <form method="post">
                {% csrf_token %}
                <div class="form-row">
                    <div class="alert alert-warning">
                        Are you sure you want to delete {{ object }}?
                    </div>
                </div>
                <button type="submit" class="btn btn-danger">
                    <span class="glyphicon glyphicon-trash"></span>
                </button>
            </form>
        </div>
    </div>
</div>
{% endblock %}

Django 3 Tutorial, Step 12 - Creating URLs

In this step, we'll create the urls to access our CRUD views.

Go to the urls.py file and update it as follows:

from django.contrib import admin
from django.urls import path
from crudapp import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('contacts/', views.IndexView.as_view(), name='index'),
    path('contacts/<int:pk>/', views.ContactDetailView.as_view(), name='detail'),
    path('contacts/edit/<int:pk>/', views.edit, name='edit'),
    path('contacts/create/', views.create, name='create'),
    path('contacts/delete/<int:pk>/', views.delete, name='delete'),
]

Django 3 Tutorial, Step 11 - Running the Local Development Server

In this step, we'll run the local development server for playing with our app without deploying it to the web.

Head back to your command-line interface and run the following command:

$ python manage.py runserver

Next, go to the http://localhost:8000/ address with a web browser.

Conclusion

In this django 3 tutorial, we have initialized a new django project, created and migrated a MySQL database, and built a simple CRUD interface. You may also find Django developer jobs with Jooble.


Sponsored Links

Latest Questions and Answers