Django 笔记
Django
Django 是 Python 下最有代表性的 Web 框架,目的是要实现简单快捷的网站开发。
对象关系映射
对象关系映射(Object Relational Mapping,简称ORM),是通过使用描述对象和数据库之间映射的元数据,将面向对象语言程序中的对象自动持久化到关系数据库中。本质上就是讲数据从一种形式转换到另外一种形式。
对象和关系数据是业务实体的两种表现形式,业务实体在内存中表现为对象,在数据库中表现为关系数据。内存中的对象之间存在关联和继承关系,而在数据库中,关系数据无法直接表达多对多关联与继承关系。因此,对象-关系映射(ORM)系统一般以中间件的形式存在,主要实现程序对象到数据库数据的映射。
Django 中的对象关系映射
Django 无需数据库就可以使用,它提供了对象关系映射器。通过此技术,你可以使用 Python 代码来描述数据库结构。
在 models.py
中,编写类来描述你的数据模型来进行对象关系映射。
from django.db import models
class Reporter(models.Model):
full_name = models.CharField(max_length=70)
def __str__(self):
return self.full_name
应用数据模型
然后,运行 Django 命令行指令来自动创建数据库表。
$ python manage.py makemigrations
$ python manage.py migrate
makemigrations
指令会查看所有可用模型,并为不存在的表创建迁移。migrate
执行迁移并在数据库中创造表。
享用便捷的API
接下来,你可以通过一套便捷而丰富的 Python API 访问你的数据。这些 API 是即时创建的,而不用显式的生成代码。
# 引入我们为 news app 创建的模块。
>>> from news.models import Article, Reporter
# 现在在我们系统中还不存在 Reporter。
>>> Reporter.objects.all()
<QuerySet []>
# 新建一个 Reporter。
>>> r = Reporter(full_name='John Smith')
# 通过调用 save() 方法来将对象保存到数据库中。
>>> r.save()
# 现在它有了自己的 ID。
>>> r.id
1
# 现在数据库中有一个新的 Reporter。
>>> Reporter.objects.all()
<QuerySet [<Reporter: John Smith>]>
# 可以通过访问 Python 对象的属性来访问字段。
>>> r.full_name
'John Smith'
# Django 提供了丰富的数据库查询 API。
>>> Reporter.objects.get(id=1)
<Reporter: John Smith>
>>> Reporter.objects.get(full_name__startswith='John')
<Reporter: John Smith>
>>> Reporter.objects.get(full_name__contains='mith')
<Reporter: John Smith>
>>> Reporter.objects.get(id=2)
Traceback (most recent call last):
...
DoesNotExist: Reporter matching query does not exist.
动态管理接口
完成模型定义后,Django 会自动生成一个专业的生产机管理接口(Django admin site)——一个允许认证用户添加、更改和删除对象的 Web 站点。你只需要简单的在 admin 站点上注册你的模型即可:
from django.contrib import admin
from . import models
admin.site.register(models.Article)
创建 Django 应用的典型流程是,先建立数据模型,然后搭建管理站点,之后用户就可以向网站里填充数据。
规划 URLs
Django 推崇优美的 URL 设计。
URLconf 模块
包含一张 URL 与 Python 回调函数之间的映射表,也利于将 Python 与 URL 进行解耦。
from django.urls import path
from . import views
urlpatterns = [
path('articles/<int:year>/', views.year_archive),
path('articles/<int:year>/<int:month>/', views.month_archive),
path('articles/<int:year>/<int:month>/<int:pk>/', views.article_detail),
]
上述代码将 URL 路径映射到了 Python 的回调函数(“视图”)。路径字符串使用参数标签从 URL 中捕获相应值。当用户请求页面时,Django 依次遍历路径,直到初次匹配到了请求的 URL。(如果无匹配项,Django 会调用 404 视图)。这个过程非常快,因为路径在加载时就编译成了正则表达式。
一旦有 URL 路径匹配成功,Django 会调用相应的视图函数。每个视图函数会接受一个请求对象——包含请求元信息——以及在匹配式中获取的参数值。
例如,当用户请求了这样的 URL "/articles/2005/05/39323/"
,Django 会调用 news.views.article_detail(request, year=2005, month=5, pk=39323)
。
编写视图
视图函数的执行结果只可能有两种:返回一个包含请求页面元素的 HttpResponse
对象,或者是抛出 Http404
这类异常。至于执行过程中的其它的动作则由你决定。
通常来说,一个视图的工作就是:从参数获取数据,装载一个模板,然后将根据获取的数据对模板进行渲染。
from django.shortcuts import render
from .models import Article
def year_archive(request, year):
a_list = Article.objects.filter(pub_date__year=year)
context = {'year': year, 'article_list': a_list}
return render(request, 'news/year_archive.html', context)
在视图里,你可以从数据库里读取记录,可以使用一个模板引擎(比如 Django 自带的,或者其他第三方的),可以生成一个 PDF 文件,可以输出一个 XML,创建一个 ZIP 文件,你可以做任何你想做的事,使用任何你想用的 Python 库。
每个视图必须要做的只有两件事:返回一个包含被请求界面内容的 HttpResponse
对象,或者抛出一个异常,比如 Http404
。
设计模板
Django 允许设置搜索模板路径,这样可以最小化模板之间的冗余。在 Django 设置中,你可以通过 DIRS 参数指定一个路径列表用于检索模板。如果第一个路径中不包含任何模板,就继续检查第二个,以此类推。
{% extends "base.html" %}
{% block title %}Articles for {{ year }}{% endblock %}
{% block content %}
<h1>Articles for {{ year }}</h1>
{% for article in article_list %}
<p>{{ article.headline }}</p>
<p>By {{ article.reporter.full_name }}</p>
<p>Published {{ article.pub_date|date:"F j, Y" }}</p>
{% endfor %}
{% endblock %}
我们可以看到变量都被大双括号括起来了。{{ article.headline }}
的意思是“输出 article 的 headline 属性值”。
{{ article.pub_date | date:"F j, Y" }}
使用了 Unix 风格的“管道符”。这是一个模板过滤器,用于过滤变量值,将对象转换为指定核实。
你可以将多个过滤器连在一起使用。还可以使用 自定义的模板过滤器,甚至可以自己编写自定义的模板标签
Django 使用了“模板继承”的概念。这就是 {{% extend "base.html %}}
的作用。它的含义是“先加载名为 base 的模板,并且用下面的标记块对模板中定义的标记块进行填充”。简而言之,模板继承可以使模板间的冗余内容最小化;每个模板只需包含与其他文档有区别的内容。
<html>
<head>
<title>{% block title %}{% endblock %}</title>
</head>
<body>
<img src="{% static "images/sitelogo.png" %}" alt="Logo">
{% block content %}{% endblock %}
</body>
</html>
编写 Django 应用
安装 Django
pip install django
创建项目
cd
到你想放置代码的目录,然后运行以下命令:
$ django-admin startproject mysite
这行代码会在当前目录下创建一个 mysite
目录。
mysite/
manage.py
mysite/
__init__.py
settings.py
urls.py
wsgi.py
这些目录和文件的用处是:
- 最外层的
mysite/
根目录为项目容器。 -
manage.py
是用来管理 Django 的命令行工具。 - 里面的一层
mysite/
包含项目,它是一个纯 Python 包。她的名字就是当你引用它内部任何东西时所需要用到的 Python 包名。(eg:mysite.urls
) -
mysite/__init__.py
:一个空文件,告诉 Python 这个目录应该被认为是一个 Pyhton 包。 -
mysite/settings.py
:Django 项目的配置文件。 -
mysite/urls.py
: Django 项目的 URL 声明。 -
mysite/wsgi.py
:作为你的项目的运行在 WSGI 兼容的 Web 服务器上的入口。
启动服务器
$ python manage.py runserver
这条指令启动了 Django 自带的用于开发的简易服务器,是一个用纯 Python 写的轻量级 Web 服务器。这个服务器只是为了开发而设计的,千万不要将这个服务器用于生产相关的场景中。
服务器在运行时,访问 https://127.0.0.1:8000
。
默认情况下,runserver
命令会将服务器设置为监听本机内部 IP 的 8000
端口。
如果你想更换服务器的监听端口,请使用命令行参数。
$ python manage.py runserver 8080
创建应用
$ python manage.py startapp [app_name]
会按照配置的项目名创建一个目录,结构如下:
[app_name]/
__init__.py
admin.py
apps.py
migrations/
__init__.py
models.py
tests.py
views.py
创建视图
在应用文件夹中view.py
中进行视图编写。
URL映射
在应用文件夹下创建一个urls.py
文件,用来处理应用内部的 URL 映射。
from django.urls import path
form . import views
urlpatterns = [
path('', views.index, name='index'),
]
再将应用 URLconfs 添加到根 URLconf 中。
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path('polls/', include('polls.urls')),
path('admin/', admin.site.urls),
]
函数 include()
允许引用其他 URLconfs。每当 Django 遇到 include()
时,它会截断与此项匹配的 URL 的部分,并将剩余的字符串发送给具体应用的 URLconf 以供进一步处理。
当包括其它 URL 模式时你应该总是使用 include() , admin.site.urls
是唯一例外。
path()
函数
此函数用于处理请求中的 url 路由分发,并调用路径所指定的视图函数。
参数:
route
:route 是一个匹配 URL 的准则(类似正则表达式)。当 Django 响应一个请求时,它会从 urlpatterns 的第一项开始,按顺序依次匹配列表中的项,直到找到匹配的项。
view
:当 Django 找到了一个匹配的准则,就会调用这个特定的视图函数,并传入一个 HttpRequest
对象作为第一个参数,被“捕获”的参数以关键字参数的形式传入。
kwargs
:任意个关键字参数可以作为一个字典传递给目标视图函数。
name
:为你的 URL 取名能使你在 Django 的任意地方唯一地引用它,尤其是在模板中。这个有用的特性允许你只改一个文件就能全局地修改某个 URL 模式。
创建模型
在 Django 里写一个数据库驱动的 Web 应用的第一步是定义模型 - 也就是数据库结构设计和附加的其它元数据。
模型是真实数据的简单明确的描述。它包含了储存的数据所必要的字段和行为。
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
类的实例。
每个 Field
类实例变量的名字(例如 question_text
或 pub_date
)也是字段名,所以最好使用对机器友好的格式。你将会在 Python 代码里使用它们,而数据库会将它们作为列名。
你也可以使用可选的选项为 Field
定义一个人类友好的名字。
定义 Field
类时填充的参数作为数据库字段的配置。
使用 ForeignKey
定义一个外键。
模型的用处在于:
- 为这个应用创建数据库 schema(生成 CREATE TABLE 语句)。
- 创建可以与对象进行交互的 Python 数据库 API。
激活应用
为了在工程中添加应用,我们需要在 settings.py
中 INSTALLED_APPS
添加设置。
此外,我们先来关注一下 INSTALLED_APPS
的设置项。这里包括了会在你项目中启用的所有 Django 应用。
通常, INSTALLED_APPS
默认包括了以下 Django 的自带应用:
-
django.contrib.admin
-- 管理员站点。 -
django.contrib.auth
-- 认证授权系统。 -
django.contrib.contenttypes
-- 内容类型框架。 -
django.contrib.sessions
-- 会话框架。 -
django.contrib.messages
-- 消息框架。 -
django.contrib.staticfiles
-- 管理静态文件的框架。
自己创建的应用或引用别人的应用,则需要在 INSTALLED_APPS
中追加。
每个应用在生成是会自动在 app.py
模块中创建包含应用名的继承于 AppConfig
类的子类。
为了在项目中包含应用,需要把项目 app.py
模块中的 AppConfig
类以点式路径的形式添加到 INSTALLED_APPS
中。
数据库配置
在项目目录下的 settings.py
中,包含了 Django 的项目设置。
Django 使用 Python 内置的 SQLite 作为默认数据库。
如果你想使用其他数据库,你需要安装合适的 database bindings
,然后改变设置文件中 DATABASES
中 'default'
项目中的一些键值。
在编辑 mysite/settings.py
文件前,先设置 TIME_ZONE
为你自己时区。
数据库建表
$ python manage.py makemigrations polls
通过运行 makemigrations 命令,Django 通过检查 INSTALLED_APPS
设置,检测模型文件的修改,把修改的部分储存为一次迁移,并自动创建数据库表的修改 SQL 语句。
迁移是 Django 对于模型定义(也就是你的数据库结构)的变化的储存形式。
Django 有一个自动执行数据库迁移并同步管理你的数据库结构的命令:
$ python manage.py migrate
这个 migrate
命令选中所有还没有执行过的迁移(Django 通过在数据库中创建一个特殊的表 django_migrations
来跟踪执行过哪些迁移)并应用在数据库上 - 也就是将你对模型的更改同步到数据库结构上。
数据库操作
为了用 Python 对象展示数据表对象,Django 使用了一套直观的系统:一个模型类代表一张数据表,一个模型类的实例代表数据库表中的一行记录。
基本概念
管理器:class Manager
Manager 是一种接口,它赋予了 Django 模型操作数据库的能力。
默认情况下,Django 为每个模型类添加了一个名为 objects 的 Manager。
QuerySet:一个 QuerySet
代表来自数据库中对象的一个集合。
它可以有 0 个,1 个或者多个 filters。 Filters,可以根据给定参数缩小查询结果量。
在 SQL 的层面上, QuerySet
对应 SELECT
语句,而过滤器对应类似 WHERE
或 LIMIT
的限制子句。
QuerySet 是惰性的,创建 QuerySet 并不会引发任何数据库活动。当要使用(查看,或输出)时才会从数据库中拿出。
对象创建
- 模型对象的实例化。
创建对象:p = Person(first_name="Bruce", last_name="Springsteen")
-
create()
方法,创建,并保存。
p = Person.objects.create(first_name="Bruce", last_name="Springsteen")
将修改保存至对象(存入数据库):p.save(force_insert=True)
-
get_or_create()
方法。尝试获取,不存在就创建。
对象检索
检索全部:all_entries = Entry.objects.all()
返回一个包含数据库中所有对象的 QuerySet
对象。
检索单个对象:one_entry = Entry.objects.get(pk=1)
get()
方法如果没有满足查询条件的结果, get()
会抛出一个 DoesNotExist
异常。该异常是执行查询的模型类的一个属性。
get()
方法总是会返回一条满足条件的记录,如果不止一条记录满足其查询条件时 Django 会抛出 MultipleObjectsReturned
,这同样也是模型类的一个属性。
此方法不返回 QuerySet
对象。
使用过滤器检索:都返回 QuerySet
对象。
- filter 过滤器:filter(**kwargs)
返回一个新的QuerySet
,包含的对象满足给定查询参数。
在ForeignKey
中,你可以指定以_id
为后缀的字段名。这种情况下,value 参数需要包含 foreign 模型的主键的原始值。
models.py
class Entry(models.Model):
blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
--snip--
Entry.objects.filter(blog_id=4)
- exclude 过滤器:exclude(**kwargs)
返回一个新的QuerySet
,包含的对象 不满足给定查询参数
eg:Entry.objects.filter(pub_date__year=2006)
- order_by()
默认情况下,QuerySet 返回的结果都会根据模型中可以被排序的元数据所构成的元祖进行排序。你可以通过此方法来覆盖默认的排序方法。
提供“字段名”作为参数进行升序排列,提供“-字段名”进行降序排列,提供“?”进行随机排列(但会有较大开销) - values()
返回查询结果的字典结构而不是模型实例对象。提供参数则字典中只有参数字段的内容。 - values_list()
基本同 value(),不过返回的结果是一个元祖。
限制 QuerySet 条目数
利用 Python 的数组切片语法将 QuerySet
切成指定长度。这等价于 SQL 的 LIMIT
和 OFFSET
子句。可以再任何返回 QuerySet 对象的方法后使用。
不支持负索引。
如果检索单个对象时,使用索引,而不是切片。然而如果没有对象满足给定条件,前者会抛出 IndexError
,而后者会抛出 DoesNotExist
。
字段查询
字段查询即你如何制定 SQL WHERE 子句。
基本的查询关键字参数遵照 field__lookuptype=value
。(有个双下划线)。
例如:
Entry.objects.filter(pub_date__lte='2006-01-01')
转换为 SQL 语句大致如下:
SELECT * FROM blog_entry WHERE pub_date <= '2006-01-01';
查询子句中指定的字段必须是模型的一个字段名。
-
exact
默认的查询类型。
如果关键字参数未包含双下划綫——查询类型会被指定为 exact。
因为exaxt
类型是最为常见的。 -
iexact
大小写不敏感的查询。 -
contains
大小写敏感的包含匹配。相当于 SQL 中的 IS。 -
-icontains
大小写不敏感的包含匹配。相当于 SQL 中的 LIKE。 -
in
在给定的列表,元祖或 QuerySet 中匹配。相当于 SQL 中的 IN。 - gt 大于
- gte 大于等于
- lt 小于
- lte 小于等于
- startswith, istartwith, endswith, iendswith
- range(a, b) 在给定的两个范围内查询。相当于 SQL 中的 BETWEEN。
- date 为了 date 类型的字段做匹配或比较而准备。
- year, iso_year, month, day, week , week_day, quater, time, hour, minute, second同上
- isnull 值取 True 或 False 时,相当于 SQL 中的 IS NULL 和 IS NOT NULL。
- regex 大小写敏感的正则匹配
- iregex 大小写不敏感的正则匹配
Django 管理界面
应用部署好之后,我们开始创建一个管理员页面。
$ python manage.py createsuperuser
根据指示创建好管理员页面后,启动开发服务器。
$ python manage.py runserver
打开"http://127.0.0.1:8000/admin/"
,你可以看到管理员登录界面。
进入站点后,可以看到 Django 管理页面的索引页。在索引页中可以进行站点的管理与模型的修改配置。
编写更多视图
在 应用的 views.py
中添加更多视图,包括接收参数的视图。
并在 urls.py
中为新增加的视图分配 url。
Django 的 URLconf 匹配机制
当你请求某一个页面时,比如说"/polls/34/",Django 会先去项目下的 settings.py
中查询 ROOT_URLCONF
中的配置。然后载入项目下的 urls
模块。然后 Django 寻找名为 urlpatterns
变量并且按序匹配正则表达式。在找到匹配项 'polls/'
后,将匹配到的文本('polls/')切除,将剩余文本 '34/'
发送到后续的 URLconf 中作进一步处理。在这里question_id=34
由 <int:question_id>
匹配生成。使用尖括号“捕获”这部分 URL,且以关键字参数的形式发送给视图函数。上述字符串的 :question_id
部分定义了将被用于区分匹配模式的变量名,而 int:
则是一个转换器决定了应该以什么变量类型匹配这部分的 URL 路径。
模板与视图
首先在你的应用目录下创建一个 templates
目录,Django 将会在这个目录里查找模板文件。
项目中的 settings.py
模块中的 TEMPLATE
配置项描述了 Django 如何载入和渲染模板。默认的设置文件设置了 DjangoTemplates
backend,并将 APP_DIRS
设置为 True
。这一选项会让 DjangoTemplates
在每个 INSTALLED_APPS
文件夹中寻找 templates
子目录。
在刚创建的 template
目录中,再创建一个应用同名的文件夹,然后在其中创建模板 html 文件。
模板命名空间
虽然我们现在可以将模板文件直接放在 应用下的 templates
文件夹中(而不是再建立一个 polls 子文件夹),但是这样做不太好。Django 将会选择第一个匹配的模板文件,如果你有一个模板文件正好和另一个应用中的某个模板文件重名,Django 没有办法 区分 它们。我们需要帮助 Django 选择正确的模板,最简单的方法就是把他们放入各自的 命名空间 中,也就是把这些模板放入一个和 自身 应用重名的子文件夹里。
一段模板示例代码:
{% 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 %}
根据模板文件编写的视图代码:
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))
在上述代码中,载入 Model Question 后,从数据库中取出数据,然后载入模板文件,并且向它传递一个上下文(context)。这个上下文是一个字典,它将模板内的变量映射为 Python 对象。再将 context 和 request 通过参数传递给 template.render
方法中。
一个快捷函数:render()
「载入模板,填充上下文,再返回由它生成的 HttpResponse
对象」是一个非常常用的操作流程。于是 Django 提供了一个快捷函数—— render()
。
将上面的代码重构后:
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)
注意我们导入了 render
模块,取消了 loader
,HttpResponse
模块的导入。
抛出 404 错误
为了网站的健壮性,我们需要对用户可能指定的非法地址抛出 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 所对应的的问题不存在,这个视图就会抛出一个 Http 404
异常。
一个快捷函数:get_object_or_404()
尝试用 get()
函数获取一个对象,如果不存在就抛出 Http404
错误也是一个普遍的流程。Django 也提供了一个快捷函数 get_object_or_404()
。
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 Model 作为它的第一个参数,然后根据之后提供的任意数量的关键字参数传递给 get()
函数并返回获取到的内容,如果这个对象于数据库中不存在,则抛出 Http404 错误。
模板常用语法
格式
Django 模板是使用 Django 模板语言标记的文本文档或 Python 字符串。模板引擎可以识别和解释这些结构,其中主要是变量和 tags。
模板是根据 view
模块中传来的 context
来转换的。转换时会在 context
中查找的变量值,替换变量,并执行 tags。其他一切按原样输出。
Django 模板语言的语法主要包含四个结构:
变量
变量 会输出一个 context 中某个键值对中的值。
变量以 {{
和 }}
包裹。
变量的值通过句点法来获取。
标签(Tags)
Tags 提供转换(Render)过程中的任意某种逻辑。
比如,可以输出内容,使用 if
操作符和for
循环,从数据库中抓取内容甚至可以访问其他模板 tag。
Tag 以 {%
和 %}
包裹。
过滤器
过滤器可以对变量和 tag 参数的值进行过滤转换。
例如:
{{ django | title }}
可以把内容 {'django': 'the web framework for perfectionists with deadlines'}
转换为:
The Web Framework For Perfectionists With Deadlines
注释
{# comment #}
去除模板中的硬编码 URL
按照我们的一般逻辑,在 模板中编写链接时,链接时硬编码的:
<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>
这个标签的原理是,url
函数会在 urls
模块中寻找指定了 name 的条目,在模板转换过程中用此条目的 url
进行替换。
因此,如果你想修改某个链接的 URL 时,就不需要对模板内容进行修改,而只需要对 URLconf
进行修改。
为 URL 名称添加 namespace
方法是:在根 URLconf 中添加 app_name
命名空间。
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('polls/', include('polls.urls', namespace='polls')),
path('admin/', admin.site.urls),
path('', admin.site.urls),
]
include()
函数接受一个 namespace
关键字参数,用来指定应用的 URL 命名空间。
接下来,需要在应用的 URLconf 中添加 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'),
]
接下来对模板进行修改,将链接指向具有命名空间的视图。
<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
表单
一个表单模板示例:
<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>
模板中设置了表单的 action
url
,method = "post"
。
forloop.counter
表示 'for' 循环的循环计数。Django 提供了一系列便于监控 for
循环的变量。
-
forloop.counter
当前循环的迭代次数(从 1 开始计数) -
forloop.counter0
当前循环的迭代次数(从 0 开始计数) -
forloop.revcounter
当前循环距离循环结束的计数(从 1 开始) -
forloop.revcounter0
当前循环距离循环结束的计数(从 0 开始) -
forloop.first
如果是第一次执行循环,返回 True。 -
forloop.last
如果是最后一次执行循环,返回 False。 -
forloop.parentloop
当存在嵌套循环时,指向当前循环的外层循环。
Django 为 POST 表单提供了 {% csrf_token %}
模板标签,防止跨站攻击。因此针对内部 URL 的 POST 表单都应该使用这个标签。
一个表单处理示例:
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
的值永远是字符串。
request.GET
与 request.POST
的情况类似。
如果在 request.POST['choice']
数据中没有提供 choice
,POST 将引发一个 KeyError
。
在请求执行完毕后,代码返回一个 HttpResponseRedirect
而不是常用的 HttpResponse
。HttpResponseRedirect
是 HttpResponse
的一个子类,只接收一个参数:用户将要被重定向的 URL。
在这个例子中,我们在 HttpResponseRedirect
的构造函数中使用 reverse()
函数。这个函数避免了我们在视图函数中硬编码 URL。
reverse()
函数用来拼接 url 字符串,第一个参数需要提供 viewname
,它可以是一个 URL 模板的 name。如果 URL 接受参数,你可以通过 args
作为第二个参数来传递,同样你可以使用 kwargs
,但你不能两者同时使用。
减少模板代码冗余——通用视图系统
基本的 Web 开发中有一个常见情况:根据 URL 中的参数从数据库中获取数据、载入模板文件然后返回渲染后的模板。 由于这种情况特别常见,Django 提供一种快捷方式,叫做“通用视图”系统。
通用视图将常见的模式抽象化,可以使你在编写应用时甚至不需要编写 Python 代码。
URL 配置:
path('', views.IndexView.as_view(), name='index'),
path('<int:pk>/', views.DetailView.as_view(), name='detail'),
视图:
class IndexView(generic.ListView):
template_name = 'polls/index.html'
context_object_name = 'latest_question_list'
def get_queryset(self):
"""Return the last five published questions."""
return Question.objects.order_by('-pub_date')[:5]
class DetailView(generic.DetailView):
model = Question
template_name = 'polls/detail.html'
在上述代码中,使用了两个通用视图:ListView
和 DetailView
。这两个视图分别抽象“显示一个对象列表”和“显示一个特定类型对象的详细信息页面”这两种概念。
- 每个通用视图需要知道它将作用于哪个模型。这由
model
属性提供。 -
DetailView
期望从 URL 中不活名为 "pk
" 的主键值。
template_name
模板是用来告诉 Django 使用一个指定的模板名字,而不是默认模板名字。默认情况下,通用视图 DetailView
使用一个叫做 <appname>/<module name>_detail.html
的模板。在我们的例子中,它默认会使用 "polls/question_detail.html"
模板。
类似的,ListView
会使用一个叫做 <app name>/<model name>_list.html
的默认模板;我们使用 template_name
来告诉 ListView
使用我们创建的已经存在的 "polls/index.html"
模板。
之前,提供模板文件时会带有一个包含 question
和 latest_question_list
变量的 context
。
对于 DetailView
, question 变量会自动提供—— 因为我们提供 model = Question
,为 Django 提供了我们的模型 (Question), Django 能够为 context
变量决定一个合适的名字。
然而对于 ListView
, 自动生成的 context 变量是 question_list
。为了覆盖这个行为,我们提供 context_object_name
属性,表示我们想使用 latest_question_list。作为一种替换方案,你可以改变你的模板来匹配新的 context 变量 —— 这是一种更便捷的方法,告诉 Django 使用你想使用的变量名。
自动化测试
测试,是用来检查代码正确性的一些简单的程序。
自动化测试是有某个系统帮你自动完成的。当你创建好了一系列测试,每次修改应用代码后,就可以自动检查出修改后的代码是否还像你曾经预期的那样正常工作。你不需要花费大量时间来进行手动测试。
创建一个 tests 模块。
import datetime
from django.test import TestCase
from django.utils import timezone
from .models import Question
class QuestionModelTests(TestCase):
def test_was_published_recently_with_future_question(self):
"""
was_published_recently() returns False for questions whose pub_date
is in the future.
"""
time = timezone.now() + datetime.timedelta(days=30)
future_question = Question(pub_date=time)
self.assertIs(future_question.was_published_recently(), False)
运行测试
$ python manage.py test polls
-
python manage.py test polls
将会寻找polls
应用里的测试代码 - 它找到了
django.test.TestCase
的一个子类 - 它创建一个特殊的数据库供测试使用
- 它在类中寻找测试方法——以
test
开头的方法。 - 在
test_was_published_recently_with_future_question
方法中,它创建了一个pub_date
值为 30 天后的Question
实例。 - 接着使用
assertls()
方法,发现was_published_recently()
返回了True
,而我们期望它返回False
。 -
python manage.py test polls
将会寻找polls
应用里的测试代码 - 它找到了
django.test.TestCase
的一个子类 - 它创建一个特殊的数据库供测试使用
- 它在类中寻找测试方法——以
test
开头的方法。 - 在
test_was_published_recently_with_future_question
方法中,它创建了一个pub_date
值为 30 天后的Question
实例。 - 接着使用
assertls()
方法,发现was_published_recently()
返回了True
,而我们期望它返回False
。
针对视图的测试
测试工具: Client
静态文件管理
django.contrib.staticfiles
:用来将各个应用的静态文件统一收集起来并分发。
自定义应用的界面和风格
在应用目录下创建一个名为 static 的目录。Django 将在目录下查找静态文件。
Django 的 STATICFILES_FINDERS
设置包含了一系列的查找器,他们知道去哪里找到 static
文件。
AppDirectoriesFinder
是默认查找器中的一个,它会在每个 INSTALLED_APPS
中指定的应用的子文件中寻找名称为 static
的特定文件夹。
管理后台采用相同的目录结构管理它的静态文件。
所以你应该将所有的静态文件以 static/应用名/文件
的路径存储。因为 AppDirectoriesFinder
的存在,你可以在 Django 中简单的使用 应用名/文件
的形式引用此文件,类似于你引用模板路径的方式。
在相应的目录下创建文件后,可以在需要引入的模块中以如下方式引用该文件。
{% load static %}
<link rel="stylesheet" type="text/css" href="{% static 'polls/style.css' %}">
{% static %}
模板标签会生成静态文件的绝对路径。