Building a Blog Application 翻译加实践

创建第一个工程

第一个django工程是创建一个完整的blog.Django提供了一个命令创建和初始化工程文件结构.

1
(django2byExample) haotianfei@tianfei-opensuse:~/PycharmProjects> django-admin startproject django_2_by_example

这会创建一个名为django_2django_2_by_example的工程,工程名应该避免使用与python或django内置模块相同的名字,以免引起冲突.
我们来看一下生成的工程结构:

1
2
3
4
5
6
7
8
9
10
(django2byExample) haotianfei@tianfei-opensuse:~/PycharmProjects> tree django_2_by_example/
django_2_by_example/
├── django_2_by_example
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
└── manage.py

1 directory, 5 files
  • manage.py: 这是一个与工程交互的命令行管理工具.这是一个对django-admin.py的简单封装,一般不需要修改这个文件.
  • django_2_by_example: 工程目录,包括4个文件:
    • init.py: 一个空文件,python将包含此文件的目录视为一个模块.
    • settings.py: 工程配置文件,默认包含一些配置都在此文件中.
    • urls.py: URL配置文件,每个条URL都映射到一个view.
    • uwsgi.py: 工程通过此文件配置部署成web server gateway interface(WSGI)应用.
      生成的settings.py文件包含工程配置,其中默认配置了SQLite3的数据库及包含默认在INSTALLED_APPS列表中的通用应用.我们稍后将在工程设置部分研究这些应用.
      为了完成工程安装,需要在数据库中创建INSTALLED_APPS列表中的应用相应的表.运行下面的命令:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      (django2byExample) haotianfei@tianfei-opensuse:~/PycharmProjects> cd django_2_by_example/
      (django2byExample) haotianfei@tianfei-opensuse:~/PycharmProjects/django_2_by_example> python manage.py migrate
      Operations to perform:
      Apply all migrations: admin, auth, contenttypes, sessions
      Running migrations:
      Applying contenttypes.0001_initial... OK
      Applying auth.0001_initial... OK
      Applying admin.0001_initial... OK
      Applying admin.0002_logentry_remove_auto_add... OK
      Applying contenttypes.0002_remove_content_type_name... OK
      Applying auth.0002_alter_permission_name_max_length... OK
      Applying auth.0003_alter_user_email_max_length... OK
      Applying auth.0004_alter_user_username_opts... OK
      Applying auth.0005_alter_user_last_login_null... OK
      Applying auth.0006_require_contenttypes_0002... OK
      Applying auth.0007_alter_validators_add_error_messages... OK
      Applying auth.0008_alter_user_username_max_length... OK
      Applying auth.0009_alter_user_last_name_max_length... OK
      Applying sessions.0001_initial... OK

上面是Django数据库迁移输出信息.通过migrations,初始应用的表将在数据库中创建.在接下来的”创建及应用迁移”这一章节将会学习migrate管理命令.

在开发环境运行服务

django提供了一个轻量web服务器以便快速测试代码,不需要花大量时间配置一个生产服务器.当运行django开发服务器,它将持续检测代码修改.重载是自动的,不需要代码修改后手工重载服务器.但有些行为可能不会被提示,例如添加新文件到工程,这里必需手动重载服务器.
使用下面的命令在工程根目录运行开发服务器:

1
2
3
4
5
6
7
8
(django2byExample) haotianfei@tianfei-opensuse:~/PycharmProjects/django_2_by_example> python manage.py runserver
Performing system checks...

System check identified no issues (0 silenced).
January 11, 2019 - 06:39:35
Django version 2.0.5, using settings 'django_2_by_example.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

打开浏览器,输入http://127.0.0.1:8000/将会显示成功运行界面.
标志着Django开发服务器已经在运行中.返回命令行终端,会有GET请求记录打印:

1
[11/Jan/2019 06:50:04] "GET / HTTP/1.1" 200 16348

