完整的Django入门指南学习笔记2
part2:
前沿
在第一节中,我们安装了项目所需要的一切:Python3.6以及在虚拟环境中运行的Django2.0,这部分教程继续在项目上编写代码。
开始写代码前,先讨论下项目的相关背景知识,然后再学习 Django 的基础,包括:模型、管理后台、视图、模板和路由。
论坛项目
在进入模型,视图等其它有趣的部分之前,花点时间简要地讨论我们将要开发的这个项目。
例图
我们的项目是一个论坛系统,整个项目的构思是维护几个论坛版块(boards),每个版块就像一个分类一样。在指定的版块里面,用户可以通过创建新主题(Topic)开始讨论,其他用户参与讨论回复。
我们需要找到一种方法来区分普通用户和管理员用户,因为只有管理员可以创建版块。下图概述了主要的用例和每种类型的用户角色:
类图
从用例图中,我们可以开始思考项目所需的实体类有哪些。这些实体就是我们要创建的模型,它与我们的Django应用程序处理的数据非常密切。
为了能够实现上面描述的用例,我们需要至少实现下面几个模型:Board,Topic,Post和User。
- Board:版块
- Topic:主题
- Post:帖子(译注:其实就是主题的回复或评论)
类与类之间的实线告诉我们,在一个主题(Topic)中,我们需要有一个字段(译注:其实就是通过外键来关联)来确定它属于哪个版块(Board)。
同样,帖子(Post)也需要一个字段来表示它属于哪个主题,这样我们就可以列出在特定主题内创建的帖子。
最后,我们需要一个字段来表示主题是谁发起的,帖子是谁发的。
用户和版块之间也有联系,就是谁创建的版块。但是这些信息与应用程序无关。还有其他方法可以跟踪这些信息,稍后你会看到。
现在我们的类图有基本的表现形式,我们还要考虑这些模型将承载哪些信息。这很容易让事情变得复杂,所以试着先把重要的内容列出来,这些内容是我们启动项目需要的信息。后面我们再使用 Django 的迁移(Migrations)功能来改进模型,你将在下一节中详细了解这些内容。
但就目前而言,这是模型最基本的内容:
这个类图强调的是模型之间的关系,这些线条和箭头最终会在稍后转换为字段。
对于 Board 模型,我们将从两个字段开始:name
和 description
。 name
字段必须是唯一的,为了避免有重复的名称。description
用于说明这个版块是做什么用的。
Topic 模型包括四个字段:subject
表示主题内容,last_update
用来定义话题的排序,starter
用来识别谁发起的话题,board
用于指定它属于哪个版块。
Post 模型有一个 message
字段,用于存储回复的内容,created_at
在排序时候用(最先发表的帖子排最前面),updated_at
告诉用户是否更新了内容,同时,还需要有对应的 User 模型的引用,Post 由谁创建的和谁更新的。
最后是 User 模型。在类图中,我只提到了字段 username
,password
,email
, is_superuser
标志,因为这几乎是我们现在要使用的所有东西。
需要注意的是,我们不需要创建 User 模型,因为Django已经在contrib包中内置了User模型,我们将直接拿来用。
关于类图之间的对应关系(数字 1,0..* 等等),这里教你如何阅读:
一个topic 必须与一个(1)Board(这意味着它不能为空)相关联,但是 Board 下面可能与许多个或者0个 topic 关联 (0..*)。这意味着 Board 下面可能没有主题。(译注:一对多关系)
一个 Topic 至少有一个 Post(发起话题时,同时会发布一个帖子),并且它也可能有许多 Post(1..*)。一个Post 必须与一个并且只有一个Topic(1)相关联。
一个 Topic 必须有一个且只有一个 User 相关联,topic 的发起者是(1)。而一个用户可能有很多或者没有 topic(0..*)。
Post 必须有一个并且只有一个与之关联的用户,用户可以有许多或没有 Post(0..*)。Post 和 User之间的第二个关联是直接关联(参见该行最后的箭头),就是 Post 可以被用户修改(updated_by
),updated_by
有可能是空(Post 没有被修改)
画这个类图的另一种方法是强调字段而不是模型之间的关系:
上面的表示方式与前面的表示方式是对等的,不过这种方式更接近我们将要使用 Django Models API 设计的内容。在这种表示方式中,我们可以更清楚地看到,在 Post 模型中,关联了 Topic,created_by
(创建者)和 updated_by
(更新者)字段。
另一个值得注意的事情是,在 Topic 模型中,有一个名为 posts()
的操作(一个类方法)。我们将通过反向关系来实现这一目标,Django 将自动在数据库中执行查询以返回特定主题的所有帖子列表。
好了,现在已经够UML了!为了绘制本节介绍的图表,我使用了 StarUML 工具。
线框图(原型图)
花了一些时间来设计应用程序的模型后,我们来创建一些线框来定义需要完成的工作,并且清楚地了解我们将要做什么。
基于线框图,我们可以更深入地了解应用程序中涉及的实体。
首先,我们需要在主页上显示所有版块:
如果用户点击一个链接,比如点击Django版块,它应该列出所有Django相关的主题:
这里有两个入口:用户点击“new topic“ 按钮创建新主题,或者点击主题链接查看或参与讨论。
“new topic” 页面:
现在,主题页面显示了帖子和讨论:
如果用户点击回复按钮,将看到下面这个页面,并以倒序的方式(最新的在第一个)显示帖子列表:
绘制这些线框,你可以使用draw.io服务,它是免费的
这一部分我们设计的系统的模型,以及模型之间的关系,分清楚了不同用户类型的角色,最后,我们把原型图也画出来了。再小的系统,我们也要先思考。下面我们将设计 Model 类。
模型
我们在本节中要做的是创建 Django 所表示的类,这些类就是在上一部分中设计的类:Board,Topic 和 Post。User 模型被命名为内置应用叫 auth,它以命名空间 django.contrib.auth的形式出现在 INSTALLED_APPS
配置中。
修改 boards/models.py 。以下是我们在Django应用程序中如何表示类图的代码:
from django.db import models
from django.contrib.auth.models import User
class Board(models.Model):
name = models.CharField(max_length=30, unique=True)
description = models.CharField(max_length=100)
class Topic(models.Model):
subject = models.CharField(max_length=255)
last_updated = models.DateTimeField(auto_now_add=True)
board = models.ForeignKey(Board, related_name='topics')
starter = models.ForeignKey(User, related_name='topics')
class Post(models.Model):
message = models.TextField(max_length=4000)
topic = models.ForeignKey(Topic, related_name='posts')
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(null=True)
created_by = models.ForeignKey(User, related_name='posts')
updated_by = models.ForeignKey(User, null=True, related_name='+')
所有模型都是django.db.models.Model类的子类。每个类将被转换为数据库表。每个字段由 django.db.models.Field子类(内置在Django core)的实例表示,它们并将被转换为数据库的列。
字段 CharField
,DateTimeField
等等,都是 django.db.models.Field 的子类,包含在Django的核心里面,随时可以使用。
在这里,我们仅使用 CharField
,TextField
,DateTimeField
,和ForeignKey
字段来定义我们的模型,当然在Django提供了更广泛的选择来代表不同类型的数据。
有些字段需要参数,例如CharField
。我们应该始终设定一个 max_length
。这些信息将用于创建数据库列。Django需要知道数据库列需要多大。该 max_length
参数也将被Django Forms API用来验证用户输入。
在Board模型定义中,更具体地说,在name
字段中,我们设置了参数 unique=True
,顾名思义,它将强制数据库级别字段的唯一性。
在Post模型中,created_at
字段有一个可选参数auto_now_add=True
:告诉Django创建Post对象时间为当前日期和时间。
模型之间的关系使用ForeignKey
字段。它将在模型之间创建一个连接,并在数据库级别创建适当的关系(译注:外键关联)。该ForeignKey
字段需要一个位置参数related_name
:用于引用它关联的模型。(译注:例如 created_by
是外键字段,关联的User模型,表明这个帖子是谁创建的,related_name=posts
表示在 User 那边可以使用 user.posts
来查看这个用户创建了哪些帖子)
例如,在Topic模型中,board
字段是Board模型的ForeignKey
。它告诉Django,一个Topic实例只涉及一个Board实例。related_name
参数将用于创建反向关系,Board实例通过属性topics访问属于这个版块下的Topic列表。
Django自动创建这种反向关系,related_name
是可选项。但是,如果我们不为它设置一个名称,Django会自动生成它:(class_name)_set
。例如,在Board模型中,所有Topic列表将用topic_set
属性表示。而这里我们将其重新命名为了topics
,以使其感觉更自然。
在Post模型中,该updated_by
字段设置related_name='+'
。这指示Django我们不需要这种反向关系,所以它会被忽略(译注:也就是说我们不需要关系用户修改过哪些帖子)。
下面可以看到类图和Django模型的源代码之间的比较,绿线表示我们如何处理反向关系。
这时,你可能会问自己:“主键/ ID呢?”?如果我们没有为模型指定主键,Django会自动为我们生成它。所以现在一切正常。在下一节中,您将看到它是如何工作的。
迁移模型
下一步是告诉Django创建数据库,以便我们可以开始使用它。
打开终端 ,激活虚拟环境,转到 manage.py 文件所在的文件夹,然后运行以下命令:
python manage.py makemigrations
你会看到输出的内容是:
Migrations for 'boards': boards/migrations/0001_initial.py - Create model Board - Create model Post - Create model Topic - Add field topic to post - Add field updated_by to post
此时,Django 在 boards/migrations 目录创建了一个名为 0001_initial.py 的文件。它代表了应用程序模型的当前状态。在下一步,Django将使用该文件创建表和列。
迁移文件将被翻译成SQL语句。如果您熟悉SQL,则可以运行以下命令来检验将是要被数据库执行的SQL指令
python manage.py sqlmigrate boards 0001
如果你不熟悉SQL,也不要担心。在本系列教程中,我们不会直接使用SQL。所有的工作都将使用Django ORM来完成,它是一个与数据库进行通信的抽象层。
下一步是将我们生成的迁移文件应用到数据库:
python manage.py migrate
输出内容应该是这样的:
Operations to perform: Apply all migrations: admin, auth, boards, 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 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 boards.0001_initial... OK Applying sessions.0001_initial... OK
因为这是我们第一次迁移数据库,所以 migrate
命令把 Django contrib app 中现有的迁移文件也执行了,这些内置app列在了 INSTALLED_APPS
。这是预料之中的。
Applying boards.0001_initial... OK
是我们在上一步中生成的迁移脚本。
好了!我们的数据库已经可以使用了。
- 需要注意的是SQLite是一个产品级数据库。SQLite被许多公司用于成千上万的产品,如所有Android和iOS设备,主流的Web浏览器,Windows 10,MacOS等。
- 但这不适合所有情况。SQLite不能与MySQL,PostgreSQL或Oracle等数据库进行比较。大容量的网站,密集型写入的应用程序,大的数据集,高并发性的应用使用SQLite最终都会导致问题。
- 我们将在开发项目期间使用SQLite,因为它很方便,不需要安装其他任何东西。当我们将项目部署到生产环境时,再将切换到PostgreSQL(译注:后续,我们后面可能使用MySQL)。对于简单的网站这种做法没什么问题。但对于复杂的网站,建议在开发和生产中使用相同的数据库。
试验 Models API
使用Python进行开发的一个重要优点是交互式shell。我一直在使用它。这是一种快速尝试和试验API的方法。
您可以使用 manage.py 工具加载我们的项目来启动 Python shell :
python manage.py shell
Python 3.6.2 (default, Jul 17 2017, 16:44:45)
[GCC 4.2.1 Compatible Apple LLVM 8.1.0 (clang-802.0.42)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>>
这与直接输入python指令来调用交互式控制台是非常相似的,除此之外,项目将被添加到sys.path
并加载Django。这意味着我们可以在项目中导入我们的模型和其他资源并使用它。
让我们从导入Board类开始:
from boards.models import Board
要创建新的 board 对象,我们可以执行以下操作:
board = Board(name='Django', description='This is a board about Django.')
为了将这个对象保存在数据库中,我们必须调用save
方法:
board.save()
save()
方法用于创建和更新对象。这里Django创建了一个新对象,因为这时Board 实例没有id。第一次保存后,Django会自动设置ID:
board.id
1
您可以将其余的字段当做Python属性访问:
board.name
'Django'
board.description
'This is a board about Django.'
要更新一个值,我们可以这样做:
board.description = 'Django discussion board.'
board.save()
每个Django模型都带有一个特殊的属性; 我们称之为模型管理器(Model Manager)。你可以通过属性objects
来访问这个管理器,它主要用于数据库操作。例如,我们可以使用它来直接创建一个新的Board对象:
board = Board.objects.create(name='Python', description='General discussion about Python.')
board.id
2
board.name
'Python'
所以,现在我们有两个版块了。我们可以使用objects
列出数据库中所有现有的版块:
Board.objects.all()
<QuerySet [<Board: Board object>, <Board: Board object>]>
结果是一个QuerySet。稍后我们会进一步了解。基本上,它是从数据库中查询的对象列表。我们看到有两个对象,但显示的名称是 Board object。这是因为我们尚未实现 Board 的__str__
方法。
__str__
方法是对象的字符串表示形式。我们可以使用版块的名称来表示它。
首先,退出交互式控制台:
exit()
现在编辑 boards app 中的 models.py 文件:
class Board(models.Model):
name = models.CharField(max_length=30, unique=True)
description = models.CharField(max_length=100)
def __str__(self):
return self.name
让我们重新查询,再次打开交互式控制台:
python manage.py shell
from boards.models import Board
Board.objects.all()
<QuerySet [<Board: Django>, <Board: Python>]>
好多了,对吧?
我们可以将这个QuerySet看作一个列表。假设我们想遍历它并打印每个版块的描述:
boards_list = Board.objects.all()
for board in boards_list:
print(board.description)
结果是:
Django discussion board.
General discussion about Python.
同样,我们可以使用模型的 管理器(Manager) 来查询数据库并返回单个对象。为此,我们要使用get()
方法:
django_board = Board.objects.get(id=1)
django_board.name
'Django'
但我们必须小心这种操作。如果我们试图查找一个不存在的对象,例如,查找id=3的版块,它会引发一个异常:
board = Board.objects.get(id=3)
boards.models.DoesNotExist: Board matching query does not exist.
get()
方法的参数可以是模型的任何字段,但最好使用可唯一标识对象的字段来查询。否则,查询可能会返回多个对象,这也会导致异常。
Board.objects.get(name='Django')
<Board: Django>
请注意,查询区分大小写,小写“django”不匹配:
Board.objects.get(name='django')
boards.models.DoesNotExist: Board matching query does not exist.
模型操作的总结
下面是我们在本节中关于模型学到的方法和操作,使用Board模型作为参考。大写的 Board 指的是类,小写的 board 指 Board 的一个实例(或对象)
操作 | 代码示例 |
---|---|
创建一个对象而不保存 | board = Board() |
保存一个对象(创建或更新) | board.save() |
数据库中创建并保存一个对象 | Board.objects.create(name='...', description='...') |
列出所有对象 | Board.objects.all() |
通过字段标识获取单个对象 | Board.objects.get(id=1) |
在下一小节中,我们将开始编写视图并在HTML页面中显示我们的版块。
视图、模板和静态文件
目前我们已经有一个视图函数叫home
,这个视图在我们的应用程序主页上显示为 “Hello,World!”
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'^admin/', admin.site.urls),
]
boards/views.py
from django.http import HttpResponse
def home(request):
return HttpResponse('Hello, World!')
我们可以从这里开始写。如果你回想起我们的原型图,图5显示了主页应该是什么样子。我们想要做的是在表格中列出一些版块的名单以及它们的描述信息。
首先要做的是导入Board模型并列出所有的版块
boards/views.py
from django.http import HttpResponse
from .models import Board
def home(request):
boards = Board.objects.all()
boards_names = list()
for board in boards:
boards_names.append(board.name)
response_html = '<br>'.join(boards_names)
return HttpResponse(response_html)
结果就是这个简单的HTML页面:
等等,我们在这里先停一下。真正的项目里面我们不会这样去渲染HTML。对于这个简单视图函数,我们做的就是列出所有版块,然后渲染部分是Django模板引擎的职责。
模板引擎设置
在 manage.py 所在的目录创建一个名为 templates 的新文件夹:
myproject/ |-- myproject/ | |-- boards/ | |-- myproject/ | |-- templates/ | +-- manage.py +-- venv/
在templates文件夹中,创建一个名为home.html的HTML文件:
templates/home.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Boards</title>
</head>
<body>
<h1>Boards</h1>
{% for board in boards %}
{{ board.name }} <br>
{% endfor %}
</body>
</html>
在上面的例子中,我们混入了原始HTML和一些特殊标签 {% for ... in ... %}
和 {{ variable }}
。它们是Django模板语言的一部分。上面的例子展示了如何使用 for
遍历列表对象。{{ board.name }}
会在 HTML 模板中会被渲染成版块的名称,最后生成动态HTML文档。
在我们可以使用这个HTML页面之前,我们必须告诉Django在哪里可以找到我们应用程序的模板。
打开myproject目录下面的settings.py文件,搜索TEMPLATES
变量,并设置DIRS
的值为 os.path.join(BASE_DIR, 'templates')
:
TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [ os.path.join(BASE_DIR, 'templates') ], '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”
我们可以使用Python shell进行调试:
python manage.py shell
from django.conf import settings
settings.BASE_DIR
'/Users/vitorfs/Development/myproject'
import os
os.path.join(settings.BASE_DIR, 'templates')
'/Users/vitorfs/Development/myproject/templates'
看到了吗?它只是指向我们在前面步骤中创建的templates文件夹。
现在我们可以更新home视图:
boards/views.py
from django.shortcuts import render
from .models import Board
def home(request):
boards = Board.objects.all()
return render(request, 'home.html', {'boards': boards})
生成的HTML:
我们可以用一个更漂亮的表格来替换,改进HTML模板:
templates/home.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Boards</title>
</head>
<body>
<h1>Boards</h1>
<table border="1">
<thead>
<tr>
<th>Board</th>
<th>Posts</th>
<th>Topics</th>
<th>Last Post</th>
</tr>
</thead>
<tbody>
{% for board in boards %}
<tr>
<td>
{{ board.name }}<br>
<small style="color: #888">{{ board.description }}</small>
</td>
<td>0</td>
<td>0</td>
<td></td>
</tr>
{% endfor %}
</tbody>
</table>
</body>
</html>
第一个测试用例
测试主页
测试将是一个反复出现的主题,我们将在整个教程系列中一起探讨不同的概念和策略。
我们来开始写第一个测试。现在,我们将在boards应用程序内的tests.py文件中操作
boards/tests.py
from django.core.urlresolvers import reverse
from django.test import TestCase
class HomeTests(TestCase):
def test_home_view_status_code(self):
url = reverse('home')
response = self.client.get(url)
self.assertEquals(response.status_code, 200)
这是一个非常简单但非常有用的测试用例,我们测试的是请求该URL后返回的响应状态码。状态码200意味着成功。
请求一下主页后,我们可以在控制台中看到响应的状态代码:
如果出现未捕获的异常,语法错误或其他任何情况,Django会返回状态代码500,这意味着是内部服务器错误。现在,想象我们的应用程序有100个视图函数。如果我们为所有视图编写这个简单的测试,只需一个命令,我们就能够测试所有视图是否返回成功代码,因此用户在任何地方都看不到任何错误消息。如果没有自动化测试,我们需要逐一检查每个页面是否有错误。
执行Django的测试套件:
python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.
----------------------------------------------------------------------
Ran 1 test in 0.041s
OK
Destroying test database for alias 'default'...
现在我们可以测试Django是否在请求的URL的时候返回了正确的视图函数。这也是一个有用的测试,因为随着开发的进展,您会发现urls.py 模块可能变得非常庞大而复杂。URL conf 全部是关于解析正则表达式的。有些情况下有一个非常宽容的URL(译注:本来不应该匹配的,却因为正则表达式写的过于宽泛而错误的匹配了),所以Django最终可能返回错误的视图函数。
我们可以这样做:
boards/tests.py
from django.core.urlresolvers import reverse
from django.urls import resolve
from django.test import TestCase
from .views import home
class HomeTests(TestCase):
def test_home_view_status_code(self):
url = reverse('home')
response = self.client.get(url)
self.assertEquals(response.status_code, 200)
def test_home_url_resolves_home_view(self):
view = resolve('/')
self.assertEquals(view.func, home)
在第二个测试中,我们使用了resolve
函数。Django使用它来将浏览器发起请求的URL与urls.py模块中列出的URL进行匹配。该测试用于确定URL /
返回 home 视图。
再次测试:
python manage.py test
Creating test database for alias 'default'... System check identified no issues (0 silenced). .. ---------------------------------------------------------------------- Ran 2 tests in 0.027s OK Destroying test database for alias 'default'...
要查看有关测试执行时更详细的信息,可将verbosity的级别设置得更高一点:
python manage.py test --verbosity=2
Creating test database for alias 'default' ('file:memorydb_default?mode=memory&cache=shared')... Operations to perform: Synchronize unmigrated apps: messages, staticfiles Apply all migrations: admin, auth, boards, contenttypes, sessions Synchronizing apps without migrations: Creating tables... Running deferred SQL... 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 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 boards.0001_initial... OK Applying sessions.0001_initial... OK System check identified no issues (0 silenced). test_home_url_resolves_home_view (boards.tests.HomeTests) ... ok test_home_view_status_code (boards.tests.HomeTests) ... ok ---------------------------------------------------------------------- Ran 2 tests in 0.017s OK Destroying test database for alias 'default' ('file:memorydb_default?mode=memory&cache=shared')...
Verbosity决定了将要打印到控制台的通知和调试信息量; 0是无输出,1是正常输出,2是详细输出。
静态文件设置
静态文件是指 CSS,JavaScript,字体,图片或者是用来组成用户界面的任何其他资源。
实际上,Django 本身是不负责处理这些文件的,但是为了让我们的开发过程更轻松,Django 提供了一些功能来帮助我们管理静态文件。这些功能可在 INSTALLED_APPS
的 django.contrib.staticfiles 应用程序中找到(译者:Django为了使得开发方便,也可以处理静态文件,而在生产环境下,静态文件一般直接由 Nginx 等反向代理服务器处理,而应用服务器专心负责处理它擅长的业务逻辑)。
市面上很多优秀前端组件框架,我们没有理由继续用简陋的HTML文档来渲染。我们可以轻松地将Bootstrap 4添加到我们的项目中。Bootstrap是一个用HTML,CSS和JavaScript开发的前端开源工具包。
在项目根目录中,除了 boards, templates 和myproject文件夹外,再创建一个名为static的新文件夹,并在static文件夹内创建另一个名为css的文件夹:
myproject/ |-- myproject/ | |-- boards/ | |-- myproject/ | |-- templates/ | |-- static/ <-- here | | +-- css/ <-- and here | +-- manage.py +-- venv/
转到getbootstrap.com并下载最新版本:
下载编译版本的CSS和JS
在你的计算机中,解压 bootstrap-4.0.0-beta-dist.zip 文件,将文件 css/bootstrap.min.css复制到我们项目的css文件夹中:
myproject/ |-- myproject/ | |-- boards/ | |-- myproject/ | |-- templates/ | |-- static/ | | +-- css/ | | +-- bootstrap.min.css <-- here | +-- manage.py +-- venv/
下一步是告诉Django在哪里可以找到静态文件。打开settings.py,拉到文件的底部,在STATIC_URL后面添加以下内容:
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static'),
]
还记得 TEMPLATES 目录吗,和这个配置是一样的
现在我们必须在模板中加载静态文件(Bootstrap CSS文件):
templates/home.html
{% load static %}<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Boards</title>
<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
</head>
<body>
<h1>Boards</h1>
<table border="1">
<thead>
<tr>
<th>Board</th>
<th>Posts</th>
<th>Topics</th>
<th>Last Post</th>
</tr>
</thead>
<tbody>
{% for board in boards %}
<tr>
<td>
{{ board.name }}<br>
<small style="color: #888">{{ board.description }}</small>
</td>
<td>0</td>
<td>0</td>
<td></td>
</tr>
{% endfor %}
</tbody>
</table>
</body>
</html>
首先,我们在模板的开头使用了 Static Files App 模板标签 {% load static %}
。
模板标签{% static %}
用于构成资源文件完整URL。在这种情况下,{% static 'css/bootstrap.min.css' %}
将返回 /static/css/bootstrap.min.css,它相当于 http://127.0.0.1:8000/static/css/bootstrap.min.css
{% static %}
模板标签使用 settings.py文件中的 STATIC_URL
配置来组成最终的URL,例如,如果您将静态文件托管在像 https://static.example.com/ 这样的子域中 ,那么我们将设置 STATIC_URL=https://static.example.com/
,然后 {% static 'css/bootstrap.min.css' %}
返回的是 https://static.example.com/css/bootstrap.min.css
如果目前这些对你来说搞不懂也不要担心。只要记得但凡是需要引用CSS,JavaScript或图片文件的地方就使用{% static %}
。稍后,当我们开始部署项目到正式环境时,我们将讨论更多。现在都设置好了。
刷新页面 http://127.0.0.1:8000 ,我们可以看到它可以正常运行:
现在我们可以编辑模板,以利用Bootstrap CSS:
{% load static %}<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Boards</title>
<link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
</head>
<body>
<div class="container">
<ol class="breadcrumb my-4">
<li class="breadcrumb-item active">Boards</li>
</ol>
<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>
{{ board.name }}
<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>
</div>
</body>
</html>
显示效果:
到目前为止,我们使用交互式控制台(python manage.py shell)添加了几个新的版块。但我们需要一个更好的方式来实现。在下一节中,我们将为网站管理员实现一个管理界面来管理这些数据。
Django Admin 简介
当我们开始一个新项目时,Django已经配置了Django Admin 在这个应用程序列出的INSTALLED_APPS。
使用 Django Admin的一个很好的例子就是用在博客中; 它可以被作者用来编写和发布文章。另一个例子是电子商务网站,工作人员可以创建,编辑,删除产品。
现在,我们将配置 Django Admin 来维护我们应用程序的版块。
我们首先创建一个管理员帐户:
python manage.py createsuperuser
按照说明操作:
Username (leave blank to use 'vitorfs'): admin
Email address: admin@example.com
Password:
Password (again):
Superuser created successfully.
在浏览器中打开该URL:http://127.0.0.1:8000/admin/
输入用户名和密码登录到管理界面:
它已经配置了一些功能。在这里,我们可以添加用户和组的权限管理,这些概念在后面我们将探讨更多。
添加Board模型非常简单。打开boards目录中的admin.py文件,并添加以下代码:
boards/admin.py
from django.contrib import admin
from .models import Board
admin.site.register(Board)
保存admin.py文件,然后刷新网页浏览器中的页面:
对!它已准备好被使用了。点击Boards链接查看现有版块列表:
我们可以通过点击 Add Board 按钮添加一个新的版块:
点击保存按钮:
我们可以检查一切是否正常,打开URL http://127.0.0.1:8000
总结
在本教程中,我们探讨了许多新概念。我们为项目定义了一些需求,创建了第一个模型,迁移了数据库,开始玩 Models API。我们创建了第一个视图并编写了一些单元测试。同时我们还配置了Django模板引擎,静态文件,并将Bootstrap 4库添加到项目中。最后,我们简要介绍了Django Admin界面。
下一部分,我们将探索Django的URL路由,表单API,可重用模板以及更多测试。
该项目的源代码在GitHub上可用。本来的代码可以在发布标签v0.2-lw下找到。下面的链接将带你到正确的地方:
https://github.com/sibtc/django-beginners-guide/tree/v0.2-lw