用django搭建自己的博客(二)-设置发布文章(转)

本文转至:http://www.jianshu.com/p/05810d38f93a

创建第一个django应用

运行以下命令:

python manage.py startapp blog

这个命令会创建应用的基本目录结构,类似下方结构:

blog/
    __init__.py
    admin.py
    migrations/
        __init__.py
    models.py
    tests.py
    views.py

设计博客的数据模型

我们将要为你的博客设计初始的数据模型。每一个model都是一个Python类,继承了django.db.models.model,其中的每一个属性对应了一个数据库字段。Djnago将会为models.py中的每一个定义好的model创建好一张表。当你创建好一个model,Django就能提供一个非常实用的API来方便的查询数据库。

首先,我们定义一个POST model。在blog应用下的models.py文件中添加以下内容:

from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User

class Post(models.Model):
    STATUS_CHOICES = (
        ('draft', 'Draft'),
        ('published', 'Published'),
    )
    title = models.CharField(max_length=250)
    slug = models.SlugField(max_length=250, unique_for_date='publish')
    author = models.ForeignKey(User, related_name='blog_posts')
    body = models.TextField()
    publish = models.DateTimeField(default=timezone.now)
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)
    status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='draft')

    class Meta:
        ordering = ('-publish',)

    def __str__(self):
        return self.title

这是一个给博客帖子使用的基础model。让我们来看下刚才在model中定义的各个字段含义:

  • title: 这个字段对应帖子的标题。这是一个CharField,在SQL数据库中对应VARCHAR类型
  • slug:这是一个用在URLs中的字段。一个slug是一个短标签之包含字母,数字,下划线或连接线。我们将通过使用slug字段来构建一个漂亮的,友好的URLs给我们的博客帖子。我们可以给每个帖子使用日期和slug来构建URLs通过添加 unique_for_date参数给这个字段。
  • author:这是一个 ForeignKey。这个字段定义了一个many-to-one(多对一)的关系。我们告诉Django每一个帖子是被一个用户编辑而一个用户又能编辑多个帖子。通过这个字段,Django将会在数据库中通过有关联的model主键来创建一个外键。在这个例子中,我们关联上了django权限系统的User model。我们指定了User和Post之间关联的名字,通过related_name属性。在之后我们将会学习到更多相关的内容。
  • body:这是帖子的内容。这个字段是TextField,在SQL数据中对应TEXT类型。
  • publish:这个字段存储了帖子发布的时间。我们使用Djnago的 timezone now方法来设定默认值。
  • created:这个字段存储了帖子创建的时间。因为我们在这儿使用了auto_now_add,当一个对象被创建的时候这个字段会自动保存当前日期。
  • updated:这个字段存储了帖子更新的时间。因为我们在这儿使用了auto_now,当我们保存一个对象的时候当前字段将会自动更新到当前日期。
  • status:这个字段是用来展示帖子的状态。我们使用了一个choices参数,所有这个字段的值只能在给予的选择内容中选择。

就像你所看到的的,Django带来了不同的字段类型所以你能够定义你的models。你可以找到所有的字段类型通过访问 Django Model field reference

在model中的Meta类包含元数据。我们告诉Django查询数据库的时候默认返回的是根据publish字段进行降序排列过的结果。我们使用负号来指定降序排列。

str()方法是对象默认的可读表现。Django将会在很多地方用到它例如管理平台。

如果你之前使用过Python2.X,请注意在Python2中所有的strings都使用unicode,因为我们的环境是python3,所以使用str()方法。unicode()方法已经废弃。

Django内置支持对日期时区的处理。你可以在项目的settings.py文件中通过USE_TZ来设置激活或停用对时区的支持。当你通过startproject命令来创建一个新项目的时候这个设置默认为True。

激活你的应用

为了让Django能保持跟踪你的应用并且通过models来创建数据表,我们必须激活你的应用。为起到效果,编辑settings.py文件在INSTALLED_APPS设置中添加blog。类似如下显示:

  INSTALLED_APPS = ( 
    'django.contrib.admin',    
    'django.contrib.auth', 
    'django.contrib.contenttypes', 
    'django.contrib.sessions', 
    'django.contrib.messages', 
    'django.contrib.staticfiles',
    'blog',
  )

