Ken
Ken

Reputation: 902

Django models slug mixin with unique constraint handling

I have three models Book, Part, Chapter, in my models.py that uses the SlugField.

For the Book class, I have written a slug handler in the custom save method that checks if an object exists with the slug. And when it does, it makes it unique by appending a count to it.

How do I rewrite the block inside Book's to SlugMixin so that I can use for the rest of the models?

models.py

...

class SlugMixin(models.Model):
    slug = models.SlugField(max_length=50, unique=True)

    class Meta:
        abstract = True

class Book(models.Model):
    title = models.CharField(max_length=50)
    slug = models.SlugField(max_length=50, unique=True)

    def save(self, *args, **kwargs):
        if not self.pk and not self.slug:
            slug = slugify(self.title, allow_unicode=True)
            slug_exists = True
            counter = 1
            self.slug = slug

            while slug_exists:
                try:
                    slug_exists = Book.objects.get(slug=slug)
                    if slug_exists:
                        slug = self.slug + '_' + str(counter)
                        counter += 1
                except Book.DoesNotExist:
                    self.slug=slug
                    break


class Part(models.Model):
    book = models.ForeignKey(Book, on_delete=models.CASCADE, related_name='parts')
    title = models.CharField(max_length=30)
    slug = models.SlugField(max_length=30, unique=True)


class Chapter(models.Model):
    part = models.ForeignKey(Part, on_delete=models.CASCADE, related_name='chapters')
    title = models.CharField(max_length=40)
    slug = models.SlugField(max_length=40, unique=True)

...

Upvotes: 0

Views: 335

Answers (2)

Josue Hernández
Josue Hernández

Reputation: 21

Instead of doing this implementation yourself, you can use the autoslug library instead.

Install pip install django-autoslug

and in your model you can define the AutoSlugField field like this:

# models.py

...
from autoslug import AutoSlugField
...

class YourModel(models.Model):
    ...
    name = models.CharField(max_length=50)
    slug = AutoSlugField(populate_from="name", unique=True, max_length=50, db_index=True)
    ...

Upvotes: 0

JPG
JPG

Reputation: 88519

How about this

SLUG_LENGTH = 50


def get_unique_slug(model_instance):
    slugify_title = slugify(model_instance.title, allow_unicode=True)
    if len(slugify_title) > SLUG_LENGTH:
        slug = slugify_title[:SLUG_LENGTH]
    else:
        slug = slugify_title
    slug_copy = slug
    num = 1
    while model_instance.__class__.objects.filter(slug=slug).exists():
        number_attached_slug = '{}-{}'.format(slug_copy, num)

        if len(number_attached_slug) > SLUG_LENGTH:
            trimmed_slug = slug_copy[:-(num + 1)]  # adding 1 because there is hyphen in the slug
            slug = '{}-{}'.format(trimmed_slug, num)
        else:
            slug = number_attached_slug
        num += 1

    return slug


class SomeLogicKlass:
    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = get_unique_slug(self)
        super().save(*args, **kwargs)

    def __str__(self):
        return self.slug


class Book(SomeLogicKlass, models.Model):
    ...


class Part(SomeLogicKlass, models.Model):
    ...

Note: make sure the value of SLUG_LENGTH is same as the max_length of models.SlugField(...)

Upvotes: 1

Related Questions