Django入门教程(简明版)
Django 拥有高度定制的对象关系映射(Object Relational mapping,ORM)、大量的 API、简单灵活的视图编写功能、优雅的 URL、适于快速开发的模板以及强大的管理后台等。这些使得它在 Python Web 开发领域占据了不可动摇的地位。
Instagram、 Firefox、国家地理杂志等著名网站都在使用 Django 进行网站开发。
图1:Instagram 图标
Django 是基于 MVT(Model-View-Template,模型-视图-模板)模型设计的。
图2:Django 的 MVT 模型
MVT 模型的说明如下:
- M 表示 Model,与 MVC(Model-View-Controller,模型-视图-控制器)设计模式中的 M 代表的功能相同,用于和数据库交互,进行数据处理。
- V 表示 View,与 MVC 模型中的 V 代表的功能相同,接收请求,进行业务处理,返回结果。
- T 表示 Template,与 MVC 模型中的 C 代表的功能相同,负责封装构造要返回的 HTML。
Django 安装
Django 的安装命令如下所示:pip install django==2.2
这里安装的是 Django 2.2,它支持 Python 3.5 以及后续版本。当然也可以根据需要安装其他版本,如图3所示。图3:Django 与 Python 的版本选择
若要验证 Django 是否能被 Python 识别,可以在 Shell 中输入 python 并按 Enter 键。然后在 Python 提示符下,尝试导入 Django。
>>> import django >>> print(django.get_version()) 2.2
Django 的请求和响应
接下来通过一个基本的投票应用程序来讲解 Django 的使用。该应用程序由以下两部分组成:
- 一个让用户查看信息和投票的公共站点;
- 一个让用户能添加、修改和删除投票的管理站点。
1) 创建项目
如果是第一次使用 Django,那么需要进行一些初始化设置。也就是说,需要用一些自动生成的代码配置一个 Djangoproject,即一个 Django 项目实例需要的设置项集合,包括数据库配置、Django 配置和应用程序配置。打开命令行界面,利用 cd 命令切换到一个你想放置代码的目录,然后运行以下命令。
django-admin startproject mysite
这行代码将会实现在当前目录下创建一个 mysite 目录。如果命令运行失败,查看运行 django-admin 时遇到的问题的相关内容,可能能给用户提供帮助。看一看 startproject 创建了些什么。
mysite/ manage.py mysite/ _ _init_ _.py settings.py urls.py wsgi.py这些目录和文件的用处如下:
- 最外层的 mysite/ 根目录只是项目的容器,Django 不关心它的名字,所以用户可以将它重命名为任何自己喜欢的名字。
- manage.py 是一个能使用各种方式管理 Django 项目的命令行工具。
- 里面一层的 mysite/ 目录包含项目内容,它是一个纯 Python 包。它的名字就是当用户引用它内部任何内容时需要用到的 Python 包的名字,比如 mysite.urls。
- mysite/_ _init_ _.py 是一个空文件,告诉 Python 这个目录应该被看作一个 Python 包。
- mysite/settings.py 是 Django 项目的配置文件。
- mysite/urls.py 是 Django 项目的 URL 声明,就像网站的“目录”。
- mysite/wsgi.py 是与 Web服务器网关接口(Web Server Gateway Interface,WSGI)兼容的 Web 服务器的入口点,用于为用户的项目提供服务。
2) 用于开发的简易服务器
让我们来确认一下 Django 项目是否真的创建成功了。如果当前目录不是外层的 mysite 目录,请切换到此目录,然后运行下面的命令。python manage.py runserver
会看到如下输出:
Watching for file changes with StatReloader
Performing system checks...
System check identified no issues (0 silenced).
You have 17 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
October 08, 2020 - 16:13:53
Django version 2.2, using settings 'mysite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
现在,服务器正在运行,浏览器访问 http://127.0.0.1:8000/,将会看到一个“祝贺(Congratulations)”页面,页面中有着火箭发射的图标,代表服务器成功运行,如图4所示。
图4:Django 服务器成功运行
默认情况下,runserver 命令会将服务器设置为监听本机内部 IP 地址的 8000 端口。
如果想更换服务器的监听端口,请使用命令行参数。举个例子,下面的命令会使服务器监听 8080 端口。
python manage.py runserver 8080
如果想要修改服务器监听的 IP 地址,那么要在端口之前输入新的 IP 地址。比如,为了监听所有服务器的公开 IP 地址(运行 Vagrant 或想要向网络上的其他计算机展示成果时很有用),使用如下命令。python manage.py runserver 0:8000
0 是 0.0.0.0 的简写。用于开发的简易服务器在需要的情况下会对每一次的访问请求重新载入一遍 Python 代码,所以不需要为了让修改的代码生效而频繁地重新启动服务器。然而,一些动作,比如添加新文件,将不会触发自动重新加载,这时得手动重启服务器。
3) 创建投票应用程序
现在开发环境配置好了,可以开始创建应用程序。在 Django 中,每一个应用程序都是一个 Python 包,并且遵循着相同的约定。Django 自带一个工具,可以帮助生成应用程序的基础目录结构,这样就能专心写代码,而不用专注于创建目录了。
应用程序和项目有什么区别?
应用程序是一个专门做某件事的网络程序,比如博客系统、公共记录的数据库,或者简单的投票应用程序等。项目则是一个网站使用的配置和应用程序的集合。项目可以包含很多个应用程序,应用程序可以被很多个项目使用。
用户的应用程序可以存放在任何 Python path 定义的路径中。在 manage.py 同级目录下创建投票应用程序,这样它就可以作为顶级模块导入,而不是作为 mysite 的子模块。
请确定应用程序现在处于 manage.py 所在的目录下,然后运行以下命令创建一个应用程序。
python manage.py startapp polls
这将会创建一个 polls 目录,它的目录结构大致如下:polls/ _ _init_ _.py admin.py apps.py migrations/ _ _init_ _.py models.py tests.py views.py这个目录结构包括投票应用程序的全部内容。
4) 实现第一个视图
下面开始编写代码实现第一个视图。打开 polls/views.py,输入以下代码。from django.http import HttpResponse def index(request): return HttpResponse("Hello, world. You're at the polls index.")这是 Django 中较简单的视图。如果想看见效果,需要将一个 URL 映射到它——这就是需要 URLconf(URL 配置)的原因。
为了创建 URLconf,请在 polls 目录里新建一个 urls.py 文件。此处的应用程序目录现在看起来应该是如下这样的。
polls/ _ _init_ _.py admin.py apps.py migrations/ _ _init_ _.py models.py tests.py urls.py views.py在 polls/urls.py 中,输入如下代码:
from django.urls import path from . import views urlpatterns = [ path('', views.index, name='index'), ]下一步是要在根 URLconf 文件中指定创建的 polls.urls 模块。在 mysite/urls.py 文件的 urlpatterns 列表里插入一个 include 函数,如下所示。
from django.contrib import admin from django.urls import include, path urlpatterns = [ path('polls/', include('polls.urls')), path('admin/', admin.site.urls), ]函数 include 允许引用其他 URLconf。每当 Django 遇到 include 时,它会截断与此项匹配的 URL 的部分,并将剩余的字符串发送到 URLconf 以供进一步处理。
设计 include 的理念是使其可以即插即用。投票应用程序有它自己的 URLconf(polls/urls.py),它们被放在 /polls/、/fun_polls/、/content/polls/,或者其他任何路径下,都能够正常工作。
应何时使用 include?当项目包括其他 URL 模式时应该总是使用 include,admin.site.urls 是唯一例外。
现在把 index 视图添加进了 URLconf。接下来通过以下命令启动服务器。
python manage.py runserver
用浏览器访问 http://127.0.0.1:8000/polls/,应该能够看见 “Hello, world. You're at the polls index.”,这是在 index 视图中定义的,如图5所示。图5:访问
函数 path 有4个参数,其中2个为必需参数——route 和 view,2个为可选参数——kwargs 和 name。现在,是时候来研究这些参数的含义了。
① 参数route
route 是一个匹配 URL 的准则(类似正则表达式)。当 Django 响应一个请求时,它会从 urlpatterns 的第一项开始,按顺序依次匹配列表中的项,直到找到匹配的项。这个准则不会匹配 GET 和 POST 参数或域名,例如:
- URLconf 在处理请求 https://www.example.com/myapp/ 时,会尝试匹配 myapp/;
- 在处理请求 https://www.example.com/myapp/?page=3 时,也只会尝试匹配 myapp/。
② 参数view
当 Django 找到了一个匹配准则时,就会调用特定的视图函数,并传入一个 HttpRequest 对象作为第一个参数,被“捕获”的参数以关键字参数的形式传入。后文会给出一个例子。③ 参数kwargs
任意一个关键字参数可以作为一个字典传递给目标视图函数,此处不会使用这一特性。④ 参数name
为 URL 起名能让用户在 Django 的任意地方唯一地引用它,尤其是在模板中。这个有用的特性允许用户只修改一个文件就能全局地修改某个 URL 模式。模型和Admin站点
接下来将讲解建立数据库,用户可以创建第一个模型,并主要关注 Django 提供的自动生成的管理页面。1) 数据库配置
现在,打开 mysite/settings.py,这是一个包含 Django 项目设置的 Python 模块。通常,这个配置文件使用 SQLite 作为默认数据库。如果不熟悉数据库,或者只是想尝试使用 Django,那么这是最简单的选择之一。Python 中内置了 sqlite3 模块,所以用户无须安装即可使用它。
当开始进行一个真正的项目时,用户可能更倾向使用一个更具扩展性的数据库,例如 PostgreSQL,以避免中途切换数据库。
如果用户想使用其他数据库,需要安装合适的 databasebindings,然后改变设置文件中 DATABASES 中的一些键值。
ENGINE 为数据库引擎,可选值有很多,比如 django.db.backends.sqlite3、django.db.backends.postgresq、django.db.backends.mysql、django.db.backends.oracle。
NAME 代表数据库的名称。如果使用的是 SQLite,数据库将是计算机上的一个文件,在这种情况下,NAME 应该是此文件的绝对路径,包括文件名。默认值 os.path.join(BASE_DIR,'db.sqlite3') 将会把数据库文件储存在项目的根目录。
如果不使用 SQLite,则必须添加一些额外设置,比如 USER、PASSWORD、HOST 等。
# SQLite DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': 'mydatabase', } } # MySQL DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'mydatabase', 'USER': 'mydatabaseuser', 'PASSWORD': 'mypassword', 'HOST': '127.0.0.1', 'PORT': '5432', } }编辑 mysite/settings.py 文件前,先设置 TIME_ZONE 为目前所处的时区。
此外,需关注文件头部的 INSTALLED_APPS 设置项,这里包括会在项目中启用的所有 Django 应用程序。这些应用程序能在多个项目中使用,也可以打包并且发布应用程序,让别人使用它们。
通常,INSTALLED_APPS 默认包括以下 Django 自带的应用程序。
- django.contrib.admin——管理员站点。
- django.contrib.auth——认证授权系统。
- django.contrib.contenttypes——内容类型框架。
- django.contrib.sessions——会话框架。
- django.contrib.messages——消息框架。
- django.contrib.staticfiles——管理静态文件的框架。
这些应用程序被默认启用是为了给常规项目提供方便。
默认启用的某些应用程序、需要至少一个数据表,所以,在使用它们之前需要在数据库中创建一些表。请运行以下命令。
python manage.py migrate
这个 migrate 命令用于检查 INSTALLED_APPS 设置,为其中的每个应用程序创建需要的数据表,至于具体会创建什么,这取决于 mysite/settings.py 设置文件和每个应用程序的数据库迁移文件。就像之前介绍的,为了方便大多数项目,系统默认激活了一些应用程序,但并不是每个人都需要它们。如果不需要某个或某些应用程序,可以在运行 migrate 命令前毫无顾虑地从 INSTALLED_APPS 里注释或者删除它们。
migrate 命令只会为在 INSTALLED_APPS 里声明了的应用程序进行数据库迁移。
2) 创建模型
在 Django 里编写一个数据库驱动的 Web 应用程序的第一步是定义模型,也就是进行数据库结构设计和定义附加的其他元数据。在这个简单的投票应用程序中,需要创建两个模型:问题(Question)模型和选项(Choice)模型。
- Question 模型包括问题描述和发布时间;
- Choice 模型有两个字段,即选项描述和当前得票数。
这些概念可以通过一个简单的 Python 类来描述。按照下面的例子来编辑 polls/models.py 文件。
from django.db import models class Question(models.Model): question_text = models.CharField(max_length=200) pub_date = models.DateTimeField('date published') class Choice(models.Model): question = models.ForeignKey(Question, on_delete=models.CASCADE) choice_text = models.CharField(max_length=200) votes = models.IntegerField(default=0)代码非常“直白”。每个模型都被表示为 django.db.models.Model 类的子类。每个模型有一些类变量,它们都表示模型里的数据库字段。
每个字段都是 Field 类的实例,比如字符字段被表示为 CharField,日期时间字段被表示为 DateTimeField。这将告诉 Django 每个字段要处理的数据类型。
每个 Field 类实例变量的名字(例如 question_text或 pub_date)也是字段名,所以最好使用对“机器友好”的格式。用户将会在 Python 代码里使用它们,而数据库会将它们作为列名。
用户可以使用可选的选项来为 Field 定义一个人类可读的名字。这个功能在很多 Django 内部组成部分中都被使用了,而且作为文档功能的一部分。如果某个字段没有提供此名字,Django 将会使用对“机器友好”的名字,也就是变量名。
定义某些 Field 类实例需要参数。例如 CharField 需要一个 max_length 参数,这个参数不仅用于定义数据库结构,也用于验证数据,后文将会介绍这方面的内容。
Field 也能够接收多个可选参数。在上面的例子中,可以看到将 votes的default(也就是默认值)设为 0。
注意,上述例子还使用 ForeignKey 定义了一个关系。这将告诉 Django 每个 Choice 对象都关联到一个 Question 对象。Django 支持常用的数据库关系:多对一、多对多和一对一。
3) 激活模型
上面用于创建模型的代码提供给 Django 很多信息,通过这些信息,Django 可以:- 为这个应用程序创建数据库 Schema(生成 CREATETABLE 语句);
- 创建可以与 Question 和 Choice 对象进行交互的 Python 数据库的 API。
但是首先得把投票应用程序安装到项目里。
为了在项目中包含这个应用程序,需要在配置类 INSTALLED_APPS 中添加设置。因为 PollsConfig 类写在文件 polls/apps.py 中,所以它的点式路径是 polls.apps.PollsConfig。
在文件 mysite/settings.py 中为 INSTALLED_APPS子 项添加点式路径后,它看起来如下所示。
INSTALLED_APPS = [ 'polls.apps.PollsConfig', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', ]现在 Django 项目会包含投票应用程序。接着运行下面的命令。
python manage.py makemigrations polls
将会看到类似下面这样的输出。Migrations for 'polls': polls/migrations/0001_initial.py: - Create model Choice - Create model Question - Add field question to choice再次运行 migrate 命令,在数据库里创建新定义的模型的数据表。
python manage.py migrate Operations to perform: Apply all migrations: admin, auth, contenttypes, polls, sessions Running migrations: Rendering model states... DONE Applying polls.0001_initial... OK这个 migrate 命令选中所有还没有运行过的迁移并应用在数据库上,也就是对模型的更改同步到数据库结构上。
Django 通过在数据库中创建一个特殊的表 django_migrations 来跟踪运行过哪些迁移。
迁移是非常强大的功能,它能让用户在开发过程中持续改变数据库结构而不需要重新删除和创建表,它专注于使数据库平滑升级而不会丢失数据。我们会在后文更加深入地讲解这部分内容,现在只需要记住,改变模型需要以下3步。
- 编辑 models.py 文件,改变模型。
- 运行 pythonmanage.pymakemigrations 为模型的改变生成迁移文件。
- 运行 pythonmanage.pymigrate 来应用数据库迁移。
数据库迁移被分解成生成和应用两个命令是为了能够在代码控制系统上提交迁移数据并使其能在多个应用程序里使用;这不仅会让开发变得更加简单,也给别的开发人员和生产环境中的使用带来了方便。
4) 初试API
现在讲解进入 Python 命令行,尝试一下 Django 为用户创建的各种 API。通过以下命令打开 Python 命令行。python manage.py shell
使用这个命令而不是简单地使用 Python 是因为 manage.py 会设置 DJANGO_SETTINGS_MODULE 环境变量,这个变量会让 Django 根据 mysite/settings.py 文件来设置 Python 包的导入路径。当成功进入命令行后,来试试 database API 吧。
>>> from polls.models import Choice, Question # Import the model classes we just wrote. # No questions are in the system yet. >>> Question.objects.all() <QuerySet []> # Create a new Question. # Support for time zones is enabled in the default settings file. # Django expects a datetime with tzinfo for pub_date. Use timezone.now() # instead of datetime.datetime.now() and it will do the right thing. >>> from django.utils import timezone >>> q = Question(question_text="What's new?", pub_date=timezone.now()) # Save the object into the database. You have to call save() explicitly. >>> q.save() # Now it has an ID. >>> q.id 1 # Access model field values via Python attributes. >>> q.question_text "What's new?" >>> q.pub_date datetime.datetime(2012, 2, 26, 13, 0, 0, 775217, tzinfo=<UTC>) # Change values by changing the attributes, then calling save(). >>> q.question_text = "What's up?" >>> q.save() # objects.all() displays all the questions in the database. >>> Question.objects.all() <QuerySet [<Question: Question object (1)>]>Question:Question object(1) 对于了解这个对象的细节没什么帮助。可以通过编辑 Question 模型的代码(位于 polls/models.py中)来修复这个问题。
给 Question 和 Choice 增加 _ _str_ _ 方法。
from django.db import models class Question(models.Model): # ... def _ _str_ _(self): return self.question_text class Choice(models.Model): # ... def _ _str_ _(self): return self.choice_text给模型增加 _ _str_ _ 方法是很重要的,这能给用户在命令行里使用模型带来方便。Django 自动生成的admin也使用这个方法来表示对象。
再为此模型添加一个自定义方法。
import datetime from django.db import models from django.utils import timezone class Question(models.Model): # ... def was_published_recently(self): return self.pub_date >= timezone.now() - datetime.timedelta(days=1)新加入的 import datetime 和前文提到的 from django.utils import timezone 分别导入了 Python 中的标准 datetime 模块和 Django 中与时区相关的 django.utils.timezone 模块。如果你不太熟悉 Python 中的时区处理,可以看一看时区支持文档。
保存文件,然后通过 python manage.py shell 命令打开 Python 命令行。
>>> from polls.models import Choice, Question # Make sure our _ _str_ _() addition worked. >>> Question.objects.all() <QuerySet [<Question: What's up?>]> # Django provides a rich database lookup API that's entirely driven by # keyword arguments. >>> Question.objects.filter(id=1) <QuerySet [<Question: What's up?>]> >>> Question.objects.filter(question_text_ _startswith='What') <QuerySet [<Question: What's up?>]> # Get the question that was published this year. >>> from django.utils import timezone >>> current_year = timezone.now().year >>> Question.objects.get(pub_date_ _year=current_year) <Question: What's up?> # Request an ID that doesn't exist, this will raise an exception. >>> Question.objects.get(id=2) Traceback (most recent call last): ... DoesNotExist: Question matching query does not exist. # Lookup by a primary key is the most common case, so Django provides a # shortcut for primary-key exact lookups. # The following is identical to Question.objects.get(id=1). >>> Question.objects.get(pk=1) <Question: What's up?> # Make sure our custom method worked. >>> q = Question.objects.get(pk=1) >>> q.was_published_recently() True # Give the Question a couple of Choices. The create call constructs a new # Choice object, does the INSERT statement, adds the choice to the set # of available choices and returns the new Choice object. Django creates # a set to hold the "other side" of a ForeignKey relation # (e.g. a question's choice) which can be accessed via the API. >>> q = Question.objects.get(pk=1) # Display any choices from the related object set -- none so far. >>> q.choice_set.all() <QuerySet []> # Create three choices. >>> q.choice_set.create(choice_text='Not much', votes=0) <Choice: Not much> >>> q.choice_set.create(choice_text='The sky', votes=0) <Choice: The sky> >>> c = q.choice_set.create(choice_text='Just hacking again', votes=0) # Choice objects have API access to their related Question objects. >>> c.question <Question: What's up?> # And vice versa: Question objects get access to Choice objects. >>> q.choice_set.all() <QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]> >>> q.choice_set.count() 3 # The API automatically follows relationships as far as you need. # Use double underscores to separate relationships. # This works as many levels deep as you want; there's no limit. # Find all Choices for any question whose pub_date is in this year # (reusing the 'current_year' variable we created above). >>> Choice.objects.filter(question_ _pub_date_ _year=current_year) <QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]> # Let's delete one of the choices. Use delete() for that. >>> c = q.choice_set.filter(choice_text_ _startswith='Just hacking') >>> c.delete()
5) 介绍 Django 管理页面
首先,注册一个能登录管理页面的账户。请运行下面的命令:python manage.py createsuperuser
输入想要使用的账户名并按 Enter 键。Username: admin
然后会提示用户输入想要使用的电子邮箱地址。E-mail address: admin@example.com
最后一步是输入密码。用户会被要求输入两次密码,第二次输入的目的是确认第一次输入的确实是用户想要的密码。
Password: **********
Password (again): *********
Superuser created successfully.
如果开发服务器未启动,用以下命令启动它。
python manage.py runserver
现在,打开浏览器,转到本地域名的 /admin/ 目录,比如 http://127.0.0.1:8000/admin/,会显示管理员登录页面,如图6所示。图6:Django 管理员登录页面
现在,试着使用已经注册的超级用户来登录,会看到 Django 管理页面的索引页,如图7所示。
图7:Django 管理页面的索引页1
用户将会看到几种可编辑的内容:组和用户。它们是由 django.contrib.auth 提供的,这是 Django 开发的认证框架。
但是投票应用程序在哪儿呢?它没在索引页里显示。
这时,用户只需要做一件事:告诉管理页面,Question 对象需要被管理。打开 polls/admin.py 文件,把它编辑成下面这样。
from django.contrib import admin from .models import Question admin.site.register(Question)现在向管理页面注册了 Question 类。Django 知道它应该被显示在索引页,管理页面的索引页如图8所示。
图8:Django 管理页面的索引页2
单击【Questions】,可以看到是 Questions 对象的列表 change list。这个页面会显示所有数据库里的 Question对象,可以选择一个来修改。
这里有在前文创建的“What's up?”问题,如图9所示。
图9:Question 对象的列表
单击【What's up?】来编辑这个 Question 对象,如图10所示。
图10:编辑 Question 对象
要注意如下事项:
- 这个表单是从 Question 模型中自动生成的。
- 不同的字段类型,如日期时间字段(DateTimeField)、字符字段(CharField),会生成对应的 HTML 输入控件。每个类型的字段都知道它们该如何在管理页面里显示自己。
- 每个 DateTimeField 都有用 JavaScript 创建的快捷按钮。日期有转到今天(Today)的快捷按钮和一个弹出式日历页面。时间有设为现在(Now)的快捷按钮和一个列出常用时间的弹出式列表。
页面的底部提供了以下几个选项:
- 保存(SAVE):保存改变,然后返回对象列表。
- 保存并继续编辑(Save and continue editing):保存改变,然后重新载入当前对象的修改页面。
- 保存并新增(Save and add another):保存改变,然后添加一个新的空对象并载入修改页面。
- 删除(Delete):显示一个确认要删除的页面。
如果显示的发布日期(Date published)和在前文创建的时间不一致,这意味着可能没有正确设置 TIME_ZONE。改变设置,然后重新载入页面看一看是否显示了正确的值。
通过单击【今天(Today)】和【现在(Now)】按钮改变“发布日期(Date published)”。然后单击【保存并继续编辑(Save and add another)】按钮。再单击右上角的【历史(HISTORY)】按钮,会看到一个列出了所有通过 Django 管理页面对当前对象进行修改的页面,其中列出了时间戳和进行修改操作的用户名,历史记录如 图11所示。
图11:历史记录
视图和模板
Django 中的视图是指一类具有相同功能和模板的网页的集合。比如在一个博客中,可能会创建如下视图:- 博客首页:展示最近发布的几项内容。
- 内容详情页:详细展示某项内容。
- 以年为单位的归档页:展示选中的年份里各月创建的内容。
- 以月为单位的归档页:展示选中的月份里各天创建的内容。
- 以天为单位的归档页:展示选中的天里创建的所有内容。
- 评论处理器:用于响应为一项内容添加评论的操作。
而在投票应用程序中,需要下列视图:
- 问题索引页:展示最近发布的几个投票问题。
- 问题详情页:展示某个投票的问题和不带结果的选项列表。
- 问题结果页:展示某个投票的结果。
- 投票处理器:用于响应用户为某个问题的特定选项投票的操作。
在 Django 中,网页和其他内容都是从视图派生而来的,每一个视图表现为一个简单的 Python 函数(或者说方法,如果是在基于类的视图里的话)。Django 将会根据用户请求的 URL 来选择使用哪个视图。
更准确地说,是根据URL中域名之后的部分进行选择。
上网时,你很可能看见过这样的 URL:ME2/Sites/dirmod.asp?sid=&type=gen&mod=Core+Pages&gid=A6CD4967199A42D9B65B1B
别担心,Django 里的 URL 要比这“优雅”得多!URL 模式定义了某种 URL 的基本格式,如 /newsarchive/<year>/<month>/。
为了将 URL 和视图关联起来,Django 使用 URLconf 来配置。URLconf 将 URL 模式映射到视图。
1) 编写更多视图
向 polls/views.py 里添加更多视图。这些视图有一些不同,因为它们接收参数。def detail(request, question_id): return HttpResponse("You're looking at question %s." % question_id) def results(request, question_id): response = "You're looking at the results of question %s." return HttpResponse(response % question_id) def vote(request, question_id): return HttpResponse("You're voting on question %s." % question_id)把这些新视图添加到 polls.urls 模块里,只要添加几个 url 函数进行调用就行。
from django.urls import path from . import views urlpatterns = [ # ex: /polls/ path('', views.index, name='index'), # ex: /polls/5/ path('<int:question_id>/', views.detail, name='detail'), # ex: /polls/5/results/ path('<int:question_id>/results/', views.results, name='results'), # ex: /polls/5/vote/ path('<int:question_id>/vote/', views.vote, name='vote'), ]然后看一看浏览器,如果转到 /polls/34/,Django 将会运行 detail 方法并且展示 URL 里提供的问题 ID。再试一试 /polls/34/vote/,将会看到暂时用于占位的结果和投票页。
当出现请求网站的某一页面时,比如说 /polls/34/,Django 将会载入 mysite.urls 模块,因为这在配置项 ROOT_URLCONF 中设置了。然后 Django 寻找 urlpatterns 变量并且按序匹配正则表达式。在找到匹配项 polls/ 后,它切掉了匹配的文本 polls/,将剩余文本 34/,发送至 polls.urls 做进一步处理。在这里剩余文本匹配了 <int:question_id>/,使得 Django 以如下形式调用 detail 方法。
detail(request=<HttpRequest object>, question_id=34)
question_id=34 由 <int:question_id> 匹配生成。使用角括号“捕获”这部分 URL,且以关键字参数的形式发送给视图函数。上述字符串的 :question_id> 部分定义了将被用于区分匹配模式的变量名,而 int: 则是一个转换器,决定了应该以什么变量类型匹配这部分的 URL。2) 编写一个真正有用的视图
每个视图必须要做的只有两件事:返回一个包含被请求页面内容的 HttpResponse 对象,或者抛出一个异常,比如 Http404。用户的视图可以从数据库里读取记录,可以使用一个模板引擎(比如 Django 自带的,或者其他第三方的),可以生成一个 PDF 文件,可以输出一个 XML 文件,可以创建一个 ZIP 文件。
Django 只要求返回一个 HttpResponse,或者抛出一个异常。
因为 Django 自带的数据库 API 很方便,所以可以试一试在视图里使用它。在 index 函数里插入了一些新内容,让它能展示数据库里以发布日期排序的最近5个投票问题,以空格分隔。
from django.http import HttpResponse from .models import Question def index(request): latest_question_list = Question.objects.order_by('-pub_date')[:5] output = ', '.join([q.question_text for q in latest_question_list]) return HttpResponse(output) # Leave the rest of the views (detail, results, vote) unchanged这里有个问题,页面的设计由视图函数的代码实现。如果想改变页面的样子,就需要编辑 Python 代码。这里使用 Django 的模板,只要创建一个视图,就可以将页面的设计从代码中“分离”出来。
首先,在 polls 目录里创建一个 templates 目录。Django 将会在这个目录里查找模板文件。
项目的 TEMPLATES 配置项描述了 Django 如何载入和渲染模板。默认的设置文件设置了 DjangoTemplates 后端,并将 APP_DIRS 设置成了 True。这一选项将会让 DjangoTemplates 在每个 INSTALLED_APPS 文件夹中寻找 templates 子目录。
这就是为什么尽管没有像在前文中介绍的那样修改 DIRS 设置,Django 也能正确找到 polls 的模板位置。
在刚刚创建的 templates 目录里,再创建一个目录 polls,然后在其中新建一个文件 index.html。换句话说,模板文件所在的路径应该是 polls/templates/polls/index.html。因为 Django 会寻找到对应的 app_directories,所以只需要使用 polls/index.html 就可以引用这一模板。
将下面的代码输入刚刚创建的模板文件。
{% if latest_question_list %} <ul> {% for question in latest_question_list %} <li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li> {% endfor %} </ul> {% else %} <p>No polls are available.</p> {% endif %}然后,更新 polls/views.py 里的 index 视图来使用模板。
from django.http import HttpResponse from django.template import loader from .models import Question def index(request): latest_question_list = Question.objects.order_by('-pub_date')[:5] template = loader.get_template('polls/index.html') context = { 'latest_question_list': latest_question_list, } return HttpResponse(template.render(context, request))上述代码的作用是,载入 polls/index.html 模板文件,并且向它传递一个上下文(context)。这个上下文是一个字典,它将模板内的变量映射为 Python 对象。
用浏览器访问 /polls/ 将会看见一个无序列表,列出了添加的“What's up?”投票问题,链接指向这个投票的详情页。
载入模板,填充上下文,再返回由它生成的 HttpResponse 对象,这是一个非常常见的操作流程。 Django 提供了一个快捷函数,可用它来重写 index 视图。
from django.shortcuts import render from .models import Question def index(request): latest_question_list = Question.objects.order_by('-pub_date')[:5] context = {'latest_question_list': latest_question_list} return render(request, 'polls/index.html', context)我们注意到,这里不再需要导入 loader 和 HttpResponse。不过如果还有其他函数(比如 detail、results 和 vote)需要用到 HttpResponse,就需要保持 HttpResponse 的导入。
3) 抛出 Http404 异常
现在来处理投票详情视图,它会显示指定投票的问题标题。下面是这个视图的代码。from django.http import Http404 from django.shortcuts import render from .models import Question # ... def detail(request, question_id): try: question = Question.objects.get(pk=question_id) except Question.DoesNotExist: raise Http404("Question does not exist") return render(request, 'polls/detail.html', {'question': question})这里有一个原则。如果指定问题 ID 所对应的问题不存在,那么这个视图就会抛出一个 Http404 异常。
4) 使用模板系统
下面回过头来看看详情视图。它向模板传递了上下文变量 question。下面是 polls/detail.html 模板里正式的代码。<h1>{{ question.question_text }}</h1> <ul> {% for choice in question.choice_set.all %} <li>{{ choice.choice_text }}</li> {% endfor %} </ul>模板系统统一使用点符号来访问变量的属性。在示例 {{question.question_text}} 中,Django 尝试对 Question 对象使用字典查找(也就是使用 obj.get(str) 操作),如果失败了就尝试属性查找(也就是 obj.str 操作),结果是成功了。如果这一操作也失败的话,将会尝试列表查找(也就是 obj[int] 操作)。
在 {%for%} 循环中发生的函数调用:question.choice_set.all 被解释为 Python 代码 question.choice_set.all(),将会返回一个可迭代的 Choice 对象,这一对象可以在 {%for%} 标签内部使用。
5) 去除模板中的硬编码 URL
在 polls/index.html 里编写投票链接的代码,链接是硬编码的。<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
问题在于,硬编码和强耦合的链接,对于一个包含很多应用程序的项目来说,修改起来是十分困难的。然而,因为在 polls.urls 的 url 函数中通过 name 参数为 URL 定义了名字,所以可以使用 {%url%} 标签代替它。<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
这个标签的工作方式是在 polls.urls 模块的 URL 定义中寻找有指定名字的条目。读者可以回忆一下,具有名字 'detail' 的 URL 是如何在如下语句中定义的。
...
# the 'name' value as called by the {% url %} template tag
path('<int:question_id>/', views.detail, name='detail'),
...
...
# added the word 'specifics'
path('specifics/<int:question_id>/', views.detail, name='detail'),
...
6) 为URL添加命名空间
该项目只有一个投票应用程序。在一个真实的 Django 项目中,可能会有 5 个、10 个、20 个,甚至更多的应用程序。Django 如何分辨重名的URL呢?举个例子,投票应用程序有详情视图,可能另一个博客应用程序也有同名的视图。Django 如何知道 {%url%} 标签到底对应哪一个应用程序的 URL 呢?
答案是:在根 URLconf 中添加命名空间。在 polls/urls.py 文件中稍做修改,加上 app_name 设置命名空间。
from django.urls import path from . import views app_name = 'polls' urlpatterns = [ path('', views.index, name='index'), path('<int:question_id>/', views.detail, name='detail'), path('<int:question_id>/results/', views.results, name='results'), path('<int:question_id>/vote/', views.vote, name='vote'), ]现在,编辑 polls/index.html 文件。
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
修改为指向具有命名空间的详情视图。<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>