我有一个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。否则它似乎提出了与我现在几乎相同的解决方案。)
答案 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)
您可以在保存方法中执行两次写入操作:
这样的事情:
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末尾的数字。