Introduction: Why Use pre_save
in Django?
In Django projects, especially when working with Django REST Framework (DRF), you often want to perform some logic before saving a model instance — like:
-
Auto-generating slugs or unique IDs
-
Sanitizing or modifying fields
-
Logging changes
-
Validating dependent fields
This is where Django's pre_save
signal and serializer hooks come in handy. With these tools, you can execute custom logic right before a model instance is saved — whether from a form, admin panel, or an API request.
Let’s explore how to do this with both models and serializers.
⚙️ Option 1: Using pre_save
Signal with Django Models
Step-by-Step Guide
1. Import Required Modules
from django.db.models.signals import pre_save
from django.dispatch import receiver
from .models import Post
2. Create a Signal Receiver
@receiver(pre_save, sender=Post)
def set_default_title(sender, instance, **kwargs):
if not instance.title:
instance.title = "Untitled Post"
3. Connect the Signal (Optional if using @receiver
)
pre_save.connect(set_default_title, sender=Post)
This ensures any time a Post
instance is saved (via ORM or API), the title
will default to "Untitled Post"
if not provided.
⚙️ Option 2: Using Serializer’s create()
or update()
Methods
In Django REST Framework, you might want to execute this logic inside the serializer layer — closer to your API.
Example: Customizing Save Logic in Serializer
from rest_framework import serializers
from .models import Post
from django.utils.text import slugify
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ['id', 'title', 'slug', 'content']
def create(self, validated_data):
# Auto-generate slug if not provided
if not validated_data.get('slug') and validated_data.get('title'):
validated_data['slug'] = slugify(validated_data['title'])
return super().create(validated_data)
def update(self, instance, validated_data):
# Update slug if title changes
if 'title' in validated_data:
validated_data['slug'] = slugify(validated_data['title'])
return super().update(instance, validated_data)
✅ This approach is more API-focused, suitable for customizing create/update logic when using Django REST Framework.
Full Functional Example: pre_save + Serializer
models.py
from django.db import models
from django.utils.text import slugify
class Post(models.Model):
title = models.CharField(max_length=100)
slug = models.SlugField(unique=True, blank=True)
content = models.TextField()
def __str__(self):
return self.title
signals.py
from django.db.models.signals import pre_save
from django.dispatch import receiver
from .models import Post
@receiver(pre_save, sender=Post)
def auto_generate_slug(sender, instance, **kwargs):
if not instance.slug and instance.title:
instance.slug = slugify(instance.title)
serializers.py
from rest_framework import serializers
from .models import Post
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ['id', 'title', 'slug', 'content']
apps.py (to connect signal)
from django.apps import AppConfig
class BlogConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'blog'
def ready(self):
import blog.signals # Ensure the signals are connected
Comparison Table: Signal vs Serializer Logic
Feature | pre_save Signal |
Serializer Method |
---|---|---|
Trigger Location | Model level | API level (DRF only) |
Works outside DRF | ✅ Yes | ❌ No |
Best for | Generic model logic | API-specific behavior |
Debugging complexity | Medium | Easy |
DRY and reusable | ✅ Yes | Partially |
⚠️ Tips & Common Pitfalls
✅ Tips
-
Use signals for logic that should always happen before saving — regardless of where the save is triggered.
-
Use serializer logic for API-specific behaviors or when validation is closely tied to request data.
-
Always import and connect signals inside
apps.py
to avoid issues.
❌ Pitfalls
-
Signals can silently fail if not properly imported.
-
Don’t overuse signals for API-only logic — it makes debugging harder.
-
Watch out for recursive updates if you're modifying the same field that triggers the signal.
Conclusion: Best Practices for Using pre_save
pre_save
signals and serializer overrides are both powerful tools in your Django toolbox. Knowing when and where to use them can help you write clean, maintainable, and DRY code.
Key Takeaways
-
Use
pre_save
when you need universal pre-save logic (e.g., slugs, defaults). -
Use
serializer.create()
orupdate()
for API-specific logic in DRF. -
Combine both for flexibility and separation of concerns.