Step-by-Step Guide to Django Signals: Using pre_save, post_save, pre_delete, and post_delete

Mehedi Khan
Django Unleashed
Published in
6 min readSep 24, 2024
Step-by-Step Guide to Django Signals: Using pre_save, post_save, pre_delete, and post_delete

Introduction:

Django signal is a powerful feature that allows us to trigger certain behaviors based on events in our application. This guide covers Django signals in detail, focusing on the most commonly used signals: pre_save, post_save, pre_delete, and post_delete. By the end of this tutorial, you’ll understand how to use these signals to enhance your Django applications effectively.

Prerequisites:

  • Basic knowledge of Django Models
  • Python installed ≤ 3.8
  • Django project setup

Table of Contents:

  1. What Are Django Signals?
  2. Setting Up Django Project
  3. Create a Django App
  4. Define a Model in the Blog App
  5. Register the Model in Admin
  6. Create a Superuser
  7. URL Configuration
  8. Create Views to Test Signals
  9. Setting Up Signals
  10. Setting Up Templates
  11. Start the Development Server
  12. Log In to the Admin Panel
  13. Best Practices and Use Cases
  14. Conclusion

1. What Are Django Signals?

Django signal allow decoupled applications to get notified when certain actions occur elsewhere in the framework. Some of the most common uses include logging, triggering additional logic when a model is saved or deleted, and ensuring data integrity.

2: Setting Up Your Django Project

2.1. Install Django

First, create a virtual environment and install Django library:

# Create virtual environment
python -m venv venv

# Activate the virtual environment
# For Windows:
venv\Scripts\activate
# For macOS/Linux:
source venv/bin/activate

# Install Django
pip install django

2.2. Create a Django Project

# Create a Django project
django-admin startproject django_signal

# Start the development server
python manage.py runserver

You should see your project running on http://127.0.0.1:8000.

3: Create a Django App

In Django an “app” is a web application that performs a specific function within your project.

# Create an app called 'blog'
python manage.py startapp blog

# Create templates directory
mkdir templates/blog

Add the app to your INSTALLED_APPS in django_signal/settings.py:

INSTALLED_APPS = [
# Django default apps
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',

# Custom apps
'blog',
]

4: Define a Model in the Blog App

In blog/models.py create a simple model to demonstrate signals:

from django.db import models

class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

def __str__(self):
return self.title

5: Register the Model in Admin

To easily manage the Post model in the admin interface, register it in blog/admin.py:

from django.contrib import admin
from .models import Post


admin.site.register(Post)

Run migrations to create the database table for the model:

# Make migrations
python manage.py makemigrations

# Apply the migrations
python manage.py migrate

6: Create a Superuser

To create a superuser (an admin user with full permissions to manage the Django admin panel), use the following command:

python manage.py createsuperuser

You’ll be prompted to input some details:

  1. Username: Choose any username (e.g., admin)
  2. Email address: Enter a valid email (e.g., admin@example.com)
  3. Password: Enter your strong password and then confirm it.

Example:

Username: admin
Email address: admin@example.com
Password: ********
Password (again): ********
Superuser created successfully.

7: URL Configuration

In blog/urls.py, add the URLs for the views:

from django.urls import path
from . import views


urlpatterns = [
path('posts/', views.post_list, name='post_list'),
path('create/', views.create_post, name='create_post'),
path('update/<int:post_id>/', views.update_post, name='update_post'),
path('delete/<int:post_id>/', views.delete_post, name='delete_post'),
]

Include the blog all URLs in the main django_signal/urls.py file:

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


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

8: Create Views to Test Signals

In blog/views.py, create simple views to handle display all post, creating, updating, and deleting posts:

from django.shortcuts import render, get_object_or_404, redirect
from .models import Post


# View to display all posts
def post_list(request):
posts = Post.objects.all()
return render(request, 'blog/post_list.html', {'posts': posts})

# Create post
def create_post(request):
post = Post.objects.create(title="Post title", content="This is a new post content for testing.")
return render(request, 'blog/post_detail.html', {'post': post})

# Update post
def update_post(request, post_id):
post = get_object_or_404(Post, id=post_id)
post.title = "Updated Post Title"
post.save()
return render(request, 'blog/post_detail.html', {'post': post})

# Delete post
def delete_post(request, post_id):
post = get_object_or_404(Post, id=post_id)
post.delete()
return redirect('post_list')

9: Setting Up Signals

Next, we’ll add signals to handle events when the Post model is saved or deleted.

9.1. Create a signals.py File

Create a new file like this blog/signals.py to define your signal handlers:

from django.db.models.signals import pre_save, post_save, pre_delete, post_delete
from django.dispatch import receiver
from .models import Post


# Pre-save signal
@receiver(pre_save, sender=Post)
def pre_save_post(sender, instance, **kwargs):
print(f"Pre-save signal triggered for: {instance.title}")

