Getting started with Django – A Simple CRUD application

Django is a free and open-source, Python-based web framework that follows the model–template–views architectural pattern. Django advertises itself as “the web framework for perfectionists with deadlines” and “Django makes it easier to build better Web apps more quickly and with less code”.  Django is known for the speed at which you can develop apps without compromising on robustness.

When learning a new language or framework, a new way to go about it is to connect to the database and to architect a simple client to perform insert, read, update and delete records to it, i.e. CRUD.

The application server of any system is a layer on top of a database which provides the ability for the client application to do the following:

  • Create a new entry in a database table
  • Read entry (single record) or entries (list of records) from the database table
  • Update an existing entry in the database either by changing certain details or by replacing it entirely with a new one.
  • Delete a database table entry either by removing it from the database or by simply marking it as deleted and deactivating it.

Django provides a way to perform all those operations and that is what we will learn in this guide.

Related content:

Setting up

Ensure that you have python 3 installed. Consult your OS installation guide. I can confirm that it is installed in my machine using this command:

➜ python --version
Python 3.9.13

Create virtualenv

Although not strictly required, it is highly recommended to start your project in a “virtual environment.” A virtual environment is a container (a directory) that holds a specific version of Python and a set of modules (dependencies), and which does not interfere with the operating system’s native Python or other projects on the same computer. By setting up a different virtual environment for each project you work on, various Django projects can run on different versions of Python, and can maintain their own sets of dependencies, without risk of conflict.

To create the Virtual environment:

python3 -m pip install virtualenv
python3 -m venv <env-folder>

The virtual environment is now set up. In order to use it, it must be activated in the terminal you want to use it.

To ‘activate’ the virtual environment (any Python version):

source /<path to venv>/bin/activate

This changes your prompt to indicate the virtual environment is active. (<env-folder>) $.

From now on, everything installed using pip will be installed to your virtual env folder, not system-wide.

To leave the virtual environment use deactivate :

(<env-folder>) $ deactivate

Install and setting up django

We can now install Django in our virtual env. Use this command to install the latest version of django:

pip install django

Next, we can create a django project. A Django project is a Python codebase that contains a Django settings file. A project can be created by the Django admin through the command django-admin startproject NAME . The project typically has a file called manage.py at the top level and a root URL file called urls.py . manage.py is a project specific version of django-admin , and lets you run management commands on that project. For example, to run your project locally, use python manage.py runserver . A project is made up of Django apps.

Use this command to create a project. I am calling mine djposts.

django-admin startproject djposts

Django app is a Python package that contains a models file (models.py by default) and other files such as app-specific urls and views. An app can be created through the command django-admin startapp NAME (this command should be run from inside your project directory). For an app to be part of a project, it must be included in the INSTALLED_APPS list in settings.py . If you used the standard configuration, Django comes with several apps of it’s own apps preinstalled which will handle things like authentication for you. Apps can be used in multiple Django projects.

cd djposts
python manage.py startapp posts

That will create a folder named posts which will contain the django app files. This is how the directory structure should look like:

➜ tree .
.
├── djposts
│   ├── __init__.py
│   ├── asgi.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── manage.py
└── posts
    ├── __init__.py
    ├── admin.py
    ├── apps.py
    ├── migrations
    │   └── __init__.py
    ├── models.py
    ├── tests.py
    └── views.py

3 directories, 13 files

Now add the app to installed apps in djposts/settings.py:

INSTALLED_APPS = [
    ...
    'posts',
]

Update the djposts/urls.py file with the following content to include the posts.urls:

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('', include('posts.urls')),
    path('admin/', admin.site.urls),
]

Database connection

By default django comes set up to work with sqlite

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}

You can also set up another database of your choice from the list of supported databases. In my case I am using postgres. First ensure that you have a postgres database set up and running then create credentials to use with django. Next install python postgres dependency:

python3 -m pip install psycopg2-binary

Next update the settings file with Postgres connection properties:

DATABASES = {  
    'default': {  
        'ENGINE': 'django.db.backends.postgresql',  
        'NAME': 'djposts',  
        'USER':'djpsts',  
        'PASSWORD':'Python@123',  
        'HOST':'localhost',  
        'PORT':'5432'  
    }  
}  

Django Model

