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.
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 ...34 def create(request):5 title = request.POST.get('title')6 date = request.POST.get('date')78 exercise = Exercise.objects.create(title=title, date=date)910 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 HttpResponseRedirect23from app import models45...67def index(request):8 if request.method == 'POST':9 models.Exercise.create(request)10 return redirect('/')1112 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 <input5 type="text"6 name="title"7 value="{{exercise.title}}"8 placeholder="Enter the title"9 />10 <input11 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.
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.POST4 update = 'update' in request.POST56 if create == True:7 models.Exercise.create(request)8 elif update == True:9 models.Exercise.update(request)1011 return redirect('/')1213 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')56 exercise = Exercise.objects.filter(pk=id).update(title=title, date=date)78 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.POST4 update = 'update' in request.POST5 delete = 'delete' in request.POST67 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)1314 return redirect('/')1516 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()45 if is_deleted == 1:6 return True78 return False
With this addition, we've successfully implemented CRUD operations in our Python and Django application.
Key Takeaways
- Django view handlers do not support
PUT
andDELETE
requests, as they do not parse the query parameters or request body for those HTTP methods. As a result, we must rely onPOST
requests and differentiate between them by passing an additional field in the request body. - 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 theaction
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 HttpResponseRedirect23def index(request):4 ...56 return redirect('/')7 # or8 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.