引言
这一节深入学习两个基本概念:URLs
和 Form
,建立 templates 并编写大量的单元测试。
首先,现修改下模型文件,增加一些内容:
boards/models.py
1
2
3
4
5
6
7
8
9
10
11
|
class Topic(models.Model):
# 省略部分...
# 为Topic增加最新更新时间字段,并设置 `auto_now=True` 表示每次save都会更新此字段为当前时间
# 原文这里使用的是 `auto_now_add=True` 表示仅在新添加时更新此字段为当前时间
last_updated = models.DateTimeField(auto_now=True)
class Post(models.Model):
# 省略部分...
# 为Post添加更新时的用户,可以为 null,当设置 related_name='+' 时,不支持反向查询
updated_by = models.ForeignKey(User, null=True, related_name='+')
|
当一个模型有两个相同模型的外键时,需要明确指定 related_name
,因为默认的 related_name 为 小写的模型名_set
,当有两个时就无法区分,必须明确指定 related_name
。例如:本项目Post 与 User 有两个外键分别为:created_by 和 updated_by, 默认的related_name都是 post_set
,此时需要明确为两个外键明确指定不同的 related_name
模型修改了需要重新生成数据迁移文件和数据库
1
2
|
$ python manage.py makemigrations
$ python manage.py migrate
|
URLs
首先,我们要实现一个列举所有Topic的页面,先在urls.py
里加入路由
myproject/urls.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
# myproject/urls.py
from django.contrib import admin
from django.urls import path
from boards import views
urlpatterns = [
# 教程中使用的Django版本是1.11,我整理时Django 2.2已经是长期支持版了,
# 老版本的路有使用 url 通过正则表达式进行匹配解析,
# 如:url(r'^$', views.home, name='home') ,
# 2.0以后使用 path 更加简洁,如果使用正则表达式实现更复杂的解析,
# 可以使用 re_path 效果和老版本的 url 一致。
path("", views.home, name="home"),
path("boards/<int:pk>/", views.board_topics, name="board_topics"),
path("admin/", admin.site.urls),
]
|
一个项目可以在各个 apps 中设置多个 urls.py ,但Django需要一个特殊的 urls.py 作为起点,默认在项目根目录下的 urls.py 就是这个特殊的URLconf, 可以在项目配置文件 settings.py 中配置
1
|
ROOT_URLCONF = 'myproject.urls' # 在创建项目时这个配置Django就帮你自动生成了,可以自定义修改
|
在URL 配置文件中,path函数使浏览器的 url 和 Django的 view视图对应,例如上面的path("", views.home, name="home"),
就是将网站的跟路径和views.home
视图对应起来,当访问 http://127.0.0.1:8000/ (当部署后可能是你配置的域名和端口) 时就由 views.home
视图解析,函数具体参数为:
1
2
3
4
5
6
7
8
9
|
# 实时上Django 2.2 中 path 和 re_path 都指向 _path 函数,是 _path 函数的一个引用,只是 Pattern 不同,一个路由匹配模式(RoutePattern),一个是正则匹配模式(RegexPattern)
def _path(route, view, kwargs=None, name=None, Pattern=None):
...
# partial 是python functools 中的高阶函数,作用是为一个函数指定默认的变量,再取一个别名,以下语句类似于:
# def path(route, view, kiew, kwargs=None, name=None, Pattern=RoutePattern):
# _path(route, view, kwargs, name, Pattern)
path = partial(_path, Pattern=RoutePattern)
re_path = partial(_path, Pattern=RegexPattern)
|
- route:一个 URL 规则匹配字符串,这里并不会区分 GET 或 POST方式,匹配规则将会根据 _path 函数的 Pattern 参数区分。
- view: 一个处理URL请求的视图函数,它也接受一个 django.conf.urls.include 函数,用于指定一个外部的 urls.py 配置文件来分级处理子路由,这样可以很好的将一类功能的路由放在一起,路由分级也较为合理。例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
# myproject/urls.py
from django.urls import path
urlpatterns = [
# 路由 http://127.0.0.1:8000/boards/ 下的所有请求都由另一个的 urls.py 来处理
path('boards/', include('boards.urls'))
]
# boards/urls.py
from django.urls import path
from boards import views
urlpatterns = [
# 对应路由 http://127.0.0.1:8000/boards/
path('', views.board_list(), name='board_list'),
# 对应路由 http://127.0.0.1:8000/boards/new/
path('new/', views.board_new(), name='board_new'),
]
|
- kwargs:通常给前面的 view 函数指定默认的参数,一般用于可重用视图, 我们不会经常用到。
- name: 给 URL 指定一个唯一标示名,它是较为重要的功能,可以在编程时通过这个名字转换为对应的URL,而不是通过硬编码在程序的其它地方写入URL
URLs 基本用法
1
2
3
4
5
6
7
|
from django.urls import path
from boards import views
urlpatterns = [
path('', views.home, name='home'),
path('about/', views.about, name='about'),
]
|
可以创建更深的URL结构:
1
2
3
4
5
6
7
8
9
10
11
12
|
from django.urls import path
from boards import views
urlpatterns = [
path('', views.home, name='home'),
path('about/', views.about, name='about'),
path('about/company/', views.about_company, name='about_company'),
path('about/author/', views.about_author, name='about_author'),
path('about/author/vitor/', views.about_vitor, name='about_vitor'),
path('about/author/erica/', views.about_erica, name='about_erica'),
path('privacy/', views.privacy_policy, name='privacy_policy'),
]
|
对应的 view 函数可能是这样的结构:
1
2
3
4
5
6
7
8
|
def about(request):
# do something...
return render(request, 'about.html')
def about_company(request):
# do something else...
# return some data along with the view...
return render(request, 'about_company.html', {'company_name': 'Simple Complex'})
|
URLs高级用法
可以通过前文提到的 RoutePattern 和 RegexPattern 灵活动态匹配 URLs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
from django.urls import path
from boards import views
urlpatterns = [
# 匹配 boards/12/12/1
# year、month、day 都是整数就可以
path('boards/<int:year>/<int:month>/<int:day>/', views.boards_archive, name='boards_archive1'),
# 匹配 boards/2012/12/13
# year、month、day 必须是包含 0-9 的数字字符串,
# year必须是4位,month和day必须是2位
# 会组成一个字典传入 view 函数,类似:
# {"year": 2012, "month": 12, "day": 13}
re_path(r'^boards/(?P<year>[0-9]{4})/(?P<month>[0-9]{2}/(?P<day>[0-9]{2}))/$', views.boards_archive, name='boards_archive2'),
# 如果正则中不加入 ?P<paramter> 则会将参数生成一个元组按照位置将参数传入
# 类似: (2012, 12, 13)
re_path(r'^boards/([0-9]{4})/([0-9]{2}/([0-9]{2}))/$', views.boards_archive, name='boards_archive2'),
]
|
以上两种方式 path 函数写路径更自然更简洁也更高效,re_path 函数方式匹配更精准,默认情况尽量使用 path 函数进行匹配,当 path 无法满足需求时,使用 re_path 函数匹配。
对应的 view 函数
1
2
3
4
5
6
7
|
# view 函数可以直接接受 url 匹配的变量
def board_topics(request, year, month, day):
# do something...
# 或
def board_topics(request, *args, **kwargs):
# do something...
|
深入理解,举例:
1
2
3
4
5
6
7
8
9
10
11
|
# urls.py
from django.contrib import admin
from django.urls import path, re_path
from boards import views
urlpatterns = [
re_path(r"^test/(?P<id>\d+)/$", views.test, name="test1"),
re_path(r"^test/(\w+)/(\w+)/$", views.test, name="test2"),
path("test/<str:name>/", views.test, name="test3"),
]
|
1
2
3
4
5
|
# views.py
from django.shortcuts import HttpResponse
def test(request, *args, **kwargs):
return HttpResponse(f"args:{args}<br>kwargs:{kwargs}")
|
url和view以及浏览器显示的内容:
url | view name | response |
/test/123 | test1 | args:() kwargs:{‘id’: ‘123’} |
/test/abc/12a | test2 | args:(‘abc’, ‘12a’) kwargs:{} |
/test/lee/ | test3 | args:() kwargs:{’name’: ’lee’} |