Fork me on GitHub

python-django框架

django

概述

python有许多不同的WEB框架,django是最具有代表性的一位,许多成功的网站和APP都基于django

django是一个开源的web应用框架,由python编写

django采用了MVC的软件设计模式,既模型M,视图V,和控制器C

MVC框架和MTV框架

MVC (Model View Controller)是软件工程中的一种软件架构模式;把软件分为了三个基本部分:模型(Model) 视图(View)和 控制器(controller)具有耦合度低,重用性高,生命周期成本低等优点。

img

Django框架的设计模式借鉴了MVC框架的思想,也是分成三部分,来降低各个部分之间的耦合性。

Django框架的不同之处在于它拆分的三部分为:Model(模型)、Template(模板)和View(视图),也就是MTV框架。

Django的MTV模式

  • Model(模型):负责业务对象与数据库的对象(ORM)
  • Template(模版):负责如何把页面展示给用户
  • View(视图):负责业务逻辑,并在适当的时候调用Model和Template

此外,Django还有一个urls分发器,它的作用是将一个个URL的页面请求分发给不同的view处理,view再调用相应的Model和Template

Django框架图示

img

前端状态码的返回

1xx  请求接受了需进一步处理
2xx  成功
3xx  重定向  
4xx  请求的错误
5xx  服务器错误

url

# url的组成
协议  域名(ip)  端口(http默认80  https 443 )   路径  查询参数(?k1=v1&k2=v2) 

请求的格式(request)

"请求方法 路径 HTTP/1.1

k1: v1

k2:v2

web框架的功能

web框架的功能:

1. socket收发消息
2. 根据不同的路径返回不同的内容 
3. 返回动态页面(字符串的替换  模板渲染) 

django的常用命令

安装ajango

# 指定安装的版本为1.11.25  -i参数指定下载源,默认国外官方,这里改成清华源
pip install django==1.11.25 -i https://pypi.tuna.tsinghua.edu.cn/simple/

创建django项目

# 创建项目
django-admin startproject mypro

django启动

 python manage.py runserver
 python manage.py runserver 0.0.0.0:80   # 指定ip:port

startproject的做的事情

mysite/
    manage.py
    mysite/
        __init__.py
        settings.py
        urls.py
        wsgi.py
  • 外部mysite/根目录只是您的项目的容器。它的名称与Django无关;您可以将其重命名为任何喜欢的名称
  • manage.py:一个命令行实用程序,可让您以各种方式与该Django项目进行交互。您可以manage.pydjango-admin和manage.py中阅读有关的所有详细信息 。
  • 内部mysite/目录是项目的实际Python包。它的名称是Python包名称,您需要使用它来导入其中的任何内容(例如mysite.urls)。
  • mysite/settings.py:此Django项目的设置/配置。 Django设置将告诉您所有设置的工作方式
  • mysite/urls.py:此Django项目的URL声明;Django支持的网站的“目录”。您可以在URL调度程序中阅读有关URL的更多信息。
  • mysite/wsgi.py:兼容WSGI的Web服务器为您的项目提供服务的入口点。有关更多详细信息,请参见如何使用WSGI进行部署

创建app程序

项目和应用之间有什么区别?应用程序是执行某项操作的Web应用程序,例如Weblog系统,公共记录数据库或简单的民意调查应用程序。项目是特定网站的配置和应用程序的集合。一个项目可以包含多个应用程序。一个应用程序可以在多个项目中。

创建应用注意 当与manage.py处于同一层目录

python .\manage.py startapp polls

include

该函数采用完整的Python导入路径到该位置应“包括”到另一个URLconf模块的路径。(可选)还可以指定条目将包含在其中的应用程序名称空间实例名称空间

通常,应用程序名称空间应由包含的模块指定。如果设置了应用程序名称空间,则该namespace参数可用于设置其他实例名称空间。

include() 还接受返回URL模式的iterable或包含该iterable的2元组以及应用程序名称空间的名称作为参数。

语法格式

includemodulenamespace = None

includepattern_list

include(pattern_listapp_namespace)namespace = None

参数:

  • module -URLconf模块(或模块名称)
  • 名称空间str)-包含URL条目的实例名称空间
  • pattern_list-path()和/或re_path()实例的可迭代。
  • app_namespacestr)-包含URL条目的应用程序名称空间

函数 include() 允许引用其它 URLconfs。每当 Django 遇到 include() 时,它会截断与此项匹配的 URL 的部分,并将剩余的字符串发送到 URLconf 以供进一步处理。

当包括其它 URL 模式时你应该总是使用 include()admin.site.urls 是唯一例外。

path()函数

URLconfs中使用的函数 pathrouteviewkwargs = Nonename = None )

例如:

from django.urls import include, path

urlpatterns = [
    path('index/', views.index, name='main-view'),
    path('bio/<username>/', views.bio, name='bio'),
    path('articles/<slug:title>/', views.article, name='article-detail'),
    path('articles/<slug:title>/<int:section>/', views.section, name='article-section'),
    path('weblog/', include('blog.urls')),
]

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 模式。

django数据库配置

现在,打开 mysite/settings.py 。这是个包含了 Django 项目设置的 Python 模块。

  • ENGINE -- 可选值有 'django.db.backends.sqlite3''django.db.backends.postgresql''django.db.backends.mysql',或 'django.db.backends.oracle'。其它 可用后端

  • NAME - 数据库的名称。如果使用的是 SQLite,数据库将是你电脑上的一个文件,在这种情况下, NAME 应该是此文件的绝对路径,包括文件名。默认值 os.path.join(BASE_DIR, 'db.sqlite3') 将会把数据库文件储存在项目的根目录。

如果你不使用 SQLite,则必须添加一些额外设置,比如 USERPASSWORDHOST 等等。想了解更多数据库设置方面的内容,请看文档:DATABASES

例如:代码示例:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'django_database',
        'HOST': '127.0.0.1',
        'PORT': 3306,
        'USER': '用户',
        'PASSWORD': '密码'
    }
}

# 注意 使用这种方法还需要加指定模块功能 让mysql绑定python的pymysql模块
import pymysql
pymysql.install_as_MySQLdb()

请确认在使用前已经创建了数据库。你可以通过在你的数据库交互式命令行中使用 "CREATE DATABASE database_name;" 命令来完成这件事。

另外,还要确保该数据库用户中提供 mysite/settings.py 具有 "create database" 权限。

编辑 mysite/settings.py 文件前,先设置 TIME_ZONE 为你自己时区。

TIME_ZONE = 'Asia/Shanghai'  # 设置时区为上海
USE_TZ = False  # 关闭随模板时间

此外,关注一下文件头部的 INSTALLED_APPS 设置项。这里包括了会在你项目中启用的所有 Django 应用。应用能在多个项目中使用,你也可以打包并且发布应用,让别人使用它们。

常, INSTALLED_APPS 默认包括了以下 Django 的自带应用:

这些应用被默认启用是为了给常规项目提供方便。

默认开启的某些应用需要至少一个数据表,所以,在使用他们之前需要在数据库中创建一些表。请执行以下命令:

 python manage.py migrate

这个 migrate 命令检查 INSTALLED_APPS 设置,为其中的每个应用创建需要的数据表,至于具体会创建什么,这取决于你的 mysite/settings.py 设置文件和每个应用的数据库迁移文件(我们稍后会介绍这个)。这个命令所执行的每个迁移操作都会在终端中显示出来。如果你感兴趣的话,运行你数据库的命令行工具,并输入 \dt (PostgreSQL), SHOW TABLES; (MySQL), .schema (SQLite)或者 SELECT TABLE_NAME FROM USER_TABLES; (Oracle) 来看看 Django 到底创建了哪些表

就像之前说的,为了方便大多数项目,我们默认激活了一些应用,但并不是每个人都需要它们。如果你不需要某个或某些应用,你可以在运行 migrate 前毫无顾虑地从 INSTALLED_APPS 里注释或者删除掉它们。 migrate 命令只会为在 INSTALLED_APPS 里声明了的应用进行数据库迁移。

创建模型

在 Django 里写一个数据库驱动的 Web 应用的第一步是定义模型 - 也就是数据库结构设计和附加的其它元数据。

设计哲学

模型是真实数据的简单明确的描述。它包含了储存的数据所必要的字段和行为。Django 遵循 DRY Principle 。它的目标是你只需要定义数据模型,然后其它的杂七杂八代码你都不用关心,它们会自动从模型生成。

来介绍一下迁移 - 举个例子,不像 Ruby On Rails,Django 的迁移代码是由你的模型文件自动生成的,它本质上只是个历史记录,Django 可以用它来进行数据库的滚动更新,通过这种方式使其能够和当前的模型匹配。

这些概念可以通过一个简单的 Python 类来描述。按照下面的例子来编辑 polls/models.py 文件:

# polls/models.py
from django.db import models

# Create your models here.
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_textpub_date )也是字段名,所以最好使用对机器友好的格式。你将会在 Python 代码里使用它们,而数据库会将它们作为列名。

你可以使用可选的选项来为 Field 定义一个人类可读的名字。这个功能在很多 Django 内部组成部分中都被使用了,而且作为文档的一部分。如果某个字段没有提供此名称,Django 将会使用对机器友好的名称,也就是变量名。在上面的例子中,我们只为 Question.pub_date 定义了对人类友好的名字。对于模型内的其它字段,它们的机器友好名也会被作为人类友好名使用。

定义某些 Field 类实例需要参数。例如 CharField 需要一个 max_length 参数。这个参数的用处不止于用来定义数据库结构,也用于验证数据,我们稍后将会看到这方面的内容。

Field 也能够接收多个可选参数;在上面的例子中:我们将 votesdefault 也就是默认值,设为0。

注意在最后,我们使用 ForeignKey 定义了一个关系。这将告诉 Django,每个 Choice 对象都关联到一个 Question 对象。Django 支持所有常用的数据库关系:多对一、多对多和一对一。

激活模型

上面的一小段用于创建模型的代码给了 Django 很多信息,通过这些信息,Django 可以:

  • 为这个应用创建数据库 schema(生成 CREATE TABLE 语句)。
  • 创建可以与 QuestionChoice 对象进行交互的 Python 数据库 API。

但是首先得把 polls 应用安装到我们的项目里。

设计哲学

Django 应用是“可插拔”的。你可以在多个项目中使用同一个应用。除此之外,你还可以发布自己的应用,因为它们并不会被绑定到当前安装的 Django 上。

为了在我们的工程中包含这个应用,我们需要在settings配置类 INSTALLED_APPS 中添加设置。因为 PollsConfig 类写在文件 polls/apps.py 中,所以它的点式路径是 'polls.apps.PollsConfig'。在文件 mysite/settings.pyINSTALLED_APPS 子项添加点式路径后,它看起来像这样:

mysite/settings.py

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

现在你的 Django 项目会包含 polls 应用。接着运行下面的命令:

python .\manage.py makemigrations polls  # 结尾跟app程序名称

你会看到这样的输出:

Migrations for 'polls':
  polls\migrations\0001_initial.py
    - Create model Question
    - Create model Choice

通过运行 makemigrations 命令,Django 会检测你对模型文件的修改(在这种情况下,你已经取得了新的),并且把修改的部分储存为一次 迁移

迁移是 Django 对于模型定义(也就是你的数据库结构)的变化的储存形式 - 没那么玄乎,它们其实也只是一些你磁盘上的文件。如果你想的话,你可以阅读一下你模型的迁移数据,它被储存在 polls/migrations/0001_initial.py 里。别担心,你不需要每次都阅读迁移文件,但是它们被设计成人类可读的形式,这是为了便于你手动修改它们。

