24
Grocery Bag using Django (Part-4) - CRUD Operations
In the previous post, we had completed User Authentication in our web application. If you haven't read it till now, make sure you read it here. In this blog, we are going to add CRUD operation functionality to our web app. It means, now a logged-in or an authenticated user can view all the items, add items in his grocery bag, update a previously added item and delete an item. Let's roll it.
The first thing we need to do is to create a model for our items. Can you guess what all fields should be there for an item? Well, they are - name, quantity, status and date. Let's create the Item model class in the bag/models.py
file:
from django.contrib.auth.models import User
from django.db import models
# Create your models here.
STATUS_CHOICES = (
('BOUGHT', 'Bought'),
('PENDING', 'Pending'),
('NOT AVAILABLE', 'Not Available'),
)
class Item(models.Model):
name = models.CharField(max_length=127)
quantity = models.CharField(max_length=63)
status = models.CharField(
max_length=15, choices=STATUS_CHOICES, default='PENDING')
user = models.ForeignKey(User, on_delete=models.CASCADE)
date = models.DateField()
updated_at = models.DateTimeField(auto_now=True)
created_at = models.DateTimeField(auto_now_add=True)
In the above script, we have created an Item
class that extends the models.Model
class. Inside the class, we have mentioned the fields as discussed above. name
will be a CharField with a maximum length of up to 127 characters. Similarly, we have set quantity
to CharField too, and not IntegerField, just for the fact that quantity can be 3 Kgs as well as 3 pcs. Next, we have a status
field that is also a CharField. The status
can be one of the three- BOUGHT , PENDING and NOT AVAILABLE. To limit the choices, we have set the choices
attribute to a STATUS_CHOICES dictionary and the default value would be PENDING. We have a user
field in the model which is a ForeignKey to the User model provided by Django. This field tells that which user has created this item and will help us later in sorting them out. The next field is date
which is a DateField. The next two fields are updated_at
and created_at
which are used to maintain timestamps when the object is created and when it's updated.
After you have created the model, let's run the migrations:
$ python manage.py makemigrations
$ python manage.py migrate
The first thing we need to do is to add a new item into our bag. Note that this operation is only available for authenticated users. Let's create a view function to add a new item in bag/views.py
file:
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from .models import Item
@login_required
def add_item(request):
if request.method == "POST":
name = request.POST.get("name")
quantity = request.POST.get("quantity")
status = request.POST.get("status")
date = request.POST.get("date")
if name and quantity and status and date:
new_item = Item(name=name, quantity=quantity,
status=status, date=date, user=request.user)
new_item.save()
messages.success(request, 'Item added successfully!')
return redirect('index')
messages.error(request, "One or more field(s) is missing!")
return redirect('add-item')
return render(request, "add.html")
Whenever a GET request is made, we serve the add.html
file. If a POST request is made, we get the values of the four fields - name , quantity , status and date. If any of the fields is empty, we flash an error and redirect to the same page. If every field is present, we create a new object of the Item model class and save it, and then redirect to the index page.
Let us create a route for this view function in our bag/urls.py file:
from django.urls import path
from bag.views import add_item, index
urlpatterns = [
path('', index, name='index'),
path('add-item', add_item, name='add-item'),
]
Now let us make the required changes in the HTML form in the add.html
file:
{% extends "base.html" %}{% load static %}
{% block title %}Add to bag{% endblock title %}
{% block content %}
<body>
<div class="container mt-5">
<h1>Add to Grocery Bag</h1>
{% include 'partials/alerts.html' %}
<form method="post" action="{% url 'add-item' %}">
{% csrf_token %}
<div class="form-group mt-2">
<label>Item name</label>
<input type="text" name="name" class="form-control" placeholder="Item name" />
</div>
<div class="form-group mt-2">
<label>Item quantity</label>
<input type="text" name="quantity" class="form-control" placeholder="Item quantity" />
</div>
<div class="form-group mt-2">
<label>Item status</label>
<select class="form-control" name="status">
<option value="PENDING">PENDING</option>
<option value="BOUGHT">BOUGHT</option>
<option value="NOT AVAILABLE">NOT AVAILABLE</option>
</select>
</div>
<div class="form-group mt-2">
<label>Date</label>
<input type="date" name="date" class="form-control" placeholder="Date" />
</div>
<div class="form-group mt-2">
<input type="submit" value="Add" class="btn btn-danger" />
</div>
</form>
</div>
</body>
{% endblock content %}
The form method and action is changed as required, as well as CSRF token is added. Notice that we have added name attribute in each form field, as this is required to fetch the value in our backend.
Once an item is added, we can further update it too. Suppose an item was PENDING, but now we have bought that item. So, we can update it and set the status to BOUGHT. Let's add this view function in our bag/views.py
file:
@login_required
def update_item(request, item_id):
item = Item.objects.get(id=item_id)
date = item.date.strftime("%d-%m-%Y")
if request.method == 'POST':
item.name = request.POST.get("name")
item.quantity = request.POST.get("quantity")
item.status = request.POST.get("status")
item.date = request.POST.get("date")
item.save()
messages.success(request, 'Item updated successfully!')
return redirect('index')
return render(request, "update.html", {'item': item, 'date': date})
This view function is quite similar to the add_item
function. But there is a change. With the request, we are getting the item_id
in the request URL. Using that item_id
, we first fetch the item from the database so that the user can view the previous values in the update form. Whenever a GET request is made, the item and date is passed with the request. If a POST request is made, we get the values from the form and update the appropriate fields and save the item.
A route for this view function will look like this:
from django.urls import path
from bag.views import add_item, index, update_item
urlpatterns = [
path('', index, name='index'),
path('add-item', add_item, name='add-item'),
path('update-item/<int:item_id>', update_item, name='update-item'),
]
Notice that we have added <int:item_id>
so that we get the item_id
in our view function.
Now let's make the required changes in the update.html
file:
{% extends "base.html" %}{% load static %}
{% block title %}Update bag{% endblock title %}
{% block content %}
<body>
<div class="container mt-5">
<h1>Update Grocery Bag</h1>
<form method="post" action="{% url 'update-item' item.id %}">
{% csrf_token %}
<div class="form-group mt-2">
<label>Item name</label>
<input
type="text"
class="form-control"
placeholder="Item name"
name="name"
value="{{item.name}}"
/>
</div>
<div class="form-group mt-2">
<label>Item quantity</label>
<input
type="text"
class="form-control"
placeholder="Item quantity"
name="quantity"
value="{{item.quantity}}"
/>
</div>
<div class="form-group mt-2">
<label>Item status</label>
<select class="form-control" name="status" id="status">
<option value="PENDING">PENDING</option>
<option value="BOUGHT" selected>BOUGHT</option>
<option value="NOT AVAILABLE">NOT AVAILABLE</option>
</select>
</div>
<div class="form-group mt-2">
<label>Date</label>
<input
type="date"
class="form-control"
placeholder="Date"
name="date"
id="date"
/>
</div>
<div class="form-group mt-2">
<input type="submit" value="Update" class="btn btn-danger" />
</div>
</form>
</div>
<script>
// Select appropriate option
var options = document.getElementById('status').options;
for (let index = 0; index < options.length; index++) {
if(options[index].value == '{{item.status}}'){
options[index].selected = true;
}
}
// Select date
var fullDate = '{{date}}';
var dateField = document.getElementById('date');
dateField.value = `${fullDate.substring(6,12)}-${fullDate.substring(3,5)}-${fullDate.substring(0,2)}`
</script>
</body>
{% endblock content %}
The form method and action is changed as required, as well as CSRF token is added. The action URL has also item.id
passed with it. Notice that we have added name attribute in each form field, as this is required to fetch the value in our backend. The next thing to be considered is the Javascript code at the below of the above script. The Javascript code is used to select the status and date automatically.
This is the easiest task in the CRUD operation. Let's see the view function:
@login_required
def delete_item(request, item_id):
item = Item.objects.get(id=item_id)
item.delete()
messages.error(request, 'Item deleted successfully!')
return redirect('index')
As in the update view function, we again get the item_id
in the delete_item
view function. We fetch that item and delete it and then redirect the user to the index page with a flash.
Let's add this view function in our urls.py:
from django.urls import path
from bag.views import add_item, delete_item, index, update_item
urlpatterns = [
path('', index, name='index'),
path('add-item', add_item, name='add-item'),
path('update-item/<int:item_id>', update_item, name='update-item'),
path('delete-item/<int:item_id>', delete_item, name='delete-item'),
]
Till now we're just rendering the index.html
file. But now we need to fetch all the items added by the user and send it to the frontend. So, let's update the index view function:
@login_required
def index(request):
items = Item.objects.filter(user=request.user).order_by('-id')
context = {
'items': items
}
return render(request, "index.html", context)
We are filtering the items according to the logged-in user, and then ordered it by the id in descending order.
Let's update the index.html file:
{% extends "base.html" %}{% load static %}
{% block title %}View Bag{% endblock title %}
{% block content %}
<body>
<div class="container mt-5">
<!-- top -->
<div class="row">
<div class="col-lg-6">
<h1>View Grocery List</h1>
<a href="{% url 'add-item' %}">
<button type="button" class="btn btn-success">Add Item</button>
</a>
</div>
<div class="col-lg-6 float-right">
<div class="row">
<div class="col-lg-6">
<!-- Date Filtering-->
<input type="date" class="form-control" />
</div>
<div class="col-lg-4">
<input type="submit" class="btn btn-danger" value="filter" />
</div>
<div class="col-lg-2">
<p class="mt-1"><a href="{% url 'signout' %}">Log Out</a></p>
</div>
</div>
</div>
</div>
<br>
{% include 'partials/alerts.html' %}
<!-- Grocery Cards -->
<div class="row mt-4">
<!-- Loop This -->
{% for item in items %}
<div class="col-lg-4 mb-3">
<div class="card">
<div class="card-body">
<h5 class="card-title">{{item.name}}</h5>
<h6 class="card-subtitle mb-2 text-muted">{{item.quantity}}</h6>
{% if item.status == 'PENDING' %}
<p class="text-info">{{item.status}}</p>
{% elif item.status == 'BOUGHT' %}
<p class="text-success">{{item.status}}</p>
{% else %}
<p class="text-danger">{{item.status}}</p>
{% endif %}
<div class="row">
<div class="col-md-6">
<a href="{% url 'update-item' item.id %}">
<button type="button" class="btn btn-primary">UPDATE</button>
</a>
</div>
<div class="col-md-6">
<a href="{% url 'delete-item' item.id %}">
<button type="button" class="btn btn-danger">DELETE</button>
</a>
</div>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</body>
{% endblock content %}
In Django templates, we can iterate over the data passed from the backend using a for loop. Thats what we are doing here. We are iterating over the items
using an iterator item
. Also we have used if condition to add colors to the status. We have two buttons - update and delete with the URLs set to the update and delete route respectively.
Django comes with a pre-built admin panel. We just need to create a superuser to access the admin panel. Let's create that superuser using the below command:
$ python manage.py createsuperuser
This will ask you username, email(optional) and the password. Just give those details and a superuser will be created. Then you can go to the http://127.0.0.1:8000/admin
route, and login with your credentials.
You can watch the demo video:
In this part, we have completed the most important step, i.e. CRUD operations on the grocery items. In the next part, we'll see how we can use the filter option available on the index page. Stay tuned!
Code till now: https://github.com/ashutoshkrris/Grocery-Bag/tree/blog4
24