CRUD Operations In Python & Django - Part 2

Jul 22, 2024
  • python
  • django
  • web
  • fullstack

In our previous article, we covered the basics of setting up a Django project and created our Exercise model, which we displayed on the front end as a list. In this article, we’ll dive into performing CRUD operations. For those unfamiliar, CRUD stands for Create, Read, Update, and Delete—essentially the four fundamental actions you can perform on your data.

Now that we have our API set up in the app folder, we’ll simply extend the index view to handle create, update, and delete requests.

The Form

Let’s set up a form that allows users to create exercises. We’ll be using HTML templates for this purpose once again. To get started, create a new template called add_exercise.html in the app/templates folder.

1<form method="POST" action="/">
2 {% csrf_token %}
3 <input type="text" name="title" placeholder="Enter the title" />
4 <input type="date" name="date" placeholder="Enter the date" />
5 <button type="submit">Save</button>
6</form>

Next, in our index.html template, we’ll include the add_exercise.html template using the following method:

1{% extends "base.html" %} {% block content %}
2 <h2>Exercises</h2>
3 {% include 'add_exercise.html' %}
4...
5{% endblock %}

We’re utilizing the include tag here, which promotes composability across HTML templates, making our code easier to maintain and understand. If you refresh the page in your browser, you should see the form appear on the screen.

Add Exercise

In our HTML, we're using the <form> tag with the method attribute set to POST and the action attribute pointing to /, which is the same endpoint we use to fetch the list of exercises.

In this context, the csrf_token is a security feature represented by a randomly generated secret value. It helps protect our form submissions from forgery attacks, which is what CSRF stands for—Cross-Site Request Forgery. A unique token is generated for each user session, and it is not accessible by third-party sites, thereby preventing unauthorised changes from occurring.

Our form includes two input fields: one for the title and another for the date, following the schema of our Exercise model. When the form is submitted, the values for title and date will be sent via a POST request to the / endpoint, which will then be processed by our index view in app/views.py.

The Model

In Django, we can enhance our Exercise model—essentially a Python class—by adding specific methods that correspond to CRUD operations. In the app/models.py file, we’ll include the following:

1class Exercise(models.Model):
2 ...
3
4 def create(request):
5 title = request.POST.get('title')
6 date = request.POST.get('date')
7
8 exercise = Exercise.objects.create(title=title, date=date)
9
10 return exercise

We can access title and date from the POST request, as shown in the code above. Then, we can utilize Django's built-in ORM to create a new exercise and return the created instance.

We'll leverage the same index view we use to retrieve exercises, expanding it to check if the request method is POST. If so, we'll pass the request object to the class method we previously defined. Once the exercise is created, we'll redirect the user back to the homepage or perform a page refresh, ensuring the newly added exercise appears on the screen.

1from django.http import HttpResponseRedirect
2
3from app import models
4
5...
6
7def index(request):
8 if request.method == 'POST':
9 models.Exercise.create(request)
10 return redirect('/')
11
12 exercises = (
13 models.Exercise.objects.all().order_by("created_at")
14 )
15 return render(request, "index.html", context={'exercises': exercises})

Try creating a new exercise now, and you should see it appear at the bottom of the list.

Update Exercise

Let's refactor our code a bit before adding update functionality to the exercises. We'll move the exercises to their own template called exercise.html.

1<h2>Exercises</h2>
2{% include 'add_exercise.html' %}
3<ul style="margin: 0; list-style: none; padding: 0">
4 {% for exercise in exercises %}
5 <li style="margin-top: 4px">
6 {% include 'exercise.html' %}
7 </li>
8 {% endfor %}
9</ul>

Create a template for exercise.html in the app/templates folder, and we’ll add the following HTML to it:

1<form method="POST" action="/">
2 {% csrf_token %}
3 <input hidden name="id" value="{{exercise.id}}" />
4 <input
5 type="text"
6 name="title"
7 value="{{exercise.title}}"
8 placeholder="Enter the title"
9 />
10 <input
11 type="date"
12 name="date"
13 placeholder="Enter the date"
14 value="{{exercise.date | date:'Y-m-d'}}"
15 />
16 <button type="submit" name="update">Update</button>
17</form>