Django 有一个自动执行数据库迁移并同步管理你的数据库结构的命令 - 这个命令是 migrate,我们马上就会接触它 - 但是首先,让我们看看迁移命令会执行哪些 SQL 语句。sqlmigrate 命令接收一个迁移的名称,然后返回对应的 SQL:

python .\manage.py sqlmigrate polls 0001

你将会看到类似下面这样的输出(我把输出重组成了人类可读的格式):

--
-- Create model Question
--
CREATE TABLE `polls_question` ( `id` INTEGER AUTO_INCREMENT NOT NULL PRIMARY KEY, `question_text` VARCHAR ( 200 ) NOT NULL, `pub_date` datetime ( 6 ) NOT NULL );
--
-- Create model Choice
--
CREATE TABLE `polls_choice` ( `id` INTEGER AUTO_INCREMENT NOT NULL PRIMARY KEY, `choice_text` VARCHAR ( 200 ) NOT NULL, `votes` INTEGER NOT NULL, `question_id` INTEGER NOT NULL );
ALTER TABLE `polls_choice` ADD CONSTRAINT `polls_choice_question_id_c5b4b260_fk_polls_question_id` FOREIGN KEY ( `question_id` ) REFERENCES `polls_question` ( `id` );

请注意以下几点:

  • 输出的内容和你使用的数据库有关,上面的输出示例使用的是 PostgreSQL。
  • 数据库的表名是由应用名(polls)和模型名的小写形式( questionchoice)连接而来。(如果需要,你可以自定义此行为。)
  • 主键(IDs)会被自动创建。(当然,你也可以自定义。)
  • 默认的,Django 会在外键字段名后追加字符串 "_id" 。(同样,这也可以自定义。)
  • 外键关系由 FOREIGN KEY 生成。你不用关心 DEFERRABLE 部分,它只是告诉 PostgreSQL,请在事务全都执行完之后再创建外键关系。
  • 生成的 SQL 语句是为你所用的数据库定制的,所以那些和数据库有关的字段类型,比如 auto_increment (MySQL)、 serial (PostgreSQL)和 integer primary key autoincrement (SQLite),Django 会帮你自动处理。那些和引号相关的事情 - 例如,是使用单引号还是双引号 - 也一样会被自动处理。
  • 这个 sqlmigrate 命令并没有真正在你的数据库中的执行迁移 - 它只是把命令输出到屏幕上,让你看看 Django 认为需要执行哪些 SQL 语句。这在你想看看 Django 到底准备做什么,或者当你是数据库管理员,需要写脚本来批量处理数据库时会很有用。

如果你感兴趣,你也可以试试运行 python manage.py check ;这个命令帮助你检查项目中的问题,并且在检查过程中不会对数据库进行任何操作。

现在,再次运行 migrate 命令,在数据库里创建新定义的模型的数据表:

$ python .\manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, polls, sessions
Running migrations:
  Applying polls.0001_initial... OK

这个 migrate 命令选中所有还没有执行过的迁移(Django 通过在数据库中创建一个特殊的表 django_migrations 来跟踪执行过哪些迁移)并应用在数据库上 - 也就是将你对模型的更改同步到数据库结构上。

迁移是非常强大的功能,它能让你在开发过程中持续的改变数据库结构而不需要重新删除和创建表 - 它专注于使数据库平滑升级而不会丢失数据。我们会在后面的教程中更加深入的学习这部分内容,现在,你只需要记住,改变模型需要这三步:

数据库迁移被分解成生成和应用两个命令是为了让你能够在代码控制系统上提交迁移数据并使其能在多个应用里使用;这不仅仅会让开发更加简单,也给别的开发者和生产环境中的使用带来方便。

通过阅读文档 Django 后台文档 ,你可以获得关于 manage.py 工具的更多信息。

初试API

现在让我们进入交互式 Python 命令行,尝试一下 Django 为你创建的各种 API。通过以下命令打开 Python 命令行:

$ python manage.py shell

我们使用这个命令而不是简单的使用 "Python" 是因为 manage.py 会设置 DJANGO_SETTINGS_MODULE 环境变量,这个变量会让 Django 根据 mysite/settings.py 文件来设置 Python 包的导入路径。

当你成功进入命令行后,来试试 database API 吧:

# 导入models里的Question Choice对象
>>> from polls.models import Question,Choice  

# 获取Question对象的所有数据 返回的是一个对象
>>> Question.objects.all() 
<QuerySet []>

# 导入django的时间模块
>>> from django.utils import timezone

# 实例化一个q对象,相当于一条Question表的一条数据
>>> q = Question(question_text="what's new?",pub_date=timezone.now())
# 将数据同步至数据库Question表中
>>> q.save()
# 获取q对象的id字段
>>> q.id
1
# 获取q对象的question_text字段
>>> q.question_text
"what's new?"
# 获取 q对象的pub_date字段
>>> q.pub_date  
datetime.datetime(2019, 11, 4, 19, 15, 44, 302682)
# 修改q对象的question_text字段内容为 what's up?
>>> q.question_text = "what's up?"
# 将q对象现在的数据同步至数据库中
>>> q.save()
# 获取数据库中的Question表的所有数据 返回的是一个数组对象 循环这个可取到每一个对象(每一条数据),每一个对象都可以使用他的字段属性
>>> Question.objects.all()
<QuerySet [<Question: Question object (1)>]>

# 取第1条数据
>>> Question.objects.all()[0]; 
<Question: Question object (1)>

 # 取第一条数据的question_text字段内容
>>> Question.objects.all()[0].question_text; 
"what's up?"
# 同上 取ID字段
>>> Question.objects.all()[0].id;            
1
# 同上 取时间字段
>>> Question.objects.all()[0].pub_date; 
datetime.datetime(2019, 11, 4, 19, 15, 44, 302682)

