Featured image of post 学习《Django完整初学者指南》的笔记2

学习《Django完整初学者指南》的笔记2

引言

这一节学习了,Django开发中的基础元素:models, admin, views, templates, 和 urls

用例图

use-case-diagram

  • User 用户可以创建新的主题和回复帖子。

  • Admin 用户拥有普通用户的权限,并且可以创建新的板块。

类图

class-diagram

  • 论坛需要4个模型,其中 Board, Topic, Post 是新建的模型,User 是Django系统的用户权限管理模块中的一个模型。
  • Board 是论坛的板块,Topic 是每个板块(Board)下的主题,一个 Board 可以有多个 Topic,所以 topics 就是 0..*
  • Post 是主题(Topic)下的帖子, 每个 Topic 至少有1个 Post,所以 posts 就是 1..*
  • 每个 Topic 由一个用户(User)创建,同一个 User 可以创建多个 Topic ,一个 Topic 只能被一个 User 创建,所以 starter 就是 0..*
  • 每个 Post 由一个用户(User)创建,同一个 User 可以创建多个 Post ,一个 Post 只能被一个 User 创建,所以 created_by 就是 1
  • 每个用户(User)只能更新自己创建的 Post ,所以 updated_by 只能是 0..1

Model类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 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) # 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) # auto_now_add=True 创建时自动填入当前时间
    # models.ForignKey Django 1.*版本 on_delete 默认为 models.CASECADE 所以不用添加;
    #                  Django 2.*版本以后必须明确指明。
    board = models.ForeignKey(Board, on_delete=models.CASCADE, related_name='topics')
    starter = models.ForeignKey(User, on_delete=models.CASCADE, related_name='topics')
    # related_name 为外键关联反向查询时使用的关键字,若不设置默认为 Model名_set
    # 例如:board.topics.all() 当设置了 related_name 时,查找某个 Board 下的所有 Topic
    #      board.topic_set.all() 没有设置 related_name 时,可以这样查找,效果同上
    
class Post(models.Model):
    message = models.TextField(max_length=4000)
    topic = models.ForeignKey(Topic, on_delete=models.CASCADE, related_name='posts')
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(null=True)
    created_by = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts'

借用原作者的一张图说明情况

use-case-diagram

ORM自动生成数据库

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
$ python manage.py makemigrations  # 生成数据库迁移文件
Migrations for 'boards':
  boards/migrations/0001_initial.py
    - Create model Board
    - Create model Topic
    - Create model Post
 
$ python manage.py migrate   # 执行迁移    
Operations to perform:
  Apply all migrations: admin, auth, boards, contenttypes, sessions
Running migrations:
  Applying admin.0003_logentry_add_action_flag_choices... 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 boards.0001_initial... OK

使用 Models API

  • 进入据用当前项目环境变量的 python 交互环境
1
$ python manage.py shell
1
2
3
4
5
Python 3.7.2 (default, Feb 12 2019, 08:15:36)
[Clang 10.0.0 (clang-1000.11.45.5)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>>
  • 引入 models.py 中的 Board
1
>>> from boards.models import Board
  • 新建 Board 类对象的实例
1
>>> board = Board(name='Django', description='This is a board about Django.')
  • 保存 board 对象到数据库
1
>>> board.save()
  • 获取 board 对象的主键
1
2
>>> board.id
>>> 1

board 对象保存到数据库后,数据库会自动生成 id 主键,当调用 board.id 时,可以获得该主键。

注:当对象没有 save() 时,不能获取 id

  • 获取其它属性
1
2
>>> board.name
'Django'
1
2
>>> board.description
'This is a board about Django.'
  • 修改属性值
1
2
>>> board.description = 'Django discussion board.'
>>> board.save()

每个 Django model 类都有一个特殊的属性,我们叫它 模型管理器(Model Manager),你可以通过 model 对象的 objects 属性访问它,它主要用于对象在数据库中的查询操作。例如,我们可以直接用它创建对象:

1
>>> board = Board.objects.create(name='Python', description='General discussion about Python.')  # 直接创建,并没有使用save()方法保存
1
2
>>> board.id 
2
1
2
>>> board.name
'Python'

现在我们有2条 Board 记录,我们可以使用 objects 列出所有数据库中存在的 boards

1
2
>>> Board.objects.all()
<QuerySet [<Board: Board object>, <Board: Board object>]>

结果是一个 QuerySet 对象,之后会详细了解这个对象。我们能看出 QuerySet 对象中包含着 2 个 Board object ,但是看不出对象中的内容。当打印一个对象时,会调用这个对象的 __str__ 方法,我们只要定义了这个方法,并将有用的信息的从方法返回,当打印对象时,就会输出 __str__ 方法返回的内容。

编辑 board app 下的 models.py 文件:

1
2
3
4
5
6
class Board(models.Model):
    name = models.CharField(max_length=30, unique=True)
    description = models.CharField(max_length=100)

    def __str__(self):
        return self.name

这时,再执行以上语句:

1
2
>>> Board.objects.all()
<QuerySet [<Board: Django>, <Board: Python>]>

这样好多了。

我们可以像使用 list 一样使用 QuerySetQuerySet 是可迭代的:

1
2
3
4
5
6
>>> board_list = Board.objects.all()
>>> for board in board_list:
...    print(board.description)

Django discussion board.
General discussion about Python.

可以使用 Managerget 方法从数据库获得一条对象记录:

1
2
3
>>> board = Board.objects.get(id=1)
>>> board.name
'Django'

get 函数可以使用 model 中的任何属性字段做条件,但 get 方法只允许返回一条对象记录,所以最好选择能够唯一标识 model 对象的属性字段做条件,否则如果通过条件找到多个符合条件的对象时会报错: boards.models.Board.MultipleObjectsReturned: ... ,当找不到符合条件的记录时,即对象不存在时也会报错:boards.models.Board.DoesNotExist:...

Model的操作总结

操作代码
新建一个对象(不保存数据库)board = Board()
保存一个对象到数据库(create or update)board.save()
一次创建并保存对象Board.objects.create(name=’…’, description=’…')
List all objectsBoard.objects.all()
通过一个唯一标识获取一条对象记录Board.objects.get(id=1)

Views, Templates, and Static Files

我们之前已经有了一个视图,它是由 urlview 两部分组成的

  • myproject/urls.py
1
2
3
4
5
6
7
8
9
from django.urls import path, re_path
from django.contrib import admin

from boards import views

urlpatterns = [
    path('', views.home, name='home'),  # url 指定我们访问 http://host:port/ 时,访问这个view
    path('admin/', admin.site.urls),
]
  • boards.views.py
1
2
3
4
from django.http import HttpResponse

def home(request):
    return HttpResponse("Hello, World!!!")

现在,我们基于此,修改 views.py 在首页上显示 Board 对象的列表

  • boards.views.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from django.http import HttpResponse

from boards.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)

访问首页如图:

image-20190412152606878

Templates设置

在项目的根目录新建 templates 文件夹

1
2
3
4
5
6
7
myproject/
 |-- myproject/
 |    |-- boards/
 |    |-- myproject/
 |    |-- templates/   <-- here!
 |    +-- manage.py
 +-- venv/

templates 目录中新建一个 home.html 文件

  • templates/home.hmtl
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<!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 }} 显示每一个 Board 对象的 name 属性。

myproject 中找到 settings.py 文件,修改文件中以下内容:

  • myprotject/settings.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [
            # 将项目根目录下的templates文件夹设置为TEMPLATES的一个搜索目录
            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',
            ],
        },
    },
]

修改 boards/views.py 文件,在 home 函数中使用 home.html 模板

  • boards/views.py
1
2
3
4
5
6
7
from django.shortcuts import render

from boards.models import Board

def home(request):
    boards = Board.objects.all()
    return render(request, 'home.html', {'boards': boards})

访问首页如图:

image-20190412154241941

home.html 模板中使用表格从新格式化数据

  • templates/home.html
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<!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>

访问首页如图:

image-20190412154558449

测试首页

boards/tests.py 中写我们的第一个测试

  • boards/tests.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Django 1.* 中 reverse 的位置
# from django.core.urlresolvers import reverse 
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)

这是一个非常简单的测试例子,我们建议响应状态码,如果为 200 表示首页访问成功。 执行 Django 测试

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$ python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
.
----------------------------------------------------------------------
Ran 1 test in 0.046s

OK
Destroying test database for alias 'default'...
(myproject) (base)

接下来,我们测试 url 匹配,当访问网站根目录时,是否访问的是 boards/views.py 中的 home 函数

  • boards/tests.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
from django.urls import reverse, 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 函数反向解析 url ,在 Django 中可以根据路径反向找到对应的处理函数。

在此执行测试

1
2
3
4
5
6
7
8
9
$ python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
..
----------------------------------------------------------------------
Ran 2 tests in 0.015s