现在Django已经知道我们项目中的应用是激活的状态并且将会审查其中的models。

创建和进行数据库迁移

让我们为model在数据库中创建一张表格。Django自带一个迁移系统来跟踪每一次models的变化并且会同步到数据库。migrate命令会应用到所有在INSTALLED_APPS中的应用,它会根据models来同步数据库。

python manage.py makemigrations blog

你会看到以下输出:

Migrations for 'blog':
    0001_initial.py;
        - Create model Post

Django已经在blog应用下的migrations目录中创建了一个0001——initial.py文件。你可以打开这个文件来看下。
让我们来看下Django根据我们的model将会为在数据库中创建的表格而执行的SQL代码。sqlmigrate命令会返回一串SQL而不会去执行。执行以下命令来看下输出:

python manage.py sqlmigrate blog 0001

输出类似如下:

BEGIN;
--
-- Create model Post
--
CREATE TABLE "blog_post" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "title" varchar(250) NOT NULL, "slug" varchar(250) NOT NULL, "body" text NOT NULL, "publish" datetime NOT NULL, "created" datetime NOT N
ULL, "updated" datetime NOT NULL, "status" varchar(10) NOT NULL, "author_id" integer NOT NULL REFERENCES "auth_user" ("id"));
CREATE INDEX "blog_post_2dbcba41" ON "blog_post" ("slug");
CREATE INDEX "blog_post_4f331e2f" ON "blog_post" ("author_id");
COMMIT;

这些输出是根据你正在使用的数据库。这些SQL语句是为SQLite准备的。就像你所看见的,Django生成的表名前缀为应用名之后跟上modle的小写(blog_post),但是你也可以自定义表名在models的Meta类中通过db_table属性。Django会自动为每个model创建一个主键,但是你可以自己指定主键通过设置primarry_key=True在期中一个model的字段你上。
让我们根据新的model来同步数据库。运行以下的命令来应用存在的数据迁移:

python manage.py migrate

我们刚刚为INSTALLED_APPS中所有的应用进行了数据迁移,包括我们的blog应用。在进行了数据迁移之后,数据库将会和我们的models对应。
如果你编辑了models.py文件无论你是添加,删除,还是改变了之前存在的字段,或者你添加了一个新的models,你将必须做一次新的迁移通过使用makemigrations命令。数据迁移允许Django来保持对model改变的跟踪。然后你必须通过migrate命令来保持数据库与我们的models同步。

为你的models创建一个管理站点

如今,我们已经定义好了Post model,我们将要创建一个简单的管理站点来管理博客的帖子。Django内置了一个非常有用的管理接口来编辑内容。这个Django管理站点会根据你的model元数据进行动态构建并且提供可读的接口来编辑内容。你可以对这个站点进行自由的定制,配置你的models在其中的展示方式。
请记住,django.contrib.admin已经被包含在我们项目的INSTALLED_APPS设置中,我们不需要再额外添加。

创建一个超级用户
首先,我们需要创建一个用户来管理这个管理站点。运行以下的命令:

python manage.py createsuperuser

Django管理站点
现在,通过python manage.py runserver
命令来启动开发服务,之后在浏览器中打开 http://127.0.0.1:8000/admin/ 。你会看到管理站点的登录页面;
使用你之前创建的超级用户信息登录。你将会看到管理站点的首页

Home

这很简单,对吧?当你在Django的管理页面注册了一个model,你会获得一个非常友好有用的接口通过简单的方式允许你在列表中编辑,创建,删除对象操作。点击Posts右侧的Add链接来添加一个新帖子。你将会看到Django为你的model动态生成了一个可编辑输入页面。

自定义models的展示形式

现在我们来看下如何自动管理站点。编辑博客应用下的admin.py文件,我们通过使用继承ModelAdmin的自定义类来告诉Django管理站点注册了哪些我们自己的model。在这个类中,我们可以包含一些信息来定制管理页面中如何展示和交互model。list_display属性允许你在管理对象的列表页面展示你想展示的model中的字段。

from django.contrib import admin
from .models import Post

class PostAdmin(admin.ModelAdmin):
    list_display = ('title', 'slug', 'author', 'publish', 'status')
    list_filter = ('status', 'created', 'publish', 'author')
    search_fields = ('title', 'body')
    prepopulated_fields = {'slug': ('title',)}
    raw_id_fields = ('author',)
    date_hierarchy = 'publish'
    ordering = ['status', 'publish']

