Slug,中文翻译过来就是标称, 单位的意思。在Django中,SlugField中的字符会被用来作为页面URL的一部分,由于slug的设定一般情况下都是unique=True,因而结合了slug的URL通常即为该页面的唯一网址路径。例如,我网站的博文详情页的链接是leeshen.net/articles/<slug>,这里的slug对应的就是每一篇博文各自的slug。一般来说,页面的slug是无法“自动”生成的——我的意思是说,Django框架,Python或者数据库系统是不会自动给你生成对应的slug。因此很多Django的新手教程都会把slug设置成手动输入。这种方法的好处是你可以完全自定义slug,且不用担心因为slug重复而造成的bug。但问题来了,如果你是自己写博客,那你自己填SlugField也无妨。但如果你是做电商的呢?成千上万个商品总不能全部自己一个个来填写slug吧?这时候我们就需要考虑有没有什么方法能够自动生成slug。以下我们就来讨论讨论几种常见的slug自动生成的方法:
1. 通过slugify库生成
slugify是一个专门用来处理unicode的python库。其主要功能是将unicode转化为ASCII。举个例子:
from slugify import slugify
txt1 = "Hello World"
print(slugify(txt1))
#结果:hello-world
txt2 = "你好"
print(slugify(txt2))
#结果:ni-hao
早期slugify其实并不支持中文,因此以前还需要先将中文通过其他库转换一下才能用slugify。不过更新后现在就方便了很多。回到正题,slugify可以说是最简单的slug自动生成方法。开发者在设计网站的model时,可以将特定的field呈递给它,进而自动生成某页面的slug。如:
from slugify import slugify
class Article(models.Model):
Title = models.CharField('Title', max_length=100)
Content = RichTextUploadingField('Content', max_length=50000)
Slug = models.SlugField(max_length=255, editable=False, unique=True)
def save(self, *args, **kwargs):
self.Slug = slugify(self.Title)
super(Article, self).save(*args, **kwargs)
上述代码实现了将文章的标题递送给slugify处理并作为该文章的slug保存。当然,slugify也并不是完美无缺。比如,在博客系统中,当你文章标题很长的话,你的slug会显得非常臃肿,尽管你可以设置长度限制。其次,对于中文来说,一个拼音对应的汉字可能有很多个,wo-ai-zhe-ni可以是我挨着你,也可以是我爱着你。当你已经有一个slug指向“我挨着你”的时候,你在想通过slugify为“我爱着你”生成slug就会报错。而反过来说,一个汉字也会对应多个读音。我高中同桌叫“单(shan)晨”。如果通过slugfiy来生成就变成了“dan-chen”。总之,slugify对于纯英文/数字的文本来说还是很友好的,而以上我提到的一些处理中文的问题其实也可以通过一些魔改来避免。如果你觉得自己写脚本自动生成slug麻烦,那slugify就是你最好的选择。
PS:初学者在使用pip安装slugify的时候,应注意安装包的名称。老版本是slugify,新版本是python-slugify。因此,当你使用python3安装时,则应使用命令:
pip install python-slugify
2. 使用特定的field作为slug
除了slugify,我们也可以通过自己写脚本来生成slug。比如,我们都知道在mysql中每个条目的ID是独一无二的。我们可以调用ID作为我们某个页面的slug。有两种方案可以选择:
2.1 修改model的save功能:
class Article(models.Model):
Title = models.CharField('Title', max_length=100)
Content = RichTextUploadingField('Content', max_length=50000)
Slug = models.SlugField(max_length=255, editable=False, unique=True)
def save(self, *args, **kwargs):
super(Article, self).save(*args, **kwargs)
if not self.Slug:
self.Slug = str(self.id)
self.save()
这边的逻辑大致是这样的:博文数据在存入数据库前我们无法获取其相应的信息,因此我们要先将其保存到数据库中。然后如果SlugField是空值,那我们则调用该博文在数据库中对应的ID作为slug填入SlugField中,然后保存。当然,除了修改model的save功能外,我们还能通过signal的post_save来进行处理。
2.2 post_save写入slug
@receiver(post_save, sender=Article)
def create_article(sender, instance, created, **kwargs):
if created:
instance.Slug = str(instance.id)
instance.save()
这段代码的逻辑跟上面一样,只不过相对上述代码而言更简洁易懂一些。不过话说回来,这两种方法都涉及到两次储存,从性能上而言并不是很完美,甚至在StackOverflow上有用户认为这段code以及idea都很“丑陋”:
It is ugly - because idea of such slug is not nice itself. I know it is easier to do it like that and forgot about, but in fact such slug is nothing more than denormalization of database. I think it would be better to refer to object by its' id and just faking the use of slug in views and urls.
– jasisz Aug 16, 2012 at 22:57
Anyway,虽然这几种方法都不是十全十美,但基本都能满足自动生成slug的需求。上述评论中的老哥所说的直接调用数据库中对应的ID来refer to特定的object听起来也是一个很好的方法,我暂时就不尝试了。当然,其实还有其他的一些繁琐的方法我也不展开讨论了,各位网友如果有更好的方案也欢迎留言!