Populating django field with pre_save()?
Solution 1
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.
Solution 2
@receiver(pre_save, sender=TodoList)
def my_callback(sender, instance, *args, **kwargs):
instance.slug = slugify(instance.title)
Solution 3
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")
Solution 4
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.
Derek
Updated on July 05, 2022Comments
-
Derek almost 2 years
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
def pre_save(self): self.slug = title
I'm guessing no. What is the code to do this?
Thanks!
-
user1066101 almost 13 years@Derek: Just override
save()
. It's much, much simpler and more predictable. -
Bernhard Vallant almost 13 yearsBetter? 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...
-
xyres about 10 yearsAwesome. This answer should be included in Django docs. There's really no example in docs about using
signals
. -
xyres almost 10 years@Firula Right, you are. I should have rather said no solid example. Thanks BTW.
-
Simon Steinberger almost 9 yearsAlso, 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).
-
msanders about 7 years@simon-steinberger pre_save is not called when you use the update method on QuerySets - the Django docs 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())."
-
Engin Yapici over 4 years@msanders do you know a way to make this work with QuerySet update method?
-
MikeyE over 4 yearsFor whatever reason the accepted solution did not work for me. But, this clean and elegant solution did.
-
Ed Menendez over 3 yearsIf 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.
-
Gnietschow about 3 yearsI 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 (docs.djangoproject.com/en/3.0/ref/signals/…), which prevented my functions from being called. -
Martijn Pieters about 3 years@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.