0

I have a model SessionType with a field title of which I'd like to create a slugified version, smattering_slug. Currently, this is implemented with the following receiver function:

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


@receiver(pre_save, sender=SessionType)
def create_smattering_slug(sender, instance, **kwargs):
    if not instance.smattering_slug:
        instance.smattering_slug = slugify(instance.title)

I also have other models, however, for which I'd like to do the same thing, but the name of the field to be slugified and the slug field is different.

To generalize this, I considered doing something like this:

def create_slug_field(sender, instance, **kwargs):
    field = kwargs.pop('field')
    slug_field = kwargs.pop('slug_field')

    if not getattr(instance, slug_field):
        setattr(instance, slug_field, slugify(field))


from functools import partial
pre_save.connect(partial(create_slug_field, field='title', slug_field='smattering_slug'))

However, in this way, I'm not passing in sender=SessionType as in the original example. (It is not clear to me from https://docs.djangoproject.com/en/2.0/topics/signals/#connecting-to-signals-sent-by-specific-senders how to do this while using .connect()).

Also, I would like to make field and slug_field required arguments, but I don't quite see how this would work because the handlers seem to expect a fixed set of arguments described in https://docs.djangoproject.com/en/2.0/ref/signals/#pre-save.

How might I implement this receiver in an 'extensible' way?

Update

From the definition of .connect(), it would appear that it accepts an optional sender argument, but when I adapt the example above to

pre_save.connect(
    sender=SessionType,
    receiver=partial(create_slug_field, field='title', slug_field='smattering_slug'))

and set a trace in the create_slug_field function, the trace doesn't cause Python to drop into the debugger, meaning that the function is not called! How is my 'generalized' version not equivalent to the original version in this case?

Kurt Peek
  • 52,165
  • 91
  • 301
  • 526
  • 1
    Does it have to be a signal handler? You could override the `save` method in a `Model` subclass and extend that instead. You can then have the field name as an attribute of your model class. – Selcuk Jun 29 '18 at 00:54
  • My CTO said he prefers signal handlers to overridden `save()` methods. I'm basically trying to achieve what is described at a high level in https://stackoverflow.com/questions/170337/django-signals-vs-overriding-save-method. – Kurt Peek Jun 29 '18 at 01:03
  • 1
    You can still define an attribute in your model class and use that in your signal handler, i.e. `instance.slug_field_name`. – Selcuk Jun 29 '18 at 01:18
  • I would like to use this signal handler for different models in which the fields are named differently. For example, in another model, I would like to slugify the `name` field into `name_slug`. – Kurt Peek Jun 29 '18 at 01:58
  • 1
    Yes, that's what I mean. Add `slug_field_name='name_slug'` in your model (maybe `Meta`?), then use that value from your handler. – Selcuk Jun 29 '18 at 03:08
  • 1
    Does `pre_save.connect` without the sender argument work? If so, `create_slug_field` could sort out the correct sender itself. Have you verified that _any_ signals work (because I do not see anything wrong with your updated signal). – CoffeeBasedLifeform Jun 29 '18 at 17:58
  • Yes, I've verified that `pre_save.connect()` works with the original function, `create_smattering_slug`, with the `sender` argument. It seems to only not work if the `receiver` is a [`functools.partial`](https://docs.python.org/3.7/library/functools.html#functools.partial) object... – Kurt Peek Jul 02 '18 at 17:12

0 Answers0