OK
Destroying test database for alias 'default'...

如果想看到测试的更多信息,可以设置 --verbosity 参数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
$ 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 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 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.033s

OK
Destroying test database for alias 'default' ('file:memorydb_default?mode=memory&cache=shared')...

--verbosity 参数: 0 表示无输出,1 表示正常输出, 2 表示详细输出

Static Files设置

静态文件包括CSS, JavaScripts, Fonts, Images和一些其它的网站使用的资源文件。 生产环境中 Django 是不提供静态资源服务的,我们应该使用 Nginx 等服务器去提供静态资源访问服务,但在开发模式下,Django 提供一个效率不高的静态资源服,这样便于我们开发。Django 还提供一些功能来方便我们管理静态资源,这些功能在 django.contrib.staticfiles app 中,在 settings.py 文件中的 INSTALLED_APPS 中已经默认配置了该 app。

前端我们使用开源的 Bootstrap 4 作为前端组件。

我们在项目跟目录创建 static 目录, 并在该目录下新建 css 目录

1
2
3
4
5
6
7
8
9
myproject/
 |-- myproject/
 |    |-- boards/
 |    |-- myproject/
 |    |-- templates/
 |    |-- static/       <-- here
 |    |    +-- css/     <-- and here
 |    +-- manage.py
 +-- venv/

访问 getbootstrap.com 下载最新的编译版本的CSS和JS(Compiled CSS and JS),并解压将其中的 bootstrap.min.css 放入 css 目录

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
myproject/
 |-- myproject/
 |    |-- boards/
 |    |-- myproject/
 |    |-- templates/
 |    |-- static/
 |    |    +-- css/
 |    |         +-- bootstrap.min.css    <-- here
 |    +-- manage.py
 +-- venv/

settings.py 文件中, 在 STATIC_URL 之后添加 STATICFILES_DIRS

1
2
3
4
5
STATIC_URL = '/static/'

STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'static'),
]

在模板中引入 Bootstrap 样式

  • templates/home.hmtl
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
{% 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 %} 标签引入静态文件App,之后我们就可以在模板中使用 {% static %} 标签,在这个例子中 {% 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 。例如:如果 STATIC_URL=http://static.example.com/,那么 {% static 'css/bootstrap.min.css' %} 返回 http://static.example.com/css/bootstrap.min.css

此时首页已经有了一些默认样式的变化:

image-20190412165228161

根据 Bootstrap 重新调整 home.html 模板

  • templates/home.html
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
{% 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-dark">
        <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>

调整后结果

image-20190412165635664

Django Admin简介

当开始一个新的Django项目时,配置文件包含一个 django.contrib.admin 配置在 INSTALLED_APPS 中。这个App为Django提供了一个管理后台可以很好的来管理数据表的数据。

现在,我们可以配置我们 Django 项目的 Django Admin 来管理我们的数据。

首先,创建一个超级用户

1
python manage.py createsuperuser

按照提示操作:

1
2
3
4
5
Username (leave blank to use 'codepanda'): admin
Email address: admin@example.com
Password:
Password (again):
Superuser created successfully.

现在使用浏览器访问:http://127.0.0.1:8000/admin/ 进入登录页面

image-20190519105313775

输入刚刚创建的超级用户的 usernamepassword 登录

image-20190519110342772

将我们自定义的 Board 模型加入到后台管理是非常简单的,只需要在 app boards 下 找到 admin.py 文件,加入如下代码:

boards/admin.py

1
2
3
4
from django.contrib import admin
from .models import Board

admin.site.register(Board)

保存 admin.py 文件,并刷新后台页面:

image-20190519110903330

现在, Board 模型已经出现在后台管理了, 单击 Boards链接可以查看已存在的 boards 数据:

image-20190519111041522

我们可以单击 ADD BOARD 按钮添加一个新的 board:

image-20190519111151794

点击 save 按钮:

image-20190519111243279

如果一切工作正常我们访问 http://127.0.0.1:8000 时,可以看到新添加的 board:

image-20190519111403721


结论

在这一节里,我们创建了第一个 models 并迁移了数据库,使用了 Models API。 我们创建了第一个 View 、写了一些单元测试。 我们配置了模板引擎、静态文件、并加入了 Bootstrap 4 到我们的项目里,最后,我们简单的使用了 Django Admin 界面。

comments powered by Disqus
Built with Hugo
主题 StackJimmy 设计