繁体   English   中英

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

[英]Django: saving unique entries by automatically appending a counter to the title/slug

我有一个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相同的段MAXCOUNT ,它会遇到问题。

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

(注意:在输入我的问题时,我遇到了SO提出的类似问题 ,但是在保存条目之前首先执行get() 。这可能允许get()save()之间有足够的时间来尝试保存重复的条目,从而导致IntegrityError。否则它似乎提出了与我现在几乎相同的解决方案。)

如果你只需要一个值来传递查询字符串,你可以只为模型添加一个属性,它结合了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)

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

您可以在save方法中执行两次写操作:

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

像这样的东西:

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写入。

如果你必须在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末尾的数字。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM