如何在Django中创建唯一的标识符

34

我正在尝试在Django中创建一个唯一的slug,以便可以通过URL访问帖子,例如:http://www.example.com/buy-a-new-bike_Boston-MA-02111_2

相关的模型:

class ZipCode(models.Model):
    zipcode = models.CharField(max_length=5)
    city = models.CharField(max_length=64)
    statecode = models.CharField(max_length=32)

class Need(models.Model):
    title = models.CharField(max_length=50)
    us_zip = models.CharField(max_length=5)
    slug = ?????

    def get_city():
        zip = ZipCode.objects.get(zipcode=self.us_zip)
        city = "%s, %s %s" % (zip.city, zip.statecode, zip.zipcode)
        return city

一个样例ZipCode记录:

  • zipcode = "02111"
  • city = "Boston"
  • statecode = "MA"

一个样例Need记录:

  • title = "购买新自行车"
  • us_zip = "02111"
  • slug = "buy-a-new-bike_Boston-MA-02111_2" (期望的)

有什么提示可以创建这个唯一的slug吗?它的组成是:

  • Need.title +“_”+ Need.get_city() +“_”+可选的递增整数,以使其唯一。所有空格都应替换为“ - ”。

注意:我上面期望的slug假定slug“buy-a-new-bike_Boston-MA-02111”已经存在,这就是为什么它在其后面添加“_2”以使其唯一的原因。

我尝试过django-extensions,但似乎它只能接受字段或字段元组来构建唯一的slug。我需要传递get_city()函数以及连接标题和城市之间的“_”。有人解决了这个问题并愿意分享吗?

谢谢!

更新

我已经在使用django-extensions的UUIDField,因此如果它也可用于其AutoSlugField,则非常不错!

13个回答

47

我使用这个代码片段来生成唯一的slug,在我的典型保存方法中,看起来像下面这样:

slug将是Django的SlugField,但blank=True,但在保存方法中强制执行slug。

Need模型的典型保存方法可能如下所示:

def save(self, **kwargs):
    slug_str = "%s %s" % (self.title, self.us_zip) 
    unique_slugify(self, slug_str) 
    super(Need, self).save(**kwargs)

这将生成类似于buy-a-new-bike_Boston-MA-02111、buy-a-new-bike_Boston-MA-02111-1等的slug。输出可能会有一些不同,但您始终可以通过代码片段进行自定义。


谢谢Srikanth!我刚刚实现了这个功能,它非常好用! - mitchf
1
只是好奇,你为什么要覆盖save而不使用信号? - art-solopov
5
我建议在Django中避免使用信号。我的经验是,由于通常将信号代码移入单独的文件中,所以调试非常困难,并且很容易忘记它。只有当您想要修改实例,而该实例并不总是相同的模型时,才使用信号。 - Bobort

8

我的小代码:

def save(self, *args, **kwargs):
    strtime = "".join(str(time()).split("."))
    string = "%s-%s" % (strtime[7:], self.title)
    self.slug = slugify(string)
    super(Need, self).save()

6
如果您想使用应用程序来帮助您完成此操作,这里有一个应用。

https://github.com/un33k/django-uuslug

UUSlug = (``U``nique + ``U``code Slug)


Unicode Test Example
=====================
from uuslug import uuslug as slugify

s = "This is a test ---"
r = slugify(s)
self.assertEquals(r, "this-is-a-test")

s = 'C\'est déjà l\'été.'
r = slugify(s)
self.assertEquals(r, "c-est-deja-l-ete")

s = 'Nín hǎo. Wǒ shì zhōng guó rén'
r = slugify(s)
self.assertEquals(r, "nin-hao-wo-shi-zhong-guo-ren")

s = '影師嗎'
r = slugify(s)
self.assertEquals(r, "ying-shi-ma")


Uniqueness Test Example
=======================
Override your objects save method with something like this (models.py)

from django.db import models
from uuslug import uuslug as slugify

class CoolSlug(models.Model):
    name = models.CharField(max_length=100)
    slug = models.CharField(max_length=200)

    def __unicode__(self):
        return self.name

    def save(self, *args, **kwargs):
        self.slug = slugify(self.name, instance=self)
        super(CoolSlug, self).save(*args, **kwargs)

Test:
=====

name = "john"
c = CoolSlug.objects.create(name=name)
c.save()
self.assertEquals(c.slug, name) # slug = "john"

c1 = CoolSlug.objects.create(name=name)
c1.save()
self.assertEquals(c1.slug, name+"-1") # slug = "john-1"

@IvanVirabyan,您所说的“并发问题”是否指线程安全?如果是这样,那么它与Django本身一样安全,没有更多的安全性。 - Avid Coder
2
这里不存在线程安全问题。我的意思是,在应用程序的不同实例之间并发,它们都可能在同一时间生成相同的slug(例如'john-1'),当它们尝试保存模型时,会出现完整性错误。 - Ivan Virabyan
我想你无法完全消除碰撞的可能性。如果你有一个增强补丁,请务必发送一个拉取请求。 - Avid Coder
1
一句警告:尽管django-uuslug是在BSD许可下发布的,但django-uuslug(1.1.8)使用python-slugify(1.2.4),而python-slugify(1.2.4)使用Unidecode(0.4.20),后者是在GPL许可下发布的。 - Niko Pasanen

6
以下是我使用的几个函数。将模型实例和所需标题传递给`unique_slugify`,如果slug不存在,则会添加,否则它将继续尝试附加4位随机字符串,直到找到唯一的slug。
import string
from django.utils.crypto import get_random_string