等等。对于我们了解这个对象的细节没什么帮助。让我们通过编辑Question模型的代码(位于polls/models.py中)来修复这个问题。给QuestionChoice增加str() 方法。

polls/models.py

from django.db import models

# Create your models here.
class Question(models.Model):

    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')

    def __str__(self):
        return self.question_text 

class Choice(models.Model):

    question = models.ForeignKey(Question,on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)

    def __str(self):
        return self.choice_text

给模型增加 __str__() 方法是很重要的,这不仅仅能给你在命令行里使用带来方便,Django 自动生成的 admin 里也使用这个方法来表示对象。

让我们再为此模型添加一个自定义方法:

polls/models.py

>>> from polls.models import Choice,Question

# 确保我们的__str __()添加有效。
>>> Question.objects.all()
<QuerySet [<Question: What's up?>]>

# Django提供了一个丰富的数据库查找API,该API完全由关键字参数。
>>> Question.objects.filter(id=1)
<QuerySet [<Question: what's up?>]>
>>> Question.objects.filter(question_text__startswith="what") 
<QuerySet [<Question: what's up?>]>

# 获取今年发布的问题。
>>> from django.utils import timezone 
>>> current_year = timezone.now().year
>>> Question.objects.get(pub_date__year=current_year) 
<Question: what's up?>

# 请求一个不存在的ID,这将引发异常。
>>> Question.objects.get(id=2) 
Traceback (most recent call last):     
  ...
polls.models.Question.DoesNotExist: Question matching query does not exist.

# 通过主键查找是最常见的情况,因此Django提供了
# 主键精确查找的快捷方式。
# 以下内容与Question.objects.get(id = 1)相同。
>>> Question.objects.get(pk=1) 
<Question: what's up?>
           
# 确保我们的自定义方法有效。
>>> q = Question.objects.get(pk=1) 
>>> q.was_published_recently()
True

# 显示相关对象集中的任何选项-到目前为止没有
>>> q.choice_set.all()
<QuerySet []>

# 创建三个对象 也是创建三条数据
>>> q.choice_set.create(choice_text="No Much",votes=0)
<Choice: Choice object (1)>
>>> q.choice_set.create(choice_text="The sky",votes=0)
<Choice: Choice object (2)>
>>> c = q.choice_set.create(choice_text="Just hacking again",votes=0)
>>> c.question
<Question: what's up?>

# 反之亦然:Question对象可以访问Choice对象。
>>> q.choice_set.all()
<QuerySet [<Choice: Choice object (1)>, <Choice: Choice object (2)>, <Choice: Choice object (3)>]>
>>> q.choice_set.count()
3

# API会根据您的需要自动遵循关系。
# 使用双下划线分隔关系。
# 这可以根据需要进行任意深度的工作; 没有限制。
# 查找今年pub_date为任何问题的所有选项
#(重用我们在上面创建的“ current_year”变量)。
>>> Choice.objects.filter(question__pub_date__year=current_year) 
<QuerySet [<Choice: Choice object (1)>, <Choice: Choice object (2)>, <Choice: Choice object (3)>]>
        
# 让我们删除其中一个选项。 为此使用delete()。
>>> c = q.choice_set.filter(choice_text__startswith='Just hacking') 
>>> c.delete()

阅读 访问关系对象 文档可以获取关于数据库关系的更多内容。想知道关于双下划线的更多用法,参见 查找字段 文档。数据库 API 的所有细节可以在 数据库 API 参考 文档中找到。

介绍 Django 管理页面

** 设计哲学 **

为你的员工或客户生成一个用户添加,修改和删除内容的后台是一项缺乏创造性和乏味的工作。因此,Django 全自动地根据模型创建后台界面。

Django 产生于一个公众页面和内容发布者页面完全分离的新闻类站点的开发过程中。站点管理人员使用管理系统来添加新闻、事件和体育时讯等,这些添加的内容被显示在公众页面上。Django 通过为站点管理人员创建统一的内容编辑界面解决了这个问题。

管理界面不是为了网站的访问者,而是为管理者准备的。

创建一个管理员账号

首先,我们得创建一个能登录管理页面的用户。请运行下面的命令:

$ python manage.py createsuperuser

过程如下:

Username (leave blank to use 'admin'): admin
Email address: 312639695@qq.com
Password: 
Password (again): 
Bypass password validation and create user anyway? [y/N]: y
Superuser created successfully.

启动开发服务器

Django 的管理界面默认就是启用的。让我们启动开发服务器,看看它到底是什么样的。

如果开发服务器未启动,用以下命令启动它:

$ python manage.py runserver

现在,打开浏览器,转到你本地域名的 "/admin/" 目录, -- 比如 "http://127.0.0.1:8000/admin/" 。

因为 翻译 功能默认是开着的,所以登录界面可能会使用你的语言,取决于你浏览器的设置和 Django 是否拥有你语言的翻译

进入管理站点页面

现在,试着使用你在上一步中创建的超级用户来登录。然后你将会看到 Django 管理页面的索引页:

Django admin index page

你将会看到几种可编辑的内容:组和用户。它们是由 django.contrib.auth 提供的,这是 Django 开发的认证框架。

向管理页面中加入投票应用

但是我们的投票应用在哪呢?它没在索引页面里显示。

只需要做一件事:我们得告诉管理页面,问题 Question 对象需要被管理。打开 polls/admin.py 文件,把它编辑成下面这样:

polls/admin.py

from django.contrib import admin

from .models import Question

admin.site.register(Question)

体验便捷的管理功能

现在我们向管理页面注册了问题 Question 类。Django 知道它应该被显示在索引页里:

Django admin index page, now with polls displayed

点击 "Questions" 。现在看到是问题 "Questions" 对象的列表 "change list" 。这个界面会显示所有数据库里的问题 Question 对象,你可以选择一个来修改。这里现在有我们在上一部分中创建的 “What's up?” 问题。

Polls change list pageEditing form for question object

注意事项:

  • 这个表单是从问题 Question 模型中自动生成的
  • 不同的字段类型(日期时间字段 DateTimeField 、字符字段 CharField)会生成对应的 HTML 输入控件。每个类型的字段都知道它们该如何在管理页面里显示自己。
  • 每个日期时间字段 DateTimeField 都有 JavaScript 写的快捷按钮。日期有转到今天(Today)的快捷按钮和一个弹出式日历界面。时间有设为现在(Now)的快捷按钮和一个列出常用时间的方便的弹出式列表。

页面的底部提供了几个选项:

  • 保存(Save) - 保存改变,然后返回对象列表。
  • 保存并继续编辑(Save and continue editing) - 保存改变,然后重新载入当前对象的修改界面。
  • 保存并新增(Save and add another) - 保存改变,然后添加一个新的空对象并载入修改界面。
  • 删除(Delete) - 显示一个确认删除页面。

如果显示的 “发布日期(Date Published)” 和你在 教程 1 里创建它们的时间不一致,这意味着你可能没有正确的设置 TIME_ZONE 。改变设置,然后重新载入页面看看是否显示了正确的值。

通过点击 “今天(Today)” 和 “现在(Now)” 按钮改变 “发布日期(Date Published)”。然后点击 “保存并继续编辑(Save and add another)”按钮。然后点击右上角的 “历史(History)”按钮。你会看到一个列出了所有通过 Django 管理页面对当前对象进行的改变的页面,其中列出了时间戳和进行修改操作的用户名:

History page for question object

视图

Django 中的视图的概念是「一类具有相同功能和模板的网页的集合」。比如,在一个博客应用中,你可能会创建如下几个视图:

  • 博客首页——展示最近的几项内容。
  • 内容“详情”页——详细展示某项内容。
  • 以年为单位的归档页——展示选中的年份里各个月份创建的内容。
  • 以月为单位的归档页——展示选中的月份里各天创建的内容。
  • 以天为单位的归档页——展示选中天里创建的所有内容。
  • 评论处理器——用于响应为一项内容添加评论的操作。

而在我们的投票应用中,我们需要下列几个视图:

  • 问题索引页——展示最近的几个投票问题。
  • 问题详情页——展示某个投票的问题和不带结果的选项列表。
  • 问题结果页——展示某个投票的结果。
  • 投票处理器——用于响应用户为某个问题的特定选项投票的操作。

在 Django 中,网页和其他内容都是从视图派生而来。每一个视图表现为一个简单的 Python 函数(或者说方法,如果是在基于类的视图里的话)。Django 将会根据用户请求的 URL 来选择使用哪个视图(更准确的说,是根据 URL 中域名之后的部分)。

在你上网的过程中,很可能看见过像这样美丽的 URL: "ME2/Sites/dirmod.asp?sid=&type=gen&mod=Core+Pages&gid=A6CD4967199A42D9B65B1B" 。别担心,Django 里的 URL 规则 要比这优雅的多!

一个 URL 模式定义了某种 URL 的基本格式——举个例子:/newsarchive///

为了将 URL 和视图关联起来,Django 使用了 'URLconfs' 来配置。URLconf 将 URL 模式映射到视图。

本教程只会介绍 URLconf 的基础内容,你可以看看 URL调度器 以获取更多内容。

编写更多视图

现在让我们向 polls/views.py 里添加更多视图。这些视图有一些不同,因为他们接收参数:

polls/views.py

from django.shortcuts import render,HttpResponse

# Create your views here.

def index(request):
    return HttpResponse("<h1>Hello world马海阳 大帅哥!</h1>")

def detail(request,question_id):
    return HttpResponse('you are looking at question %s.' %question_id)

def results(request,question_id):
    response = "You are 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() 函数调用就行:

polls/urls.py

from django.urls import path
from . import views

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')
]

然后看看你的浏览器,如果你转到 "/index/34/" ,Django 将会运行 detail() 方法并且展示你在 URL 里提供的问题 ID。再试试 "/index/34/vote/" 和 "/index/34/vote/" ——你将会看到暂时用于占位的结果和投票页。

当某人请求你网站的某一页面时——比如说, "/index/34/" ,Django 将会载入 mysite.urls 模块,因为这在配置项 ROOT_URLCONF 中设置了。然后 Django 寻找名为 urlpatterns 变量并且按序匹配正则表达式。在找到匹配项 'index/',它切掉了匹配的文本("index/"),将剩余文本——"34/",发送至 'polls.urls' URLconf 做进一步处理。在这里剩余文本匹配了 '/',使得我们 Django 以如下形式调用 detail():

detail(request=<HttpRequest object>, question_id=34)

question_id=34 由 `` 匹配生成。使用尖括号“捕获”这部分 URL,且以关键字参数的形式发送给视图函数。上述字符串的 :question_id> 部分定义了将被用于区分匹配模式的变量名,而 int: 则是一个转换器决定了应该以什么变量类型匹配这部分的 URL 路径。

为每个 URL 加上不必要的东西,例如 .html ,是没有必要的。不过如果你非要加的话,也是可以的:

path('polls/latest.html', views.index),

但是,别这样做,这太傻了。

写一个真正有用的视图

每个视图必须要做的只有两件事:返回一个包含被请求页面内容的 HttpResponse 对象,或者抛出一个异常,比如 Http404 。至于你还想干些什么,随便你。

你的视图可以从数据库里读取记录,可以使用一个模板引擎(比如 Django 自带的,或者其他第三方的),可以生成一个 PDF 文件,可以输出一个 XML,创建一个 ZIP 文件,你可以做任何你想做的事,使用任何你想用的 Python 库。

Django 只要求返回的是一个 HttpResponse ,或者抛出一个异常。

因为 Django 自带的数据库 API 很方便,我们曾在 教程第 2 部分 中学过,所以我们试试在视图里使用它。我们在 index() 函数里插入了一些新内容,让它能展示数据库里以发布日期排序的最近 5 个投票问题,以空格分割:

polls/views.py

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 就可以引用到这一模板了。

模板命名空间

虽然我们现在可以将模板文件直接放在 polls/templates 文件夹中(而不是再建立一个 polls 子文件夹),但是这样做不太好。Django 将会选择第一个匹配的模板文件,如果你有一个模板文件正好和另一个应用中的某个模板文件重名,Django 没有办法 区分 它们。我们需要帮助 Django 选择正确的模板,最简单的方法就是把他们放入各自的 命名空间 中,也就是把这些模板放入一个和 自身 应用重名的子文件夹里。

修改一个地方:settings里的templates 下的dir

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR,'templates')], # 这里哈
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