每个HTTP请求都会被开发服务器记录并打印在终端.任何的错误信息同样会输出到终端.
还可以订制django开发服务器使用指定IP与port,加载指定配置文件(当你拥有多个环境需要多个配置文件时可以为每个环境创建一个配置文件)的方式运行工程,如下:

1
(django2byExample) haotianfei@tianfei-opensuse:~/PycharmProjects/django_2_by_example> python manage.py runserver 127.0.0.1:8001 --settings=django_2_by_example.settings

当然开发服务器只能在开发时使用不能用于生产环境.生产部署django需要使用WSGI应用部署在真实的web服务器上,如apache,gunicorn或uWSGI.关于部署wsgi可以参阅https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/.

工程设置

让我们打开settings.py文件看看我们工程的配置.这个文件包含许多配置,但这只是settings的一部分.全部参数参考https://docs.djangoproject.com/en/2.0/ref/settings/
比较重要的设置如下:

  • DEBUG: 布尔值,打开或关闭工程调试模式.默认是true,如果应用有非预期的错误,django会显示详细的错误信息.迁移到生产环境时记得设置为false.永远不要在生产环境打开DEBUG部署一个站点,因为有可能泄漏与工程相关的敏感信息.
  • ALLOWED_HOSTS: DEBUG开启及tests时不生效.一旦迁移到生产环境,你可以添加域名允许解析站点.
  • INSTALLED_APPS: 配置在django站点中启用的应用.Django默认包含以下应用:
    • django.contrib.admin: 一个admin管理框架.
    • django.contrib.auth: 一个认证框架.
    • django.contrib.contenttypes: 内容类型处理框架.
    • django.contrib.sessions: 会话框架.
    • django.contrib.messages: 消息系统框架.
    • django.contrib.staticfiles: 静态文件管理框架.
  • MIDDLEWARE: 将要执行的中间件列表.
  • ROOT_URLCONF: 应用定义url匹配位置.
  • DATABASES: 工程中所有数据库信息.必须设置一个默认数据库.默认是SQLite3.
  • LANGUAGE_CODE: 站点默认语言编码.
  • USE_TZ: 打开或关闭时区支持.

工程和应用

在django中,工程被视为带有一些设置的django安装.
一个应用站点是模型,视图,模板和URLS的组合.应用与django框架交互提供一些特殊的功能,然后可以在其它工程中重用.可以把工程视作你的网站,包含了许多应用,如blog,wiki和forum.然后这些应用也可以在其它网站重用.

创建一个应用

现在,让我创建每一个django应用.我们将一步步创建一个blog应用.进入工程根目录,运行下面的命令:

1
(django2byExample) haotianfei@tianfei-opensuse:~/PycharmProjects/django_2_by_example> python manage.py startapp blog

这将创建一个基本的应用结构,如下所示:

1
2
3
4
5
6
7
8
9
10
11
(django2byExample) haotianfei@tianfei-opensuse:~/PycharmProjects/django_2_by_example> tree
.
├── blog
│   ├── admin.py
│   ├── apps.py
│   ├── __init__.py
│   ├── migrations
│   │   └── __init__.py
│   ├── models.py
│   ├── tests.py
│   └── views.py

包括如下文件:

  • admin.py: 注册到django admin管理框架模型,可选项.
  • apps.py: blog应用的主配置文件.
  • migrations: 应用数据库迁移文件目录.迁移允许django追踪model修改,然后相应的同步到数据库.
  • models.py: 应用数据模型,所有的django文件都需要有一个数据模型文件,但可以内容为空.
  • tests.py: 添加测试到应用.
  • views.py: 定义应用逻辑; 每个视图接收http请求,处理请求,然后返回响应.

设置blog应用的数据库结构

通过定义数据模型来设计blog的数据结构.一个模型是django.db.models.Model子类,每个属性对应数据库字段.django会为每个模型创建一个数据库表.通过创建模型,django提供了一个易于查询数据库的事实上的API接口.
首先,我们将定义一个post模型.添加如下内容到models.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from django.db import models

# create your models here.
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='published')
author = models.ForeignKey(User,
on_delete=models.CASCADE,
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 = ('-published',)

def __str__(self):
return self.title

模型属性:

  • title: 文章标题,对应数据库中的varchar字段.
  • slug: 这个字段被打算用于URLs.slug是一个只包含字母,数字,下划线,连字号的一个简短的标记.使用slug可以构建漂亮的,SEO友好的urls.我们为其添加了unique_for_data参数,以便创建使用发布日期与slub结合的文章urls.Django会阻止同一日期使用相同slug的文章.
  • author: 这是一个外键.默认是多对一的关系.我们可以告诉Django文章由一个用户书写,一个用户可以书写一定数量的文章.使用外键,Django将会使用关联模型的主键在数据库中创建一个外键.我们可以信赖Django认证系统的User数据模型.on_delete参数指明当指向的对象删除时采纳的相应动作.这是SQL的基本动作.使用models.CASCADE,我们设定当指定的用户删除时,数据库将删除用户关联的所有文章.关于CASCADE可参考我们同时指定了一个反向关联名称related_name,表示从User到posts.这将轻松访问关联对象.
  • body: 文章的主体内容,一个文本字段,对应SQL数据库的TEXT字段.
  • publish: 当文章发布时的时间.使用Django的timezone的now方法生成,返回值是timezone可识别格式的当前时间.可以看作是python datatime.now方法.
  • created: 文章创建时间.auto_now_add参数表示时间会自动保存到创建对象.
  • updated: 文章更新时间.auto_now表示当更新时日期时间会自动保存.
  • status: 发布状态.使用choice参数,只能在所预设的值中选择.
    Django在不同的模型中使用不同的类型,查阅参考
    Meta类包含模型的元数据.这里使用发布日期的倒序排序查询结果.最近发布的文章将排在第一位.
    str方法返回一个对人友好的描述.Django大量使用此方法,如管理站点.

激活应用

为了让Django可以持续追踪应用,通过模型创建数据库表,首先要激活应用.修改settings.py在INSTALLED_APPS变量中添加blog.apps.BlogConfig.

1
2
3
4
5
6
7
8
9
10
11
# Application definition

INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'blog.apps.BlogConfig',
]

BlogConfig是blog应用主配置文件apps.py中定义的一个类.现在已经激活应用,可以加载应用模型了.

创建及应用migrations

下面来创建数据库表.Django使用一个迁移系统追踪模型修改然后相应的应用到数据库.migrate命令为INSTALLED_APPS表中所有的应用实施与数据库的同步.
首先,创建一个初始migration.执行下面的指令:

1
2
3
4
(django2byExample) haotianfei@tianfei-opensuse:~/PycharmProjects/django_2_by_example> python manage.py makemigrations blog
Migrations for 'blog':
blog/migrations/0001_initial.py
- Create model Post

初始化操作仅是创建了blog/migrations/0001_initial.py文件,一个迁移操作依赖其它迁移,当模型修改时执行数据库同步操作.
我们来看一下将要对模型进行操作的SQL代码.附带了迁移名称的sqlmigrate命令可以返回SQL语句但不真正执行.

1
2
3
4
5
6
7
8
9
(django2byExample) haotianfei@tianfei-opensuse:~/PycharmProjects/django_2_by_example> 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 NULL, "updated" datetime NOT NULL, "status" varchar(10) NOT NULL, "author_id" integer NOT NULL REFERENCES "auth_user" ("id") DEFERRABLE INITIALLY DEFERRED);
CREATE INDEX "blog_post_slug_b95473f2" ON "blog_post" ("slug");
CREATE INDEX "blog_post_author_id_dd7a8485" ON "blog_post" ("author_id");
COMMIT;

Django默认使用应用+类名作为数据库表明,可以在meta类中使用db_table属性指定数据库表名.Django同时为每个模型创建一个主键,也可以便利属性primary_key=True手工指定一个字段为主键.默认主键字段是id,一个自增长整数字段.
下面来实施数据库同步:

1
2
3
4
5
(django2byExample) haotianfei@tianfei-opensuse:~/PycharmProjects/django_2_by_example> python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, blog, contenttypes, sessions
Running migrations:
Applying blog.0001_initial... OK

如果修改了model模型,需要makemigrates,然后使用migrate同步到数据库.

创建一个管理数据库站点

已经创建好post模型,接下来创建一个简单的管理站点来管理blog文章.Django自带一个非常有用的管理接口编辑内容.Django管理站点通过读取模型元数据动态创建,提供了一个编辑内容的产品级接口.你可以开箱即用,在上面显示你想模型.django.contrib.admin默认已经在INSTALLED_APPS中,不需要自己添加.

创建超级用户

首先,需要一个用户来管理站点.

1
2
3
4
5
6
7
8
9
10
(django2byExample) haotianfei@tianfei-opensuse:~/PycharmProjects/django_2_by_example> python manage.py createsuperuser
Username (leave blank to use 'haotianfei'): admin
Email address: talenhao@gmail.com
Password:
Password (again):
This password is too short. It must contain at least 8 characters.
This password is too common.
Password:
Password (again):
Superuser created successfully.

管理站点

现在打开开发服务器,使用浏览器打开http://127.0.0.1:8000/admin.

1
python manage.py runserver

使用刚才创建的超级用户及密码登陆.
Group和User模型是由Django认证框架django.contrib.auth提供的.之前的Post模型使用auther字段就是关联到这里的User.

添加模型到管理站点

现在添加blog post模型到管理站点.修改admin.py文件:

1
2
3
4
5
6
from django.contrib import admin

# Register your models here.
from blog.models import Post

admin.site.register(Post)

打开http://127.0.0.1:8000/admin将会看到新添加的Post.
是不是很简单?当你的模型注册到管理站点,你会得到由内置框架生成的一个友好的界面,你可以轻松查看,编辑,创建和删除对象.
点击右边的添加链接添加一篇新文章.Django针对不同的字段类型使用不同的插件,甚至混合字段如DateTimeField使用javascript 日期插件,也很好的显示出来.
填入内容,然后点击保存按钮,会重定向到文章列表及显示一个成功的消息.

定制模型显示方式

下面来定制管理站点.编辑admin.py:

1
2
3
4
5
6
7
8
9
10
11
12
from django.contrib import admin

# Register your models here.
from blog.models import Post

@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ('title',
'slug',
'author',
'publish',
'status')

注册模型Post继承admin.ModelAdmin,使用自定制类.在这个自定类中,可以包括显示,及如何交互.list_display属性允许要显示在管理站对象列表的模型字段.@admin.register()装饰器代替admin.site.register(),注册我们的自定类PostAdmin如何ModelAdmin注册Post.
下面来扩展更多参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from django.contrib import admin

# Register your models here.
from blog.models import Post

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


使用QuerySet和managers

现在你拥有了全功能的管理站点来管理blog的内容,现在来从数据库接收信息及与之交互.Django自带了一个非常强大的抽象接口,可以轻松创建,接收,更新,删除对象.Django ORM(object-relateional mapper)可以兼容MySQL,PostgreSQL,SQLite,Oracle.不要忘记你可以在settings.py的DATABASES部分定义工程数据库.Django可以同时配置多个数据库,可以编辑数据库路由来自定义路由结构.
一旦创建数据模型,可以自由的在Django中与之交互.数据模型参考官方文档

创建对象

打开终端,然后运行下面的命令来打开python shell:

1
2
3
4
5
6
7
8
9
10
11
12
(django2byExample) haotianfei@tianfei-opensuse:~/PycharmProjects/django_2_by_example> python manage.py shell
Python 3.6.5 (default, Mar 31 2018, 19:45:04) [GCC] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from django.contrib.auth.models import User
>>> from blog.models import Post
>>> user = User.objects.get(username="admin")
>>> post = Post(title="Auther admin post",
... slug="Auther_admin_post",
... body="Post body",
... author=user)
>>> post.save()

首先使用用户名’admin’接收user对象.get()方法允许你从数据库中接收单个对象.这个方法预期会有一个匹配结果.如果没有结果匹配到,会返回DoesNotExist错误;如果返回多个结果,将会抛出MultipleObjectsReturned错误.
接下来创建了一个post实例,填入title,slug,body,author使用上一步接收到的user,最后使用实例方法save()保存到数据库.
save()会执行一个INSERT SQL语句,我们可以看到如何在内存中首先创建一个对象,然后持久化到数据库中.但我们还可以只需要一个create()操作创建对象然后持久化到数据库:

1
2
>>> Post.objects.create(title='One more post', slug='one-more-post', body='Post body', author=user)
<Post: One more post>

更新对象

现在修改一个post然后保存对象

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

这里的save()方法执行的UPDATE SQL语句

接收对象

Django的 ORM基于QuerySets.一个QuerySets是从数据库中收集对象,然后设置多个过滤去限制结果.上面已经知道使用get()接收单个对象.每个Django模型都至少有一个管理器,这个默认的管理器叫做objects.你得到一个QuerySet对象使用模型管理器.为了从一个表中接收所有的对象,使用objects.all()方法

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

使用filter()方法

过滤QuerySets使用objects的filter()方法.举个例子,我们需要接收所有发布时间为2019年的文章使用如下的QuerySet:

1
2
>>> Post.objects.filter(publish__year=2019)
<QuerySet [<Post: One more post>, <Post: New title>, <Post: This is the second post>, <Post: This is the first post.>]>

使用多个过滤条件:

1
2
>>> Post.objects.filter(publish__year=2019, author__username='admin')
<QuerySet [<Post: One more post>, <Post: New title>, <Post: This is the second post>, <Post: This is the first post.>]>

上面的命令等效:

1
2
3
>>> Post.objects.filter(publish__year=2019)\
... .filter(author__username='admin')
<QuerySet [<Post: One more post>, <Post: New title>, <Post: This is the second post>, <Post: This is the first post.>]>

使用exclude()

可以使用objects.exclude()从结果中排除适当的内容.

1
2
3
>>> Post.objects.filter(publish__year=2019)\
... .exclude(title__startswith="New")
<QuerySet [<Post: One more post>, <Post: This is the second post>, <Post: This is the first post.>]>

使用order_by()

可以使用objects.order_by()方法按不同的字段排序结果.-号表示反序.

1
2
3
4
>>> Post.objects.order_by('-title')
<QuerySet [<Post: This is the second post>, <Post: This is the first post.>, <Post: One more post>, <Post: New title>]>
>>> Post.objects.order_by('title')
<QuerySet [<Post: New title>, <Post: One more post>, <Post: This is the first post.>, <Post: This is the second post>]>

删除对象

如果想删除一个对象,可以使用对象实例方法delete()

1
2
3
4
5
>>> post_new = Post.objects.get(id=1)
>>> post_new
<Post: This is the first post.>
>>> post_new.delete()
(1, {'blog.Post': 1})

QuerySet评估操作

你可以串连尽可能多的过滤到QuerySet,在没有评估之间是不会去数据库的.
QuerySet只会在下面的情况下被评估:

  • 第一次迭代
  • 当有切片操作,对于实例,Post.objectes.all()[:3]
  • 当数据有pickle或缓存操作
  • 使用repr()或len()
  • 明确的调用list()
  • 使用测试语句,如bool(),or,and,if

创建模型管理器

objects是每个模型在数据库中的默认管理器.但我们还可以为模型定制管理器.我们将创建一个定制管理器接收所有published状态的文章.
有2种方式添加管理器:扩展默认管理器方法或修改初始QuerySets.前者提供一个QuerySets接口如Post.objects.my_manager(),后者提供如Post.my_manager.all().管理器允许我们使用Post.published.all()接收文章.
编辑model.py文件添加自定管理器:

1
2
3
4
5
6
7
8
class PublishedManager(models.Manager):
def get_queryset(self):
return super(PublishedManager, self).get_queryset().filter(status='published')


class Post(models.Model):
objects = models.Manager()
published = PublishedManager()

get_queryset方法用于管理器返回QuerySet.上面采取了覆盖方法,包含了自定过滤条件.

1
2
>>> Post.published.all()
<QuerySet [<Post: This is the second post>]>

构建列表及详细视图

现在已经具有了ORM的知识,现在可以为blog应用创建视图了.Django视图是能够接收web请求然后返回一个web响应的python函数.所有预期的响应逻辑都是包含在视图中.
首先,我们将创建应用视图,然后为每一个视图定义一个URL匹配,最后创建HTML模板渲染视图生成的数据.
每个视图都会传递变量到渲染的模板,然后伴随渲染输出返回一个HTTP响应.

创建列表及详细视图

现在开始创建一个显示文章列表的视图.编辑blog应用的views.py文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
from django.shortcuts import render

# Create your views here.
from django.shortcuts import get_object_or_404
from .models import Post


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

这是每个django视图.post_list视图只需要带入request对象作为惟一参数.request也被所有视图作为参数.在这个视图中,我们使用自建管理器published接收了所有published状态的文章.
最后我们使用django提供的render()快捷函数使用模板渲染文章列表.这个函数带入request对象,模板路径,内容变量渲染指定模板.最终返回一个包含渲染文本(通常是HTML代码)的HttpResponse对象.render快捷函数request上下文考虑在内,因此模板上下文处理器设置的任何变量都可以被给定的模板访问.模板上下文处理器是可调用的,用于将变量设置到上下文中.你将会在第三章扩展blog应用中学习如何使用他们.
让我们创建第二个视图以显示单个post.添加下面的函数到view.py文件

1
2
3
4
5
6
7
8
9
10
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})

这是文章详细视图.这个视图带入year,month,day和post参数来接收给定slug和日期的发布状态文章.在这里,我们确认使用slug与日期只匹配一篇文章.在详细视图,我们使用get_object_or_404()快捷函数接收预期的文章.这个函数接收给定参数所匹配到的对象或在没有对象找到时明确的弹出HTTP404.最后,我们使用render快捷函数使用一个模板渲染接收到的文章.

为视图添加URL匹配

URL匹配允许你映射URLs到视图.一个URL匹配由一个字符串匹配,一个视图和一个工程域可用指向这个URL的名称的可选项.Django顺序执行每个URL匹配然后停止在第一个匹配到的请求URL.然后Django导入匹配URL规则的视图然后执行它,传递一个HttpRequest类和关键字或位置参数.
创建一个urls.py文件在blog应用目录然后添加如下的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
from django.urls import path
from . import views


app_name = 'blog'

urlpatterns = [
# post views
path('', views.post_list, name='post_list'),
path('<int:year>/<int:month>/<int:day>/<slug:post>/',
views.post_detail,
name='post_detail'),
]

在上面的代码中,我们定义了一个使用app_name变量的应用空间.这允许我们通过指定它们的名字的应用组织URLs.我们使用path()定义了两个不同的匹配.第一个URL匹配不带入任何参数,指向post_list视图.第二个带入下面四个参数然后映射到post_detail视图.

新的URL匹配规则使用blog/的路径,include指向blog中定义的urls匹配规则.我们在Namespace(命令空间)blog下使用那些匹配.NameSpace在整个工程环境下是非重复的,后面,我们在Namespace中轻松指定我们的blog应用,类似blog:post_list,blog:blog_detail.你可以学习到更多的namesapce通过下面的链接(https://docs.djangoproject.com/en/2.0/topics/http/urls/#url-namespaces)

models正式的urls

你可以使用上面定义的post_detail为post对象创建正式的URL.在django中习惯在model中添加一个get_absolute_url()方法.使用这个方法,我们将使用reverse()方法带入名称及转入可选参数创建urls.编辑models.py文件然后添加如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
from django.urls import reverse


class Post(models.Model):
...
def get_absolute_url(self):
return reverse('blog:post_detail',
args=[
self.published.year,
self.published.month,
self.published.day,
self.slug
])

我们将在templates中使用get_absolute_url()方法链接到指定posts

为views创建templates

我们将为blog应用创建views和url patterns.
现在,是时候使用用户友好的方式添加templates显示posts.
在blog应用目录下创建下面的目录及文件:

1
2
3
4
5
6
7
8
9
10
haotianfei@tianfei-opensuse:~/PycharmProjects/django_2_by_example/blog> pwd
/home/haotianfei/PycharmProjects/django_2_by_example/blog
h
haotianfei@tianfei-opensuse:~/PycharmProjects/django_2_by_example/blog> tree templates/
templates/
└── blog
├── base.html
└── post
├── detail.html
└── list.html

base.html包含网站的主HTML,分成主内容区及侧边栏区.list.html和detail.html文件继承base.html文件分别来渲染blog post列表和详细视图.
Django拥有一个强大的template语言允许你指定数据如何显示.它基于模板标签,模板变量和模板过滤器:

  • 模板标签控制渲染模板,如

    1
    {% tag %}
  • 模板变量在渲染时替换值, 如

    1
    {{ variable }}
  • 模板过滤器允许修改变量显示, 如

    1
    {{ variable|filter }}
  • 你能够看到所有内置的模板标签和过滤器在这个页面:https://docs.djangoproject.com/en/2.0/ref/templates/builtins/
    让我们来修改base.html文件添加如下的代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    {% load static %}
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <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>Tf's blog</h2>
    <p>This is my blog.</p>
    </div>
    </body>
    </html>
1
{% load static %}

告知Django加载在INSTALLED_APPS中设置的由dango.contrib.staticfiles应用提供的static静态模板标签.加载后就可以在模板的任何地方使用

1
{% static %}

模板过滤器.通过这个过滤器,你可以过滤静态文件,比如位于static目录下的blog.css文件.
在这里有2个

1
{% block %}

标签.这些标签通告Django我们想要在那个区域定义一个代码块.继承于此模板的所有模板能够用内容填允在这个些块中.我们这里定义了title和content块.
让我们来编辑post/list.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{% extends "blog/base.html" %}

{% block title %}
Tianfei's Blog
{% endblock %}

{% block content %}
<h1>我的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|truncatechars:60|linebreaks }}
{% endfor %}

{% endblock %}

使用

1
{% extends %}

模板标签,我们告知Django继承blog/base.html模板.然后,我们将内容填充到base.html的title和content块中.整合posts,显示post的title,data,author,body字段,为title配置规划的链接到post.post的body部分应用了2个过滤器,truncatechars截取了部分字符 ,linebreaks转换输出为html格式.你可以按照你的想法将过滤器可以多个串起;每一个过滤器都将作用于上一个过滤器的输出.
此时运行python manage.py runserver开启开发服务器.打开http://127.0.0.1/blog.

然后编辑post/detail.html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{% 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 %}

返回浏览器,点击列表中的post标题,将会在新的页面查看post内容.

url被设计成针对blog post SEO友好的.

添加分页