admin.site.register(Post, PostAdmin)

重新刷新管理站点的页面,现在应该如下所示:


postadmin

你可以看到在帖子列表页中展示的字段都是你在list-dispaly属性中指定的。列表页面现在包含了一个右边栏允许你根据list_filter属性中指定的字段来过滤返回结果。在页面还出现了一个搜索框。这是因为我们还定义了一个搜索字段通过使用search_fields属性。在搜索框的下方,有个可以通过时间层快速操作的栏。这是通过定义date_hierarchy属性。你还能看到这些帖子可以通过StatusPublish列进行了默认排序。那是因为你指定了默认排序通过使用ordering属性。

现在,点击Add post链接。你还会在这儿看到更多的改变。当你输入完成新帖子的标题,slug字段将会自动填充。我们告诉Django通过输入的标题来填充slug字段是通过使用prepoupulated_fields属性。同时,如今的author字段展示变为了一个搜索控件,这样当你的用户达到成千上万的时候比使用下拉的选择更加的人性化,如下图所示:

author

通过短短的几行代码,我们就在管理站点中自定义了我们的model的展示形式。还有更多的方式可以用来自定义Django的管理站点。在这本书的后面,我们还会进一步讲述。

使用QuerySet和managers

现在,你已经有个完整的管理站点来管理你的博客内容,是时候学习如何从数据库中检索信息和操作了。Django自带了一个强大的数据库抽象API可以让你轻松的创建,检索,更新以及删除对象。Django的Object-relational Mapper(ORM)可以兼容MySQL,PostgreSQL,SQLite以及Oracle。请记住你可以在你项目下的setting.py中编辑DATABASES设置来指定数据库。Django可以同时与多个数据库进行工作,这样你可以编写数据库路由来操作数据通过任何你喜欢的方式。
一旦你创建好了你的数据models,Django会提供你一个API来与它们进行交互。你可以找到数据model的官方参考文档通过访问 https://docs.djangoproject.com/en/1.8/ref/models/

创建对象

打开终端运行以下命令来打开Python shell:

python manage.py shell

然后依次输入以下内容:

from django.contrib.auth.models import User 
from blog.models import Post
user = User.objects.get(username='admin')
Post.objects.create(title='One more post', slug='one-more-post', body='Post body.', author=user)
post.save()

让我们来研究下这些代码做了什么。首先,我们取回了一个username为admin的用户对象:

user = User.objects.get(username='admin')

get()方法允许你从数据库取回一个单独的对象。注意这个方法只希望有唯一的一个匹配在查询中。如果在数据库中没有返回结果,这个方法会抛出一个DoesNotExist异常,如果数据库返回多个匹配结果,将会抛出一个MultipleObjectsReturned异常。当查询执行的时候,所有的异常都是model类的属性。
然后,我们来创建一个拥有自定义标题,slug和内容的Post实例,该实例中author关联上我们之前去除的user,如下所示:

post = Post(title='Another post', slug='another-post', body='Postbody.', author=user)

# 这个对象只是存在内存中,并没有执行到数据库中

最后,我们通过使用save()方法来保存该对象到数据库中:

post.save()

这步操作将会在之后执行一段SQL的插入语句。我们已经知道如何在内存中创建一个对象并且之后才在数据库中进行插入,但是我们也可以直接在数据库中创建对象通过使用create()方法,如下所示:

post = Post(title='Another post', slug='another-post', body='Postbody.', author=user)

更新对象

现在,改变这篇帖子的标题并且再次保存对象:

>>> post.title = 'New title'
>>> post.save()

这一次,save()方法执行了一条更新语句。
# 你对对象的改变一直存在内存中直到你执行到save()方法。

取回对象

Django的Object-relational mapping(ORM)是以QuerySet为基础。QuerySet是从你的数据库中根据一些过滤条件范围取回的结果进行采集产生的对象。你已经知道通过get()方法可以从数据库中取回单独的对象。就像你所看到的:Post.objects.get()
。每一个Django model至少有一个manager,默认叫做objects。你能获得一个QuerySet对象就是通过你的models的manager。获取table中所有的对象,你只需要在默认的objects manager上使用all()方法即可:

>>> all_posts = Post.objects.all()

我们创建一个返回数据库中所有对象的QuerySet。注意这个QuerySet并还没有执行。Django的QuerySets是惰性的,他们只会被动的去执行。这样可以保证QuerySet非常效率。如果没有把QuerySet赋值给一个变量,直接在Python shell中编写,这样QuerySet的SQL语句将立马执行因为我们迫使它在输出中返回结果:

>>> Post.objects.all()

使用filter()方法

过滤QuerySet,你可以在manager上使用filter()方法。举个例子,我们可以返回所有在2015年发布的帖子,如下所示:

Post.objects.filter(publish__year=2015)

你也可以使用多个字段来进行过滤。举个例子,我们可以返回2015年发布的所有作者用户名为admin的帖子,如下所示:

Post.objects.filter(publish__year=2015, author__username='admin')

上面的写法和下面的写法产生的结果是一致的:

Post.objects.filter(publish__year=2015).filter(author__username='admin')

使用exclude()

你可以在manager上使用exclude()方法来排除某些返回结果。例如:我们可以返回所有2015年发布的帖子但是这些帖子的题目开头不能是Why:

Post.objects.filter(publish__year=2015).exclude(title__startswith='Why')

使用order_by()

你可以对结果进行排序通过在manager上使用order_by()方法来对不同的字段进行排序。例如:你可以取回所有对象通过它们的标题进行排序:

Post.objects.order_by('title')

默认是升序。你可以通过负号来指定使用降序:

Post.objects.order_by('-title')

删除对象

如果你想删除一个对象,你可以对对象实例进行下面的操作:

post = Post.objects.get(id=1)post.delete()

# 请注意,删除对象也将删除其中的依赖关系

QuerySet什么时候会执行

你可以连接许多过滤给QuerySet只要你喜欢而且不会在数据库中执行直到这个QuerySet被执行。QuerySet只有在以下情况中才会执行:

  • 在你第一次迭代它们的时候
  • 当你对它们的实例进行切片:Post.objects.all()[:3]
  • 当你对它们进行了打包或缓存
  • 当你对它们调用了repr()len()方法
  • 当你明确的对它们调用了list()方法
  • 当你再一些声明中测试它们,例如bool(), or, and, or if

创建model manager

我们之前提到过, objects是每一个models的默认manager,会返回数据库中所有的结果。但是我们也可以我们的models定义一些自定义的manager。我们准备创建一个自定义的manager来返回所有状态为已发布的帖子。
有两种方式来为你的models添加managers:你可以添加临时的manager方法或者继承manager的QuerySets进行修改。

第一种方法类似Post.objects.my_manager(),
第二种方法类似Post.my_manager.all()。我们的manager将会允许我们返回所有帖子通过使用Post.published。

编辑你的博客应用下的models.py文件添加如下代码来创建一个manager:

class PublishedManager(models.Manager):
   def get_queryset(self):
       return super(PublishedManager,
                    self).get_queryset()\
                         .filter(status='published')
class Post(models.Model):
   # ...
   objects = models.Manager() # The default manager
   published = PublishedManager() # Our custom manager

get_queryset()是返回执行的QuerySet的方法。我们通过使用它来包含我们自定义的过滤在完整的QuerySet中。我们定义我们自定义的manager然后添加到Post model中。我们现在可以来执行它。例如,
我们可以返回所有标题开头为Who的并且是已经发布的帖子:

Post.published.filter(title__startswith='Who')

构建列表和详情views

现在你学会了一些ORM的基本使用方法,你已经准备好为博客应用创建views了。一个Django view 就是一个Python方法可以接收一个web请求然后返回一个web响应。在vies中通过逻辑处理返回期望的响应。
首先我们会创建一个应用view,然后我们将会为每个view定义一个URL,最后,我们将会为每个views生成的数据通过创建HTML templates来进行渲染。每个view将会通过一个变量来渲染template并且会返回一个经过渲染输出的HTTP响应。

创建列表和详情views

让我们开始创建一个视图来展示帖子的列表。编辑你的博客应用下中views.py文件,如下所示:

from django.shortcuts import render, get_object_or_404
from .models import Post

