如何在Django中生成唯一的slug,避免竞态条件?
我正在使用Django REST Framework为一个博客站点创建一个简单的API。我希望每篇博客文章都拥有一个唯一的、易读的slug,将在URL中使用,就像 /api/posts/my-first-post 那样。下面是我当前的实现:
# models.py
class Post(models.Model):
title = models.CharField(max_length=255)
slug = models.SlugField(unique=True, blank=True, max_length=255)
body = models.TextField()
def save(self, *args, **kwargs) -> None:
if not self.slug:
import string, random
self.slug = slugify(self.title)
while Post.objects.filter(slug=self.slug).exists():
suffix = '-' + ''.join(random.choices(string.ascii_lowercase + string.digits, k=5))
self.slug = slugify(self.title + suffix)
super().save(*args, **kwargs)
# views.py
class PostList(generics.ListCreateAPIView):
serializer_class = PostSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
lookup_field = 'slug'
def perform_create(self, serializer):
return serializer.save(author=self.request.user)
但显然 Post.objects.filter(slug=self.slug).exists() 会产生竞态条件,因为它是在应用层工作,而不是在数据库层工作,因此如果两个具有相同slug的帖子在同一时刻保存,可能会抛出一个 IntegrityError。我不确定该如何修复。
解决方案
我觉得可能找到了一个解决办法,采用EAFP风格,就像 @jonrsharpe 所建议的那样:
def save(self, *args, **kwargs) -> None:
if self.slug:
return super().save(*args, **kwargs)
for _ in range(10):
try:
self.slug = f'{slugify(self.title)[:246]}-{uuid.uuid4().hex[:8]}'
return super().save(*args, **kwargs)
except IntegrityError:
self.slug = None
raise RuntimeError(f'Could not generate unique slug for post {self.title}')
我们让它先失败,然后再重试,直到成功。不过这也可能会有问题。
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。