本文主要介绍 Django 中 Model, Views, Templates 和 Static Files, 并简单引入了 Django Admin.
本系列是根据 Vitor Freitas 的Django教程整理而来,如有疑问可参考 原文
点击查看索引我们的目标是建立一个 Forum(论坛) ,其中包括 Board(板块) , Topic(话题) , Post(跟帖) , User(用户) 四个 Class 。
这部分原文有十分详细的介绍,此处不再赘述。
其中 User 已经在 Django 的内置 app auth 定义,因此我们只需要定义其余三个。
Models
定义 Models
在 boards/models.py 中加入以下代码:
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', on_delete=models.CASCADE)
starter = models.ForeignKey(User, related_name='topics', on_delete=models.CASCADE)
class Post(models.Model):
message = models.TextField(max_length=4000)
topic = models.ForeignKey(Topic, related_name='posts', on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(null=True)
created_by = models.ForeignKey(User, related_name='posts', on_delete=models.CASCADE)
updated_by = models.ForeignKey(User, null=True, related_name='+', on_delete=models.CASCADE)
所有 model 都是 django.db.models.Model 类的子类。每个 class 都将转换为数据库表。每个 field 由 django.db.models.Field 子类的实例表示,并将被转换为数据库列。
CharField
、DateTimeField
等 field 都是 django.db.models.Field 的子类,它们包含在 Django 核心中——随时可用。
某些 field 具有必需的参数,例如 CharField
。我们应该 始终 设置一个 max_length
。此信息将用于创建数据库列。 Django 需要知道数据库列需要多大。 Django Forms API 也将使用 max_length
参数来验证用户输入。
在 Board 定义中,更具体地说,在 name
field 中,我们还设置了参数 unique=True
,顾名思义,它将在数据库级别 强制该 field 的 唯一性。
在 Post 中, created_at
field 有一个可选参数, auto_now_add
设置为 True
。这将指示 Django 在创建 Post 对象时 设置当前日期和时间 。
在 model 之间 创建关系 的一种方法是使用 ForeignKey
。它将在 model 之间创建链接并在数据库级别创建适当的关系。 ForeignKey
field 需要一个位置参数,其中包含对其相关模型的引用。
例如,在 Topic
model 中,board
field 是 Board
model 的 ForeignKey
。它告诉 Django 一个 Topic
实例只与一个 Board
实例相关。 related_name
参数将用于创建 反向关系 ,其中 Board
实例将有权访问属于它的 Topic
实例列表。
Django 自动创建这种反向关系,也就是说, related_name
是可选的。但是如果我们不为它设置名称,Django 将使用名称生成它:(class_name)_set。例如,在 Board
model 中,Topic
实例将在 topic_set
属性下可用。我们只是将其 重命名 为 topics
,以使其感觉更自然。
在 Post 模型中,updated_by 字段设置了
related_name=’+’` 。这告诉 Django 我们不需要这种反向关系,所以它会忽略它。
注意:在 Django2.0 后,定义 ForeignKey 和 OneToOneField 时需要加上 on_delete 参数,此参数为了避免两个表里的数据不一致问题,不然会报错:
TypeError: init() missing 1 required positional argument: ‘on_delete’
迁移 Models
下一步是告诉 Django 创建数据库,以便我们可以使用它。
在 manage.py 所在目录打开 Terminal,运行以下命令:
python manage.py makemigrations
此时,Django 在 board/migrations 目录中创建了一个名为 0001_initial.py 的文件。它代表我们应用程序模型的当前状态。在下一步中,Django 将使用这个文件来创建表和列。迁移文件被翻译成 SQL 语句。
现在下一步是将我们生成的迁移应用到数据库:
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 admin.0003_logentry_add_action_flag_choices... 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 auth.0009_alter_user_last_name_max_length... OK
Applying auth.0010_alter_group_name_max_length... OK
Applying auth.0011_update_proxy_permissions... OK
Applying auth.0012_alter_user_first_name_max_length... OK
Applying boards.0001_initial... OK
Applying sessions.0001_initial... OK
因为这是我们第一次迁移数据库,所以 migrate
命令还应用了来自 Django contrib apps 的现有迁移文件,在 INSTALLED_APPS
中列出。Applying board.0001_initial... OK
就是我们在上一步生成的迁移。
使用 Models API
使用以下命令打开 Python Shell:
python manage.py shell
会进入如下界面:
在 Python Shell 中,Ctrl+C
会中断当前的 Python 任务,而不会退出 Python Shell,如果想要退出 Python Shell,需要输入命令:
exit()
或使用快捷键 Ctrl+D
。
在 Python Shell 中输入以下命令以导入 Board class:
from boards.models import Board
使用以下命令创建新的 board 实例:
board = Board(name='Django', description='This is a board about Django.')
使用以下命令保存该实例于数据库中:
board.save()
save
方法用于创建和更新对象。在这里 Django 创建了一个新对象,因为 Board 实例没有 id。第一次保存后,Django会自动设置id:
也可通过 board.name
和 board.description
访问其他 field。
通过以下命令可以更新 field 的值:
board.description = 'Django discussion board.'
board.save()
我们也可以通过如下命令创建一个新的 board 实例:
board = Board.objects.create(name='Python', description='General discussion about Python.')
输入以下命令得到所有已存在的 boards:
Board.objects.all()
输出如下:
<QuerySet [<Board: Board object (1)>, <Board: Board object (2)>]>
这个结果是一个 QuerySet ,我们可以看到我们有两个对象,但我们只能读取 Board object 。那是因为我们没有在 Board model 中定义 __str__
method。
首先我们需要退出 Python Shell。
然后编辑 boards 中的 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
这时再进行上面的步骤,就会得到:
对于这个 QuerySet ,其具有类似 list 的某些用法,此处不再赘述。
我们可以使用 model Manager 来查询数据库并返回单个对象。为此,我们使用 get
method:
django_board = Board.objects.get(id=1)
django_board.name
输出:
'Django'
但是我们必须谨慎操作。如果我们试图获取一个不存在的对象,例如,一个 id=3
的 board,它会引发 exception。
我们可以对任何 model field 使用 get
method,但最好使用可以 唯一标识对象 的 field。否则,查询可能会返回多个对象,这将导致异常。
需要注意的是,get
查询是 区分大小写 的。
Model’s Operations 总结
下面是我们在本节中学习的方法和操作的摘要,使用 Board model 作为参考。大写的 Board 指的是 class,小写的 board 指的是 Board model class 的一个 instance/object:
Operation | 代码示例 |
---|---|
创建 object 而不保存 | board = Board() |
保存 object(创建或更新) | board.save() |
在数据库中创建并保存 object | Board.objects.create(name=’…’, description=’…’) |
列出所有 object | Board.objects.all() |
获取由 field 标识的单个object | Board.objects.get(id=1) |
Views, Templates 和 Static Files
目前我们已经有一个名为 home
的 view,显示“Hello, World!”在我们的 app 的 homepage 上。现在我们想要做的是在表格中显示一个 board 列表及一些其他信息。
首先要做的是导入 Board model 并列出所有现有的 boards:
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。我们只需要一个 broads 列表,渲染部分是 Django Template Engine (Django 模板引擎)的工作。
Django Template Engine
在 manage.py 和 boards 文件夹同级目录创建一个名为 templates 的新文件夹:
- myproject/
- manage.py
- myproject/
- boards/
- templates/ <– here!
然后在 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 Template 语言的一部分。上面的示例显示了如何使用 for
遍历对象列表。 {{ board.name }}
在 HTML 中呈现 board 的名称,生成动态的 HTML 文档。
在使用这个 HTML 页面之前,我们必须告诉 Django 在哪里可以找到我们的app 的 template。
打开 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 可以更直观地理解:
现在我们可以更新我们的主页视图:
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 模板:
<!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>
Testing the Homepage
让我们编写我们的第一个 test。现在,我们将在 board app 中的 tests.py 文件中进行编辑:
boards/tests.py
from django.urls 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)
这是一个非常简单的测试用例,但十分有用。我们正在测试响应的 status code(状态码)。status code 显示为 200 时表示 成功 。
如果有未捕获的异常、语法错误或其他任何情况,Django 将返回 status code 500,这意味着 内部服务器错误 。现在,假设我们的应用程序有 100 个 views。我们只用一个命令为所有 views 编写这个简单的测试,我们将能够测试是否所有视图都返回成功代码。如果没有自动化测试,我们将需要一一检查每一页。
执行 Django 的 test 套件:
python manage.py test
运行结果:
现在我们可以测试 Django 是否为请求的 URL 返回了正确的 view function。这也是一个有用的测试,因为随着我们的开发进展,urls.py module 会变得非常大与复杂。 URL conf 是解析正则表达式的,在某些情况下,我们有一个非常宽松的 URL,因此 Django 最终可能会返回错误的 view function。下面是实现测试的代码:
boards/tests.py
from django.urls 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 module 中列出的 URL 列表进行匹配。此测试将确保作为根 URL 的 /
返回 home view。
如果想查看有关测试执行的更多详细信息,需要将详细程度设置为更高级别:
python manage.py test --verbosity=2
运行结果:
详细程度决定了将输出到控制台的通知和调试信息的数量: 0 为无输出,1 为正常输出,2 为详细输出。
Static Files
Static Files (静态文件)是 CSS、JavaScript、字体、图像或可能用来组成用户界面的任何其他资源。
Django 提供了一些功能来帮助我们管理静态文件。这些功能在 INSTALLED_APPS
配置中已经列出的 django.contrib.staticfiles app 中。
有如此多可用的前端组件库,我们没有理由继续渲染基本的 HTML 文档。我们可以轻松地将 Bootstrap 4 添加到我们的项目中。 Bootstrap 是一个开源工具包,其用于使用HTML, CSS, 和 JavaScript所开发的项目。
在 manage.py 和 boards 文件夹同级目录创建一个名为 static 的新文件夹,然后在 static 文件夹中创建一个名为 css 的文件夹:
- myproject/
- manage.py
- myproject/
- boards/
- templates/
- static/ <– here!
- css/ <– and here!
访问 Bootstrap 官网 并下载最新版本的 Compiled CSS and JS version :
解压从 Bootstrap 网站下载的 bootstrap-x.x.x-dist.zip 文件,将文件 css/bootstrap.min.css 复制到我们项目的 css 文件夹中:
- myproject/
- manage.py
- myproject/
- boards/
- templates/
- static/
- css/
- bootstrap.min.css <– here!
- css/
下一步是指示 Django 在哪里可以找到 static files。打开 settings.py ,在文件底部,STATIC_URL 之后,添加以下内容:
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static'),
]
这其实与我们在 Templates 处的设置 方法相同。
现在我们要在 template 中加载 static files(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>
<!-- body suppressed for brevity ... -->
</body>
</html>
首先,我们使用 template 开头的 {% load static %}
加载 Static Files App template 标签。{% 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 。
现在我们可以编辑模板以利用 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>
<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>
运行结果如下:
Django Admin 简介
到目前为止,我们正在使用交互式控制台(python manage.py shell
)添加新的 broads。但我们需要一种更好的方法。我们将实现一个管理界面,供网站管理员管理。
当我们开始一个新项目时,Django 已经配置了 INSTALLED_APPS 中列出的 Django Admin 。
Django Admin 的一个很好的用例是在博客中:作者可以使用它来撰写和发表文章。另一个例子是电子商务网站:工作人员可以在其中创建、编辑、删除产品。
现在,我们将配置 Django Admin 来维护我们的 app 的 boards。
让我们从创建 Admin 帐户开始:
python manage.py createsuperuser
填写相应的内容后,运行我们的服务器并打开 http://127.0.0.1:8000/admin/ 。
可以看到 Admin 的登录界面:
登录后的界面:
它已经配置了一些功能。在这里我们可以添加 user 和 group 来管理权限。稍后我们将探讨更多这些概念。
添加 Board model 非常简单。打开boards目录下的admin.py文件,添加如下代码:
boards/admin.py
from django.contrib import admin
from .models import Board
admin.site.register(Board)
保存 admin.py 文件,然后在浏览器上刷新页面,即可看到:
之后我们即可在此界面对网站进行管理。