将下面的代码输入到刚刚创建的模板文件中:

templates/polls/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    {% if latest_question_list %}
        <ul>
            {% for question in latest_question_list %}
                <li><a href="/index/{{ question.id }}/">{{ question.question_text }}</a></li>
            {% endfor %}
        </ul>
    {% else %}
        <p>No polls are arailable.</p>>
    {% endif %}
</body>
</html>

然后,让我们更新一下 polls/views.py 里的 index 视图来使用模板:

polls/views.py

from django.shortcuts import render,HttpResponse
from django.template import loader 

from .models import Question

# Create your views here.

def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    template = loader.get_template('polls/index.html')
    content = {
        'latest_question_list': latest_question_list
    }
    return HttpResponse(template.render(content,request))

上述代码的作用是,载入 polls/index.html 模板文件,并且向它传递一个上下文(context)。这个上下文是一个字典,它将模板内的变量映射为 Python 对象。

用你的浏览器访问 "/polls/" ,你将会看见一个无序列表,列出了我们在 教程第 2 部分 中添加的 “What's up” 投票问题,链接指向这个投票的详情页。

一个快捷函数: render()

「载入模板,填充上下文,再返回由它生成的 HttpResponse 对象」是一个非常常用的操作流程。于是 Django 提供了一个快捷函数,我们用它来重写 index() 视图:

