68
class TodoList(models.Model):
    title = models.CharField(maxlength=100)
    slug = models.SlugField(maxlength=100)
    def save(self):
        self.slug = title
        super(TodoList, self).save()

I'm assuming the above is how to create and store a slug when a title is inserted into the table TodoList, if not, please correct me!

Anyhow, I've been looking into pre_save() as another way to do this, but can't figure out how it works. How do you do it with pre_save()?

Is it like the below code snippet?

def pre_save(self):
        self.slug = title

I'm guessing not. What is the code to do this?

Thanks!

Javad
  • 2,033
  • 3
  • 13
  • 23
Derek
  • 11,980
  • 26
  • 103
  • 162

5 Answers5

113

Most likely you are referring to django's pre_save signal. You could setup something like this:

from django.db.models.signals import pre_save
from django.dispatch import receiver
from django.template.defaultfilters import slugify

@receiver(pre_save)
def my_callback(sender, instance, *args, **kwargs):
    instance.slug = slugify(instance.title)

If you dont include the sender argument in the decorator, like @receiver(pre_save, sender=MyModel), the callback will be called for all models.

You can put the code in any file that is parsed during the execution of your app, models.py is a good place for that.

Algorithmatic
  • 1,824
  • 2
  • 24
  • 41
Bernhard Vallant
  • 49,468
  • 20
  • 120
  • 148
  • 13
    @Derek: Just override `save()`. It's much, much simpler and more predictable. – S.Lott Jun 24 '11 at 01:27
  • 15
    Better? It does basically the same... If you want to change an existing app's functionality going with the signal is for sure the preferred way... – Bernhard Vallant Jun 24 '11 at 09:43
  • 3
    Awesome. This answer should be included in Django docs. There's really no example in docs about using `signals`. – xyres Apr 28 '14 at 15:02
  • @Firula Right, you are. I should have rather said *no **solid** example*. Thanks BTW. – xyres Jul 05 '14 at 04:52
  • Also, pre_save is called even for list updates, whereas the save method is only used when actually calling save on an instance. Thus, pre_save is the only choice if you do something like MyModel.objects.all().update(field_x=x). – Simon Steinberger Jun 16 '15 at 06:32
  • 10
    @simon-steinberger pre_save is _not_ called when you use the update method on QuerySets - the [Django docs](https://docs.djangoproject.com/en/1.10/ref/models/querysets/#update) say "Finally, realize that update() does an update at the SQL level and, thus, does not call any save() methods on your models, nor does it emit the pre_save or post_save signals (which are a consequence of calling Model.save())." – msanders Mar 30 '17 at 09:58
  • @msanders do you know a way to make this work with QuerySet update method? – Engin Yapici Sep 27 '19 at 22:43
31
@receiver(pre_save, sender=TodoList)
def my_callback(sender, instance, *args, **kwargs):
    instance.slug = slugify(instance.title)
Leandro Souza
  • 311
  • 3
  • 2
19

you can use django signals.pre_save:

from django.db.models.signals import post_save, post_delete, pre_save

class TodoList(models.Model):
    @staticmethod
    def pre_save(sender, instance, **kwargs):
        #do anything you want

pre_save.connect(TodoList.pre_save, TodoList, dispatch_uid="sightera.yourpackage.models.TodoList") 
Eyal Ch
  • 9,552
  • 5
  • 44
  • 54
  • 2
    For whatever reason the accepted solution did not work for me. But, this clean and elegant solution did. – MikeyE Nov 15 '19 at 06:53
  • 1
    If you're going to modify the model you might as well modify the save() method. Signals should only be used if you need to decouple the code from the model. – Ed Menendez Feb 10 '21 at 20:49
16

The pre_save() signal hook is indeed a great place to handle slugification for a large number of models. The trick is to know what models need slugs generated, what field should be the basis for the slug value.

I use a class decorator for this, one that lets me mark models for auto-slug-generation, and what field to base it on:

from django.db import models
from django.dispatch import receiver
from django.utils.text import slugify

def autoslug(fieldname):
    def decorator(model):
        # some sanity checks first
        assert hasattr(model, fieldname), f"Model has no field {fieldname!r}"
        assert hasattr(model, "slug"), "Model is missing a slug field"

        @receiver(models.signals.pre_save, sender=model, weak=False)
        def generate_slug(sender, instance, *args, raw=False, **kwargs):
            if not raw and not instance.slug:
                source = getattr(instance, fieldname)
                slug = slugify(source)
                if slug:  # not all strings result in a slug value
                    instance.slug = slug
        return model
    return decorator

This registers a signal handler for specific models only, and lets you vary the source field with each model decorated:

@autoslug("name")
class NamedModel(models.Model):
    name = models.CharField(max_length=100)
    slug = models.SlugField()

@autoslug("title")
class TitledModel(models.Model):
    title = models.CharField(max_length=255)
    slug = models.SlugField()

Note that no attempt is made to generate a unique slug value. That would require checking for integrity exceptions in a transaction or using a randomised value in the slug from a large enough pool as to make collisions unlikely. Integrity exception checking can only be done in the save() method, not in signal hooks.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • I needed to add `weak=False` to the `@receiver` decorator to get this working. Local functions can be garbage collected otherwise as stated in the docs (https://docs.djangoproject.com/en/3.0/ref/signals/#module-django.db.models.signals), which prevented my functions from being called. – Gnietschow Apr 06 '21 at 13:05
  • @Gnietschow: ah, good point! I happen to have a more involved implementation that I've simplified down for this answer, and.. that lost a reference. So I didn't run into that issue myself. I'll update the answer. – Martijn Pieters Apr 06 '21 at 21:19
-4

Receiver functions must be like this:

def my_callback(sender, **kwargs):
    print("Request finished!")

Notice that the function takes a sender argument, along with wildcard keyword arguments (**kwargs); all signal handlers must take these arguments.

All signals send keyword arguments, and may change those keyword arguments at any time.

Reference here.

Rockallite
  • 16,437
  • 7
  • 54
  • 48