Django初学者入门指南2-基础知识(译&改)

Django初学者入门指南1-初识(译&改)

Django初学者入门指南2-基础知识(译&改)

Django初学者入门指南3-高级概念(译&改)

Django初学者入门指南4-登录认证(译&改)

Django初学者入门指南5-存储数据(译&改)

Django初学者入门指南6-基于类的页面(译&改)

Django初学者入门指南7-部署发布(译&改)

>>原文地址 By Vitor Freitas

简介

欢迎来到Django教程的第二部分!在上一部分中,我们安装了所需的python3.6,并在虚拟环境中运行django1.11,同时也已经创建了我们的第一个项目。接下来,我们将继续在同一个项目中编写代码。

在下一节中,我们讨论将要开发的项目来了解一些背景知识。然后学习Django的基础知识:模型(models)、管理员(admin)、视图(views)、模板(templates)和路由(URLs)。

让我们动起来!


网页面板

不知道大家是不是这样,就我个人而言,通过实际的例子和代码片段可以让我更快地掌握想要学习的内容。我较难通过抽象简单的例子,如Class AClass 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模型,我们将从两个字段开始:namedescriptionname字段必须是唯一的,以避免重复的线路板名称。这个description只是为了给大家一个提示,说明这个版块是关于什么的。

Topic模型由四个字段组成:subjectlast_update上次更新的时间,用于定义主题排序,starter用于标识发起TopicUser,以及一个名为board的字段,用于定义特定Topic属于哪个Board

Post模型将有一个message字段,该字段将用于存储帖子回复的文本;一个created_at的时间字段,主要用于在Topic内对Post进行排序;一个updated_at的时间字段,用于记录User何时编辑了某个帖子。与时间字段一样,我们还需要引用User模型:created_byupdate_by

最后是User模型,在类图中,只提到了字段usernamepasswordemailis_superuser标志,这就是现在要使用的全部内容。需要注意的是,我们不需要创建User模型,因为Django已经在contrib包中提供了一个内置的User模型。我们可以直接使用它。

关于类图中的多重性(数字10..*,等等),这里简单说明一下:

一个Topic必须与一个(1Board关联(即不能为空),一个Board可以关联多个Topic或者没有(0..*)。也就是说Board可能没有一个Topic而存在。

版块和主题的一对多关系

一个Topic应该至少有一个Post(主题帖Post),也可以有很多Post1..*)。一个Post必须与一个Topic1)关联。

主题和帖子的一对多关系

一个Topic必须有一个且只有一个User关联:主题发起者User1)。一个User可能有许多或没有Topic0..*)。

用户和主题的一对多关系

一个Post必须有一个,并且只有一个UserPostcreated_by1)关联。一个User可能创建了许多或没有Post0..*)。Post还有一个updated_by字段与User关联,多重性0..1表示updated_by字段可能为空(Post未编辑),最多只能关联一个User(最多被1个用户编辑)。

帖子和用户的对应关系

<span id='Figure_4'>
绘制类图的另一种方法是强调字段而不是模型之间的关系:
</span>

图 4: 强调类字段的类图

上面的表示与前面的表示相同,它也更接近于我们将使用Django模型API设计的内容。在这个类图中,我们可以更清楚地看到在Post模型中,关联topiccreated_byupdated_by成为模型字段。另一个有趣的地方是,在Topic模型中,我们现在有一个名为posts()operation(一个类方法)。我们将通过实现一个反向关系来实现这一点,Django将在数据库中自动执行一个查询,返回属于特定Topic的所有Post列表。

类图完成了,这就够了!为了绘制本节中的图表,我使用了StarUML工具。

线框

在花了一些时间设计应用程序模型之后,我喜欢创建一些线框草图来定义需要做的事情,并且对我们的发展方向有一个清晰的了解。

然后基于这些线框,我们可以更深入地了解应用程序中涉及的实体。

<span id='Figure_5'>
首先,我们需要在主页上显示所有的版块Board
</span>

图 5: 项目首页,显示所有的版块

如果用户点击一个版块Board的链接,比如Django,应该显示出所有主题Topic

图6 : 版块内容,显示该版块下的所有主题

这里有两个主要功能:用户单击“New topic(新建主题)”按钮创建新主题,或者用户单击某个主题查看或参与讨论。

“New topic(新建主题)”页面:

图7 : “New topic(新建主题)”页面

进入主题后的页面,显示帖子和讨论:

图8 :指定主题下的帖子

如果用户单击“Reply(回复)”按钮,将看到下面的页面,并以时间倒序显示帖子的摘要(最新消息在最上面):

图9 :回复帖子的页面

要绘制线框,可以使用免费工具draw.io


模型Models

这些模型基本上是应用程序数据库表的表示。在本节中,我们要做的是创建上一节中建模的类的Django表示:BoardTopicPostUser模型已经在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

字段CharFieldDateTimeField等都是django.db.models.Field,它们包含在Django core中,随时可以使用。

这里我们只使用CharFieldTextFieldDateTimeFieldForeignKey字段来定义模型。但是Django提供了各种各样的选项来表示不同类型的数据,比如IntegerFieldBooleanFieldDecimalField等等,根据实际情况进行定义。

有些字段需要参数,例如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在大容量网站、写密集应用、巨型数据集、高并发的使用场景中的表现,不如MySQLPostgreSQLOracle等数据库。

我们将在项目开发期间使用SQLite,因为它很方便,而且不需要安装任何其他东西。需要将项目部署到生产环境中时,我们将切换到PostgreSQL,对于简单的网站来说这样就可以了。但对于复杂的网站,最好使用相同的数据库进行开发和生产。

尝试使用Models API

使用Python进行开发的最大优点之一是交互式shell。我一直在用它,这是一种快速的方法,可以用它调试LibrariesAPIs功能。

使用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)返回的状态码为200200表明请求成功(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根目录下,与boardstemplatesmyproject文件夹一起,新建一个名为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/

使用刚刚创建的超级管理员进行登录,输入usernamepassword

现在已经可以看到默认为我们添加了一些功能,可以配置UsersGroups的权限,稍后我们再对此进行深入讨论。

添加对版块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下找到。下面的链接将带您找到正确的位置:

https://github.com/sibtc/django-ginners-guide/tree/v0.2-lw

上一节:Django初学者入门指南1-初识(译&改)

下一节:Django初学者入门指南3-高级概念(译&改)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容