您的位置:首页 > 编程语言 > Go语言

一个完整的Django入门指南 - 第3部分:高级概念

2018-03-10 23:04 579 查看
原文地址:https://simpleisbetterthancomplex.com/series/2017/09/18/a-complete-beginners-guide-to-django-part-3.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实例,并且我们将urlresponse移动到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 -->
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: