一个完整的Django入门指南 - 第3部分:高级概念
2018-03-10 23:04
579 查看
原文地址:https://simpleisbetterthancomplex.com/series/2017/09/18/a-complete-beginners-guide-to-django-part-3.html
如果您从第一部分开始就遵循本系列教程,编写您的项目并逐步按照教程进行学习,那么开始前你可能需要更新您的models.py:
boards/models.py
现在在virtualenv激活状态下运行命令:
如果你已经在
我们将从编辑myproject文件夹中的urls.py开始:
myproject/urls.py
这次我们花点时间分析一下
URL分派器和URLconf(URL配置)是Django应用程序的基本部分。一开始,它看起来很混乱;我记得我第一次与Django一起开发时遇到了困难。
实际上,现在Django开发人员正在开发一种简化路由语法的建议。但现在,就像版本1.11一样,这就是我们所拥有的。让我们试着去理解它是如何工作的。
一个项目可以有许多url.py分布在应用程序中。但是Django需要一个url.py作为起始点。这种特殊的url.py被称为root URLconf。它定义在settings.py文件中。
myproject/settings.py
它已经定义好了,所以你不需要做任何改变。
当Django收到一个请求时,它开始在项目的URLconf中搜索匹配项。它从
如果Django找到一个匹配的,它将把请求传递给视图函数,后者是
这是对
regex:在字符串中匹配URL模式的正则表达式。注意,这些正则表达式不搜索GET或POST参数。在请求http://127.0.0.1:8000/boards/?page=2时,只有/board/会被处理。
view:用于处理匹配URL的用户请求的视图函数。它还接受django.conf.urls.include的返回函数,它用于引用外部url.py文件。例如,您可以使用它来定义一组特定于应用程序的url,并使用前缀将其包含在根url中。稍后我们将进一步探讨这一概念。
kwargs:任意关键字参数传递到目标视图。它通常用于对可重用视图进行一些简单的定制。我们不经常使用它。
name:给定URL的唯一标识符。这是一个非常重要的特性。永远记得命名你的url。通过修改正则表达式,您可以在整个项目中更改一个特定的URL。因此,在视图或模板中永远不要硬编码url是很重要的,并且总是用它的名称来引用url。
在实践中,你不需要成为正则表达式的专家
你只需要学习如何匹配简单的模式
稍后我将与你分享有用的URL模式列表
基础URLs
基础URLs可以非常简单的创建。它只是一个匹配字符串的问题。比如说我们想创建一个“about”页面,它可以像这样定义:
我们还可以创建更深层的URL结构:
这是一些简单的URL路由的例子。对于上面的所有示例,视图函数将遵循这个结构:
高级URLs
通过利用正则表达式来匹配特定类型的数据并创建动态URL,可以实现更高级的URL路由。
例如,要创建一个个人信息页面,就像许多服务器如 github.com/vitorfs 或者 twitter.com/vitorfs,其中“vitorfs”是我的用户名,我们可以这样做:
这将匹配Django用户模型的所有有效用户名。
现在注意上面的示例是一个非常宽松的URL。这意味着它将匹配许多URL模式,因为它是在URL的根中定义的,没有如
如果“about”页面是在用户名URL模式之后定义的,Django将永远不会找到它,因为“about”这个词将与用户名regex匹配,而视图
这有一些副作用。例如,从现在开始,我们将不得不把“about”当作禁止用户名,因为如果用户选择“about”作为他们的用户名,这个人将永远不会看到他们的个人资料页面。
urlpatters中的url顺序很重要
宽松的url正则表达式应该总是在后面
所以,要谨记!并且总是测试你的路线
这种URL路由的思路是创建动态页面,其中URL的一部分将用作特定资源的标识符,用于构成页面。这个标识符可以是一个整数ID或一个字符串。
最初,我们将使用版块ID来为主题创建一个动态页面。让我们再看一下我在url部分开始时给出的例子:
正则表达式
下面是我们如何为它写一个视图函数:
因为我们使用的
如果我们想用任意名称,我们可以这样做:
视图函数可以定义成这样:
或者
名字不重要。但是使用名称参数是一种很好的做法,因为当我们开始编写大量的url来捕获多个id和变量时,就很容易阅读了。
使用URLs API
是时候写一些代码了。让我们实现在url部分开始时提到的主题清单页面。
首先,编辑url.py添加我们的新URL路径:
myproject/urls.py
现在让我们来创建视图函数
boards/views.py
在templates文件夹中,创建一个名为topics.html的模板:
templates/topics.html
注意:目前,我们只是在创建新的HTML模板。不用担心,在下面的部分中,我将向您展示如何创建可重用的模板。
在浏览器中输入http://127.0.0.1:8000/boards/1/。结果应该是像下图一样:
该写点测试了!编辑tests.py文件,在文件底部添加以下测试代码:
boards/tests.py
有些事情需要注意一下。这次我们使用的是
在
现在是时候运行测试了:
输出结果:
测试test_board_topics_view_not_found_status_code失败了。我们可以在Traceback中看到它返回一个异常“boards.models.DoesNotExist: Board matching query does not exist.”
在
我们想要展示一个404 Page Not Found。那么让我们来重构我们的视图:
boards/views.py
让我们再次测试:
现在它像预期的那样工作。
这是Django在
这是一个很常见的用例。事实上,Django有一个尝试获取对象的快捷方式,或者返回一个对象不存在的404。
所以,让我们再次对board_topics视图进行重构:
修改好代码后测试:
没有任何问题,我们可以继续开发。
下一步是在屏幕中创建导航链接。首页应该有一个链接,让访问者进入一个给定的版块的主题页面。类似地,主题页面应该有一个链接返回到主页。
我们可以从编写一些
boards/tests.py
注意,现在我们为HomeTests添加了一个setUp方法。这是因为现在我们需要一个Board实例,并且我们将url和response移动到setUp中,这样我们就可以在新的测试中重用相同的响应。
新的测试是test_home_view_contains_link_to_topics_page。这里,我们使用assertContains方法来测试响应主体是否包含给定的文本。我们在测试中使用的文本是一个
让我们运行测试:
现在我们可以编写能够让测试通过的代码了。
编辑home.html模板:
templates/home.html
简介
在本教程中,我们将深入探讨两个基本概念:url和表单。在这个过程中,我们将探索许多其他的概念,比如创建可重用模板和安装第三方库。我们还将编写大量单元测试。如果您从第一部分开始就遵循本系列教程,编写您的项目并逐步按照教程进行学习,那么开始前你可能需要更新您的models.py:
boards/models.py
class Topic(models.Model): # other fields... # Add `auto_now_add=True` to the `last_updated` field last_updated = models.DateTimeField(auto_now_add=True) class Post(models.Model): # other fields... # Add `null=True` to the `updated_by` field updated_by = models.ForeignKey(User, null=True, related_name='+')
现在在virtualenv激活状态下运行命令:
python manage.py makemigrations python manage.py migrate
如果你已经在
updated_by中添加了
null=True,并且在
last_updated中添加了
auto_now_add=True,您可以放心地忽略上面的说明。
URLs
在我们的应用程序的开发过程中,现在我们必须实现一个新的页面来列出所有属于给定的版块的主题。简单回顾下,你可以看到我们在上一篇教程中所画的线框:我们将从编辑myproject文件夹中的urls.py开始:
myproject/urls.py
from django.conf.urls import url from django.contrib import admin from boards import views urlpatterns = [ url(r'^$', views.home, name='home'), url(r'^boards/(?<pk>\d+)/$', views.board_topics, name='board_topics'), url(r'^admin/', admin.site.urls), ]
这次我们花点时间分析一下
urlpatterns和
url。
URL分派器和URLconf(URL配置)是Django应用程序的基本部分。一开始,它看起来很混乱;我记得我第一次与Django一起开发时遇到了困难。
实际上,现在Django开发人员正在开发一种简化路由语法的建议。但现在,就像版本1.11一样,这就是我们所拥有的。让我们试着去理解它是如何工作的。
一个项目可以有许多url.py分布在应用程序中。但是Django需要一个url.py作为起始点。这种特殊的url.py被称为root URLconf。它定义在settings.py文件中。
myproject/settings.py
ROOT_URLCONF = 'myproject.urls'
它已经定义好了,所以你不需要做任何改变。
当Django收到一个请求时,它开始在项目的URLconf中搜索匹配项。它从
urlpatterns变量的第一个条目开始,并在每个
url条目上测试所请求的URL。
如果Django找到一个匹配的,它将把请求传递给视图函数,后者是
url的第二个参数。
urlpatterns的顺序很重要,因为Django会在找到匹配的时候停止搜索。如果Django在URLconf中没有找到匹配的,它将抛出404异常,这是页面未找到的错误代码。
这是对
url函数的剖析:
def url(regex, view, kwargs=None, name=None): # ...
regex:在字符串中匹配URL模式的正则表达式。注意,这些正则表达式不搜索GET或POST参数。在请求http://127.0.0.1:8000/boards/?page=2时,只有/board/会被处理。
view:用于处理匹配URL的用户请求的视图函数。它还接受django.conf.urls.include的返回函数,它用于引用外部url.py文件。例如,您可以使用它来定义一组特定于应用程序的url,并使用前缀将其包含在根url中。稍后我们将进一步探讨这一概念。
kwargs:任意关键字参数传递到目标视图。它通常用于对可重用视图进行一些简单的定制。我们不经常使用它。
name:给定URL的唯一标识符。这是一个非常重要的特性。永远记得命名你的url。通过修改正则表达式,您可以在整个项目中更改一个特定的URL。因此,在视图或模板中永远不要硬编码url是很重要的,并且总是用它的名称来引用url。
在实践中,你不需要成为正则表达式的专家
你只需要学习如何匹配简单的模式
稍后我将与你分享有用的URL模式列表
基础URLs
基础URLs可以非常简单的创建。它只是一个匹配字符串的问题。比如说我们想创建一个“about”页面,它可以像这样定义:
from django.conf.urls import url from boards import views urlpatterns = [ url(r'^$', views.home, name='home'), url(r'^about/$', views.about, name='about'), ]
我们还可以创建更深层的URL结构:
from django.conf.urls import url from boards import views urlpatterns = [ url(r'^$', views.home, name='home'), url(r'^about/$', views.about, name='about'), url(r'^about/company/$', views.about_company, name='about_company'), url(r'^about/author/$', views.about_author, name='about_author'), url(r'^about/author/vitor/$', views.about_vitor, name='about_vitor'), url(r'^about/author/erica/$', views.about_erica, name='about_erica'), url(r'^privacy/$', views.privacy_policy, name='privacy_policy'), ]
这是一些简单的URL路由的例子。对于上面的所有示例,视图函数将遵循这个结构:
def about(request): # do something... return render(request, 'about.html') def about_company(request): # do something... # return some data along with the view return render(request, 'about_company.html', {'company_name': 'Simple Complex'})
高级URLs
通过利用正则表达式来匹配特定类型的数据并创建动态URL,可以实现更高级的URL路由。
例如,要创建一个个人信息页面,就像许多服务器如 github.com/vitorfs 或者 twitter.com/vitorfs,其中“vitorfs”是我的用户名,我们可以这样做:
from django.conf.urls import url from boards import views urlpatterns = [ url(r'^$', views.home, name='home'), url(r'^(?P<username>[\w.@+-]+)/$', views.user_profile, name='user_profile'), ]
这将匹配Django用户模型的所有有效用户名。
现在注意上面的示例是一个非常宽松的URL。这意味着它将匹配许多URL模式,因为它是在URL的根中定义的,没有如
/profile/<username>/的前缀。在本例中,如果我们想定义一个名为/about/的URL,我们将在用户名URL模式之前定义它:
from django.conf.urls import url from boards import views urlpatterns = [ url(r'^$', views.home, name='home'), url(r'^about/$', views.about, name='about'), url(r'^(?P<username>[\w.@+-]+)/$', views.user_profile, name='user_profile'), ]
如果“about”页面是在用户名URL模式之后定义的,Django将永远不会找到它,因为“about”这个词将与用户名regex匹配,而视图
user_profile将被处理而不是
about视图函数。
这有一些副作用。例如,从现在开始,我们将不得不把“about”当作禁止用户名,因为如果用户选择“about”作为他们的用户名,这个人将永远不会看到他们的个人资料页面。
urlpatters中的url顺序很重要
宽松的url正则表达式应该总是在后面
所以,要谨记!并且总是测试你的路线
这种URL路由的思路是创建动态页面,其中URL的一部分将用作特定资源的标识符,用于构成页面。这个标识符可以是一个整数ID或一个字符串。
最初,我们将使用版块ID来为主题创建一个动态页面。让我们再看一下我在url部分开始时给出的例子:
url(r'^boards/(?P<pk>\d+)/$', views.board_topics, name='board_topics')
正则表达式
\d会匹配任意大小的整数。该整数将用于从数据库中检索板块。现在,我们注意到,我们将正则表达式编写为
(?P<pk>\d+),这是告诉Django将值捕获到一个名为pk的关键字参数。
下面是我们如何为它写一个视图函数:
def board_topics(request, pk): # do something...
因为我们使用的
(?P<pk>\d+)正则表达式,所以
board_topics中的关键字参数必须命名为pk。
如果我们想用任意名称,我们可以这样做:
url(r'^boards/(\d+)/$', views.board_topics, name='board_topics')
视图函数可以定义成这样:
def board_topics(request, board_id): # do something...
或者
def board_topics(request, id): # do something...
名字不重要。但是使用名称参数是一种很好的做法,因为当我们开始编写大量的url来捕获多个id和变量时,就很容易阅读了。
使用URLs API
是时候写一些代码了。让我们实现在url部分开始时提到的主题清单页面。
首先,编辑url.py添加我们的新URL路径:
myproject/urls.py
from django.conf.urls import url from django.contrib import admin from boards import views urlpatterns = [ url(r'^$', views.home, name='home'), url(r'^boards/(?P<pk>\d+)/$', views.board_topics, name='board_topics'), url(r'^admin/', admin.site.urls), ]
现在让我们来创建视图函数
board_topics:
boards/views.py
from django.shortcuts import render from .models import Board def home(request): # code suppressed for brevity def board_topics(request, pk): board = Board.objects.get(pk=pk) # 我不懂为什么pk也有用……囧 return render(request, 'topics.html', {'board': board})
在templates文件夹中,创建一个名为topics.html的模板:
templates/topics.html
{% load static %}<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>{{ board.name }}</title> <link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}"> </head> <body> <div class="container"> <ol class="breadcrumb my-4"> <li class="breadcrumb-item">Boards</li> <li class="breadcrumb=item active">{{ board.name }}</li> </ol> </div> </body> </html>
注意:目前,我们只是在创建新的HTML模板。不用担心,在下面的部分中,我将向您展示如何创建可重用的模板。
在浏览器中输入http://127.0.0.1:8000/boards/1/。结果应该是像下图一样:
该写点测试了!编辑tests.py文件,在文件底部添加以下测试代码:
boards/tests.py
# Django2.x from django.urls import resolve, reverse from django.test import TestCase from .views import home, board_topics from .models import Board class HomeTest(TestCase): ... class BoardTopicTests(TestCase): def setUp(self): Board.objects.create(name='Django', description='Django board.') def test_board_topics_view_success_status_code(self): url = reverse('board_topics', kwargs={'pk': 1}) response = self.client.get(url) self.assertEquals(response.status_code, 200) def test_board_topics_view_not_found_status_code(self): url = reverse('board_topics', kwargs={'pk': 99}) response = self.client.get(url) self.assertEquals(response.status_code, 404) def test_board_topics_url_resolves_board_topics_view(self): view = resolve('/boards/1/') self.assertEquals(view.func, board_topics)
有些事情需要注意一下。这次我们使用的是
setUp方法。在这个方法中,我们创建了一个Board实例用来测试,我们必须这样做,因为Django测试套件不会针对当前数据库运行你的测试。要运行测试,Django会动态创建一个新的数据库,应用所有的模型迁移,运行测试,并且当完成时,会销毁测试数据库。
在
setUp方法中,我们准备环境来运行测试,以便模拟一个场景。
test_board_topics_view_success_status_code方法:测试Django是否正在为现有的Board返回状态码200(成功)。
test_board_topics_view_not_found_status_code方法:测试Django是否正在为一个数据库中不存在的Board返回一个状态码404(未找到的页面)。
test_board_topics_url_resolves_board_topics_view方法:测试Django是否使用了正确的视图函数来呈现主题。
现在是时候运行测试了:
python manage.py test
输出结果:
Creating test database for alias 'default'... System check identified no issues (0 silenced). .E... ====================================================================== ERROR: test_board_topics_view_not_found_status_code (boards.tests.BoardTopicsTests) ---------------------------------------------------------------------- Traceback (most recent call last): # ... boards.models.DoesNotExist: Board matching query does not exist. ---------------------------------------------------------------------- Ran 5 tests in 0.093s FAILED (errors=1) Destroying test database for alias 'default'...
测试test_board_topics_view_not_found_status_code失败了。我们可以在Traceback中看到它返回一个异常“boards.models.DoesNotExist: Board matching query does not exist.”
在
DEBUG=False的生产环境中,访客将看到一个500 Internal Server Error的页面。但这不是我们想要的。
我们想要展示一个404 Page Not Found。那么让我们来重构我们的视图:
boards/views.py
from django.shortcuts import render from django.http import Http404 from .models import Board def home(request): # code suppressed for brevity def board_topics(request, pk): try: board = Board.objects.get(pk=pk) except Board.DoesNotExist: raise Http404 return render(request, 'topics.html', {'board': board})
让我们再次测试:
python manage.py test
Creating test database for alias 'default'... System check identified no issues (0 silenced). ..... ---------------------------------------------------------------------- Ran 5 tests in 0.042s OK Destroying test database for alias 'default'...
现在它像预期的那样工作。
这是Django在
DEBUG=False状态下显示的默认页面。稍后,我们可以定制404页面来显示其他内容。
这是一个很常见的用例。事实上,Django有一个尝试获取对象的快捷方式,或者返回一个对象不存在的404。
所以,让我们再次对board_topics视图进行重构:
from django.shortcuts import render, get_object_or_404 from .models import Board def home(request): # code suppressed for brevity def board_topics(request, pk): board = get_object_or_404(Board, pk=pk) return render(request, 'topics.html', {'board': board})
修改好代码后测试:
python manage.py test
Creating test database for alias 'default'... System check identified no issues (0 silenced). ..... ---------------------------------------------------------------------- Ran 5 tests in 0.052s OK Destroying test database for alias 'default'...
没有任何问题,我们可以继续开发。
下一步是在屏幕中创建导航链接。首页应该有一个链接,让访问者进入一个给定的版块的主题页面。类似地,主题页面应该有一个链接返回到主页。
我们可以从编写一些
HomeTests类的测试开始:
boards/tests.py
class HomeTests(TestCase): def setUp(self): self.board = Board.objects.create(name='Django', description='Django board.') url = reverse('home') self.response = self.client.get(url) def test_home_view_status_code(self): self.assertEquals(self.response.status_code, 200) def test_home_url_resolves_home_view(self): view = resolve('/') self.assertEquals(view.func, home) def test_home_view_contains_link_to_topics_page(self): board_topics_url = reverse('board_topics', kwargs={'pk': self.board.pk}) self.assertContains(self.response, 'href="{0}"'.format(board_topics_url))
注意,现在我们为HomeTests添加了一个setUp方法。这是因为现在我们需要一个Board实例,并且我们将url和response移动到setUp中,这样我们就可以在新的测试中重用相同的响应。
新的测试是test_home_view_contains_link_to_topics_page。这里,我们使用assertContains方法来测试响应主体是否包含给定的文本。我们在测试中使用的文本是一个
a标签的
href部分。所以基本上我们在测试响应主体是否有文本
href="/boards/1/"。
让我们运行测试:
python manage.py test
Creating test database for alias 'default'... System check identified no issues (0 silenced). ....F. ====================================================================== FAIL: test_home_view_contains_link_to_topics_page (boards.tests.HomeTests) ---------------------------------------------------------------------- # ... AssertionError: False is not true : Couldn't find 'href="/boards/1/"' in response ---------------------------------------------------------------------- Ran 6 tests in 0.034s FAILED (failures=1) Destroying test database for alias 'default'...
现在我们可以编写能够让测试通过的代码了。
编辑home.html模板:
templates/home.html
<!-- code suppressed for brevity --> <tbody> {% for board in boards %} <tr> <td> <a href="{% url 'board_topics' board.pk %}">{{ board.name }}</a> <small class="text-muted d-block">{{ board.description }}</small> </td> <td class="align-middle">0</td> <td class="align-middle">0</td> <td></td> </tr> {% endfor %} </tbody> <!-- code suppressed for brevity -->
相关文章推荐
- 一个完整的Django入门指南 - 第1部分
- [置顶] 【第三部分-django论坛从搭建到部署】一个完整的Django入门指南学习笔记
- [置顶] 【第一部分-django论坛从搭建到部署】一个完整的Django入门指南学习笔记
- 一个完整的Django入门指南(三)
- Django入门指南-第2部分(系统设计)
- 用Python和OpenCV创建一个图片搜索引擎的完整指南 The complete guide to building an image search engine with Python and
- Django Channels 入门指南
- Django构建一个Blog入门例子
- 一个完整的Installshield安装程序实例—艾泽拉斯之海洋女神出品(三) --高级设置一
- 一个基于Stage3D API的2D框架:Starling Framework(附入门指南一本)
- 一个完整的安装程序实例—艾泽拉斯之海洋女神出品(五) --补遗 (已补充第三部分完整版)
- 用Python和OpenCV创建一个图片搜索引擎的完整指南
- 高级文件系统实现者指南,第 13 部分
- 一个完整的Installshield安装程序实例—艾泽拉斯之海洋女神出品(四) --高级设置二
- 高级 Linux 命令精通指南,第 3 部分:资源管理
- 关于CSS布局的核心概念的快速入门指南
- Asp.Net MVC4入门指南(3):添加一个视图
- ReactiveCocoa-Swift部分入门指南-Signal
- 高级I/O复用技术:Epoll的使用及一个完整的C实例