Django完整入门指南-第3部分-1
- 2017年9月18日
- 71分钟阅读
- 166 comments
- 意见
- 7第3部分
苹果电脑 视窗 的Linux
介绍
在本教程中,我们将深入探讨两个基本概念:URL和表单。 在此过程中,我们将探索许多其他概念,例如创建可重复使用的模板和安装第三方库。 我们还将编写大量的单元测试。
如果您从第一部分开始就遵循本教程系列,对项目进行编码并逐步按照本教程进行操作,则可能需要在开始之前更新models.py :
board/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
,则可以放心地忽略上面的说明。
如果您愿意将我的源代码用作起点,可以在GitHub上获取它。
项目的当前状态可以在发行标签v0.2-lw下找到。 下面的链接将带您到正确的地方:
https://github.com/sibtc/django-beginners-guide/tree/v0.2-lw
开发将在这里进行。
URLs
继续开发我们的应用程序,现在我们必须实现一个新页面,以列出属于给定Board的所有主题。 回顾一下,您可以在下面看到我们在上一教程中绘制的线框:
图1:Boards项目线框,列出了Django Board中的所有主题。
我们将从编辑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/(?P<pk>\d+)/$', views.board_topics, name='board_topics'),
url(r'^admin/', admin.site.urls),
]
这次让我们花点时间分析urlpatterns
和url
。
URL调度程序和URLconf (URL配置)是Django应用程序的基本部分。 一开始,它看起来很混乱;我记得刚开始使用Django时遇到困难。
实际上,目前Django开发人员正在研究简化路由语法的提案 。 但是目前,按照1.11版本,这就是我们所拥有的。 因此,让我们尝试了解其工作原理。
一个项目可以在应用程序之间分布许多urls.py。 但是Django需要使用url.py作为起点。 这个特殊的urls.py称为root URLconf 。 它在settings.py文件中定义。
myproject / settings.py
ROOT_URLCONF = 'myproject.urls'
它已经配置好了,因此您无需在此处进行任何更改。
Django收到请求后,便开始在项目的URLconf中搜索匹配项。 它从urlpatterns
变量的第一个条目开始,并针对每个url
条目测试请求的URL。
如果Django找到匹配项,它将把请求传递给view函数 ,该函数是url
的第二个参数。urlpatterns
的顺序urlpatterns
重要,因为Django一旦找到匹配项就会停止搜索。 现在,如果Django在URLconf中找不到匹配项,它将引发404异常,这是Page Not Found的错误代码。
这是url
函数的剖析:
def url ( regex , view , kwargs = None , name = None ): # ...
- regex :用于匹配字符串中的URL模式的正则表达式。 请注意,这些正则表达式不会搜索GET或POST参数。 在对http://127.0.0.1:8000/boards/?page=2的请求中,仅/ boards /将被处理。
- view :一种视图功能,用于处理用户对匹配URL的请求。 它还接受django.conf.urls.include函数的返回,该函数用于引用外部urls.py文件。 例如,您可以使用它来定义一组特定于应用程序的URL,并使用前缀将其包含在根URLconf中。 稍后我们将进一步探讨这个概念。
- kwargs :传递给目标视图的任意关键字参数。 通常用于对可重用视图进行一些简单的自定义。我们不经常使用它。
- name :给定URL的唯一标识符。 这是一个非常重要的功能。 永远记住为您的URL命名。 这样,您只需更改正则表达式即可更改整个项目中的特定URL。 因此,切勿在视图或模板中对URL进行硬编码,并始终使用其名称来引用URL,这一点很重要。
基本URLs
基本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结构:
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路由示例。 对于上述所有示例,view函数将遵循以下结构:
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'),
]
进阶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”作为其用户名,则此人将永远不会看到其个人资料页面。
旁注:如果要为用户配置文件设计很酷的URL,避免URL冲突的最简单解决方案是添加一个前缀,例如/ u / vitorfs / ,或者像Medium一样添加/ @ vitorfs / ,其中“ @”是前缀。
如果根本不需要前缀,请考虑使用这样的禁止名称列表: github.com/shouldbee/reserved-usernames 。 另一个例子是我在学习Django时开发的应用程序; 我当时创建了清单: github.com/vitorfs/parsifal/ 。
这些冲突很常见。 以GitHub为例; 他们有此URL可以列出您当前正在查看的所有存储库: github.com/watching 。 某人在GitHub上注册了一个名为“ watching”的用户名,因此该人看不到他的个人资料页面。 我们可以通过尝试以下URL来查看具有该用户名的用户: github.com/watching/repositories ,它应该列出用户的存储库,例如我的github.com/vitorfs/repositories 。
这种URL路由的整个想法是创建动态页面,其中部分URL将用作特定资源的标识符,该标识符将用于构成页面。 例如,该标识符可以是整数ID或字符串。
最初,我们将使用Board ID来为Topics创建一个动态页面。 让我们再次阅读我在URL部分开头给出的示例:
url ( r'^boards/(?P<pk> \ d+)/$' , views . board_topics , name = 'board_topics' )
正则表达式\d+
将匹配任意大小的整数。 该整数将用于从数据库中检索Board 。 现在观察到我们将正则表达式写为(?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和变量时,它将更易于阅读。
旁注: PK还是ID?
PK代表主键 。 这是访问模型主键的快捷方式。 所有Django模型都具有此属性。
在大多数情况下,使用pk
属性与id
相同。 这是因为,如果我们不为模型定义主键,则Django将自动创建一个名为id
的AutoField
,它将作为其主键。
例如,如果您为模型定义了其他主键,那么假设字段email
是您的主键。 要访问它,您可以使用obj.email
或obj.pk
使用URL API
现在该写一些代码了。 让我们实现我在URL部分开头提到的主题列表页面(参见图1 )。
首先,编辑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/(?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)
return render(request, 'topics.html', {'board': board})
在模板文件夹中,创建一个名为topic.html的新模板:
template / topics.html
{% load static %}<!DOCTYPE html>
<html>
<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模板。 不用担心,在下一节中,我将向您展示如何创建可重用的模板。
现在,在网络浏览器中检查URL http://127.0.0.1:8000/boards/1/ 。 结果应为以下页面:
是时候编写一些测试了! 编辑tests.py文件,并在文件底部添加以下测试:
boards / tests.py
from django.core.urlresolvers import reverse
from django.urls import resolve
from django.test import TestCase
from .views import home, board_topics
from .models import Board
class HomeTests(TestCase):
# ...
class BoardTopicsTests(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:董事会匹配查询不存在。”
在DEBUG=False
生产中,访问者将看到500 Internal Server Error页面。 但这不是我们想要的行为。
我们要显示404页面未找到 。 因此,让我们重构一下观点:
板/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 ))
注意,现在我们还为HomeTest添加了setUp方法。 这是因为现在我们将需要一个Board实例,并且还将url和响应移动到setUp ,因此我们可以在新测试中重用相同的响应。
此处的新测试是test_home_view_contains_link_to_topics_page 。 在这里,我们使用assertContains方法测试响应主体是否包含给定的文本。 我们在测试中使用的文本是标签的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 -->
所以基本上我们改变了这一行:
{{ board.name }}
至:
<a href= " {% url 'board_topics' board.pk %} " > {{ board.name }} </a>
一律使用{ % url % }
{ % url % }
{ % url % }
模板标记来组成应用程序URL。 第一个参数是URL的名称 (在URLconf中定义,即urls.py ),然后可以根据需要传递任意数量的参数。
如果它是一个简单的URL(例如首页),则将只是{ % url 'home' % }
{ % url 'home' % }
{ % url 'home' % }
。
保存文件并再次运行测试:
python manage.py test
Creating test database for alias 'default'... System check identified no issues (0 silenced). ...... ---------------------------------------------------------------------- Ran 6 tests in 0.037s OK Destroying test database for alias 'default'...
好! 现在我们可以检查它在网络浏览器中的外观:
现在链接回来了。 我们可以先编写测试:
boards / tests.py
class BoardTopicsTests ( TestCase ): # code suppressed for brevity... def test_board_topics_view_contains_link_back_to_homepage ( self ): board_topics_url = reverse ( 'board_topics' , kwargs = { 'pk' : 1 }) response = self . client . get ( board_topics_url ) homepage_url = reverse ( 'home' ) self . assertContains ( response , 'href="{0}"' . format ( homepage_url ))
运行测试:
python manage.py test
Creating test database for alias 'default'... System check identified no issues (0 silenced). .F..... ====================================================================== FAIL: test_board_topics_view_contains_link_back_to_homepage (boards.tests.BoardTopicsTests) ---------------------------------------------------------------------- Traceback (most recent call last): # ... AssertionError: False is not true : Couldn't find 'href="/"' in response ---------------------------------------------------------------------- Ran 7 tests in 0.054s FAILED (failures=1) Destroying test database for alias 'default'...
更新董事会主题模板:
template / topics.html
{% load static %}<!DOCTYPE html> <html> <head> <!-- code suppressed for brevity --> </head> <body> <div class= "container" > <ol class= "breadcrumb my-4" > <li class= "breadcrumb-item" ><a href= " {% url 'home' %} " > Boards </a></li> <li class= "breadcrumb-item active" > {{ board.name }} </li> </ol> </div> </body> </html>
运行测试:
python manage.py test
Creating test database for alias 'default'... System check identified no issues (0 silenced). ....... ---------------------------------------------------------------------- Ran 7 tests in 0.061s OK Destroying test database for alias 'default'...
如前所述,URL路由是Web应用程序的基本部分。 有了这些知识,我们应该能够进行开发。 接下来,要完成关于URL的部分,您将找到有用的URL模式的摘要。
有用的网址格式列表
技巧部分是正则表达式 。 因此,我准备了最常用的URL模式列表。 需要特定的URL时,您始终可以参考此列表。
主键自动字段 | |
---|---|
正则表达式 | (?P<pk>\d+) |
例 | url(r'^questions/(?P<pk>\d+)/$', views.question, name='question') |
有效网址 | /questions/934/ |
捕获 | {'pk': '934'} |
子弹场 | |
---|---|
正则表达式 | (?P<slug>[-\w]+) |
例 | url(r'^posts/(?P<slug>[-\w]+)/$', views.post, name='post') |
有效网址 | /posts/hello-world/ |
捕获 | {'slug': 'hello-world'} |
带主键的子弹场 | |
---|---|
正则表达式 | (?P<slug>[-\w]+)-(?P<pk>\d+) |
例 | url(r'^blog/(?P<slug>[-\w]+)-(?P<pk>\d+)/$', views.blog_post, name='blog_post') |
有效网址 | /blog/hello-world-159/ |
捕获 | {'slug': 'hello-world', 'pk': '159'} |
Django用户名 | |
---|---|
正则表达式 | (?P<username>[\w.@+-]+) |
例 | url(r'^profile/(?P<username>[\w.@+-]+)/$', views.user_profile, name='user_profile') |
有效网址 | /profile/vitorfs/ |
捕获 | {'username': 'vitorfs'} |
年 | |
---|---|
正则表达式 | (?P<year>[0-9]{4}) |
例 | url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive, name='year') |
有效网址 | /articles/2016/ |
捕获 | {'year': '2016'} |
年/月 | |
---|---|
正则表达式 | (?P<year>[0-9]{4})/(?P<month>[0-9]{2}) |
例 | url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive, name='month') |
有效网址 | /articles/2016/01/ |
捕获 | {'year': '2016', 'month': '01'} |
您可以在这篇文章中找到有关这些模式的更多详细信息: 有用的URL模式列表 。
可重用模板
到目前为止,我们一直在复制和粘贴HTML,重复执行HTML文档的多个部分,从长远来看,这并不是很可持续。 这也是一个坏习惯。
在本部分中,我们将重构HTML模板,创建一个母版页,并仅为每个模板添加唯一的部分。
在模板文件夹中创建一个名为base.html的新文件:
templates / base.html
{% load static %}<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{% block title %}Django Boards{% endblock %}</title>
<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
</head>
<body>
<div class="container">
<ol class="breadcrumb my-4">
{% block breadcrumb %}
{% endblock %}
</ol>
{% block content %}
{% endblock %}
</div>
</body>
</html>
这将是我们的主页。 我们创建的每个模板都将扩展此特殊模板。 现在观察,我们介绍了{ % block % }
标签。 它用于在模板中保留一个空间,“子”模板(扩展了母版页)可以在该空间中插入代码和HTML。
对于{ % block title % }
我们还设置了默认值“ Django Boards”。如果我们没有为{ % block title % }
在子模板中。
现在,让我们重构两个模板: home.html和topic.html 。
templates / home.html
{% extends 'base.html' %}
{% block breadcrumb %}
<li class="breadcrumb-item active">Boards</li>
{% endblock %}
{% block content %}
<table class="table">
<thead class="thead-inverse">
<tr>
<th>Board</th>
<th>Posts</th>
<th>Topics</th>
<th>Last Post</th>
</tr>
</thead>
<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>
</table>
{% endblock %}
home.html模板的第一行是{ % extends 'base.html' % }
{ % extends 'base.html' % }
{ % extends 'base.html' % }
。 这个标记告诉Django将base.html模板用作母版页。 之后,我们使用这些块来放置页面的唯一内容。
template / topics.html
{% extends 'base.html' %}
{% block title %}
{{ board.name }} - {{ block.super }}
{% endblock %}
{% block breadcrumb %}
<li class="breadcrumb-item"><a href="{% url 'home' %}">Boards</a></li>
<li class="breadcrumb-item active">{{ board.name }}</li>
{% endblock %}
{% block content %}
<!-- just leaving it empty for now. we will add core here soon. -->
{% endblock %}
在topic.html模板中,我们正在更改{ % block title % }
默认值。 请注意,我们可以通过调用{ { block.super } }
。 因此,这里我们使用的是网站标题,我们在base.html中将其定义为“ Django Boards”。因此,对于“ Python” board页面,标题将为“ Python-Django Boards”,即“ Random” board。标题为“ Random-Django Boards”。
现在让我们运行测试,看看我们没有破坏任何东西:
python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.......
----------------------------------------------------------------------
Ran 7 tests in 0.067s
OK
Destroying test database for alias 'default'...
好! 一切看起来都很好。
现在有了base.html模板,我们可以轻松添加带有菜单的顶部栏:
templates / base.html
{% load static %}<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{% block title %}Django Boards{% endblock %}</title>
<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" href="{% url 'home' %}">Django Boards</a>
</div>
</nav>
<div class="container">
<ol class="breadcrumb my-4">
{% block breadcrumb %}
{% endblock %}
</ol>
{% block content %}
{% endblock %}
</div>
</body>
</html>
我使用的HTML是Bootstrap 4导航栏组件的一部分 。
我想添加的一个不错的方法是更改页面“徽标”( .navbar-brand
)中的字体。
前往,输入“ Django Boards”或您为项目指定的任何名称,然后单击“ 应用于所有字体” 。 浏览一下,找到您想要的。fonts.google.com
在base.html模板中添加字体:
{% load static %}<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{% block title %}Django Boards{% endblock %}</title>
<link href="https://fonts.googleapis.com/css?family=Peralta" rel="stylesheet">
<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
<link rel="stylesheet" href="{% static 'css/app.css' %}">
</head>
<body>
<!-- code suppressed for brevity -->
</body>
</html>
现在,在static / css文件夹中创建一个名为app.css的新CSS文件:
static/css/app.css
.navbar-brand { font-family : 'Peralta' , cursive ; }
表格
表单用于处理用户输入。 在任何Web应用程序或网站中,这都是非常常见的任务。 标准的方法是通过HTML表单,其中用户输入一些数据,将其提交到服务器,然后服务器对它进行处理。
表单处理是一项相当复杂的任务,因为它涉及与应用程序的许多层进行交互。 还有很多问题要注意。 例如,所有提交给服务器的数据都采用字符串格式,因此在对其执行任何操作之前,我们必须将其转换为适当的数据类型(整数,浮点数,日期等)。 我们必须验证有关应用程序业务逻辑的数据。 我们还必须正确地清理,清理数据,以避免诸如SQL Injection和XSS攻击之类的安全问题。
好消息是,Django Forms API使整个过程变得更加容易,并自动完成了大部分工作。 而且,最终结果是比大多数程序员自己能够实现的代码安全得多。 因此,无论HTML表单多么简单,请始终使用表单API。
如何不执行表格
起初,我考虑过直接跳转到表单API。 但是我认为,花一些时间来了解表单处理的基本细节对我们来说是个好主意。 否则,它将最终看起来像魔术,这是一件坏事,因为当事情出错时,您将不知道在哪里寻找问题。
对某些编程概念有了更深入的了解,我们可以更好地控制这种情况。 处于控制状态很重要,因为它使我们可以更加自信地编写代码。 一旦我们确切知道发生了什么,就可以轻松实现可预测行为的代码。 由于您知道要查找的位置,因此调试和查找错误也容易得多。
无论如何,让我们从实现以下表单开始:
这是我们在上一教程中绘制的线框之一。 我现在意识到这可能是一个不好的例子,因为这种特殊形式涉及处理两个不同模型的数据: 主题 (主题)和帖子 (消息)。
到目前为止,我们还没有讨论过另一个重要方面,那就是用户身份验证。 我们只应向经过身份验证的用户显示此屏幕。 这样我们就可以知道是谁创建了主题或帖子 。
因此,现在让我们抽象一些细节,并集中于了解如何在数据库中保存用户输入。
首先,让我们创建一个名为new_topic的新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'^boards/(?P<pk>\d+)/new/$', views.new_topic, name='new_topic'),
url(r'^admin/', admin.site.urls),
]
我们构建URL的方式将帮助我们识别正确的Board 。
现在让我们创建new_topic视图函数:
boards/views.py
from django.shortcuts import render, get_object_or_404
from .models import Board
def new_topic(request, pk):
board = get_object_or_404(Board, pk=pk)
return render(request, 'new_topic.html', {'board': board})
目前为止, new_topic视图函数的外观与board_topics完全相同 。 这是有目的的,让我们一次迈出一步。
现在,我们只需要一个名为new_topic.html的模板即可查看一些代码的工作情况:
templates / new_topic.html
{% extends 'base.html' %}
{% block title %}Start a New Topic{% endblock %}
{% block breadcrumb %}
<li class="breadcrumb-item"><a href="{% url 'home' %}">Boards</a></li>
<li class="breadcrumb-item"><a href="{% url 'board_topics' board.pk %}">{{ board.name }}</a></li>
<li class="breadcrumb-item active">New topic</li>
{% endblock %}
{% block content %}
{% endblock %}
现在,我们只需要保证导航即可。 观察到我们将URL包括回到board_topics视图。
打开URL http://127.0.0.1:8000/boards/1/new/ 。 现在的结果是以下页面:
我们仍然没有实现到达此新页面的方法,但是如果将URL更改为http://127.0.0.1:8000/boards/2/new/ ,它应该带我们到Python Board :
注意:
用于URL来标识正确的资源。
我们已经可以添加一些测试:
boards / tests.py
from django.core.urlresolvers import reverse
from django.urls import resolve
from django.test import TestCase
from .views import home, board_topics, new_topic
from .models import Board
class HomeTests(TestCase):
# ...
class BoardTopicsTests(TestCase):
# ...
class NewTopicTests(TestCase):
def setUp(self):
Board.objects.create(name='Django', description='Django board.')
def test_new_topic_view_success_status_code(self):
url = reverse('new_topic', kwargs={'pk': 1})
response = self.client.get(url)
self.assertEquals(response.status_code, 200)
def test_new_topic_view_not_found_status_code(self):
url = reverse('new_topic', kwargs={'pk': 99})
response = self.client.get(url)
self.assertEquals(response.status_code, 404)
def test_new_topic_url_resolves_new_topic_view(self):
view = resolve('/boards/1/new/')
self.assertEquals(view.func, new_topic)
def test_new_topic_view_contains_link_back_to_board_topics_view(self):
new_topic_url = reverse('new_topic', kwargs={'pk': 1})
board_topics_url = reverse('board_topics', kwargs={'pk': 1})
response = self.client.get(new_topic_url)
self.assertContains(response, 'href="{0}"'.format(board_topics_url))
我们的新类NewTopicTests的测试的快速摘要:
- setUp:创建要在测试期间使用的Board实例
- test_new_topic_view_success_status_code:检查对视图的请求是否成功
- test_new_topic_view_not_found_status_code:检查在委员会不存在时视图是否引发404错误
- test_new_topic_url_resolves_new_topic_view : check if the right view is being used
- test_new_topic_view_contains_link_back_to_board_topics_view : ensure the navigation back to the list of topics
Run the tests:
python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
...........
----------------------------------------------------------------------
Ran 11 tests in 0.076s OK Destroying test database for alias 'default'...
Django完整入门指南-第3部分--2
https://blog.csdn.net/weixin_41131063/article/details/102846762