Posted on

Django CRUD with Class Based Views

Here I want to present a simple implementation of a CRUD with class based views of Django.

Are you in a hurry?

If so, go straight to the working version of this project: https://github.com/albertosdneto/django_simple_crud_cbv

If you want to read just a little more, go on reading the rest of this post.

Virtual Environment

First thing we need to do is to prepare a virtual environment for our project. Open the console (on Linux) and proceed as follows:

Create a directory and enter:

mkdir djangoCrud
cd djangoCrud

Create the virtual environment with Python 3:

python3 -m venv venv

Then activate the virtual environment:

. venv/bin/activate

Upgrade the virtual environment:

pip install --upgrade pip setuptools

Install Django:

pip install django

Project Creation and Setup

So far we have prepared our environment to develop our project with a certain control of the dependencies. Now we want to create the Django project.

Create the project:

django-admin startproject crudCbv

Enter the crudCBV folder and run the project to see if everything is working:

cd crudCBV
python manage.py runserver

Go to http://127.0.0.1:8000/ and you should see the page below:

Default page for new Django project

Stop the sever (use Ctrl + c on terminal) and create the app books:

python manage.py startapp books

Go to crudCBV/settings.py and register the books app:

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

Create the folders templates and static at the same level as books app. At this moment our directory tree should look like this:

crudCBV/
├── books
│   ├── admin.py
│   ├── apps.py
│   ├── __init__.py
│   ├── migrations
│   │   └── __init__.py
│   ├── models.py
│   ├── tests.py
│   └── views.py
├── crudCBV
│   ├── asgi.py
│   ├── __init__.py
│   ├── __pycache__
│   │   ├── __init__.cpython-36.pyc
│   │   ├── settings.cpython-36.pyc
│   │   ├── urls.cpython-36.pyc
│   │   └── wsgi.cpython-36.pyc
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── db.sqlite3
├── manage.py
├── static
└── templates

Go back to the file settings.py and configure it for the folder templates:

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': ['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',
            ],
        },
    },
]

Still at settings.py configure it for static files:

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.0/howto/static-files/

STATIC_URL = '/static/'
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'static'),
]

Enter the templates folder and create the file base.html:

{% load static %}
<!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">

     <!-- Principal CSS do Bootstrap -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
    
    <link rel="stylesheet" href="{% static 'css/master.css'%}">
    
    <title>Django simple crud with cbv</title>
</head>

<body>

    <nav class="navbar navbar-expand-md navbar-dark fixed-top bg-info">
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarCollapse">
            <ul class="navbar-nav ml-auto mr-auto">
                <li class="nav-item active">
                    <a class="nav-link" href="{% url 'home' %}">Home</a>
                </li>
                <li class="nav-item active">
                    <a class="nav-link" href="{% url 'create' %}">New</a>
                </li>
            </ul>
        </div>
    </nav>
    <main role="main" class="container">

        <div class="container mt-5 mb-5 page-section">
            <div class="col">   
                {% block content %} {% endblock content %}
            </div>
        </div>



    </main>

<!-- Principal JavaScript do Bootstrap
    ================================================== -->

    <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
    <script>
        window.jQuery || document.write('<script src="../../assets/js/vendor/jquery-slim.min.js"><\/script>')
    </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>

Go to the static folder and create the folder css and inside it create the file master.css:

body {
    background-color: white;
    padding-top: 4.5rem;
}

Model for our data

In order to persist data on sqlite we need to create a model for it. Inside the books app open the file models.py and paste the code below:

from django.db import models

# Create your models here.
class Book(models.Model):
    title = models.CharField(max_length=250)
    author = models.CharField(max_length=250)
    pages = models.IntegerField()

Inside the database our table will have the columns title, author and pages.

Views for our books app

Let’s create the views for our app. Inside the folder books open the file views.py and make it the way it is below:

from .models import Book
from django.shortcuts import render
from django.views.generic import DetailView
from django.views.generic.list import ListView
from django.views.generic.edit import CreateView, UpdateView, DeleteView


class BookListView(ListView):
    model = Book


class BookCreateView(CreateView):
    model = Book
    fields = ['title', 'author', 'pages']
    success_url = '/'


class BookDetailView(DetailView):
    model = Book


class BookUpdateView(UpdateView):
    model = Book
    fields = ['title', 'author', 'pages']
    success_url = '/'


class BookDeleteView(DeleteView):
    model = Book
    success_url = '/'

When we use Class Based Views, Django expects us to provide templates with certain name patterns. So, inside the folder books, create a folder templates and a sub-folder books. inside this new books folder create the files book_confirm_delete.html, book_detail.html, book_form.html, book_list.html and book_update_form.html.

Our directory tree for books app should be like this:

books/
├── admin.py
├── apps.py
├── __init__.py
├── migrations
│   └── __init__.py
├── models.py
├── templates
│   └── books
│       ├── book_confirm_delete.html
│       ├── book_detail.html
│       ├── book_form.html
│       ├── book_list.html
│       └── book_update_form.html
├── tests.py
└── views.py

For file book_confirm_delete.html (necessary for BookDeleteView) we should have:

{% extends 'base.html' %}

{% block content %}
<form method='post'>
{% csrf_token %}
<p>Are you sure you want to delete?</p>
<input type="submit" Value="Delete">
</form>
{% endblock content%}

For file book_detail.html (BookDetailView):

{% extends 'base.html' %}

{% block content %}
<p>
<strong>Title: </strong>{{ object.title }}<br/>
<strong>Author: </strong>{{ object.author }}<br/>
<strong>Pages: </strong>{{ object.pages }}<br/>
</p>
{% endblock content %}

For file book_form.html (BookCreateView):

{% extends 'base.html' %}

{% block content %}

<form method='post'>
{% csrf_token %}
{{ form.as_p }}
<input type='submit' value='save'>
</form>

{% endblock content %}

For book_list.html (BookListView):

{% extends 'base.html'%}

{% block content %}

    {% if object_list %}
        <ul>
        {% for book in object_list %}
        <li>
            <a href="{% url 'detail' book.id %}">{{book.title}}</a> | 
            <a href="{% url 'update' book.id %}">Update</a> | 
            <a href="{% url 'delete' book.id %}">Delete</a>
        </li>
        {% endfor %}
        </ul>
    {% else %}
        <p>No book on database.</p>
    {% endif %}

{% endblock content%}

For book_update_form.html (BookUpdateView):

{% extends 'base.html' %}

{% block content %}

<form method='post'>
{% csrf_token %}
{{ form.as_p }}
<input type='submit' value='save'>
</form>

{% endblock content %}

URLs for our app

For the sake of simplicity at this tutorial, we are going to keep our urls at the file urls.py contained at crudCBV folder. Open it and make it as below:

"""crudCBV URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/3.0/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include
from books.views import BookListView, BookCreateView, BookDetailView, BookUpdateView, BookDeleteView

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', BookListView.as_view(), name='home'),
    path('create/', BookCreateView.as_view(), name='create'),
    path('detail/<int:pk>', BookDetailView.as_view(), name='detail'),
    path('update/<int:pk>', BookUpdateView.as_view(), name='update'),
    path('delete/<int:pk>', BookDeleteView.as_view(), name='delete'),
]

Running the app

Now that all our code is in place, let’s run it.

First, make migrations in order to prepare the database:

python manage.py makemigrations
python manage.py migrate

Now run the project:

python manage.py runserver

If everything was fine you should the following:

Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
January 26, 2020 - 16:30:39
Django version 3.0.2, using settings 'crudCBV.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

At http://127.0.0.1:8000/ you should see:

Test it. Try to add some entries, edit and delete.

Good Luck!