polls/views.py

from django.shortcuts import render,HttpResponse

from .models import Question

# Create your views here.

def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    content = {'latest_question_list': latest_question_list}
    return render(request,'polls/index.html',content)

注意到,我们不再需要导入 loaderHttpResponse 。不过如果你还有其他函数(比如说 detail, results, 和 vote )需要用到它的话,就需要保持 HttpResponse 的导入。

render()函数将请求对象作为第一个参数,将模板名称作为第二个参数,并将字典作为第三个可选参数。 它返回使用给定上下文呈现的给定模板的HttpResponse对象。

抛出 404 错误

现在,我们来处理投票详情视图——它会显示指定投票的问题标题。下面是这个视图的代码:

polls/views.py

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 异常。

我们稍后再讨论你需要在 polls/detail.html 里输入什么,但是如果你想试试上面这段代码是否正常工作的话,你可以暂时把下面这段输进去:

polls/templates/polls/detail.html

{{ question }}

这样你就能测试了。

一个快捷函数: get_object_or_404()

尝试用 get() 函数获取一个对象,如果不存在就抛出 Http404 错误也是一个普遍的流程。Django 也提供了一个快捷函数,下面是修改后的详情 detail() 视图代码:

polls/views.py

from django.shortcuts import get_object_or_404, render

from .models import Question
# ...
def detail(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, 'polls/detail.html', {'question': question})

get_object_or_404()函数将Django模型作为其第一个参数,并将任意数量的关键字参数传递给模型管理器的get()函数。 如果对象不存在,它将引发Http404。

设计哲学

为什么我们使用辅助函数 get_object_or_404() 而不是自己捕获 ObjectDoesNotExist 异常呢?还有,为什么模型 API 不直接抛出 ObjectDoesNotExist 而是抛出 Http404 呢?

因为这样做会增加模型层和视图层的耦合性。指导 Django 设计的最重要的思想之一就是要保证松散耦合。一些受控的耦合将会被包含在 django.shortcuts 模块中。

也有 get_list_or_404() 函数,工作原理和 get_object_or_404() 一样,除了 get() 函数被换成了 filter() 函数。如果列表为空的话会抛出 Http404 异常。

使用模板系统

回过头去看看我们的 detail() 视图。它向模板传递了上下文变量 question 。下面是 polls/detail.html 模板里正式的代码:

polls/templates/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 %} 标签内部使用。

查看 模板指南 可以了解关于模板的更多信息。

去除模板中的硬编码 URL

还记得吗,我们在 polls/index.html 里编写投票链接时,链接是硬编码的:

<li><a href="/index/{{ question.id }}/">{{ question.question_text }}</a></li>

问题在于,硬编码和强耦合的链接,对于一个包含很多应用的项目来说,修改起来是十分困难的。然而,因为你在 polls.urlsurl() 函数中通过 name 参数为 URL 定义了名字,你可以使用 {% url %} 标签代替它:

<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>

这个标签的工作方式是在 polls.urls 模块的 URL 定义中寻具有指定名字的条目。你可以回忆一下,具有名字 'detail' 的 URL 是在如下语句中定义的:

...
# 由{%url%}模板标记调用的“名称”值
path('<int:question_id>/', views.detail, name='detail'),
...

如果你想改变投票详情视图的 URL,比如想改成 polls/specifics/12/ ,你不用在模板里修改任何东西(包括其它模板),只要在 polls/urls.py 里稍微修改一下就行:

