I'm trying to create a unique SlugField using a post_save model signal. If the SlugField already exists, a number should be appended to the slug to make it unique.
However, Django does not seem to to recognize when the SlugField already exists.
I'm using a single-tabled inherited MPTT model:
class Text(MPTTModel):
type = models.CharField(max_length=255, blank=False) # for STI. Essentially returns the class name.
title = models.CharField(max_length=255, blank=True)
slug = models.SlugField(max_length=255, blank=True)
slug_order = models.CharField(max_length=255, blank=True)
def __init__(self, *args, **kwargs):
super(Text, self).__init__(*args, **kwargs)
# If we don't have a subclass at all, then we need the type attribute to match
# our current class.
if not self.__class__.__subclasses__():
self.type = self.__class__.__name__.lower()
else:
subclass = [
x
for x in self.__class__.__subclasses__()
if x.__name__.lower() == self.type
]
if subclass:
self.__class__ = subclass[0]
else:
self.type = self.__class__.__name__.lower()
class Book(Text):
objects = BookManager()
class Meta:
proxy = True
@receiver(post_save, sender=Book, dispatch_uid="create_book_slug")
def create_book_slug(sender, instance, **kwargs):
slug = slugify(instance.title)
slug_exists = Book.objects.filter(slug=slug).exists() # This always seems to be False
counter = 1
while slug_exists:
counter += 1
new_slug = f"{slug}-{counter}"
slug_exists = Book.objects.filter(slug=new_slug).exists()
if counter > 1:
slug = f"{slug}-{counter}"
instance.slug = slug
My test:
b1 = Book.objects.create(title="book book")
b2 = Book.objects.create(title="book book")
self.assertEqual(b1.slug, "book-book") # True
self.assertEqual(b2.slug, "book-book-2") # False - b2.slug gives "book-book"
self.assertEqual(b1.slug, b2.slug) # This is True... obviously not what I want.
self.assertEqual(Book.objects.filter(slug=b1.slug).exists(), True) # False. I have no idea why.
self.assertEqual(Book.objects.filter(title=b1.title).exists(), True) # True. This works.
I have to use post_save
as I actually want to leverage the default MPTT fields (lft
, rght
, level
, get_root()
etc.) I actually have another STI model called Chapter
that has will utilize slug_order
:
class Chapter(Text):
objects = ChapterManager()
class Meta:
proxy = True
@receiver(post_save, sender=Chapter, dispatch_uid="create_chapter_slug")
def create_chapter_slug(sender, instance, **kwargs):
print(instance.get_root()) # This works and points to the Chapter's top parent
print(instance.get_root().slug) # This doesn't work and returns nothing
instance.slug = instance.get_root().slug
# slug_order code below works
order = instance.rght - instance.lft
if instance.level == 1:
instance.slug_order = order
else:
instance.slug_order = f"{instance.parent.slug_order}/{order}"
Looks like the the post_save
signal doesn't actually save.
i.e.
b1 = Book.objects.create(title="book book")
self.assertEqual(b1.slug, "book-book") # True
b1.refresh_from_db()
self.assertEqual(b1.slug, "") # True
And adding instance.save()
to the post_save
receiver gives a recursion error.
My initial problem is solved with using pre_save
instead for the create_book_slug
.
My create_chapter_slug
is actually a whole other problem (my method for calculating the order is wrong) that I will be posting on another thread...