# Post-save signal
@receiver(post_save, sender=Post)
def post_save_post(sender, instance, created, **kwargs):
if created:
print(f"New post created: {instance.title}")
else:
print(f"Post updated: {instance.title}")

# Pre-delete signal
@receiver(pre_delete, sender=Post)
def pre_delete_post(sender, instance, **kwargs):
print(f"Pre-delete signal triggered for: {instance.title}")

# Post-delete signal
@receiver(post_delete, sender=Post)
def post_delete_post(sender, instance, **kwargs):
print(f"Post deleted: {instance.title}")

9.2. Connect Signals in apps.py

To ensure the signals are connected when the app is loaded, update blog/apps.py:

from django.apps import AppConfig


class BlogConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'blog'

def ready(self):
# This will import the signals when the app is ready
import blog.signals

Add the app to your TEMPLATES in django_signal/settings.py:


TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'], # ADD templates directory localtion
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]

10: Setting Up Templates

10.1. post_detail.html (To display post details after creating or updating)

Create a templates directory inside the root and then create a post_detail.html inside blog/templates/blog/.

{% extends 'base.html' %}

{% block title %}
{{ post.title }}
{% endblock %}

{% block content %}
<h1>{{ post.title }}</h1>
<p>{{ post.content }}</p>
<p>Created at: {{ post.created_at }}</p>
<p>Updated at: {{ post.updated_at }}</p>

<a href="{% url 'update_post' post.id %}">Update Post</a>
<a href="{% url 'delete_post' post.id %}">Delete Post</a>
{% endblock %}

10.2. home.html (For redirection after deletion)

This is a simple home page to redirect after a post is deleted.

Create home.html in blog/templates/blog/:

{% extends 'base.html' %}
{% block title %}
Home
{% endblock %}

{% block content %}
<h1>Welcome to the Blog</h1>
<p>The post has been deleted successfully.</p>
<a href="{% url 'create_post' %}">Create a New Post</a>
{% endblock %}

10.3. post_list.html (For display all post)

Create a template post_list.html inside blog/templates/blog/:

{% extends 'base.html' %}

{% block title %}
All Posts
{% endblock %}

{% block content %}
<h1>All Posts</h1>
<ul>
{% for post in posts %}
<li>
<a href="{% url 'update_post' post.id %}">{{ post.title }}</a> - {{post.created_at}}
</li>
{% endfor %}
</ul>
{% endblock %}

10.4. base.html (Optional, if you want to extend templates)

If you plan to create more views or templates in the future, you might want to have a base template for the layout.

Create base.html in blog/templates/blog/:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}My Blog{% endblock %}</title>
</head>
<body>
<header>
<h1>My Blog</h1>
</header>

<main>
{% block content %}
<!-- Content goes here -->
{% endblock %}
</main>

<footer>
<p>&copy; 2024 My Blog</p>
</footer>
</body>
</html>

11: Start the Development Server

Run the development server to access the Django admin panel the command given below:

python manage.py runserver

Click or browse to the following URL in your browser:

http://127.0.0.1:8000/admin

12: Log In to the Admin Panel

You will see the Django admin login page. Log in using the superuser credentials you created in Step 2 (username and password).

12.1. Input Data Using the Admin Interface

  1. After logging in, you will see the Post model listed in the Django admin interface.
  2. Click on “Posts”.
  3. Click “Add Post” in the top-right corner.
  4. Fill out the form with a title and content for your post.
  5. Click “Save” to create the post.

You can also update and delete posts directly from the admin panel by clicking on a post title.

12.2. Test the Signals

Now that everything is set up, you can test the signals by visiting the following URLs in your browser:posts

Check your terminal for the print statements that confirm the signals are being triggered.

13. Best Practices and Use Cases

  • Decoupling Logic: Signals allow you to decouple logic that might otherwise be embedded within model methods or views.
  • Avoid Overusing Signals: While powerful, avoid over-relying on signals as they can make your application harder to debug.
  • Handling Complex Operations: Use signals to trigger complex actions, like sending emails or updating related objects, to ensure everything happens seamlessly.

14. Conclusion

Django signals provide a great way to trigger actions based on the lifecycle of your models. By understanding how to use pre_save, post_save, pre_delete, and post_delete effectively, you can build more robust and scalable Django applications.

Thank you for reading. If you find something wrong or better ways to do it, let me know in the comments below.

If you like the post, hit the 👏 button below so that others may find it useful. You can follow me on

GitHub | daily.dev | LinkedIn | YouTube

More Libraries

Django

28 stories

Python

12 stories

--

--

Django Unleashed
Django Unleashed

Published in Django Unleashed

Unleashing the Full Potential of Web Development

Mehedi Khan
Mehedi Khan

Written by Mehedi Khan

I'm a Software engineer. I'm comfortable with Python, Django, and Full-stack Web Development. Follow To Support Me On Medium 🫠