...
# added the word 'specifics'
path('specifics/<int:question_id>/', views.detail, name='detail'),
...

为 URL 名称添加命名空间

教程项目只有一个应用,polls 。在一个真实的 Django 项目中,可能会有五个,十个,二十个,甚至更多应用。Django 如何分辨重名的 URL 呢?举个例子,polls 应用有 detail 视图,可能另一个博客应用也有同名的视图。Django 如何知道 {% url %} 标签到底对应哪一个应用的 URL 呢?

答案是:在根 URLconf 中添加命名空间。在 polls/urls.py 文件中稍作修改,加上 app_name 设置命名空间:

polls/urls.py

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 文件,从:

polls/templates/polls/index.html

<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>

修改为指向具有命名空间的详细视图:

polls/templates/polls/index.html

<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>

编写一个简单的表单

让我们更新一下在上一个教程中编写的投票详细页面的模板 ("polls/detail.html") ,让它包含一个 HTML <form> 元素:

polls/templates/polls/detail.html

<h1>{{ question.question_text }}</h1>

{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}

<form action="{% url 'polls:vote' question.id %}" method="post">
{% csrf_token %}
{% for choice in question.choice_set.all %}
    <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}">
    <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br>
{% endfor %}
<input type="submit" value="Vote">
</form>

