博客五部曲之一 - 简单博客


1265 浏览 5 years, 4 months

29 SlugField

版权声明: 转载请注明出处 http://www.codingsoho.com/

目前的URL里使用的是id,但是不利于分享,这一掌将会讲解用SlugField去优化URL

class Post(models.Model):
    title = models.CharField(max_length=120)
    slug = models.SlugField(unique=True)

因为slug是unique,那么我们必须删除当前的database,否则新生成的slug字段会相互冲突。

删除db.sqlite3以及所有的migrations文件,原media也删除掉,随便不影响后面的功能。

一般来说slug支持自动生成,这儿我们用到了pre_save信号,它能够让我们在数据存储到数据库之前做一些处理。

from django.db.models.signals import pre_save
from django.utils.text import slugify

def create_slug(instance, new_slug=None):
    slug = slugify(instance.title)
    if new_slug is not None:
        slug =  new_slug
    qs  = Post.objects.filter(slug=slug).order_by("-id")
    exists = qs.exists()
    if exists:
        new_slug = "%s-%s" % (slug, qs.first().id)
        return create_slug(instance, new_slug=new_slug)    
    return slug

def pre_save_post_receiver(sender, instance, *args, **kwargs):
    if not instance.slug:
        instance.slug = create_slug(instance)

pre_save.connect(pre_save_post_receiver, sender=Post)

这儿有个问题是生成的slug可能是重复的,我们采用递归的方式来生成新的slug
当创建一个新的实例时,pre_save过程中,id还没有生成,所有我们可以用一个折中的办法就是用前一个相同slug的实例ID
比如我们创建实例名字为new post,那么对应的slug为new-post (ID=1),再创建一个,名字为new-post-1,反复创建,名字分别为new-post-1-2, new-post-1-2-3 ….

最后修改详情视图的访问URL从ID改到slug

class Post(models.Model):
    def get_absolute_url(self):
        return reverse("posts:detail", kwargs={"slug":self.slug})

并修改post_detail, post_delete和post_update函数,将实例获取方式从ID改到slug,和ID一样,slug也是唯一的。

def post_detail(request, slug=None):
    instance = get_object_or_404(Post, slug=slug)
    context = {
        "title" : instance.title,
        "instance" : instance
    }
    return render(request, "post_detail.html",context)   

更新URL映射方式

urlpatterns = [
    url(r'^$', views.post_list, name="list"),
    url(r'^create/', views.post_create),
    url(r'^(?P<slug>[\w-]+$)', views.post_detail, name="detail"),    
    url(r'^(?P<slug>[\w-]+)/edit$', views.post_update, name="update"), 
    url(r'^(?P<slug>[\w-]+)/delete$', views.post_delete, name="delete"), 
]