def post_list(request):
    posts = Post.published.all()
    return render(request, 'blog/post/list.html', {'posts': posts})

你刚创建了你的第一个Django view。post_list view将request对象作为唯一的参数。记住所有的的view都有需要这个参数。在这个view中,我们获取到了所有状态为已发布的帖子通过使用我们之前创建的published manager。
最后,我们使用Django提供的快捷方法render()来渲染帖子列表通过给予的template。这个函数将request对象,template路径,和给予渲染的变量最为参数。它返回一个渲染文本(一般是HTML代码)HttpResponse对象。render()方法将许多变量投入template环境中,以便template环境中进行调用。你会在第三章,扩展你的博客应用学习到。
让我们创建第二个view来展示一篇单独的帖子。添加如下代码到views.py文件中:

def post_detail(request, year, month, day, post):
   post = get_object_or_404(Post, slug=post,
                                  status='published',
                                  publish__year=year,
                                  publish__month=month,
                                  publish__day=day)
   return render(request,
                 'blog/post/detail.html',
                 {'post': post})

这是一个帖子详情view。这个view使用year,month,day以及post参数来获取一个发布的帖子通过给予的slug和日期。请注意,当我们创建Post model的时候,我们在slgu字段上添加了unique_for_date参数。这样我们可以确保只有一个帖子会带有给予的slug,因此,我们能取回单独的帖子通过日期和slug。在这个详情view中,我们通过使用get_object_or_404()快捷方法来检索期望的帖子。这个函数能取回匹配给予的参数的对象,或者返回一个HTTP 404异常当没有匹配的对象找到。最后,我们使用render()快捷方法来使用template去渲染取回的帖子。

为你的views添加URL patterns

一个URL pattern 是由一个Python正则表达,一个view,一个全项目范围内的命名组成。Django在运行中会遍历所有URL pattern直到第一个匹配的请求URL才停止。之后,Django导入匹配的URL pattern中的view并且执行它,使用关键字或指定参数来执行一个HttpRequest类的实例。如果你之前没有接触过正则表达式,你需要去稍微了解下,通过访问 https://docs.python.org/3/howto/regex.html
在博客应用目录下创建一个urls.py文件,输入以下代码:

from django.conf.urls import url
from . import views

urlpatterns = [
    # post views
    url(r'^$', views.post_list, name='post_list'),
    url(r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/'\
        r'(?P<post>[-\w]+)/$',
        views.post_detail,
        name='post_detail'),
]

第一条URL pattern并没有带入任何参数,它映射post_list view。第二条URL pattern带上了一下4个参数映射到post_detail view。让我们看下这个URL pattern中的正则表达式:

* year:需要四位数
* month:需要两位数。不及两位数,开头带上0,比如 01,02
* day:需要两位数。不及两位数开头带上0
* post:可以由单词和连字符组成

为每一个应用创建单独的urls.py文件是最好的方法来保证你的应用可以为别的项目再度使用。

现在你需要将你博客中的URL patterns包含到项目的主URL patterns中。编辑你的项目中的mysite文件夹中urls.py文件,如下所示:

from django.conf.urls import url, include
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^blog/', include('blog.urls', namespace='blog', app_name='blog')),
]

通过这样的方式,你告诉Django在blog/路径下包含了博客应用中的urls.py定义的URL patterns。你可以给他们一个命名空间叫做blog这样你可以方便的引用这个URLs组。

models的标准URLs

你可以使用之前定义的post_detil URL对Post对象构建标准URL。Django的惯例是添加get_absolute_url()方法给model用来返回一个对象的标准URL。在这个方法中,我们使用reverse()方法允许你通过名字和可选的参数来构建URLS。编辑你的models.py文件添加如下代码:

from django.core.urlresolvers import reverse
Class Post(models.Model):

   # ...
    def get_absolute_url(self):
        return reverse('blog:post_detail',
                       args=[self.publish.year,
                            self.publish.strftime('%m'),
                            self.publish.strftime('%d'),
                            self.slug])

请注意,我们通过使用strftime()方法来保证个位数的月份和日期需要带上0来构建URL.我们将会在我们的templates中使用get_absolute_url()方法。

为你的views创建templates

我们为我们的应用创建了views和URL patterns。现在该添加模板来展示人性化的帖子了。在你的博客应用目录下创建一下目录结构和文件:

