Slug is not created correctly due to using django parler

I have a problem creating a slug based on the title and pk, due to the use of parler. I'm using Parler for multilinguality in my API, and when I try to create a slug like title-pk I'm getting various bugs. My model:

class Events(TranslatableModel):
    ...
    translations = TranslatedFields(
        title=models.CharField(max_length=255, verbose_name=_("Event title")),
        body=MDTextField(verbose_name=_("Event body")),
    )
    slug = models.SlugField(blank=True, max_length=255, verbose_name=_("Event slug"))
    ...

So, I tried using the save() method, but I got a slug like -None or -pk.

    def save(self, *args, **kwargs):
        if not self.slug:
            title = self.safe_translation_getter("title", language_code="uk")
            base_slug = slugify(title)
            self.slug = f"{base_slug}-{self.pk}"
        super().save(*args, **kwargs)

I also used a signal, which also did not work.

@receiver(post_save, sender=Events)
def generate_slug_after_translation(sender, instance, **kwargs):
    if not instance.slug:
        uk_title = instance.safe_translation_getter("title", language_code="uk")
        if uk_title:
            base_slug = f"{uk_title}-{instance.pk}"
            instance.slug = slugify(base_slug)[:255]
            instance.save(update_fields=["slug"])

What did I do wrong, and what should I do next?

Thanks for the suggestion in the comments — debugging helped me find the issue.
So, the issue. The save() method didn’t generate the slug correctly because it was being executed before the object was saved to the database — which means pk was None and the translated title wasn't guaranteed to exist yet.
I tried calling super().save(*args, **kwargs) before accessing the translated title, like this:

def save(self, *args, **kwargs):
        super().save(*args, **kwargs)
        if not self.slug:
            title = self.safe_translation_getter("title", language_code="uk")
            base_slug = slugify(title)
            logging.debug(f"[DEBUG] Event base_slug: {base_slug}")
            self.slug = f"{base_slug}-{self.pk}"
            logging.debug(f"[DEBUG] Event slug: {self.slug}")
        super().save(*args, **kwargs)

But the logs showed this:


[DEBUG] Event base_slug:
[DEBUG] Event slug: -9

Which confirmed that title was still empty.

Working solution: use post_save signal
I found the root of the issue: the generated slug was incorrect when using Cyrillic characters.
My previous signal produced the following logs:


[DEBUG] base_slug: тест-11
[DEBUG] instance.slug: 11

Turns out, slugify() doesn't transliterate non-ASCII characters by default.
I installed unidecode and used it to transliterate the base string before passing it to slugify():

from unidecode import unidecode

@receiver(post_save, sender=Event)
def generate_slug_after_translation(sender, instance, **kwargs):
    if not instance.slug:
        uk_title = instance.safe_translation_getter("title", language_code="uk")
        if uk_title:
            base_slug = f"{uk_title}-{instance.pk}"
            slug = slugify(unidecode(base_slug))[:255]
            logging.debug(f"[DEBUG] instance.slug: {slug}")
            instance.slug = slug
            instance.save(update_fields=["slug"])

Now the Cyrillic title gets properly transliterated to Latin characters in the slug: "тест-11" → "test-11"

Вернуться на верх