简介
欢迎来到Django教程的第二部分!在上一部分中,我们安装了所需的python3.6,并在虚拟环境中运行django1.11,同时也已经创建了我们的第一个项目。接下来,我们将继续在同一个项目中编写代码。
在下一节中,我们讨论将要开发的项目来了解一些背景知识。然后学习Django的基础知识:模型(models
)、管理员(admin
)、视图(views
)、模板(templates
)和路由(URLs
)。
让我们动起来!
网页面板
不知道大家是不是这样,就我个人而言,通过实际的例子和代码片段可以让我更快地掌握想要学习的内容。我较难通过抽象简单的例子,如Class A
、Class B
和经典示例foo(bar)
,来学习和掌握知识。所以我不打算这么干,在开始学习模型、视图以及其他东西前,让我们花点时间来简单讨论一下我们要开发的这个项目。
如果你有web开发的经验,觉得这部分比较啰嗦的话,可以简单浏览一下插图了解我们要构建的东西,就可以跳到本教程的模型(models)部分。
但如果你是一位web开发新手,那就强烈建议你继续阅读,这将给你讲解关于web应用程序建模和设计的方法,web开发,乃至程序开发,可不仅仅知识编码而已。
用例图
我们的项目是一个论坛,整体思路是通过维护几个版块(board),它们类似于类别,区分各个版块的不同内容方向。在某一版块里,用户可以通过创建新的主题(topic)来开始新的讨论。在某一主题中,其他用户可以发布、回复帖子(post)来参与讨论。
我们需要找到一种方法来区分普通用户和管理员用户,因为只有管理员才应该创建新的版块。下面是我们的主要用例和每种类型用户的角色的概述:
图 1: 用户权限用例图类图
我们可以依据用例图来考虑项目的实体(entities)。实体就是是我们将创建的模型,它与我们的Django应用程序将处理的数据密切相关。
为了能够实现上一节中描述的用例,我们至少需要实现以下模型:版块(board)、主题(topic)、帖子(post)和用户(user)。
图 2 : 基本类图花时间思考模型之间的相互关系也至关重要。图中的实线告诉我们,在主题(topic)中,需要一个字段来标识它属于哪个版块(board)。同样,帖子(post)需要一个字段来表示它属于哪个主题(topic),这样就可以在讨论中只列出帖子(post)在一个特定的主题(topic)中创建。最后,需要在主题(topic)和帖子(post)中增加字段来记录是谁发起了讨论,这样就可以确定谁在发布回复。
我们还可以给版块(board)与用户(user)模型建立联系,这样就可以确定谁创建了一个特定的版块(board)。但这些信息与应用程序无关。还有其他方法可以跟踪这些信息,稍后我们再进行探讨。
现在我们已经有了基本的类图,必须考虑每个模型将携带什么样的信息。这个过程很容易考虑得过于宽泛,我们试着把注意力集中在重要的点上,仅关注开发所需的信息。后面可以使用迁移(migrations)来改进模型,我们将在下一篇教程中详细介绍这一点。
下图的设计包含了我们现在需要的信息:
图 3: 包含基本关系和基础信息的类图这个类图强调了模型之间的关系,线条和箭头最终将被转换成字段。
对于Board模型,我们将从两个字段开始:name
和description
。name
字段必须是唯一的,以避免重复的线路板名称。这个description
只是为了给大家一个提示,说明这个版块是关于什么的。
Topic模型由四个字段组成:subject
,last_update
上次更新的时间,用于定义主题排序,starter
用于标识发起Topic的User,以及一个名为board
的字段,用于定义特定Topic属于哪个Board。
Post模型将有一个message
字段,该字段将用于存储帖子回复的文本;一个created_at
的时间字段,主要用于在Topic内对Post进行排序;一个updated_at的时间字段,用于记录User何时编辑了某个帖子。与时间字段一样,我们还需要引用User模型:created_by
和update_by
。
最后是User模型,在类图中,只提到了字段username
、password
、email
和is_superuser
标志,这就是现在要使用的全部内容。需要注意的是,我们不需要创建User模型,因为Django已经在contrib包中提供了一个内置的User模型。我们可以直接使用它。
关于类图中的多重性(数字1
、0..*
,等等),这里简单说明一下:
一个Topic必须与一个(1
)Board关联(即不能为空),一个Board可以关联多个Topic或者没有(0..*
)。也就是说Board可能没有一个Topic而存在。
一个Topic应该至少有一个Post(主题帖Post),也可以有很多Post(1..*
)。一个Post必须与一个Topic(1
)关联。
一个Topic必须有一个且只有一个User关联:主题发起者User(1
)。一个User可能有许多或没有Topic(0..*
)。
一个Post必须有一个,并且只有一个User与Post的created_by
(1
)关联。一个User可能创建了许多或没有Post(0..*
)。Post还有一个updated_by
字段与User关联,多重性0..1
表示updated_by
字段可能为空(Post未编辑),最多只能关联一个User(最多被1个用户编辑)。
<span id='Figure_4'>
绘制类图的另一种方法是强调字段而不是模型之间的关系:
</span>
上面的表示与前面的表示相同,它也更接近于我们将使用Django模型API设计的内容。在这个类图中,我们可以更清楚地看到在Post模型中,关联topic
、created_by
、updated_by
成为模型字段。另一个有趣的地方是,在Topic模型中,我们现在有一个名为posts()
的operation(一个类方法)。我们将通过实现一个反向关系来实现这一点,Django将在数据库中自动执行一个查询,返回属于特定Topic的所有Post列表。
类图完成了,这就够了!为了绘制本节中的图表,我使用了StarUML工具。
线框
在花了一些时间设计应用程序模型之后,我喜欢创建一些线框草图来定义需要做的事情,并且对我们的发展方向有一个清晰的了解。
然后基于这些线框,我们可以更深入地了解应用程序中涉及的实体。
<span id='Figure_5'>
首先,我们需要在主页上显示所有的版块Board:
</span>
如果用户点击一个版块Board的链接,比如Django,应该显示出所有主题Topic:
图6 : 版块内容,显示该版块下的所有主题这里有两个主要功能:用户单击“New topic(新建主题)”按钮创建新主题,或者用户单击某个主题查看或参与讨论。
“New topic(新建主题)”页面:
图7 : “New topic(新建主题)”页面进入主题后的页面,显示帖子和讨论:
图8 :指定主题下的帖子如果用户单击“Reply(回复)”按钮,将看到下面的页面,并以时间倒序显示帖子的摘要(最新消息在最上面):
图9 :回复帖子的页面要绘制线框,可以使用免费工具draw.io。
模型Models
这些模型基本上是应用程序数据库表的表示。在本节中,我们要做的是创建上一节中建模的类的Django表示:Board、Topic和Post。User模型已经在Django的一个名为auth的内置应用程序中定义,它在INSTALLED APPS
配置下的命名空间django.contrib.auth
中.
我们将在boards/models.py文件中完成所有的模型工作,(参考 图 4)。完成后的内容如下:
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的子类。每个类将被转换为database table。每个字段是django.db.models.Field的子类(Django core的定义),并将被转换为database columns。
字段CharField
、DateTimeField
等都是django.db.models.Field,它们包含在Django core中,随时可以使用。
这里我们只使用CharField
、TextField
、DateTimeField
和ForeignKey
字段来定义模型。但是Django提供了各种各样的选项来表示不同类型的数据,比如IntegerField
、BooleanField
、DecimalField
等等,根据实际情况进行定义。
有些字段需要参数,例如CharField
。我们应该设置一个max_length
。此信息将用于创建database column。Django需要知道database column的大小。Django Forms API还将使用max_length
参数来验证用户输入。这个我们后面再探讨。
在Board模型定义中,我们还为name
字段设置了参数unique=True
,顾名思义,这个字段将在数据库级别强制唯一。
在Post模型中,created_at
字段有一个可选参数,auto_now_add
设置为True
。这将指示Django在创建Post对象时自动设置为当前的日期和时间。
在模型之间创建关系的一种方法是使用ForeignKey
字段。它将在模型之间创建链接,并在数据库级别创建适当的关系。ForeignKey
字段需要一个位置参数引用与之相关的模型。
例如,在Topic模型中,board
字段是Board模型的ForeignKey
。它告诉Django一个Topic实例只与一个Board实例相关。related_name
参数将用于创建反向关系,其中Board实例将访问属于它的Topic实例列表。
Django会自动创建这种反向关系,related_name
是可选的,如果我们不为它设置一个名称,Django将用以下规则生成它:(class_name)_set
。例如,在Board模型中,Topic实例将在Topic_set
属性下可用。我们把它改名为topics
,让它感觉更自然。
在Post模型中,updated_by
字段设置related_name='+'
。这个设置告诉Django我们不需要这种反向关系,所以Django将会忽略这个反向关系。
下面您可以看到类图和用Django生成模型的源代码之间的关系。绿线表示我们如何处理反向关系。
此时,你可能会问:“主键呢?我应该怎么处理?”如果我们不为模型指定主键,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就会使用这个文件来创建数据库表和列database tables and columns。
迁移文件被转换成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内置应用程序中的迁移文件,这些文件列在INSTALLED_apps
里。
Applying boards.0001_initial... OK
指的就是我们前面生成的迁移文件。
好了,数据库准备好了,我们可以继续开发了!
** 注意:** SQLite是一款可以在生产环境直接使用的数据库产品,许多公司的成千上万个产品都是使用的SQLite数据库,如Android和iOS设备、还有主流的网络浏览器、Windows 10、macOS等等。但它并不适用于所有场景,SQLite在大容量网站、写密集应用、巨型数据集、高并发的使用场景中的表现,不如MySQL、PostgreSQL、Oracle等数据库。
我们将在项目开发期间使用SQLite,因为它很方便,而且不需要安装任何其他东西。需要将项目部署到生产环境中时,我们将切换到PostgreSQL,对于简单的网站来说这样就可以了。但对于复杂的网站,最好使用相同的数据库进行开发和生产。
尝试使用Models API
使用Python进行开发的最大优点之一是交互式shell。我一直在用它,这是一种快速的方法,可以用它调试Libraries和APIs功能。
使用manage.py工具可以直接启动shell:
python manage.py shell
这与只需键入python
调用交互式控制台非常相似,使用python manage.py shell
会自动将我们的项目添加到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
方法可以创建或者更新对象的数据。我们这里创建Board对象时并未赋值id,Django会自动为它分配一个值,通过下面的方式我们可以查看值:
board.id
1
你同样可以通过这种方式访问该对象的其他属性:
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,在后面的教程中我们会深入探讨。这个数据集就是在数据库中读取出来的对象列表。这里我们可以看出,数据库中存储了2条版块数据对象,打印的时候输出为对象类型Board object,这是因为我们没有定义和实现Board类的__str__
方法。
__str__
方法是对象的字符串描述,这里我们可以返回版块的名称。
先让我们离开shell:
exit()
编辑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
现在让我们再来试试:
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.
同样的,当我们想用模型管理器(Model Manager)查询某一个对象时,可以使用get
方法:
django_board = Board.objects.get(id=1)
django_board.name
'Django'
在使用get
方法时一定要注意,如果我们试图获取一个不存在的对象,比如说id=3
的版块,它将抛出一个异常boards.models.DoesNotExist
:
board = Board.objects.get(id=3)
boards.models.DoesNotExist: Board matching query does not exist.
我们也可以在get
方法里使用其他的对象属性进行查询,但最好是可以定位到某一个对象的属性值,否则这个查询方法将返回多个结果可能导致错误。
Board.objects.get(name='Django')
<Board: Django>
也需要注意查询语句是对大小写敏感的case sensitive,通过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页面中显示版块Board。
页面视图(Views),模版(Templates),和静态文件(Static Files)
现在我们的应用已经有一个显示Hello, World!
的页面home
。
<details>
<summary>原始版本</summary>
原始版本的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),
]
</details>
修订版本的myproject/urls.py
from django.urls import re_path
from django.contrib import admin
from boards import views
urlpatterns = [
re_path(r'^$', views.home, name='home'),
re_path(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。对于这个简单的视图,只需要一个版块对象的列表,然后页面渲染部分就交给Django Template Engine来完成吧。
Django Template Engine(模板引擎)
在项目目录下,与boards同级的位置创建一个名为templates的文件夹:
myproject/
|-- myproject/
| |-- boards/
| |-- myproject/
| |-- templates/ <-- 是的,放这里!
| +-- manage.py
+-- venv/
在templates目录下,创建一个html文件,取名为home.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
拼起来,这样就可以得到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模板,添加一个列表:
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>
测试首页(homepage)
测试将始终贯穿这个教程,我们将讨论不同的测试概念和策略。
让我们来创建第一个测试用例,这里将会用到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)
这是一个简单的测试用例,但是它非常有用。我们测试请求的返回的状态码(status_code),断言(assertEquals)返回的状态码为200
,200
表明请求成功(success).
通常我们可以直接在控制台中看到请求返回的状态码(status_code):
如果出现未捕获的异常、语法错误或其他任何情况,Django将返回状态代码500,这意味着Internal Server Error。现在,假设我们的应用程序有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返回了正确的view函数。这也是一个有用的测试,因为随着开发的进展,您将看到urls.py模块可以变得非常大和复杂,而URL路由是通过正则匹配定位的,在某些特定情况下可能匹配到目标以外的URL,因此Django最终可能返回错误的view函数。
按下面的方式编写测试用例:
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/
返回的是首页(homepage)。
再测试一次看看:
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表示详细输出。
静态文件(Static Files)配置
静态文件指的是CSS、JavaScripts、字体、图片或我们可以用来生成界面的任何其他资源。
事实上,Django不提供这些文件,除非能在开发过程中为我们提供更多的便利。不过Django提供了一些功能来帮助我们管理静态文件。这些功能由已在INSTALLED_APPS
配置中列出的django.contrib.staticfiles应用程序提供。
有这么多的前端组件库可用,没有理由继续使用简陋的HTML文档,我们可以很容易地将bootstrap4添加到我们的项目中。Bootstrap是一个开源工具包,用于使用HTML、CSS和JavaScript进行开发。
在myproject根目录下,与boards、templates、myproject文件夹一起,新建一个名为static的文件夹,在static文件夹中再创建一个名为css的文件夹:
myproject/
|-- myproject/
| |-- boards/
| |-- myproject/
| |-- templates/
| |-- static/ <-- 这里
| | +-- css/ <-- 这里这里!
| +-- manage.py
+-- venv/
打开getbootstrap.com,下载他们的最新Release版本:
下载Compiled CSS and JS版本.
在电脑上打开下载好的bootstrap-4.0.0-beta-dist.zip文件(这里可能是其他更新的版本),并将css/bootstrap.min.css拷贝到刚刚创建的css目录下:
myproject/
|-- myproject/
| |-- boards/
| |-- myproject/
| |-- templates/
| |-- static/
| | +-- css/
| | +-- bootstrap.min.css <-- 这里
| +-- manage.py
+-- venv/
下一步就是配置Django项目,让它能够定位到静态文件。打开settings.py文件,在文档最后面,紧跟STATIC_URL
添加下面的代码:
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static'),
]
还记得吗?跟之前配置TEMPLATES
一样。
现在我们需要在我们的html文件中使用这些静态文件(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>
首先我们在模板最开始加上{% load static %}
。
{% static %}
这个标签会通过设置文件settings.py去找到资源文件的目录STATIC_URL
,在这里就将{% static 'css/bootstrap.min.css' %}
表示为/static/css/bootstrap.min.css,最终就是访问到http://127.0.0.1:8000/static/css/bootstrap.min.css。
假如需要将STATIC_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 %}
标签就可以了。我们会在后面更加详细的谈到这个问题,但现在,所有的配置都搞定了。
刷新链接127.0.0.1:8000,我们就可以看到它的作用:
现在我们来编辑一下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>
然后再刷新:
到目前为止,我们还是使用交互式控制台(python manage.py shell
)来对数据进行维护和管理。但我们需要一个更好的方法。在下一节中,将实现一个管理界面来进行管理。
Django管理模块简介
当创建一个项目时,Django就自动为我们创建了Django Admin应用程序并记录在INSTALLED_APPS
下。
举两个用户管理权限的例子:在博客类应用中,作者角色有编写和发布文章的权限;而在电商类网站中,工作人员有创建、编辑、删除产品的权限。
现在,我们先为Django管理员添加版块的管理权限。
首先创建一个超级管理员账户:
python manage.py createsuperuser
按照说明补充信息(可以自行决定信息内容):
Username (leave blank to use 'vitorfs'): admin
Email address: admin@example.com
Password:
Password (again):
Superuser created successfully.
现在让我们打开网页: http://127.0.0.1:8000/admin/
使用刚刚创建的超级管理员进行登录,输入username和password:
现在已经可以看到默认为我们添加了一些功能,可以配置Users和Groups的权限,稍后我们再对此进行深入讨论。
添加对版块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按钮:
然后点击save按钮:
我们可以打开首页http://127.0.0.1:8000来看看是否添加成功:
小结
在本教程中,我们探讨了许多新概念。我们为我们的项目定义了一些需求,创建了第一个模型,迁移了数据库,开始使用模型API。我们创建了第一个视图并编写了一些单元测试。我们还配置了Django模板引擎、静态文件,并将bootstrap4库添加到项目中。最后,我们简要介绍了Django管理接口。
项目的源代码可以在GitHub上找到。项目的当前状态可以在发布标签v0.2-lw下找到。下面的链接将带您找到正确的位置:
网友评论