A model is the single, definitive source of information about your data. It contains the essential fields and behaviors of the data you’re storing. Generally, each model maps to a single database table.

Here we create a post model with the fields title, post, created_at and updated_at.

Create posts/models.py

from django.db import models


class Post(models.Model):
    title = models.CharField(max_length=100)
    post = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.title

    class Meta:
        db_table = "posts"

Django Forms

Django’s form functionality can simplify and automate vast portions of this work, and can also do it more securely than most programmers would be able to do in code they wrote themselves.

Django handles three distinct parts of the work involved in forms:

  • preparing and restructuring data to make it ready for rendering
  • creating HTML forms for the data
  • receiving and processing submitted forms and data from the client

It is possible to write code that does all of this manually, but Django can take care of it all for you.

Save this in posts/forms.py:

#-*- coding: utf-8 -*-
from django import forms

from .models import Post

class PostForm(forms.ModelForm):
    class Meta:
        model = Post

        # specify fields to be used
        fields = [
            "title",
            "post",
        ]

Django Views

A view function, or view for short, is a Python function that takes a web request and returns a web response. This response can be the HTML contents of a web page, or a redirect, or a 404 error, or an XML document, or an image . . . or anything, really. The view itself contains whatever arbitrary logic is necessary to return that response. This code can live anywhere you want, as long as it’s on your Python path. There’s no other requirement–no “magic,” so to speak. For the sake of putting the code somewhere, the convention is to put views in a file called views.py, placed in your project or application directory.

Add this content to the posts/views.py file:

from django.shortcuts import render, redirect

from .forms import PostForm
from .models import Post

from logging import getLogger
log = getLogger(__name__)


def index(request):
    posts = Post.objects.all()
    if request.method == "POST":
        form = PostForm(request.POST)
        if form.is_valid():
            try:
                form.save()
                return redirect('/')
            except Exception(e):
                log.error("failed to save post: %s" % e)
    else:
        form = PostForm()
    return render(request, "index.html", {'posts': posts, 'form': form})


def show(request, id):
    post = Post.objects.get(id=id)
    return render(request,'show.html', {'post': post})


def edit(request, id):
    post = Post.objects.get(id=id)
    if request.method == "POST":
        form = PostForm(request.POST, instance=post)
        if form.is_valid():
            form.save()
            return redirect("posts:show", post.id)
    else:
        form = PostForm(instance=post)
    return render(request,'edit.html', {'post': post, 'form': form})


def delete(request, id):
    post = Post.objects.get(id=id)
    post.delete()
    return redirect("/")

Urls

A clean, elegant URL scheme is an important detail in a high-quality web application. Django lets you design URLs however you want, with no framework limitations.

To design URLs for an app, you create a Python module informally called a URLconf (URL configuration). This module is pure Python code and is a mapping between URL path expressions to Python functions (your views).

This mapping can be as short or as long as needed. It can reference other mappings. And, because it’s pure Python code, it can be constructed dynamically.

Django also provides a way to translate URLs according to the active language.

Save this in posts/urls.py:

from django.urls import path

from django.urls import re_path as url
from . import views

app_name = 'posts'

urlpatterns = [
    path('', views.index, name='index'),
    path('show/<int:id>', views.show, name='show'),
    path('edit/<int:id>', views.edit, name='edit'),
    path('delete/<int:id>', views.delete, name='delete'),
]

Django Templates

Being a web framework, Django needs a convenient way to generate HTML dynamically. The most common approach relies on templates. A template contains the static parts of the desired HTML output as well as some special syntax describing how dynamic content will be inserted. 

Create base template, save it in templates/base.html:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="">
    <meta name="author" content="Mark Otto, Jacob Thornton, and Bootstrap contributors">
    <meta name="generator" content="Hugo 0.101.0">
    <title> {% block 'title' %}DJ Posts{% endblock %}</title>

    <link rel="canonical" href="https://citizix.com/">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-gH2yIJqKdNHPEq0n4Mqa/HGKIhSkIHeL5AyhkYV8i59U5AR6csBvApHHNl/vI1Bx" crossorigin="anonymous">

    <meta name="theme-color" content="#712cf9">

    <style>
      .main-content {
        padding: 70px;
        min-height: 400px;
      }
    </style>
  </head>
  <body>

    <nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
      <div class="container">
        <a class="navbar-brand" href="/">DJ Posts</a>
        <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
        <span class="navbar-toggler-icon"></span>
        </button>
      </div>
    </nav>

    <main class="container main-content">
      {% block 'content' %}
      {% endblock %}
    </main>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-A3rJD856KowSb7dwlZdYEkO39Gagi7vIsF0jrRAoQmDKKtQBHUuLZ9AsSv4jD4Xa" crossorigin="anonymous"></script>

  </body>
