Django:通过自动将计数器附加到title / slug来保存唯一条目

时间:2014-01-09 15:05:37

标签: python django

我有一个Django表单条目,我希望保存到数据库的模型具有唯一的标题,或者更确切地说是一个独特的“slug”(从标题派生)。我通过在slu子上附加一个计数器来做到这一点。所以像“这是一个标题1”,可能后来“这是一个标题2”。

但是如果两个用户同时提交具有相同标题的条目,则只有一个条目将进入数据库(slug字段是唯一的);保存另一个将抛出IntegrityError。为了创建一个独特的计数器,我现在循环遇到这样一个重复的条目,如下所示:

class MyClass(models.Model):
    self.title = models.CharField()
    self.slug = models.SlugField(unique=True, blank=False)

    def save(*args, **kwargs):
        baseslug = slugify(self.title)
        self.slug = baseslug + "-1"
        count = 1
        while count < MAXCOUNT:  # MAXCOUNT something like 1000
            try:
                super(MyClass, self).save(*args, **kwargs)
            except IntegrityError as exc:
                count += 1
                self.slug = baseslug + "-{:d}".format(count)
            else:
                break  # we're fine; break out of the loop

我通过管理员执行此操作,其中未明确输入slug(但是在save()方法中创建)。

我的问题是:这是个好主意吗?不知何故,感觉就像一个解决方案有更好的解决方案。

我能想到的另一个解决方案是向不幸的第二个用户弹出一条消息,但这只会说“此时无法保存您的条目。请在几秒钟内重试。”然后我还需要从现有的(相同的 - 除了计数器)slug中获取当前count。上面的方法会自动避免这种情况,尽管如果有超过MAXCOUNT个相同的段塞,它会遇到问题。

还有其他建议吗,或者这似乎是一个可行的解决方案?

(注意:我在输入问题时遇到了this similar question提出的问题,但是在保存条目之前首先执行get()。这可能会在{{1}之间留出足够的时间}和get()仍然尝试保存重复的条目,从而导致IntegrityError。否则它似乎提出了与我现在几乎相同的解决方案。)

3 个答案:

答案 0 :(得分:2)

如果你只需要一个值来传递查询字符串,你可以只为模型添加一个属性,它结合了slug + pk:

class MyClass(models.Model):
    title = models.CharField(max_length=255)
    slug = models.SlugField(max_length=300)

    @property
    def url_slug(self):
        return '{}-{}'.format(self.slug, self.id)

然后你不必担心计数关闭,竞争条件等,你只有一次写操作。

答案 1 :(得分:0)

您可以在保存方法中执行两次写入操作:

  1. 使用临时随机slug保存实例(以满足唯一性约束)
  2. 使用PK计算slu and并再次保存
  3. 这样的事情:

    class MyClass(models.Model):
        title = models.CharField(max_length=255)
        slug = models.SlugField(max_length=300, blank=True)
    
        def save(self, *args, **kwargs):
            if not self.slug:
                # Slug field must be unique, so give it a temporary throw-away value
                temp_slug = uuid.uuid4().hex
                self.slug = temp_slug
                super(MyClass, self).save(*args, **kwargs)
                self.slug = "{}-{}".format(slugify(self.title), self.pk)
            super(MyClass, self).save(*args, **kwargs)
    

    这样做的好处是每次保存调用最多只需要两次DB写入。

答案 2 :(得分:0)

如果你必须在slug内有一个计数器,那么更好的方法是:

class MyClass(models.Model):
    self.title = models.CharField()
    self.slug = models.SlugField(unique=True, blank=False)

    def save(self, *args, **kwargs):
        baseslug = slugify(self.title)
        try:
            last = MyClass.objects.filter(slug__regex="%s-\d+" % baseslug).latest('id')
            # safe, becouse slugify won't return regex special chars
            # also field with latest id will have highest counter in slug if it was populated that way

            trash, count = last.slug.rsplit('-', 1)            
            count = int(count)+1
        except:
            count = 1
        while count < MAXCOUNT:
            self.slug = "%s-%d" % (baseslug, count)
            try: 
                super(MyClass, self).save(*args, **kwargs)
                break
            except IntegrityError as exc:
                count += 1

它只会查询数据库一次以检查slug末尾的数字。