【第二部分-django论坛从搭建到部署】一个完整Django入门指南

【学习目录】

【第一部分-django论坛从搭建到部署】一个完整的Django入门指南学习笔记

【第二部分-django论坛从搭建到部署】一个完整的Django入门指南学习笔记

【第三部分-django论坛从搭建到部署】一个完整的Django入门指南学习笔记

【第四部分-django论坛从搭建到部署】一个完整的Django入门指南学习笔记

【第五部分-django论坛从搭建到部署】一个完整的Django入门指南学习笔记

【第六部分-django论坛从搭建到部署】一个完整的Django入门指南学习笔记

【第七部分-django论坛从搭建到部署】一个完整的Django入门指南学习笔记(未翻译)


【本文目录】



前沿

在第一节中,我们安装了项目所需要的一切:Python3.6以及在虚拟环境中运行的Django2.0,这部分教程继续在项目上编写代码。

开始写代码前,先讨论下项目的相关背景知识,然后再学习 Django 的基础,包括:模型、管理后台、视图、模板和路由。


论坛项目

在进入模型,视图等其它有趣的部分之前,花点时间简要地讨论我们将要开发的这个项目。

例图

我们的项目是一个论坛系统,整个项目的构思是维护几个论坛版块(boards),每个版块就像一个分类一样。在指定的版块里面,用户可以通过创建新主题(Topic)开始讨论,其他用户参与讨论回复。

我们需要找到一种方法来区分普通用户和管理员用户,因为只有管理员可以创建版块。下图概述了主要的用例和每种类型的用户角色:

类图

从用例图中,我们可以开始思考项目所需的实体类有哪些。这些实体就是我们要创建的模型,它与我们的Django应用程序处理的数据非常密切。

为了能够实现上面描述的用例,我们需要至少实现下面几个模型:Board,Topic,Post和User。

  • Board:版块
  • Topic:主题
  • Post:帖子(译注:其实就是主题的回复或评论)

类与类之间的实线告诉我们,在一个主题(Topic)中,我们需要有一个字段(译注:其实就是通过外键来关联)来确定它属于哪个版块(Board)。

同样,帖子(Post)也需要一个字段来表示它属于哪个主题,这样我们就可以列出在特定主题内创建的帖子。

最后,我们需要一个字段来表示主题是谁发起的,帖子是谁发的。

用户和版块之间也有联系,就是谁创建的版块。但是这些信息与应用程序无关。还有其他方法可以跟踪这些信息,稍后你会看到。

现在我们的类图有基本的表现形式,我们还要考虑这些模型将承载哪些信息。这很容易让事情变得复杂,所以试着先把重要的内容列出来,这些内容是我们启动项目需要的信息。后面我们再使用 Django 的迁移(Migrations)功能来改进模型,你将在下一节中详细了解这些内容。

但就目前而言,这是模型最基本的内容:

这个类图强调的是模型之间的关系,这些线条和箭头最终会在稍后转换为字段。

对于 Board 模型,我们将从两个字段开始:namedescriptionname字段必须是唯一的,为了避免有重复的名称。description 用于说明这个版块是做什么用的。

Topic 模型包括四个字段:subject 表示主题内容,last_update 用来定义话题的排序,starter 用来识别谁发起的话题,board 用于指定它属于哪个版块。

Post 模型有一个 message 字段,用于存储回复的内容,created_at 在排序时候用(最先发表的帖子排最前面),updated_at 告诉用户是否更新了内容,同时,还需要有对应的 User 模型的引用,Post 由谁创建的和谁更新的。

最后是 User 模型。在类图中,我只提到了字段 usernamepasswordemailis_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,TopicPostUser 模型被命名为内置应用叫 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)的实例表示,它们并将被转换为数据库的列

字段 CharFieldDateTimeField等等,都是 django.db.models.Field 的子类,包含在Django的核心里面,随时可以使用。

在这里,我们仅使用 CharFieldTextFieldDateTimeField,和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 指的是类,小写的 boardBoard 的一个实例(或对象)

操作 代码示例
创建一个对象而不保存 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

我的博客即将搬运同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=35dceaz1u604ot/21/