def unique_slugify(instance, slug):
    model = instance.__class__
    unique_slug = slug
    while model.objects.filter(slug=unique_slug).exists():
        unique_slug = slug + get_random_string(length=4)
    return unique_slug

我通常通过覆盖模型的save方法来使用它。

class YourModel(models.Model):
    slug = models.SlugField()
    title = models.CharField()

    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = unique_slugify(self, slugify(self.title))
        super().save(*args, **kwargs)

4
这是我用于生成唯一标识符的简单小代码,您只需要一个字段即可创建您自己的唯一标识符字段。
from random import randint

def save(self, *args, **kwargs):
    if Post.objects.filter(title=self.title).exists():
        extra = str(randint(1, 10000))
        self.slug = slugify(self.title) + "-" + extra
    else:
        self.slug = slugify(self.title)
    super(Post, self).save(*args, **kwargs)

我希望你喜欢这个。


2
Django提供了SlugField模型字段来使这个过程更加简单。以下是一个“博客”应用中使用它的示例。
class Post(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField(blank=True)

    slug = models.SlugField(unique=True)

    @models.permalink
    def get_absolute_url(self):
        return 'blog:post', (self.slug,)

请注意,我们为slug字段设置了unique=True——在这个项目中,我们将按照slug查找帖子,因此我们需要确保它们是唯一的。以下是我们应用程序的views.py可能要这样做的示例:
from .models import Post

def post(request, slug):
    post = get_object_or_404(Post, slug=slug)

    return render(request, 'blog/post.html', {
        'post': post,
    })

我认为你不理解Unique的工作原理。如果为True,则此字段必须在整个表中唯一。这是在数据库级别和模型验证中强制执行的。如果您尝试保存具有唯一字段中重复值的模型,则模型的save()方法会引发django.db.IntegrityError异常。 https://docs.djangoproject.com/en/2.1/ref/models/fields/#unique这并不意味着它会自动使该字段对您来说是唯一的。 - dbinott
我正在使用SlugField,如何使其唯一,我已经阅读了其他答案,但我不理解它是如何工作的,或者应该将代码放在哪里。 - AnonymousUser

2

你好,请尝试使用这个函数。

class Training(models.Model):
    title = models.CharField(max_length=250)
    text = models.TextField()
    created_date = models.DateTimeField(
    auto_now_add=True, editable=False, )
    slug = models.SlugField(unique=True, editable=False, max_length=250)

    def __unicode__(self):
       return self.title

    def get_unique_slug(id,title,obj):
      slug = slugify(title.replace('ı', 'i'))
      unique_slug = slug
      counter = 1
      while obj.filter(slug=unique_slug).exists():
         if(obj.filter(slug=unique_slug).values('id')[0]['id']==id):
             break
         unique_slug = '{}-{}'.format(slug, counter)
         counter += 1
      return unique_slug.  

    def save(self, *args, **kwargs):
       self.slug =self.get_unique_slug(self.id,self.title,Training.objects)
       return super(Training, self).save(*args, **kwargs)

2
这是一个简单的实现,可以从标题生成slug,它不依赖其他片段:
from django.template.defaultfilters import slugify

class Article(models.Model):
    ...
    def save(self, **kwargs):
        if not self.slug:
            slug = slugify(self.title)
            while True:
                try:
                    article = Article.objects.get(slug=slug)
                    if article == self:
                        self.slug = slug
                        break
                    else:
                        slug = slug + '-'
                except:
                    self.slug = slug
                    break

        super(Article, self).save()

2

from django.utils.text import slugify

这个代码非常有用,它的概念非常清晰。以下是使用from django.utils.text import slugify自动生成slug的一个示例。

utils.py

from django.utils.text import slugify
import random
import string

# Random string generator
def random_string_generator(size=10, chars=string.ascii_lowercase + string.digits):
    return ''.join(random.choice(chars) for _ in range(size))

# Unique Slug Generator 
def unique_slug_generator(instance, new_slug=None):
    """
    It assumes your instance has a model with a slug field and a title character (char) field.
    """
    if new_slug is not None:
        slug = new_slug  
    else:
        slug = slugify(instance.title)  

    Klass = instance.__class__

    qs_exists = Klass.objects.filter(slug=slug).exists()

    if qs_exists:
        new_slug = "{slug}-{randstr}".format(slug=slug, randstr=random_string_generator(size=4))
        return unique_slug_generator(instance, new_slug=new_slug)
    return slug

models.py

from django.db.models.signals import pre_save # Signals
# import the unique_slug_generator from .utils.py 
from .utils import unique_slug_generator

class Product(models.Model):
    title  = models.CharField(max_length=120)
    # set blank to True
    slug  = models.SlugField(blank=True, unique=True)

def product_pre_save_receiver(sender, instance, *args, **kwargs):
    if not instance.slug:
        instance.slug = unique_slug_generator(instance)


pre_save.connect(product_pre_save_receiver, sender=Product)

Django文档解释了Django.utils.text的导入,以便自动生成slug。您可以在此处阅读更多详细信息。

实施代码后,在创建产品时,您可以将slug字段留空,这将进一步获得自动生成的唯一产品slug。


0

对我来说最好的解决方案:

def get_slug(self):
    slug = slugify(self.title)
    unique_slug = slug

    number = 1
    while Recipe.objects.filter(slug=unique_slug).exists():
        unique_slug = f'{slug}-{number}'
        number += 1

    return unique_slug

def save(self, *args, **kwargs):
    if not self.slug:
        self.slug = self.get_slug()
    return super().save(*args, **kwargs)

这段代码可以生成类似于以下的slug:

  • string-slug
  • string-slug-1(如果之前已经存在)
  • string-slug-2(如果之前已经存在)
  • 以此类推...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接