Django for Beginners #3 - The CRUD Operations
Tags: django, python, webdev, beginners
Published: 2023-03-28 14:30:00

> [Download source code here.](https://www.ericsdevblog.com/posts/django-for-beginners-1?ref=Dev_Community/) ⬅️

We introduced many new concepts in the previous articles, and you probably feel a bit lost. But don't worry, in this article, we will dig deeper and find out how the URL dispatchers, models, views, and templates can work together to create a functional Django application.

To make things easier to understand, in this article, we are not going to create a full-featured blog application with categories, tags and etc. Instead, we will create only a post page that displays a post article, a home page that shows a list of all articles, and a create/update/delete page that modifies the post.

## Designing the database structure

Let's start with the model layer. The first thing you need to do is design the database structure. Since we are only dealing with posts right now, you can go ahead to the `models.py` file, and create a new `Post` model:

`blog/models.py`

```python
from django.db import models


class Post(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()
```

This `Post` contains only two fields, `title`, which is a `CharField` with a maximum of 100 characters, and `content`, which is a `TextField`.

Generate the corresponding migration files with the following command:

```cmd
python manage.py makemigrations
```

Apply the migrations:

```cmd
python manage.py migrate
```

## The CRUD operations

Now, it is time for us to dive into the application itself. When building real-life applications, it is unlikely for you to create all the controllers first, and then design the templates, and then move on to the routers. Instead, you need to think from the user's perspective, and think about what actions the user might want to take.

In general, the user should have the ability to perform four operations on each resource, which in our case, is the `Post`.

- Create: This operation is used to insert new data into the database.
- Read: This operation is used to retrieve data from the database.
- Update: This operation is used to modify existing data in the database.
- Delete: This operation is used to remove data from the database.

Together, they are referred to as the CRUD operations.

## The create action

First, let's start with the create action. Currently, the database is still empty, so the user must create a new post. To complete this **create** action, you need a URL dispatcher that points the URL pattern `/post/create/` to the `post_create()` view function.

The `post_create()` view function should have a flow control (`if` statement), if the request method is `GET`, return a template that contains an HTML form, allowing the user to pass information to the backend (form submission should be a `POST` request). If the request method is `POST`, a new `Post` resource should be created and saved.

> **Here is a brief review on HTTP methods in case you need a refresher:**
>
> - The `GET` method is the most commonly used HTTP request method. It is used to request data and resources from the server.
> - The `POST` method is used to send data to the server, used for creating/updating a resource.
> - The `HEAD` method works just like the `GET` method. Except the HTTP response will only contain the head and not the body. This method is usually used by developers for debugging purposes.
> - The `PUT` method is similar to `POST`, with one small difference. When you `POST` a resource that already exists on the server, this action would not cause any difference. The `PUT` method, however, will duplicate that resource, every time you make the request.
> - The `DELETE` method removes a resource from the server.

Let us start with the URL dispatcher, go to `urls.py`:

`djangoBlog/urls.py`

```python
from django.urls import path
from blog import views

urlpatterns = [
    path("post/create/", views.post_create, name="create"),
]
```

Then you'll need a `post_create()` view function. Go to `views.py` and add the following code:

`blog/views.py`

```python
from django.shortcuts import redirect, render
from .models import Post


def post_create(request):
    if request.method == "GET":
        return render(request, "post/create.html")
    elif request.method == "POST":
        post = Post(title=request.POST["title"], content=request.POST["content"])
        post.save()
        return redirect("home")
```

The `post_create()` function first examines the method of the HTTP request, if it is a `GET` method, return the `create.html` template, if it is a `POST`, use the information passed by that `POST` request to create a new `Post` instance, and after it is done redirect to the home page (we'll create this page in the next step).

Next, time to create the `create.html` template. First of all, you need a `layout.html`:

`templates/layout.html`

```django
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://cdn.tailwindcss.com"></script>
    {% block title %}{% endblock %}
</head>

<body class="container mx-auto font-serif">
    <div class="bg-white text-black font-serif">
        <div id="nav">
            <nav class="flex flex-row justify-between h-16 items-center shadow-md">
                <div class="px-5 text-2xl">
                    <a href="/">
                        My Blog
                    </a>
                </div>
                <div class="hidden lg:flex content-between space-x-10 px-10 text-lg">
                    <a href="{% url 'create' %}" class="hover:underline hover:underline-offset-1">New Post</a>
                    <a href="https://github.com/ericnanhu" class="hover:underline hover:underline-offset-1">GitHub</a>
                </div>
            </nav>
        </div>

        {% block content %}{% endblock %}

        <footer class="bg-gray-700 text-white">
            <div
                class="flex justify-center items-center sm:justify-between flex-wrap lg:max-w-screen-2xl mx-auto px-4 sm:px-8 py-10">
                <p class="font-serif text-center mb-3 sm:mb-0">Copyright © <a href="https://www.ericsdevblog.com/"
                        class="hover:underline">Eric Hu</a></p>

                <div class="flex justify-center space-x-4">
                    . . .
                </div>
            </div>
        </footer>
    </div>
</body>

</html>
```

Notice the `{% url 'create' %}` on line 22. This is how you can reverse resolute URLs based on their names. The name `create` matches the name you gave to the `post/create/` dispatcher.

I also added TailwindCSS through a CDN on line 8 to make this page look better, but you shouldn't do this in the production environment.

Next, create the `create.html` template. I choose to create a `post` directory for it to make it clear that this template is for creating a post, but you can do this differently as long as it makes sense to you:

`templates/post/create.html`

```django
{% extends 'layout.html' %}

{% block title %}
<title>Create</title>
{% endblock %}

{% block content %}
<div class="w-96 mx-auto my-8">
    <h2 class="text-2xl font-semibold underline mb-4">Create new post</h2>
    <form action="{% url 'create' %}" method="POST">
        {% csrf_token %}
        <label for="title">Title:</label><br>
        <input type="text" id="title" name="title"
            class="p-2 w-full bg-gray-50 rounded border border-gray-300 focus:ring-3 focus:ring-blue-300"><br>
        <br>
        <label for="content">Content:</label><br>
        <textarea type="text" id="content" name="content" rows="15"
            class="p-2 w-full bg-gray-50 rounded border border-gray-300 focus:ring-3 focus:ring-blue-300"></textarea><br>
        <br>
        <button type="submit"
            class="font-sans text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-md text-sm w-full px-5 py-2.5 text-center">Submit</button>
    </form>
</div>
{% endblock %}
```

Line 10 specifies the action this form will take when it is submitted, and the request method it will use.

Line 11 adds CSRF protection to the form for security purposes.

Also pay attention to the `<input>` field on line 13 and 14. Its `name` attribute is very important. When the form is submitted, the user input will be tied to this name attribute, and you can then retrieve that input in the view function like this:

```python
title=request.POST["title"]
```

Same for the `<textarea>` on line 17 and 18.

And finally, the button must have `type="submit"` for it to work.

## The list action

Now let's create a home page where you can show a list of all posts. You can start with the URL again:

`djangoBlog/urls.py`

```python
path("", views.post_list, name="home"),
```

And then the view function:

`blog/views.py`

```python
def post_list(request):
    posts = Post.objects.all()
    return render(request, "post/list.html", {"posts": posts})
```

The `list.html` template:

```django
{% extends 'layout.html' %}

{% block title %}
<title>My Blog</title>
{% endblock %}

{% block content %}
<div class="max-w-screen-lg mx-auto my-8">
    {% for post in posts %}
    <h2 class="text-2xl font-semibold underline mb-2"><a href="{% url 'show' post.pk %}">{{ post.title }}</a></h2>
    <p class="mb-4">{{ post.content | truncatewords:50 }}</p>
    {% endfor %}
</div>
{% endblock %}
```

The `{% for post in posts %}` iterates over all `posts`, and each item is assigned to the variable `post`.

The `{% url 'show' post.pk %}` passes the primary key of the `post` to the `show` URL dispatcher, which we'll create later.

And finally, `{{ post.content | truncatewords:50 }}` uses a filter `truncatewords` to truncate the `content` to the first 50 words.

## The show action

Next, the show action should display the content of a particular post, which means its URL should contain something unique that allows Django to locate just one `Post` instance. That something is usually the primary key.

`djangoBlog/urls.py`

```python
path("post/<int:id>", views.post_show, name="show"),
```

The integer following `post/` will be assigned to the variable `id`, and passed to the view function.

`blog/views.py`

```python
def post_show(request, id):
    post = Post.objects.get(pk=id)
    return render(request, "post/show.html", {"post": post})
```

And again, the corresponding template:

`templates/post/show.html`

```django
{% extends 'layout.html' %}

{% block title %}
<title>{{ post.title }}</title>
{% endblock %}

{% block content %}
<div class="max-w-screen-lg mx-auto my-8">

    <h2 class="text-2xl font-semibold underline mb-2">{{ post.title }}</h2>
    <p class="mb-4">{{ post.content }}</p>

    <a href="{% url 'update' post.pk %}" class="font-sans text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-md text-sm w-full px-5 py-2.5 text-center">Update</a>
</div>
{% endblock %}
```

## The update action

The URL dispatcher:

`djangoBlog/urls.py`

```python
path("post/update/<int:id>", views.post_update, name="update"),
```

The view function:

`blog/views.py`

```python
def post_update(request, id):
    if request.method == "GET":
        post = Post.objects.get(pk=id)
        return render(request, "post/update.html", {"post": post})
    elif request.method == "POST":
        post = Post.objects.update_or_create(
            pk=id,
            defaults={
                "title": request.POST["title"],
                "content": request.POST["content"],
            },
        )
        return redirect("home")
```

The [`update_or_create()`](https://docs.djangoproject.com/en/4.1/ref/models/querysets/#update-or-create) method is a new method added in Django 4.1.

The corresponding template:

`templates/post/update.html`

```django
{% extends 'layout.html' %}

{% block title %}
<title>Update</title>
{% endblock %}

{% block content %}
<div class="w-96 mx-auto my-8">
    <h2 class="text-2xl font-semibold underline mb-4">Update post</h2>
    <form action="{% url 'update' post.pk %}" method="POST">
        {% csrf_token %}
        <label for="title">Title:</label><br>
        <input type="text" id="title" name="title" value="{{ post.title }}"
            class="p-2 w-full bg-gray-50 rounded border border-gray-300 focus:ring-3 focus:ring-blue-300"><br>
        <br>
        <label for="content">Content:</label><br>
        <textarea type="text" id="content" name="content" rows="15"
            class="p-2 w-full bg-gray-50 rounded border border-gray-300 focus:ring-3 focus:ring-blue-300">{{ post.content }}</textarea><br>
        <br>
        <div class="grid grid-cols-2 gap-x-2">
            <button type="submit"
            class="font-sans text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-md text-sm w-full px-5 py-2.5 text-center">Submit</button>
        <a href="{% url 'delete' post.pk %}"
            class="font-sans text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-red-300 font-medium rounded-md text-sm w-full px-5 py-2.5 text-center">Delete</a>
        </div>

    </form>
</div>
{% endblock %}
```

## The delete action

Finally, for the delete action:

`djangoBlog/urls.py`

```python
path("post/delete/<int:id>", views.post_delete, name="delete"),
```

`blog/views.py`

```python
def post_delete(request, id):
    post = Post.objects.get(pk=id)
    post.delete()
    return redirect("home")
```

This action does not require a template, since it just redirects you to the home page after the action is completed.

## Start the server

Finally, let's start the dev server and see the result.

```cmd
python manage.py runserver
```

The home page:

![The home page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1p243z8zbx72uonogmdc.png)

The create page:

![The create page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u8335jui3ebzqqv9o1d1.png)

The show page:

![The show page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4bgwv7kqn6za7yjd460t.png)

The update page:

![The update page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7yijccl5w56gp4z2v7ey.png)

> If you liked this article, please also check out my other tutorials:
> - [Laravel for Beginners](https://www.ericsdevblog.com/posts/laravel-for-beginners-1?ref=Dev_Community/)
> - [Vue.js for Beginners](https://www.ericsdevblog.com/posts/vuejs-for-beginners-1?ref=Dev_Community/)
> - [Create a Modern Application with Django and Vue](https://www.ericsdevblog.com/posts/create-a-modern-application-with-django-and-vue-1?ref=Dev_Community/)
> - [Beginner’s Roadmap to Web Development](https://www.ericsdevblog.com/series/beginners-roadmap-to-web-development?ref=Dev_Community/)