当你已经开添加内容到blog,你很快就会领悟到需要分割post列表到多个页面上.Django拥有一个内置的分布类轻松管理分页数据.
编辑blog的view.py文件然后导入分布类,修改post_list视图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
def post_list(request):
# 获取所有的post对象
object_list = Post.published.all()
# 以每页3篇post初始化paginator对象实例
paginator = Paginator(object_list, 3)
# 获取当前所处页数.从页面传递过来一个"page"
page = request.GET.get('page')

try:
# 获取当前页的posts对象
posts = paginator.page(page)
except PageNotAnInteger:
# 如果返回page页数不足1,即非整数,只显示一页即第一页
posts = paginator.page(1)
except EmptyPage:
# if page is empty, disable the lastest page
posts = paginator.page(paginator.num_pages)

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

pagination工作流程是:
1 把我们想每页显示的post数作为参数实例化Paginator类
2 我们使用request.GET.get得到当前页数值
3 我们继承使用Paginator实例对象的page()方法得到要显示的posts
4 在执行上步过程中,如果当前页数值是非整数(有可能输入了非法字符),我们将接收第一个页面.如果当前请求面大于最大页面数,我们将显示最后一页(有可能是在URL直接输入了更大的值).
5 传递页数及接收到的posts对象到模板.
在这里我们单独创建一个pagination模板,以便其它有需要分布的模板可以直接套用.
在blog应用的templates目录下创建pagination.html.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div class="pagination">
<span class="step-links">
{% if page.has_previous %}
<a href="?page={{ page.previous_page_number }}">
Previous
</a>

{% endif %}
</span>
<span>
Page {{ page.number }} of {{ page.paginator.num_pages }}
</span>

<span>
{% if page.has_next %}
<a href="?page={{ page.next_page_number }}">Next</a>
{% endif %}
</span>
</div>

这个pagination模板需要一个page对象来渲染上一个与下一个链接,显示当前页在总页数中的位置.让我们返回blog/templates/list.html的内容块中插入pagination模板:

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

自此我们传递了一个page=posts的参数到list.html模板.pagination.html模板包含在list.html中会正确渲染.可以使用此方法在其它需要分页的模型中重用pagination模板.

使用基于类视图

基于类视图是一种使用python对象代替函数实施视图的辅助方法.自从视图调用带入一个web请求然后返回一个web响应,你还可以调用你的视图用类方法.Django提供继承于view类的基于类视图,可以快速处理HTTP方法及公共的功能.
基于类视图提供高于基于函数视图的使用.它们具有以下特性:

  • 关联HTTP方法组织代码,如GET,POST,PUT,使用特殊的方法代替分歧.
  • 使用多少继承创建可重用的视图类.
    我们将修改我们的post_list视图使用由Django提供的基于类的视图ListView.这个基本视图允许你列举任何对象.
    修改view.py:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    # Use class-base view
    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'

基于类视图与之前的post_list视图非常相似.在上面的代码中,我们告诉ListView做如下的事情:

  • 使用指定queryset代替接收所有对象.定义一个queryset属性,我们能够指定使用model=Post,Django会为我们创建Post.object.all() queryset对象.
  • 使用上下文变量posts接收查询结果.如果没有指定context_object_name默认变量是object_list.
  • 使用自定义模板渲染页面.如果未指定模板,ListView将使用blog/post_list.html
    关于基本类视图可查看https://docs.djangoproject.com/en/2.0/topics/class-based-views/intro/
    现在,修改urls.py注释掉post_list URL匹配规则,添加使用类URL匹配规则:
    1
    2
    3
    4
    5
    6
    urlpatterns = [
    # post views
    path('list/', views.post_list, name='post_list_old'),
    path('', views.PostListView.as_view(), name='post_list'),
    ...
    ]

同时为了保持pagination正常,我们需要修改接收的page对象.Django的ListView通用视图使用page_obj变量传递,所以我们修改post/list.html模板:

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

总结

在这一章节,我们学习了Django web 框架的基础.创建了视图,模板,URLS,包含了一个分页对象.

坚持原创技术分享,您的支持将鼓励我继续创作!