</html>

For it to be discovered, add this to settings:

import os
...

TEMPLATES = [
    {
        ...
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
       ...
    },
]

For posts/templates/index.html:

{% extends 'base.html' %}

{% block 'title' %} Post List {% endblock %}

{% block 'content' %}

  <div>
    <form class="form-horizontal" action="{% url 'posts:index' %}" method="post">
        {% csrf_token %}
        <div class="form-group row">
            <label class="col-sm-2 col-form-label" for="title">Title</label>
            <div class="col-sm-8">
                <input type="title" id="title" name="title" class="form-control" placeholder="Title"/>
                <span class="help-block text-danger"></span>
            </div>
        </div>
        <div class="form-group row">
            <label class="col-sm-2 col-form-label" for="Post">Post</label>
            <div class="col-sm-8">
              <textarea name="post" id="post" rows="5" class="form-control placeholder="Post"></textarea>
              <span class="help-block text-danger"></span>
            </div>
        </div>
        <div class="form-group row">
            <div class="col-sm-8">
            <button type="submit" class="btn btn-primary">Post</button>
            </div>
        </div>
    </form>
    <hr />
    {% if posts %}
      {% for post in posts %}
        <div>
          <a href="{% url 'posts:show' post.id %}">{{post.title}}</a> - {{post.post}}
          <a href="{% url 'posts:edit' post.id %}">Edit</a> |
          <a href="{% url 'posts:delete' post.id %}" onclick="return confirm('Are you sure?');">Delete</a>
        </div>
      {% endfor %}
    {% else %}
    <p>No posts</p>
    {% endif %}
  </div>
{% endblock %}

For posts/templates/edit.html

{% extends 'base.html' %}

{% block 'title' %} Post Update {% endblock %}

{% block 'content' %}

<div>
    <h1>Edit Post</h1>
    <form class="form-horizontal" action="" method="post">
        {% csrf_token %}

        {% for field in form.visible_fields %}
          <div class="form-group row {{ field.html_name }} {% if field.errors %} error {% endif %}">
            <label class="col-sm-2 col-form-label" for="title">{{ field.label }}{% if field.field.required %} *{% endif %}</label>
            <div class="col-sm-8">
              {{ field }}
              {% if field.errors %}
                  <span class="help-block text-danger">{{ field.errors.as_text }}</span>
              {% endif %}
            </div>
          </div>
        {% endfor %}
        <div class="form-group row">
            <div class="col-sm-8">
            <button type="submit" class="btn btn-primary">Save Changes</button>
            </div>
        </div>
    </form>
</div>

{% endblock %}

For posts/templates/show.html

{% extends 'base.html' %}

{% block 'title' %} Post Details {% endblock %}

{% block 'content' %}

<div>
    <h1>{{ post.title }}</h1>
    <small>Posted on {{ post.created_at }}, Updated at {{ post.updated_at }}</small>
    <div>
        {{ post.post }}
    </div>
</div>

{% endblock %}

Running The Django Project

Migrations are Django’s way of propagating changes you make to your models (adding a field, deleting a model, etc.) into your database schema. They’re designed to be mostly automatic, but you’ll need to know when to make migrations, when to run them, and the common problems you might run into.

First we will need to make migrations for our app:

➜ python manage.py makemigrations

Migrations for 'posts':
  posts/migrations/0001_initial.py
    - Create model Post

Then we can apply the migrations to the database:

➜ python manage.py migrate

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

Finally we can run server to check the content we just created:

➜ python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
July 26, 2022 - 18:35:25
Django version 4.0.6, using settings 'djposts.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

From here you can visit http://127.0.0.1:8000/ then navigate the pages to test the crud functionality we created.

Conclusion

In this guide we learnt how to work with django to create a CRUD system.

Checkout this related content:

comments powered by Disqus
Citizix Ltd
Built with Hugo
Theme Stack designed by Jimmy