Implementing Soft Delete in Django: A Comprehensive Guide

Introduction

Soft deletion is a technique where a record is not actually removed from the database but is marked as deleted. This approach helps maintain data integrity and allows for data recovery if necessary. In this tutorial, we will walk through the steps to implement soft delete functionality in a Django application, covering model definition, view creation, template setup, and admin configuration.


Step-by-Step Guide

Step 1: Setting Up the Django Project

First, ensure you have Django installed. If not, you can install it using pip:

pip install django

Create a new Django project and app:

django-admin startproject myproject
cd myproject
django-admin startapp delete_app

Add the app to your INSTALLED_APPS in settings.py:

# myproject/settings.py

INSTALLED_APPS = [
    ...
    'delete_app',
]

Step 2: Creating the Contact Model

In delete_app/models.py, define the Contact model with an is_deleted field:

# delete_app/models.py

from django.db import models

class Contact(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    email = models.EmailField()
    phone_number = models.CharField(max_length=15, blank=True, null=True)
    message = models.TextField()
    is_deleted = models.BooleanField(default=False)

    def __str__(self):
        return f"{self.first_name} {self.last_name}"

    def delete(self, using=None, keep_parents=False):
        self.is_deleted = True
        self.save()

Run the migrations to create the table:

python manage.py makemigrations
python manage.py migrate

Step 3: Creating Views for Contact Operations

In delete_app/views.py, implement the necessary views:

# delete_app/views.py

from django.shortcuts import render, get_object_or_404, redirect
from django.views.decorators.http import require_POST
from .models import Contact

def contact_list(request):
    contacts = Contact.objects.filter(is_deleted=False)
    return render(request, 'contacts/contact_list.html', {'contacts': contacts})

def contact_detail(request, pk):
    contact = get_object_or_404(Contact, pk=pk)
    return render(request, 'contacts/contact_detail.html', {'contact': contact})

@require_POST
def soft_delete_contact(request, pk):
    contact = get_object_or_404(Contact, pk=pk)
    contact.is_deleted = True
    contact.save()
    return redirect('delete_app:contact_list')

def create_contact(request):
    if request.method == 'POST':
        first_name = request.POST.get('first_name')
        last_name = request.POST.get('last_name')
        email = request.POST.get('email')
        phone_number = request.POST.get('phone_number')
        message = request.POST.get('message')

        new_contact = Contact.objects.create(
            first_name=first_name,
            last_name=last_name,
            email=email,
            phone_number=phone_number,
            message=message
        )
        return redirect('delete_app:contact_list')

    return render(request, 'contacts/create_contact.html')

Step 4: Configuring URLs

In delete_app/urls.py, define the URL patterns:

# delete_app/urls.py

from django.urls import path
from . import views

app_name = 'delete_app'

urlpatterns = [
    path('contacts_list', views.contact_list, name='contact_list'),
    path('',views.create_contact, name='create_contact'),
    path('contact-detail/<int:pk>/', views.contact_detail, name='contact_detail'),
    path('soft-delete/<int:pk>/', views.soft_delete_contact, name='soft_delete_contact'),
]

Include these URLs in the project’s main urls.py:

# myproject/urls.py

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

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

Step 5: Creating Templates

Create the necessary templates in delete_app/templates/contacts/:

  1. contact_list.html:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Contact List</title>
    <style>
        body {
            font-family: 'Arial', sans-serif;
            background-color: #f4f4f4;
            margin: 0;
            padding: 0;
        }

        h1 {
            color: #333;
            text-align: center;
            padding: 20px;
            background-color: #007bff;
            color: #fff;
        }

        ul {
            list-style-type: none;
            padding: 0;
            margin: 0;
            display: flex;
            flex-wrap: wrap;
            justify-content: center;
        }

        li {
            margin: 10px;
            padding: 15px;
            background-color: #fff;
            border-radius: 5px;
            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
            text-align: center;
            display: flex;
            flex-direction: column;
            align-items: center;
        }

        a {
            text-decoration: none;
            color: #007bff;
            font-weight: bold;
            margin-bottom: 10px;
        }

        .contact-details {
            font-size: 16px;
            margin-top: 10px;
            color: #555;
        }

        form {
            margin-top: 10px;
        }
    </style>
</head>
<body>
    <h1>Contact List</h1>
    <ul>
        {% for contact in contacts %}
            <li>
                <a href="{% url 'delete_app:contact_detail' pk=contact.pk %}">
                    {{ contact.first_name }} {{ contact.last_name }}
                </a>
                <div class="contact-details">
                    <p>Email: {{ contact.email }}</p>
                    <p>Phone Number: {{ contact.phone_number }}</p>
                    <p>Message: {{ contact.message }}</p>
                </div>
                <form method="post" action="{% url 'delete_app:soft_delete_contact' pk=contact.pk %}" style="display: inline;">
                    {% csrf_token %}
                    <input type="submit" value="Soft Delete">
                </form>
            </li>
        {% endfor %}
    </ul>
</body>
</html>
  1. contact_detail.html:
<!DOCTYPE html>
<html>
<head>
    <title>Contact Detail</title>
</head>
<body>
    <h1>Contact Detail</h1>
    <p><strong>Name:</strong> {{ contact.first_name }} {{ contact.last_name }}</p>
    <p><strong>Email:</strong> {{ contact.email }}</p>
    <p><strong>Phone Number:</strong> {{ contact.phone_number }}</p>
    <p><strong>Message:</strong> {{ contact.message }}</p>
            <li>
                <a href="{% url 'delete_app:contact_detail' pk=contact.pk %}"></a>
                <form method="post" action="{% url 'delete_app:soft_delete_contact' pk=contact.pk %}" style="display: inline;">
                    {% csrf_token %}
                    <input type="submit" value="Soft Delete">
                </form>
            </li>
</body>
</html>
  1. create_contact.html:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Create a New Contact</title>
    <style>
        body {
            font-family: 'Montserrat', sans-serif;
            background-color: #f8f8f8;
            margin: 0;
            display: flex;
            align-items: center;
            justify-content: center;
            min-height: 100vh;
        }

        .contact-form-container {
            background-color: #fff;
            border-radius: 10px;
            box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
            padding: 40px;
            width: 400px;
        }

        .contact-form {
            display: flex;
            flex-direction: column;
            gap: 20px;
        }

        .form-group {
            position: relative;
        }

        label {
            font-size: 14px;
            color: #555;
            position: absolute;
            top: 12px;
            left: 12px;
            transition: all 0.3s ease;
        }

        input,
        textarea {
            padding: 16px;
            border: none;
            border-radius: 8px;
            font-size: 16px;
            background-color: #f2f2f2;
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
            transition: all 0.3s ease;
            color: #444;
        }

        input:focus,
        textarea:focus {
            outline: none;
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
            background-color: #e0e0e0;
        }

        input:not(:placeholder-shown) + label,
        textarea:not(:placeholder-shown) + label {
            top: -12px;
            font-size: 12px;
            color: #007bff;
        }

        button {
            background-color: #007bff;
            color: #fff;
            padding: 16px;
            border: none;
            border-radius: 8px;
            cursor: pointer;
            font-size: 18px;
            transition: background-color 0.3s ease;
        }

        button:hover {
            background-color: #0056b3;
        }

        h2 {
            text-align: center;
            color: #007bff;
        }
    </style>
</head>
<body>
    <div class="contact-form-container">
        <h2>Create a New Contact</h2>
        <form method="post" action="{% url 'delete_app:create_contact' %}" class="contact-form">
            {% csrf_token %}

            <div class="form-group">
                <input type="text" id="first_name" name="first_name" required>
                <label for="first_name">First Name</label>
            </div>

            <div class="form-group">
                <input type="text" id="last_name" name="last_name" required>
                <label for="last_name">Last Name</label>
            </div>

            <div class="form-group">
                <input type="email" id="email" name="email" required>
                <label for="email">Email</label>
            </div>

            <div class="form-group">
                <input type="text" id="phone_number" name="phone_number">
                <label for="phone_number">Phone Number</label>
            </div>

            <div class="form-group">
                <textarea id="message" name="message" rows="4" required></textarea>
                <label for="message">Message</label>
            </div>

            <button type="submit">Submit</button>
        </form>
    </div>
</body>
</html>

Step 6: Configuring the Admin Interface

In delete_app/admin.py, customize the admin interface:

# delete_app/admin.py

from django.contrib import admin
from .models import Contact

@admin.register(Contact)
class ContactAdmin(admin.ModelAdmin):
    list_display = ('first_name', 'last_name', 'email', 'phone_number', 'message', 'is_deleted')
    list_filter = ('is_deleted',)
    search_fields = ('first_name', 'last_name', 'email', 'phone_number', 'message')
    actions = ['restore_selected']

    def restore_selected(modeladmin, request, queryset):
        queryset.update(is_deleted=False)

    restore_selected.short_description = "Restore selected contacts"

Conclusion

You’ve now implemented soft delete functionality in your Django application. This approach helps you keep track of deleted records and allows for data recovery if needed. The provided steps cover model definition, view creation, template setup, and admin configuration, ensuring a comprehensive implementation. Happy coding!

1 thought on “Implementing Soft Delete in Django: A Comprehensive Guide”

  1. Very interesting, helpfull and easy to understood, thanks a lot for sharing your knowledge. that is the way to improve the world and make a sociaty more equal. I replicated in a case of study on my university, it help me a lot.

    by the way, can I ask you a question? Is there any way in django to make the delete bottom in Django Admin to works as soft Delete when clicking on it. I meant what I would like to the delete bottom do after clicking on it is to works as soft delete instead of remove the records as it works by default:

Leave a Comment

Your email address will not be published. Required fields are marked *


Shopping Basket
Scroll to Top