简要说明:

  • 上面的模板在 Question 的每个 Choice 前添加一个单选按钮。 每个单选按钮的 value 属性是对应的各个 Choice 的 ID。每个单选按钮的 name"choice" 。这意味着,当有人选择一个单选按钮并提交表单提交时,它将发送一个 POST 数据 choice=# ,其中# 为选择的 Choice 的 ID。这是 HTML 表单的基本概念。

  • 我们设置表单的 action{% url 'polls:vote' question.id %} ,并设置 method="post" 。使用 method="post"``(与其相对的是 ``method="get")是非常重要的,因为这个提交表单的行为会改变服务器端的数据。 无论何时,当你需要创建一个改变服务器端数据的表单时,请使用 ``method="post"` 。这不是 Django 的特定技巧;这是优秀的网站开发技巧。

  • forloop.counter 指示 for 标签已经循环多少次。

    for循环设置循环中可用的许多变量:

    变量 描述
    forloop.counter 循环的当前迭代(1索引)
    forloop.counter0 循环的当前迭代(0索引)
    forloop.revcounter 从循环末尾开始的迭代次数(1索引)
    forloop.revcounter0 从循环末尾开始的迭代次数(0索引)
    forloop.first 如果这是第一次循环,则为真
    forloop.last 如果这是最后一次循环,则为true
    forloop.parentloop 对于嵌套循环,这是围绕当前循环的循环
  • 由于我们创建一个 POST 表单(它具有修改数据的作用),所以我们需要小心跨站点请求伪造。 谢天谢地,你不必太过担心,因为 Django 已经拥有一个用来防御它的非常容易使用的系统。 简而言之,所有针对内部 URL 的 POST 表单都应该使用 {% csrf_token %} 模板标签。

现在,让我们来创建一个 Django 视图来处理提交的数据。记住,在 教程第 3 部分 中,我们为投票应用创建了一个 URLconf ,包含这一行:

polls/urls.py

path('<int:question_id>/vote/', views.vote, name='vote'),

我们还创建了一个 vote() 函数的虚拟实现。让我们来创建一个真实的版本。 将下面的代码添加到 polls/views.py

polls/views.py

from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse

from .models import Choice, Question
# ...
def vote(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    try:
        selected_choice = question.choice_set.get(pk=request.POST['choice'])
    except (KeyError, Choice.DoesNotExist):
        # Redisplay the question voting form.
        return render(request, 'polls/detail.html', {
            'question': question,
            'error_message': "You didn't select a choice.",
        })
    else:
        selected_choice.votes += 1
        selected_choice.save()
        # Always return an HttpResponseRedirect after successfully dealing
        # with POST data. This prevents data from being posted twice if a
        # user hits the Back button.
        return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))

以上代码中有些内容还未在本教程中提到过:

  • request.POST 是一个类字典对象,让你可以通过关键字的名字获取提交的数据。 这个例子中, request.POST['choice'] 以字符串形式返回选择的 Choice 的 ID。 request.POST 的值永远是字符串。

    注意,Django 还以同样的方式提供 request.GET 用于访问 GET 数据 —— 但我们在代码中显式地使用 request.POST ,以保证数据只能通过 POST 调用改动。

  • 如果在 request.POST['choice'] 数据中没有提供 choice , POST 将引发一个 KeyError 。上面的代码检查 KeyError ,如果没有给出 choice 将重新显示 Question 表单和一个错误信息。

  • 在增加 Choice 的得票数之后,代码返回一个 HttpResponseRedirect 而不是常用的 HttpResponseHttpResponseRedirect 只接收一个参数:用户将要被重定向的 URL(请继续看下去,我们将会解释如何构造这个例子中的 URL)。

    正如上面的Python注释所指出的那样,在成功处理POST数据后,您应该始终返回HttpResponseRedirect。 这个技巧不是特定于Django的; 这只是Web开发的好习惯。

  • 在这个例子中,我们在 HttpResponseRedirect 的构造函数中使用 reverse() 函数。这个函数避免了我们在视图函数中硬编码 URL。它需要我们给出我们想要跳转的视图的名字和该视图所对应的 URL 模式中需要给该视图提供的参数。 在本例中,使用在 教程第 3 部分 中设定的 URLconf, reverse() 调用将返回一个这样的字符串:

    '/polls/3/results/'
    

    其中 3question.id 的值。重定向的 URL 将调用 'results' 视图来显示最终的页面。

HttpRequest 是一个 HttpRequest 对象。更多关于 HttpRequest 对象的内容,请参见 请求和响应的文档

当有人对 Question 进行投票后, vote() 视图将请求重定向到 Question 的结果界面。让我们来编写这个视图:

polls/views.py

from django.shortcuts import get_object_or_404, render

def results(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, 'polls/results.html', {'question': question})

detail() 视图几乎一模一样。唯一的不同是模板的名字。 我们将在稍后解决这个冗余问题。

现在,创建一个 polls/results.html 模板:

polls/templates/polls/results.html

<h1>{{ question.question_text }}</h1>

<ul>
{% for choice in question.choice_set.all %}
    <li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
{% endfor %}
</ul>

<a href="{% url 'polls:detail' question.id %}">Vote again?</a>

现在,在你的浏览器中访问 /polls/1/ 然后为 Question 投票。你应该看到一个投票结果页面,并且在你每次投票之后都会更新。 如果你提交时没有选择任何 Choice,你应该看到错误信息。

注解

我们的 vote() 视图代码有一个小问题。代码首先从数据库中获取了 selected_choice 对象,接着计算 vote 的新值,最后把值存回数据库。如果网站有两个方可同时投票在 同一时间 ,可能会导致问题。同样的值,42,会被 votes 返回。然后,对于两个用户,新值43计算完毕,并被保存,但是期望值是44。

这个问题被称为 竞争条件 。如果你对此有兴趣,你可以阅读 Avoiding race conditions using F() 来学习如何解决这个问题。

使用通用视图:代码还是少点好

detail() (在 教程第3部分 中)和 results() 视图都很简单 —— 并且,像上面提到的那样,存在冗余问题。用来显示一个投票列表的 index() 视图(也在 教程第 3 部分 中)和它们类似。

这些视图反映基本的 Web 开发中的一个常见情况:根据 URL 中的参数从数据库中获取数据、载入模板文件然后返回渲染后的模板。 由于这种情况特别常见,Django 提供一种快捷方式,叫做“通用视图”系统。

通用视图将常见的模式抽象化,可以使你在编写应用时甚至不需要编写Python代码。

让我们将我们的投票应用转换成使用通用视图系统,这样我们可以删除许多我们的代码。我们仅仅需要做以下几步来完成转换,我们将:

  1. 转换 URLconf。
  2. 删除一些旧的、不再需要的视图。
  3. 基于 Django 的通用视图引入新的视图。

为什么要重构代码?

一般来说,当编写一个 Django 应用时,你应该先评估一下通用视图是否可以解决你的问题,你应该在一开始使用它,而不是进行到一半时重构代码。本教程目前为止是有意将重点放在以“艰难的方式”编写视图,这是为将重点放在核心概念上。

就像在使用计算器之前你需要掌握基础数学一样。

改良 URLconf

首先,打开 polls/urls.py 这个 URLconf 并将它修改成:

polls/urls.py

from django.urls import path

from . import views

app_name = 'polls'
urlpatterns = [
    path('', views.IndexView.as_view(), name='index'),
    path('<int:pk>/', views.DetailView.as_view(), name='detail'),
    path('<int:pk>/results/', views.ResultsView.as_view(), name='results'),
    path('<int:question_id>/vote/', views.vote, name='vote'),
]

注意,第二个和第三个匹配准则中,路径字符串中匹配模式的名称已经由 <question_id> 改为 <pk>

改良视图

下一步,我们将删除旧的 index, detail, 和 results 视图,并用 Django 的通用视图代替。打开 polls/views.py 文件,并将它修改成:

polls/views.py

from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.views import generic

from .models import Choice, Question


class IndexView(generic.ListView):
    template_name = 'polls/index.html'
    context_object_name = 'latest_question_list'

    def get_queryset(self):
        """返回最近发布的五个questions."""
        return Question.objects.order_by('-pub_date')[:5]


class DetailView(generic.DetailView):
    model = Question
    template_name = 'polls/detail.html'


class ResultsView(generic.DetailView):
    model = Question
    template_name = 'polls/results.html'


def vote(request, question_id):
    ... # same as above, no changes needed.

我们在这里使用两个通用视图: ListViewDetailView 。这两个视图分别抽象“显示一个对象列表”和“显示一个特定类型对象的详细信息页面”这两种概念。

  • 每个通用视图需要知道它将作用于哪个模型。 这由 model 属性提供。
  • DetailView 期望从 URL 中捕获名为 "pk" 的主键值,所以我们为通用视图把 question_id 改成 pk

默认情况下,通用视图 DetailView 使用一个叫做 /_detail.html 的模板。在我们的例子中,它将使用 "polls/question_detail.html" 模板。template_name 属性是用来告诉 Django 使用一个指定的模板名字,而不是自动生成的默认名字。 我们也为 results 列表视图指定了 template_name —— 这确保 results 视图和 detail 视图在渲染时具有不同的外观,即使它们在后台都是同一个 DetailView

类似地,ListView 使用一个叫做 /_list.html 的默认模板;我们使用 template_name 来告诉 ListView 使用我们创建的已经存在的 "polls/index.html" 模板。

在之前的教程中,提供模板文件时都带有一个包含 questionlatest_question_list 变量的 context。对于 DetailViewquestion 变量会自动提供—— 因为我们使用 Django 的模型 (Question), Django 能够为 context 变量决定一个合适的名字。然而对于 ListView, 自动生成的 context 变量是 question_list。为了覆盖这个行为,我们提供 context_object_name 属性,表示我们想使用 latest_question_list。作为一种替换方案,你可以改变你的模板来匹配新的 context 变量 —— 这是一种更便捷的方法,告诉 Django 使用你想使用的变量名。

启动服务器,使用一下基于通用视图的新投票应用。

更多关于通用视图的详细信息,请查看 通用视图的文档

posted @ 2019-12-03 16:07  Anesthesias  阅读(640)  评论(0编辑  收藏  举报