We’re using the <form> tag again for each exercise in the list and adding a hidden input for exercise.id, which will be used to update the exercise. Go back to the browser and refresh the page; you should see a form for each exercise in the list, with each input pre-filled with the corresponding exercise data.

Final Look

Notice that we’re not using PUT as the form method; instead, we’re using POST. This is because the view handlers can only parse data sent through GET and POST requests, with no built-in support for PUT and DELETE. When we created the create class method in the Exercise class, you might have noticed we used request.POST.get('title'). While this works for POST requests, there are no PUT or DELETE methods available in the request object.

But how do we differentiate between a POST and a PUT request? If you check the form we created earlier, you’ll notice that we assigned a name attribute to the submit button. We can access this attribute in the same way we accessed title and date, using request.POST.get('update').

Let's update the create exercise form to include the same change.

1<form method="POST" action="/">
2 ...
3 <button type="submit" name="create">Save</button>
4</form>

And in our exercises view, we’ll make the following changes to differentiate between requests.

1def index(request):
2 if request.method == 'POST':
3 create = 'create' in request.POST
4 update = 'update' in request.POST
5
6 if create == True:
7 models.Exercise.create(request)
8 elif update == True:
9 models.Exercise.update(request)
10
11 return redirect('/')
12
13 exercises = (
14 models.Exercise.objects.all().order_by("created_at")
15 )
16 return render(request, "index.html", context={'exercises': exercises})

We check for the button name and forward the request to the appropriate Exercise method accordingly.

Let's add an update class method to the Exercise model in app/models.py.

1def update(request):
2 id = request.POST.get('id')
3 title = request.POST.get('title')
4 date = request.POST.get('date')
5
6 exercise = Exercise.objects.filter(pk=id).update(title=title, date=date)
7
8 return exercise

To update a row in the database, we can use the update method available on the Exercise model. However, before updating, we need to ensure that we are updating the correct exercise. To do this, we filter the exercises by the primary key, which is id, and update only that specific exercise.

Delete Exercise

Similarly, we’ll add a delete button next to each exercise in the exercise.html template.

1<form method="POST" action="/">
2 ...
3 <button type="submit" name="update">Update</button>
4 <button type="submit" name="delete">Delete</button>
5</form>

We’ll set delete as the value of the name attribute, and in views.py, we’ll extend the if...elif statements to handle the delete operation.

1def index(request):
2 if request.method == 'POST':
3 create = 'create' in request.POST
4 update = 'update' in request.POST
5 delete = 'delete' in request.POST
6
7 if create == True:
8 models.Exercise.create(request)
9 elif update == True:
10 models.Exercise.update(request)
11 elif delete == True:
12 models.Exercise.delete(request)
13
14 return redirect('/')
15
16 exercises = (
17 models.Exercise.objects.all().order_by("created_at")
18 )
19 return render(request, "index.html", context={'exercises': exercises})

And in the Exercise model, we'll add the class method delete.

1def delete(request):
2 id = request.POST.get('id')
3 is_deleted = Exercise.objects.filter(pk=id).delete()
4
5 if is_deleted == 1:
6 return True
7
8 return False

With this addition, we've successfully implemented CRUD operations in our Python and Django application.

Key Takeaways

  1. Django view handlers do not support PUT and DELETE requests, as they do not parse the query parameters or request body for those HTTP methods. As a result, we must rely on POST requests and differentiate between them by passing an additional field in the request body.
  2. Noticed that I'm making the POST request to the same route from which I'm fetching the exercises. This is important because if you were to create an endpoint like /api/exercises to handle requests, you would need to manage redirection manually. Otherwise, the behavior of the <form> tag after the request is to redirect the user to the endpoint specified in the action attribute. Therefore, you will need to manually redirect the user back to the desired page, or in our case, keep the user on the same page.
1from django.http import HttpResponseRedirect
2
3def index(request):
4 ...
5
6 return redirect('/')
7 # or
8 return HttpResponseRedirect(request.META['HTTP_REFERER'])

In summary, by effectively managing our POST requests and ensuring proper redirection, we can create a seamless user experience while implementing CRUD operations in our Django application.

Get in touch

Have a project for me, or just want to say Hi🙋🏽‍♂️? Feel free to email me at prvnbist@gmail.com.