简体   繁体   English

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

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

I have a Django form entry where I want the model saved to the database to have a unique title, or rather a unique "slug" (derived from the title). 我有一个Django表单条目,我希望保存到数据库的模型具有唯一的标题,或者更确切地说是一个独特的“slug”(从标题派生)。 I do this by appending a counter to the slug. 我通过在slu子上附加一个计数器来做到这一点。 So something like "this-is-a-title-1" and possibly then later "this-is-a-title-2". 所以像“这是一个冠军1”,可能后来“这是一个冠军2”。

But if two users at the same time submit an entry with the same title, only one entry will make it to the database (the slug field is unique); 但是如果两个用户同时提交具有相同标题的条目,则只有一个条目将进入数据库(slug字段是唯一的); saving the other will throw an IntegrityError. 保存另一个将抛出IntegrityError。 To create a unique counter, I now loop upon encountering such aa duplicate entry, as follows: 为了创建一个独特的计数器,我现在循环遇到这样一个重复的条目,如下所示:

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

I do this through the admin, where the slug is not entered explicitly (but is created in the save() method). 我通过管理员执行此操作,其中未明确输入slug(但是在save()方法中创建)。

My question is : is this a good idea? 我的问题是 :这是一个好主意吗? Somehow it feels like a work-around that has a better solution. 不知何故,感觉就像一个解决方案有更好的解决方案。

Another solution I can think of is to pop-up a message to the unfortunate second user, but that would only say that "your entry can't be saved at this time. Try again in a few seconds." 我能想到的另一个解决方案是向不幸的第二个用户弹出一条消息,但这只会说“此时无法保存您的条目。请在几秒钟后重试。” I would then also first need to get the current count from existing (identical-except-for-the-counter) slugs. 然后我还要首先从现有的(相同的除了计数器)slug中获取当前count The above method automatically avoids that, although it will run into problems if ever there are more than MAXCOUNT identical slugs. 上述方法自动避免了这种情况,尽管如果有超过MAXCOUNT相同的段MAXCOUNT ,它会遇到问题。

Are there any other suggestions, or does this seems like a viable solution? 有没有其他建议,或者这似乎是一个可行的解决方案?

(Note: I came across this similar question suggested by SO while entering my question, but there, a get() is first performed before saving the entry. This potentially allows enough time between the get() and save() to still attempt to save duplicate entries, thus causing an IntegrityError. Otherwise it appears to suggest pretty much the same solution as I have now.) (注意:在输入我的问题时,我遇到了SO提出的类似问题 ,但是在保存条目之前首先执行get() 。这可能允许get()save()之间有足够的时间来尝试保存重复的条目,从而导致IntegrityError。否则它似乎提出了与我现在几乎相同的解决方案。)

If you're just needing to have a value to pass through the querystring, you could just add a property to the model, that combines the slug + the pk: 如果你只需要一个值来传递查询字符串,你可以只为模型添加一个属性,它结合了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)

Then you don't have to worry about counts being off, race conditions, etc and you only have one write operation. 然后你不必担心计数关闭,竞争条件等,你只有一个写操作。

You could do two writes in your save method: 您可以在save方法中执行两次写操作:

  1. Save the instance with a temporary random slug (to satisfy uniqueness constraint) 使用临时随机slug保存实例(以满足唯一性约束)
  2. Calculate the slug using the PK and save again 使用PK计算slu and并再次保存

Something like this: 像这样的东西:

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)

This has the advantage of only requiring a max of two DB writes per save call. 这样做的优点是每次保存调用最多只需要两次DB写入。

If You must have an counter inside slug, much better way will be: 如果你必须在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

it will query database only once to check what number put at the end of slug. 它只会查询数据库一次,以检查slug末尾的数字。

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

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