Как избежать IntegrityError и столкновения с db при сохранении модели после санации slug с помощью slugify в Django?

В настоящее время я работаю над простым веб-проектом, в котором у меня есть несколько моделей, использующих SlugField в качестве URL, таких как Page, Note и Tag. Из панели администратора пользователь может редактировать slug и другие поля модели.

Пользователь может вручную модифицировать slug, чтобы сделать его более удобным для пользователя.

Пользователь также может ввести содержимое в верхнем регистре и специальные символы, либо по ошибке, либо по злому умыслу.

Валидация slug как уникального поля выполняется правильно во фронтенде, если он введен правильно, но не выполняется, если введены заглавные или специальные символы.

Вот пример:

Пользователь создает новый тег со slug: new-tag Нажмите сохранить, и он выполнит свою работу.

Пользователь создает новый тег со словом new-tag. Нажимаем сохранить, и пользователь получает предупреждение о валидации, говорящее, что это невозможно. Хорошо, как и ожидалось.

Пользователь создает новый тег со slug: New-Tag (обратите внимание на заглавные буквы). Нажимаем сохранить и получаем.

  • Тип исключения: IntegrityError
  • Значение исключения: UNIQUE constraint failed: notes_tag.slug Что ожидаемо, и я пытаюсь решить эту проблему.

Это моя модель: Полный проект можно посмотреть здесь: Github repo


class Tag(models.Model):
    id = models.UUIDField(default=uuid.uuid4, unique=True,
                          primary_key=True, editable=False)
    name = models.CharField(max_length=255)
    slug = models.SlugField(unique=True)

    def save(self, *args, **kwargs):
        # todo: check colision with others URLs
        self.slug = slugify(self.slug)
        super(Tag, self).save(*args, **kwargs)

Мой первый подход к решению коллизии:

def save(self, *args, **kwargs):
        self.slug = slugify(self.slug)
        original_slug = self.slug
        queryset = Tag.objects.filter(slug=self.slug).exists()
        counter = 1
        while queryset:
            self.slug = f"{original_slug}-{counter}"
            counter += 1
            queryset = Tag.objects.filter(slug=self.slug).exists()
        super(Tag, self).save(*args, **kwargs)

Для меня это не похоже на то, что так и должно быть, проверять базу данных с помощью цикла while было бы опасно. Но ладно, мне это не нравится, и проблема в том, что этот метод сохранения выполняется всегда, при создании новых элементов и при обновлении.

Я пытаюсь использовать if self.id для проверки того, является ли элемент новым или только что обновленным, но поскольку моя модель переопределяет поле id, к тому времени, когда выполнение достигает метода сохранения, элемент уже создан и получил id.

Итак, есть ли какие-либо рекомендации по этому вопросу? Какой-нибудь способ проследить?

По моему мнению, более простым решением будет проверка slug после slugify и использование того же фронт-энда, который встроен в django.

Но мне интересно, почему self.id существует после того, как выполнение достигло метода сохранения...?

Я уже проверил:

Заранее спасибо.

Я пытаюсь проверить, существует ли сущность в базе данных, затем добавить '-number' в конце slug, но не могу проверить, создан ли элемент или обновлен, поэтому добавление '-number' происходит всегда после того, как я нажимаю кнопку save.

лучшим решением является проверка slug в form.clean(). или перед созданием объекта.

но, если вы хотите проверить slug в Tag.save()...:

class Tag(models.Model):
    ...
    def save(self, *args, **kwargs):
        self.slug = slugify(self.slug)
        if self.__class__.objects.filter(slug=self.slug).count():
           # do error process
        else: retrun super(T).save(*args, **kwargs)
        return
Вернуться на верх