Using pre_save in Django with Models and Serializers

Last updated 4 months ago | 326 views 75     5

Tags:- Python Django DRF

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() or update() for API-specific logic in DRF.

  • Combine both for flexibility and separation of concerns.