templates/
blog/
    base.html
    post/
        list.html
        detail.html

以上就是我们的templates的文件目录结构。base.html文件将会包含站点主要的HTML结构,分割内容区域和导航栏。list.html和detail.html文件会继承base.html文件来渲染各自的博客帖子列表和详情view。

Django有一个强大的templates语言允许你指定数据的展示形式。它立足于templates tags, 例如{% tag %},{{ variable }}以及templates filters,可以对变量进行过滤,例如{{ variable|gilter }}。你可以找到所有的内置templates tags和filters通过访问 https://docs.djangoproject.com/en/1.8/ ref/templates/builtins/ 。
让我们来编辑base.html文件添加如下代码:

{% load staticfiles %}
<!DOCTYPE html>
<html>
<head>
 <title>{% block title %}{% endblock %}</title>
 <link href="{% static "css/blog.css" %}" rel="stylesheet">
</head>
<body>
 <div id="content">
   {% block content %}
   {% endblock %}
 </div>
 <div id="sidebar">
   <h2>My blog</h2>
     <p>This is my blog.</p>
 </div>
</body>
</html>

{% load staticfiles %} 告诉Django去加载django.contrib.staticfiles应用提供的staticfiles temaplate tags。通过加载,你可以在这个template中使用{% static %}template filter。通过使用这个template filter,你可以包含一些静态文件比如说blog.css文件,你可以在本书的范例代码例子中知道,在博客应用的static/目录中(译者注:给大家个地址去拷贝 https://github.com/levelksk/django-by-example-book )拷贝这个目录到你的项目下的相同路径来使用这些静态文件。

你可以看到有两个{% block %}tags。这些是用来告诉Django我们想在这个区域中定义一个block。继承这个template的templates可以使用自定义的内容来填充block。我们定义了一个block叫做title,另一个block叫做content

让我们编辑post/list.html文件使它如下所示:

{% extends "blog/base.html" %}
{% block title %}My Blog{% endblock %}
{% block content %}
 <h1>My Blog</h1>
 {% for post in posts %}
   <h2>
     <a href="{{ post.get_absolute_url }}">
       {{ post.title }}
     </a>
   </h2>
   <p class="date">
     Published {{ post.publish }} by {{ post.author }}
   </p>
   {{ post.body|truncatewords:30|linebreaks }}
 {% endfor %}
{% endblock %}

通过{% extends %}
template tag,我们告诉Django需要继承blog/base.html template。然后我们在titlecontent blocks中填充内容。我们通过循环迭代帖子来展示他们的标题,日期,作者和内容,在中标题还包括一个帖子的标准URL链接。在帖子的内容中,我们应用了两个template filters: truncatewords用来缩短内容限制在一定的字数内,linebreaks用来转换内容中的换行符为HTML的换行符。你可以连接许多tempalte filters只要你喜欢,每一个都会应用到上个输出生成的结果上。

打开终端执行命令python manage.py runserver来启动开发环境。浏览器中打开 http://127.0.0.1:8000/blog/ 你会看到运行的结果。注意,你需要添加一些发布状态的帖子才能在这儿看到它们。你会看到如下图所示:

post_list

这之后,让我们来编辑post/detail.html文件使它如下所示:

{% extends "blog/base.html" %}
{% block title %}{{ post.title }}{% endblock %}
{% block content %}
 <h1>{{ post.title }}</h1>
 <p class="date">
   Published {{ post.publish }} by {{ post.author }}
 </p>
 {{ post.body|linebreaks }}
{% endblock %}

现在,你可以返回你的浏览器中点击其中一个帖子的标题来看帖子的详细view。你会看到类似的以下输出:

post_detail

添加页码

当你开始给你的博客添加内容,你很快会意识到你需要将帖子分页显示。Django有一个内置的pagination类允许你方便的管理分页。

编辑博客应用下的views.py文件导入Django的paginator类修改post_list如下所示:

from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger

def post_list(request):
    object_list = Post.published.all()
    paginator = Paginator(object_list, 3) # 3 posts in each page
    page = request.GET.get('page')
    try:
        posts = paginator.page(page)
    except PageNotAnInteger:
        # If page is not an integer deliver the first page
        posts = paginator.page(1)
    except EmptyPage:
        # If page is out of range deliver last page of results
        posts = paginator.page(paginator.num_pages)
    return render(request,'blog/post/list.html',{'page': page, 'posts': posts})

pagination是如何工作的:

  1. 我们通过希望在每页中显示的对象的数量实例化了Paginator
  1. 我们获取到page GET参数来获取页码
  2. 我们通过调用Paginator的 page()方法在期望的页面中获得了对象
  3. 如果page参数不是一个整形数字,我们就返回第一页的结果。如果这个参数的数字超出了最后的页数,我们就展示最后一页的结果
  4. 我们传递页数并且取到对象给template。

现在,我们必须创建一个template来展示分页处理,它可以被任意的template包含来使用页码。在博客应用的templates文件夹下创建一个新文件命名为pagination.html。在文件中添加如下HTML代码:

<div class="pagination">
 <span class="step-links">
   {% if page.has_previous %}
     <a href="?page={{ page.previous_page_number }}">Previous</a>
   {% endif %}
   <span class="current">
     Page {{ page.number }} of {{ page.paginator.num_pages }}.
   </span>
     {% if page.has_next %}
       <a href="?page={{ page.next_page_number }}">Next</a>
     {% endif %}
 </span>
</div>

这个分页template期望一个Page对象为了渲染上一页与下一页的链接并且展示页面数据和所有页面的结果。让我们回到blog/post/list.htm tempalte中将pagination.htmltemplate包含在{% content %}block中,如下所示:

{% block content %} 
... 
  {% include "pagination.html" with page=posts %}
{% endblock %}

我们传递给template的Page对象叫做posts,我们将分页tempalte包含在帖子列表template中指定参数来对它进行渲染。这是一种方法你可以重复利用你的分页template对不同的models views进行分页处理。
现在,在你的浏览器中打开 http://127.0.0.1:8000/blog/ 你会看到帖子列表的底部有分页信息:

pagination

使用基于类的views

当一个view被调用通过web请求并且返回一个web响应,你还可以将你的views定义成类方法。Django为此定义了基础的view类。它们都从View类继承而来,View类可以操控HTTP方法分派和其他的功能。这是一个可代替的方法来创建你的views。

我们准备使我们的post_list view转变为一个基于类的视图通过使用Django提供的通ListView。这个基础view允许你对任意的对象进行排列。
编辑你的博客应用下的views.py文件,如下所示:

from django.views.generic import ListView

class PostListView(ListView):
    queryset = Post.published.all()
    context_object_name = 'posts'
    paginate_by = 3
    template_name = 'blog/post/list.html'

这个基于类的的view类似与之前的post_list view。这儿,我们告诉ListView做了以下操作:

使用一个特定的QuerySet代替取回所有的对象。代替定义一个queryset属性,我们可以指model = Post,并且Django将会构建一个通过的Post.objects.all() QuerySet给我们。
使用环境变量posts给查询结果。如果我们不指定任意的context_object_name默认的变量将会是object_list
对结果进行分页处理每页只显示3个对象。
使用自定义的template来渲染页面。如果我们不设置默认的template,ListView将会使用blog/post_list.html。

现在,打开你的博客应用下的urls.py文件,在post_listURL pattern之前添加一个新的URL pattern使用PostlistView类,如下所示:

from django.conf.urls import url
from . import views

urlpatterns = [
    # post views
    # url(r'^$', views.post_list, name='post_list'),
    url(r'^$', views.PostListView.as_view(), name='post_list'),
    url(r'^(?P<year>\d{4})/(?P<month>\d{2})/(?P<day>\d{2})/(?P<post>[-\w]+)/$',views.post_detail,name='post_detail'),
]

为了保持分页处理能工作,我们必须将正确的页面对象传递给tempalte。Django的ListView通过叫做page_obj的变量来传递被选择的页面,所以你必须编辑你的post_list_html template因此去包含使用了正确的变量的分页处理,如下所示:

{% include "pagination.html" with page=page_obj %}

在你的浏览器中打开 http://127.0.0.1:8000/blog/ 然后检查每一样事情是否都和之前的post_list view一样工作。这是一个简单的基于类的view例子通过使用Django提供的通用类。

推荐阅读更多精彩内容