django 是一款基于 python 编写并且采用 mvc 设计模式的开源的 web 应用框架,早期是作为劳伦斯出版集团新闻网站的 cms 内容管理系统而开发,后于 2005 年 7 月在 bsd 许可协议下开源,并于 2017 年 12 月 2 日 发布 2.0 正式版。
本文基于《django 官方 tutorials》以及《django rest framework 官方 tutorials》编写,发稿时所使用的 django 版本为 2.1.4,python 版本为 3.6.6,文中涉及的代码都已经由笔者验证运行通过,最终形成了一个简单项目并推送至笔者github上的jungle项目当中,需要的朋友可以基于此来逐步步完善成为一个产品化的项目。
新建 django 项目
下面的命令行展示了在 windows 操作系统下,基于 venv 虚拟环境搭建一个 django 项目的步骤:
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
|
# 建立虚拟环境
c:\workspace\django
λ python - m venv venv
# 激活虚拟环境
c:\workspace\django
λ .\venv\scripts\activate.bat
(venv) λ
# 安装django
c:\workspace\django
(venv) λ pip install django
looking in indexes: https: / / mirrors.aliyun.com / pypi / simple /
collecting django
using cached https: / / mirrors.aliyun.com / pypi / packages / fd / 9a / 0c028ea0fe4f5803dda1a7afabeed958d0c8b79b0fe762ffbf728db3b90d / django - 2.1 . 4 - py3 - none - any .whl
collecting pytz ( from django)
using cached https: / / mirrors.aliyun.com / pypi / packages / f8 / 0e / 2365ddc010afb3d79147f1dd544e5ee24bf4ece58ab99b16fbb465ce6dc0 / pytz - 2018.7 - py2.py3 - none - any .whl
installing collected packages: pytz, django
successfully installed django - 2.1 . 4 pytz - 2018.7
# 进入虚拟环境目录,新建一个django项目
c:\workspace\django
(venv) λ django - admin startproject mysite
c:\workspace\django
(venv) λ ls
mysite / venv /
# 进入新建的django项目,建立一个应用
c:\workspace\django
(venv) λ cd mysite\
c:\workspace\django\mysite
(venv) λ python manage.py startapp demo
c:\workspace\django\mysite
(venv) λ ls
demo / manage.py * mysite /
# 同步数据库
c:\workspace\django\mysite
(venv) λ 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 admin. 0003_logentry_add_action_flag_choices ... 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
# 启动开发服务
(venv) λ python manage.py runserver 8080
performing system checks...
system check identified no issues ( 0 silenced).
january 03 , 2019 - 21 : 31 : 48
django version 2.1 . 4 , using settings 'mysite.settings'
starting development server at http: / / 127.0 . 0.1 : 8080 /
quit the server with ctrl - break .
# 返回uinika虚拟环境目录,并将当前虚拟环境的依赖导入至requirements.txt
c:\workspace\django\mysite
(venv) λ cd ..
c:\workspace\django
(venv) λ pip freeze > requirements.txt
c:\workspace\django
(venv) λ ls
mysite / requirements.txt venv /
|
通过 django-admin startproject
命令创建的外部 mysite/
目录是 web 项目的容器,而 manage.py
文件是用于与 django 项目交互的命令行工具,更多的使用方式可以参阅 django-admin 文档 。。
1
2
3
4
5
6
7
|
mysite /
manage.py
mysite /
__init__.py
settings.py
urls.py
wsgi.py
|
内部嵌套的 mysite/
目录是用于放置项目中具体的 python 包,它的名称是您需要用来导入其中任何内容的 python 包名称,例如 mysite.urls
。
-
mysite/__init__.py
: 空文件,用于提示系统将当前目录识别为一个 python 包。 -
mysite/settings.py
: django 项目的配置文件,更多配置请查阅 django settings 。 -
mysite/urls.py
: 当前 django 项目的 url 声明,更多内容请参阅 url dispatcher 。 -
mysite/wsgi.py
: 兼容 wsgi 规范的当前项目入口点,更多细节可以阅读 如果使用 wsgi 进行部署 。
建立 mysite
项目之后,上面的命令行还通过了 py manage.py startapp
建立了一个 demo/
应用目录,django 当中一个项目( mysite
)可以拥有多个应用( demo
), demo/
目录下的文件结构如下:
1
2
3
4
5
6
7
8
9
|
demo /
__init__.py
admin.py
apps.py
migrations /
__init__.py
models.py
tests.py
views.py
|
请求与响应
首先进入 python 虚拟环境并进入 mysite
目录后,执行如下命令:
1
2
|
c:\workspace\django\mysite (master - > origin)
(venv) λ python manage.py startapp polls
|
新建一个 polls
应用之后,打开该目录下的 polls/views.py
源码文件,输入以下代码:
1
2
3
4
|
from django.http import httpresponse
def index(request):
return httpresponse( "你好,这是一个投票应用!" )
|
接下来,我们需要将上面修改的视图文件 views.py
映射到一个 url,先在 polls/
目录下新建一个 urls.py
文件,然后键入下面这段代码:
1
2
3
4
5
6
7
|
from django.urls import path
from . import views
urlpatterns = [
path(' ', views.index, name=' index'),
]
|
最后,将上面定义的应用的 url 声明文件 polls/urls.py
包含至项目的 mysite/urls.py
当中,
1
2
3
4
5
6
7
|
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path( 'polls/' , include( 'polls.urls' )),
path( 'admin/' , admin.site.urls),
]
|
上面代码中出现的 include()
函数主要用于引入其它 url 配置文件,这样我们就可以通过 http://localhost:8080/polls/
路径访问到如下信息了:
模型和管理页面
mysite/settings.py
文件包含了项目的基本配置,该文件通过如下声明默认使用 django 内置的 sqlite 作为项目数据库。
1
2
3
4
5
6
|
databases = {
'default' : {
'engine' : 'django.db.backends.sqlite3' ,
'name' : os.path.join(base_dir, 'db.sqlite3' ),
}
}
|
如果使用其它数据库,则可以将配置书写为下面的格式:
1
2
3
4
5
6
7
8
9
10
|
databases = {
'default' : {
'engine' : 'django.db.backends.mysql' , # 数据库引擎名称
'name' : 'db' , # 数据库连接名称
'user' : 'uinika' , # 数据库连接用户名
'password' : 'test' , # 数据库连接密码
'host' : 'localhost' , # 数据库主机地址
'port' : '3306' , # 数据库端口
}
}
|
其中 engine
属性可以根据项目所使用数据库的不同而选择如下值:
- sqlite:django.db.backends.sqlite3
- mysql:django.db.backends.mysql
- postgresql:django.db.backends.postgresql
- oracle:django.db.backends.oracle
接下来继续修改 mysite/settings.py
,设置 time_zone
属性为项目使用国家的时区。
1
2
|
language_code = 'zh-hans'
time_zone = 'asia/chongqing'
|
mysite/settings.py
文件头部的 installed_apps
属性定义了当前项目使用的应用程序。
1
2
3
4
5
6
7
8
|
installed_apps = [
'django.contrib.admin' , # 管理员站点
'django.contrib.auth' , # 认证授权系统
'django.contrib.contenttypes' , # 内容类型框架
'django.contrib.sessions' , # 会话框架
'django.contrib.messages' , # 消息框架
'django.contrib.staticfiles' , # 静态文件管理
]
|
在前面命令行中执行的 python manage.py migrate
命令会检查 installed_apps
属性的设置,并为其中的每个应用创建所需的数据表,实际上 migrate
命令只会为对 installed_apps
里声明了的应用进行数据库迁移 。
了解项目配置文件的一些设置之后,现在来编辑 polls/models.py
文件新建 question(问题)
和 choice(选项)
两个数据模型:
1
2
3
4
5
6
7
8
9
10
|
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
类的实例。注意在 choice
使用了 foreignkey
属性定义了一个与 question
的外键关联关系,django 支持所有常用的多对一、多对多和一对一数据库关系。
数据库模型建立完成之后,由于 pollsconfig
类位于 polls/apps.py
文件当中,所以其对应的点式路径为 polls.apps.pollsconfig
,现在我们需要将该路径添加至 mysite/settings.py
文件的 installed_apps
属性:
1
2
3
4
5
6
7
8
9
|
installed_apps = [
'polls.apps.pollsconfig' , # 添加pollsconfig
'django.contrib.admin' ,
'django.contrib.auth' ,
'django.contrib.contenttypes' ,
'django.contrib.sessions' ,
'django.contrib.messages' ,
'django.contrib.staticfiles' ,
]
|
通过 manage.py
提供的 makemigrations
命令,将模型的修改迁移到 sqlite 数据库当中。
1
2
3
4
5
6
7
|
c:\workspace\django\mysite (master - > origin)
(venv) λ python manage.py makemigrations polls
migrations for 'polls' :
polls\migrations\ 0001_initial .py
- create model choice
- create model question
- add field question to choice
|
我们还可以通过 manage.py
提供的 sqlmigrate
命令,查看数据迁移过程中执行了哪些 sql 语句,该命令并不会实质性执行 django 模型到数据库的迁移任务。
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
|
c:\workspace\django\mysite (master - > origin)
(venv) λ python manage.py sqlmigrate polls 0001
begin;
- -
- - create model choice
- -
create table "polls_choice" ( "id" integer not null primary key autoincrement, "choice_text" varchar( 200 ) not null, "v
otes" integer not null);
- -
- - create model question
- -
create table "polls_question" ( "id" integer not null primary key autoincrement, "question_text" varchar( 200 ) not null
, "pub_date" datetime not null);
- -
- - add field question to choice
- -
alter table "polls_choice" rename to "polls_choice__old" ;
create table "polls_choice" ( "id" integer not null primary key autoincrement, "choice_text" varchar( 200 ) not null, "v
otes " integer not null, " question_id " integer not null references " polls_question " (" id ") deferrable initially deferr
ed);
insert into "polls_choice" ( "id" , "choice_text" , "votes" , "question_id" ) select "id" , "choice_text" , "votes" , null fr
om "polls_choice__old" ;
drop table "polls_choice__old" ;
create index "polls_choice_question_id_c5b4b260" on "polls_choice" ( "question_id" );
commit;
|
django 模型的数据库主键 id 会被自动创建, 并会在外键字段名称后追加 _id
字符串作为后缀。
接下来运行 manage.py
提供的 migrate
命令,在根据新定义的模型创建相应的数据库表。
1
2
3
4
5
6
|
c:\workspace\django\mysite (master - > origin)
(venv) λ python manage.py migrate
operations to perform:
apply all migrations: admin, auth, contenttypes, polls, sessions
running migrations:
applying polls. 0001_initial ... ok
|
为了便于在版本管理系统提交迁移数据,django 将模型的修改分别独立为 生成 和 应用 两个命令,因此修改 django 模型会涉及如下 3 个步骤:
- 编辑models.py文件修改模型。
- 运行python manage.py makemigrations为模型的改变生成迁移文件。
- 运行python manage.py migrate来应用数据库迁移。
完成上述 django 模型与数据库的同步之后,接下来可以通过 manage.py
提供的 shell
命令,在命令行工具内运行 django 提供的交互式 api。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
c:\workspace\django\mysite (master - > origin)
(venv) λ python manage.py shell
python 3.6 . 6 (v3. 6.6 : 4cf1f54eb7 , jun 27 2018 , 03 : 37 : 03 ) [msc v. 1900 64 bit (amd64)] on win32
type "help" , "copyright" , "credits" or "license" for more information.
(interactiveconsole)
>>> from polls.models import choice, question
>>> question.objects. all ()
<queryset []>
>>> from django.utils import timezone
>>> q = question(question_text = "what's new?" , pub_date = timezone.now())
>>> q.save()
>>> q. id
1
>>> q.question_text
"what's new?"
>>> q.pub_date
datetime.datetime( 2019 , 1 , 4 , 9 , 10 , 1 , 955820 , tzinfo = <utc>)
>>> q.question_text = "what's up?"
>>> q.save()
>>> question.objects. all ()
<queryset [<question: question object ( 1 )>]>
|
上面命令行执行结果中的 <question: question object (1)>
对于实际开发没有意义,因此可以考虑为上面建立的 django 模型增加 __str__()
方法直接打印模型对象的属性数据。为了便于进一步测试,这里还为 question
类添加一个自定义的 was_published_recently()
方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
import datetime
from django.db import models
from django.utils import timezone
class question(models.model):
question_text = models.charfield(max_length = 200 )
pub_date = models.datetimefield( 'date published' )
# 自定义was_published_recently()方法
def was_published_recently( self ):
return self .pub_date > = timezone.now() - datetime.timedelta(days = 1 )
# 添加__str__()方法
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 )
# 添加__str__()方法
def __str__( self ):
return self .choice_text
|
完成修改工作之后,再一次运行 python manage.py shell
命令:
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
c:\workspace\django\mysite (master - > origin)
(venv) λ python manage.py shell
python 3.6 . 6 (v3. 6.6 : 4cf1f54eb7 , jun 27 2018 , 03 : 37 : 03 ) [msc v. 1900 64 bit (amd64)] on win32
type "help" , "copyright" , "credits" or "license" for more information.
(interactiveconsole)
>>> from polls.models import choice, question
>>> question.objects. all ()
<queryset [<question: what's up?>]>
>>> 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?>
>>> question.objects.get( id = 2 )
traceback (most recent call last):
file "<console>" , line 1 , in <module>
file "c:\workspace\django\venv\lib\site-packages\django\db\models\manager.py" , line 82 , in manager_method
return getattr ( self .get_queryset(), name)( * args, * * kwargs)
file "c:\workspace\django\venv\lib\site-packages\django\db\models\query.py" , line 399 , in get
self .model._meta.object_name
polls.models.question.doesnotexist: question matching query does not exist.
>>> question.objects.get(pk = 1 )
<question: what's up?>
>>> q = question.objects.get(pk = 1 )
>>> q.was_published_recently()
true
>>> q = question.objects.get(pk = 1 )
>>> q.choice_set. all ()
<queryset []>
>>> 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 )
>>> c.question
<question: what's up?>
>>> q.choice_set. all ()
<queryset [<choice: not much>, <choice: the sky>, <choice: just hacking again>]>
>>> q.choice_set.count()
3
>>> choice.objects. filter (question__pub_date__year = current_year)
<queryset [<choice: not much>, <choice: the sky>, <choice: just hacking again>]>
>>> c = q.choice_set. filter (choice_text__startswith = 'just hacking' )
>>> c.delete()
( 1 , { 'polls.choice' : 1 })
|
管理站点
django 能够根据模型自动创建后台管理界面, 这里我们执行 manage.py
提供的 createsuperuser
命令创建一个管理用户:
1
2
3
4
5
6
7
|
c:\workspace\django\mysite (master - > origin)
(venv) λ python manage.py createsuperuser
username (leave blank to use 'zhenghang' ): hank
email address: uinika@outlook.com
password: * * * * * * * *
password (again): * * * * * * * *
superuser created successfully.
|
启动 django 服务之后,就可以通过 url 地址 http://localhost:8080/admin/login
并使用上面新建的用户名和密码进行登陆管理操作:
登陆后默认只能对权限相关的 user
和 group
进行管理,如果我们需要将 question
数据模型纳入管理,那么必须要在 polls/admin.py
文件对其进行注册。
1
2
3
4
|
from django.contrib import admin
from .models import question
admin.site.register(question)
|
完成注册之后,刷新管理站点页面即可查看到 question
管理选项:
视图与模板
django 使用 urlconfs
配置将 url 与视图关联,即将 url 映射至视图,下面我们将向 polls/views.py
文件添加一些能够接收参数的视图:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
from django.http import httpresponse
def index(request):
return httpresponse( "你好,这是一个投票应用!" )
def detail(request, question_id):
return httpresponse( "你正在查看问题 %s 。" % question_id)
def results(request, question_id):
response = "你看到的是问题 %s 的结果。"
return httpresponse(response % question_id)
def vote(request, question_id):
return httpresponse( "你正在对问题 %s 进行投票。" % question_id)
|
然后将这些新的视图添加至 polls.urls
模块:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
from django.urls import path
from . import views
urlpatterns = [
# 访问 http://localhost:8080/polls/
path(' ', views.index, name=' index'),
# 访问 http://localhost:8080/polls/5/
path( '<int:question_id>/' , views.detail, name = 'detail' ),
# 访问 http://localhost:8080/polls/5/results/
path( '<int:question_id>/results/' , views.results, name = 'results' ),
# 访问 http://localhost:8080/polls/5/vote/
path( '<int:question_id>/vote/' , views.vote, name = 'vote' ),
]
|
django 的每个视图只会完成两个任务:第 1 是返回一个包含被请求页面内容的 httpresponse
对象,或者抛出一个 http404
这样的异常。这里为了展示数据库里按照发布日期排序的最近五个投票问题,我们再向 polls/views.py
代码文件的 index()
函数添加如下内容:
1
2
3
4
5
6
|
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)
|
这样直接将数据库查询结果输出到页面的方式并不优雅,实际开发环境当中我们通常会使用模板页面来展示数据,首先在 polls
应用目录下创建一个用来存放模板文件的 templates
目录。由于站点配置文件 mysite/settings.py
里 templates
属性的默认设置,能够让 django 在每个 installed_apps
文件夹中自动寻找 templates
子目录,从而正确定位出模板的位置。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
templates = [
{
'backend' : 'django.template.backends.django.djangotemplates' ,
'dirs' : [],
'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
文件,此时通过 url 地址 http://localhost:8080/polls/
就可以访问到这个模板文件,模板文件会将按照发布日期排序了的 question
列表 latest_question_list
放置到 httpresponse
上下文,并在 polls/index.html
模板当中完成数据绑定。
1
2
3
4
5
6
7
8
9
10
11
|
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))
|
事实上,通过使用 render()
方法,django 能够以更加简化的方式完成载入模板、填充上下文、返回 httpresponse 对象这一系列步骤:
1
2
3
4
5
6
7
|
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)
|
接下来处理投票详情页面,这里会有一个新原则,即如果指定 id
所对应的 question
不存在,那么视图就会抛出一个 http404
异常。在 polls/views.py
添加如下代码,
1
2
3
4
5
6
7
8
9
10
|
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( "问题不存在!" )
return render(request, 'polls/detail.html' , { 'question' : qu
|
然后暂时向 polls/templates/polls/detail.html
添加一行简单的 代码便于测试上面的代码。
django 提供了诸如 get_object_or_404()
、 get_list_or_404()
这样的快捷函数语法糖来解决 http404
判断的问题,因而上一步的代码依然可以进一步简化为下面这样:
1
2
3
4
5
6
|
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})
|
让我们进一步完善polls/templates/polls/detail.html,填充完整的视图代码:
1
2
3
4
5
6
|
<h1>{{ question.question_text }}< / h1>
<ul>
{ % for choice in question.choice_set. all % }
<li>{{ choice.choice_text }}< / li>
{ % endfor % }
< / ul>
|
通过在模板代码中使用.符号来访问变量属性,例如对于上面代码中的, django 首先会尝试对question对象使用字典查找(既obj.get(str)),如果失败再尝试属性查找(既obj.str),如果依然失败就会尝试列表查找(即obj[int])。另外循环for中的question.choice_set.all语句会被解析为question.choice_set.all()的 python 的函数调用,完成后将返回一个可迭代的choice对象,该对象仅限于for循环标签内部使用。
在polls/templates/polls/index.html编写的投票链接里使用了诸如<a href="/polls//" rel="external nofollow" ></a>这样的硬编码,但是这样容易造成视图与后端业务的耦合,因此 django 提供了url标签来解决这个问题。
1
|
<a href = "{% url 'detail' question.id %}" rel = "external nofollow" >{{ question.question_text }}< / a>
|
实际上在mysite/polls/urls.py内的函数调用path('<int:question_id>/', views.detail, name='detail')当中,path()的name属性就是作用于url标签中的这个特性的。
为了避免项目当中各种应用的 url 重名,避免url标签被使用时产生歧义,需要在polls/urls.py上添加应用的命名空间作为区分。
1
2
3
4
5
6
7
8
9
10
|
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/templates/polls/index.html文件,为每个url标签添加上面声明的polls:命名空间。
1
|
<li><a href = "{% url 'polls:detail' question.id %}" rel = "external nofollow" >{{ question.question_text }}< / a>< / li>
|
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。
原文链接:https://uinika.github.io/web/server/django.html