我们首先在终端命令行更新 pip3:
$ sudo pip3 install -U pip
安装 Django2 的最终版本 2.2.9:
$ pip install django==2.2.9
此外我们还要安装一些其它的基础工具包,先安装 ipython 和 mysqlclient 这两个,其它的在需要时再进行安装:
$ pip install ipython mysqlclient
使用命令pip freeze
查看当前 python 环境下已经安装的各种包及其依赖包:
2.创建Web项目
接下来我们来学习创建属于我们自己的 Web 项目。
执行命令django-admin startproject
初始化一个 Django 项目,后面的参数为项目名称,我们这里的项目名称定为 myProject。执行 tree myProject 命令查看项目的目录结构:
$ django-admin startproject myProject
$ ll
$ tree myProject
对主目录下的文件和目录依次说明:
- manage.py 项目的入口文件,在后面的实验中我们会大量使用它来执行一些命令用来创建应用、启动项目、控制数据表迁移等。
- myProject 主目录下的同名子目录,为项目的核心目录,它里面包含配置文件和管理应用的文件。
- myProject/init.py 每个子目录都会包含这样一个 init.py 文件,它是一个空文件,在需要的时候会引入目录下的对象。
- myProject/settings.py 配置文件,里面包含对数据库的设置项、CSRF Token 的设置项、模板的设置项等全部设置。
- myProject/urls.py 路由控制文件,处理客户端请求,分发到对应的视图函数去处理。
- myProject/wsgi.py 处理请求和响应,我们很少去动它。
在终端执行 cd myProject 命令进入到项目的主目录,然后执行如下命令创建一个名为 myApp 的应用,注意实验环境里不可以创建名为 test 的应用,某些情况下它与 Python 模块冲突:
$ cd myProject
$ python3 manage.py startapp myApp
如上所示,创建了名为 myApp 的应用后,在项目的主目录下出现了名为 myApp 的目录,这就是应用目录。
应用中的文件说明如下:
- myApp/admin.py 用于控制后台管理的文件,在后面的实验中会用到。
- myApp/apps.py 用于管理应用本身的文件,包括应用的名字如何命名,默认就是 myApp 。
- myApp/init.py 空文件,前面已经介绍过。
- myApp/migrations 这是用于记录数据库变更信息的目录,Django 中自带的数据库版本控制功能就体现在这个目录,在学习数据存储时会详细介绍。
- myApp/models.py 创建映射类的文件,熟悉 Flask 的同学一定不陌生。
- myApp/tests.py 编写测试代码的文件。
- myApp/views.py 创建视图函数的文件,视图函数用于处理客户端发来的请求。
3.配置文件
创建一个应用后,该应用并未与项目产生联系,需要在配置文件中添加此应用。
$ vim myProject/settings.py
修改 INSTALLED_APPS 项,添加我们创建的应用名称 myApp,如下:
修改完成后,键盘按 Esc 之后,输入:wq
保存并退出。
启动项目
我们的实验环境为 Web 环境,其实本质上就是一个在线集成工具,类似 VSCode。它内部没有浏览器,但提供了一个 8080 端口的 Web 服务。我们可以在启动项目之后,点击右侧工具栏的 “Web 服务”,会自动在你的浏览器上打开一个新标签来访问你的项目:
新打开一个终端并输入命令:
$ cd myProject
$ python3 manage.py runserver 0:8080
其中 manage.py 作为入口文件,它有很多选项提供各种各样的功能。runserver 为启动项目的选项,后面的 0:8080 为 0.0.0.0:8080 的简写,冒号前面为 IP 地址,后面为端口号。
启动项目后,点击工具栏的“Web 服务”按钮,打开新的页面,会出现如下图所示的报错:
这是因为我们的实验环境稍有些特殊,需要在配置文件中增加一项设置。
打开另一个终端标签,进入项目的主目录 myProject,打开 myProject/settings.py 文件,修改 ALLOWED_HOSTS 这一项为上述划线字符(注意:每次打开内容可能不一样,每启动一次都需要修改一下该项内容)。它也是一个列表,在列表中添加报错中所示的 header 信息,也就是单引号中的内容:
同时,我们将欢迎页适配为中文,方便后续操作和阅读,将 LANGUATE_CODE 和 TIME_ZONE 修改如下:
修改并保存后,ctrl+c 关闭服务器并重新打开 Web 服务,如下:
恭喜你,成功运行起第一个 Django 项目。
运行完毕过后 ctrl+c 关闭服务器,接下来开始介绍 Django 各个模块的用法。
4.视图View
创建应用
Django 中,每一个应用都是一个 Python 包,并且遵守着相同的约定。
Django 自带一个工具,可以帮你生成应用的基本目录,极大地提高了开发效率。
在实验二中我们已经创建一个 myProject 的目录,并创建了一个 myApp 的应用目录,终端输入命令按照以下顺序(实验二已创建则无需再执行):
$ sudo pip3 install -U pip
$ pip install django==2.2.9
$ pip install ipython mysqlclient
$ django-admin startproject myProject
$ cd myProject
$ python3 manage.py startapp myApp
由于实验二已经详细讲述创建步骤,这里不再赘述,但是相关环境的搭建以及各配置文件请按照实验二的要求修改(注:新开终端进到 myProject 目录并打开 8080 服务端口python3 manage.py runserver 0:8080
,点击 Web 服务进入 Django 欢迎页)。
我们创建的 myProject 总体目录结构如下:
myProject
├── manage.py
├── myApp
│ ├── admin.py
│ ├── apps.py
│ ├── __init__.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models.py
│ ├── tests.py
│ └── views.py
└── myProject
├── __init__.py
├── __pycache__
│ ├── __init__.cpython-35.pyc
│ └── settings.cpython-35.pyc
├── settings.py
├── urls.py
└── wsgi.py
我们可以通过直接打开右侧文件来编辑,同时也可以直接在终端输入命令编辑相关文件:
编写视图
编辑 vim myApp/views.py 文件:
# myApp/views.py
from django.shortcuts import render
from django.http import HttpResponse
def index(request):
return HttpResponse("Hello, world!")
大家可以看出来,我们想要看到的就是在页面中输出Hello, world!
。
但仅编写视图函数,并不能在 Django 页面上显示出来。如果想要看到效果,我们需要使用 URL 来映射到它。
myProject/urls.py
接下来需要思考一下,视图函数是有了,它映射到那个 URL 路径下呢?也就是说浏览器访问了哪个网址,才会调用这个视图函数处理?
需要编辑myProject/urls.py
文件,修改为以下代码:
# myProject/urls.py
from django.contrib import admin
from django.urls import path
from myApp import views # 引入视图函数
urlpatterns = [
path('admin/', admin.site.urls),
path('', views.index, name='index'), # 新增路由映射
]
最外层的 myApp 目录也就是 /home/project/myProject 为项目的主目录,引入其它文件中的对象,可以使用绝对路径或相对路径。
urlpatterns 为路由映射到视图函数的控制列表,当服务器收到浏览器发送过来的请求时,首先到这里检查是否有对应的视图函数。如果是https://www.abc.com就让 index 来处理,如果是https://www.abc.com/admin/就让 admin.site.urls 来处理。
这一步我们将应用myApp
里视图函数与 URL 映射到了一起。保存后刷新欢迎页,效果如下:
这样就可以看到我们定义的 index 视图函数生效了。
我们来详细讲解一下path()
函数,它共有 4 个参数:
参数 | 意义 | 是否必须 |
---|---|---|
route | route 是一个匹配 URL 的准则(类似正则表达式)。当 Django 响应一个请求时,它会从 urlpatterns 的第一项开始,按顺序依次匹配列表中的项,直到找到匹配的项。 | 必须 |
view | 当 Django 找到了一个匹配的准则,就会调用这个特定的视图函数,并传入一个 HttpRequest 对象作为第一个参数,被“捕获”的参数以关键字参数的形式传入。 | 必须 |
kwargs | 任意个关键字参数可以作为一个字典传递给目标视图函数。 | 可选 |
name | 为你的 URL 取名能使你在 Django 的任意地方唯一地引用它,尤其是在模板中。这个有用的特性允许你只改一个文件就能全局地修改某个 URL 模式。 | 可选 |
在上面的代码中,route
为空意味着我们可以直接在桌面环境下用链接http://localhost:8000/myApp/
访问该视图函数。
5.模型Model
配置数据库
Django 的项目设置都包含在了myProject/myProject/settings.py
中。
对于数据库,配置文件使用了SQLite
作为默认的数据库文件。对于只是初步尝试 Django 的我们来说,这十分方便,无需再去配置其他的东西。
在实际开发中,我们会用到其它更具扩展性的数据库。例如MySQL
、Oracle
等。
如果你选择使用这些数据库,你需要安装相应数据库的绑定,然后改变设置文件中的DATABASE default
。
本课程默认使用 SQLite 数据库,想要使用其它数据库,可以参考Django 官方文档 DATABASE。记得配置设置文件中的TIME_ZONE
为自己所在地的时区,中国地区为Asia/Shanghai
。
TIME_ZONE = 'Asia/Shanghai'
创建模型
模型是真实数据的简明描述。它包含了存储的数据所必要的字段和行为。Django 遵循不要重复自己(DRY 原则)。它的目标是让你只需要定义数据模型,然后其它的东西你都不用关心,都会自动从模型生成。
实验前,先进入到项目主目录:
$ cd myProject
在我们创建的图书馆应用中,需要创建一个模型Book
。Book
模型包括四个字段:书名、作者、出版社、出版日期。
向myApp/models.py
文件中写入如下代码:
# myApp/models.py
from django.db import models
class Book(models.Model):
name = models.CharField(max_length=200)
author = models.CharField(max_length=100)
pub_house = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
从代码可以看出,模型是django.db.models.Model
类的子类。每个模型有一些类变量,它们都表示模型里的一个数据库字段。
每个字段都是Field
类的实例。比如字符字段是CharField
,日期字段被表示为DateTImeField
。这将告诉 Django 每个字段要处理的数据类型。
定义某些Field
类实例需要参数。如上面的max_length=100
中的max_length
。这个参数的用处不止于用来定义数据结构,也用于验证数据。
激活模型
通过前面的代码,Django 可以:
- 为这个应用创建数据库 schema(生成 CREATE TABLE 语句)。
- 创建可以与 Book 对象进行交互的 Python 数据库 API。
为了实现上述功能,我们首先要将 myApp 应用安装到我们项目中。
因为MyappConfig
类写在文件 myApp/apps.py 中,所以它的路径为myApp.apps.MyappConfig
。
在设置文件中添加路径:
# myProject/settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# 'myApp', 这一注释掉,不注释掉会因重复而报错
'myApp.apps.MyappConfig',
]
现在你的 Django 项目会包含 myApp 应用。 运行下面的命令:
$ python3 manage.py makemigrations myApp
你会看到这样的输出:
通过运行makemigrations
命令,Django 会检测你对模型文件的修改,并且把修改的部分储存为一次迁移。
让我们看看迁移命令会执行哪些 SQL 语句。
$ python3 manage.py sqlmigrate myApp 0001
可以看到创建的 SQL 语句:
现在运行 migrate 命令,在数据库里创建新定义的模型的数据表:
$ python3 manage.py migrate
看到如下画面,则表示成功!
使用API
现在尝试一下 Django 为我们创建的各种 API:
$ python3 manage.py shell
使用这个命令而不是简单的使用 “Python” 是因为 manage.py 会设置DJANGO_SETTINGS_MODULE
环境变量,这个变量会让 Django 根据myProject/settings.py
文件来设置 Python 包的导入路径。
In [1]: from myApp.models import Book
In [2]: Book.objects.all() # 获取 Book 所有对象
Out[1]: <QuerySet []>
In [3]: from django.utils import timezone
In [4]: b = Book(name='Business', author='Tom', pub_house='First Press', pub_date=timezone.now()) #创建
In [5]: b.save() #保存
In [6]: b.id
Out[6]: 1
In [7]: b.name
Out[7]: 'Business'
In [8]: b.pub_date
Out[8]: datetime.datetime(2020, 4, 27, 7, 37, 59, 123686, tzinfo=<UTC>)
实验总结
本实验,我们学习了以下知识点:
- Django 模型
- 数据库配置
- 模型 API
记住,改变模型需要这三步:
- 编辑
models.py
文件,改变模型。 - 运行
python manage.py makemigrations
为模型的改变生成迁移文件。 - 运行
python manage.py migrate
来应用数据库迁移。
6.模板Template
模板是一个文本,用于分离文档的表现形式和内容。
创建模板
首先,在你的 myProject/myApp 目录里创建一个 templates 目录。Django 将会在这个目录里查找模板文件。
在 myProject/myProject/settings.py 文件中的TEMPLATES
配置项描述了 Django 如何载入和渲染模板。默认的设置文件设置了DjangoTemplates
后端,并设置APP_DIRS = True
。这一选项将会让DjangoTemplates
在每个INSTALLED_APPS
文件夹中寻找 templates 子目录。
新建模板文件 myApp/templates/myApp/detail.html,并向其中写入如下代码:
<!--myApp/templates/myApp/detail.html-->
<h1>Book List</h1>
<table>
<tr>
<td>书名</td>
<td>作者</td>
<td>出版社</td>
<td>出版时间</td>
</tr>
{% for book in book_list.all %}
<tr>
<td>{{ book.name }}</td>
<td>{{ book.author }}</td>
<td>{{ book.pub_house }}</td>
<td>{{ book.pub_date }}</td>
</tr>
{% endfor %}
</table>
模板统一使用点符号.
来访问变量的属性。在示例{{ book.name }}
中,首先 Django 尝试对 book 对象使用字典查找(也就是使用obj.get(str)
操作),如果失败了就尝试属性查找(也就是obj.str
操作),结果是成功了。如果这一操作也失败的话,将会尝试列表查找(也就是obj[int]
操作)。
在{% for ... in ... %}
循环中发生的函数调用:book_list.all
被解释为 Python 代码book_list.objects.all()
,将会返回一个可迭代的Book
对象,这一对象可以在{% for ... in ... %}
标签内部使用。
创建视图
现在我们要创建视图来返回图书列表:
# myApp/views.py
from django.shortcuts import render
from django.http import HttpResponse
from myApp.models import Book
def detail(request):
book_list = Book.objects.order_by('-pub_date')[:5]
context = {'book_list': book_list}
return render(request, 'myApp/detail.html', context)
在此视图函数detail
中,首先将数据库的 Book 列表按照pub_date
时间来排序,存储到变量book_list
中。
“载入模板,填充上下文,再返回由它生成的 HttpResponse 对象”是一个非常常用的操作流程。于是 Django 提供了一个快捷函数render()
。
render()
函数把request
对象作为它的第一个参数,模板作为第二个参数,字典作为它的可选的第三个参数。它返回给定模板呈现的给定文本的一个HttpResponse
对象。
在这里,context
信息将会返回到模板myApp/detail.html
。
绑定链接
将新视图添加进 myProject.urls 模块里:
# myProject/urls.py
from django.contrib import admin
from django.urls import path
from myApp import views
urlpatterns = [
# path('admin/', admin.site.urls),
path('', views.detail, name='detail'),
]
运行
为了达到好的效果,可以参考前面模型实验,多创建几个 Book 实例,添加步骤见实验三。
现在运行我们的项目:
$ python3 manage.py runserver 0:8080
当得到如下结构时,表示我们成功!
7.使用 MTV 模式实现数据库增删改
本实验,我们将实战前面学习的知识点,完成对数据的增删改。
知识点
- Django 基础
- HTML 基础
实验代码
以下是本节实验的关键代码下载链接,大家可以下载作为参考:
$ wget https://labfile.oss.aliyuncs.com/courses/1660/Django_Book.zip
$ unzip Django_Book.zip
编程学习最重要的是动手实践,所以希望大家尽可能按照实验步骤写出代码。
设计表单
在上一个实验中的 myApp/templates/myApp/detail.html 里,添加一个表单。
<form action="" method="post" name="addBook">
{% csrf_token %}
<p><span>书名:</span><input type="text" name="name" /></p>
<p><span>作者:</span><input type="text" name="author" /></p>
<p><span>出版社:</span><input type="text" name="pub_house" /></p>
<input type="submit" value="添加" />
</form>
简要说明:
- 上面的模板是输入相应的书名、作者和出版社后,点击添加,将数据存储到数据库并刷新页面显示出来。
- 我们设置表单的
action="/addBook/"
,并设置method="post"
。使用method="post"
(与其相对的是method="get"
)是非常重要的,因为这个提交表单的行为会改变服务器端的数据,无论何时,当你需要创建一个改变服务器端数据的表单时,请使用method="post"
。这不是 Django 的特定技巧,这是优秀的网站开发技巧。 - 由于我们创建一个 POST 表单(它具有修改数据的作用),所以我们需要小心跨站点请求伪造。 但你不必太过担心,因为 Django 已经拥有一个用来防御它的非常容易使用的系统。 简而言之,所有针对内部 URL 的 POST 表单都应该使用
{% csrf_token %}
模板标签。
命名空间
教程项目只有一个应用 myApp。在一个真实的 Django 项目中,可能会有五个,十个,二十个,甚至更多应用。Django 如何分辨重名的 URL 呢?
举个例子,myApp 应用有 detail 视图,可能另一个博客应用也有同名的视图。Django 如何知道action=""
标签到底对应哪一个应用的 URL 呢?
答案是:在根 URLconf 中添加命名空间。在myProject/urls.py
文件中稍作修改,加上app_name
设置命名空间:
from django.contrib import admin
from django.urls import path
from myApp import views
app_name = 'myApp' # 添加这一行
urlpatterns = [
# path('admin/', admin.site.urls),
path('', views.detail, name='detail'),
path('addBook/', views.addBook, name='addBook'),
]
现在回到myApp/templates/myApp/detail.html
更改action
:
<form action="/addBook/" method="post" name="addBook"></form>
添加书籍
创建addBook
函数来实现我们添加书籍的功能。
将下面的代码添加到 myApp/views.py:
# myApp/views.py
from django.http import HttpResponseRedirect
from django.urls import reverse
def addBook(request):
if request.method == 'POST':
temp_name = request.POST['name']
temp_author = request.POST['author']
temp_pub_house = request.POST['pub_house']
from django.utils import timezone
temp_book = Book(name=temp_name, author=temp_author, pub_house=temp_pub_house, pub_date=timezone.now())
temp_book.save()
# 重定向
return HttpResponseRedirect(reverse('detail'))
在 myProject/urls.py 里添加 URL 地址映射:
# myProject/urls.py
path('addBook/', views.addBook, name='addBook'),
简单说明:
request.POST
是一个类字典对象,可以通过关键字的名字获取提交的数据。 这个例子中,request.POST['name']
以字符串形式返回name
的值。request.POST
的值永远是字符串。- 在添加书籍之后,代码返回一个
HttpResponseRedirect
而不是常用的HttpResponse
,HttpResponseRedirect
只接收一个参数:用户将要被重定向的 URL。 - 你应该在每次处理 POST 数据时,都返回
HttpResponseRedirect
。这也不是 Django 的特定技巧,这是优秀的网站开发的实践。 - 在这个例子中,我们在
HttpResponseRedirect
的构造函数中使用reverse()
函数。这个函数避免了我们在视图函数中硬编码 URL。它需要我们给出想要跳转的视图的名字和该视图所对应的 URL 模式中需要给该视图提供的参数。reverse()
调用后将返回这样一个字符串:/detail/
。
添加书籍功能完成,现在可以随意添加书籍。
打开 Web 服务访问链接就可以查看效果:
添加前:
添加后:
删除书籍
删除书籍功能实现起来也很简单。
首先,在myApp/detail.html
中设计我们的模板。
{% for book in book_list.all %}
<tr>
<td>{{ book.name }}</td>
<td>{{ book.author }}</td>
<td>{{ book.pub_house }}</td>
<td>{{ book.pub_date }}</td>
<td><a href="{% url 'delBook' book.id %}">删除</a></td><!--只添加这一行-->
</tr>
{% endfor %}
</table>
可以看出,只需要在每个书籍后面添加一个删除的按钮。删除时也传递了需要删除的图书的id
。
接着,配置 url,只需在myProject/urls.py
中添加这一行:
# myProject/urls.py
path('delBook/<int:book_id>', views.deleteBook, name='delBook'),
这里<int:book_id>
是接收传递的参数book_id
。
最后设计视图函数:
# myApp/views.py
def deleteBook(request, book_id):
bookID = book_id
Book.objects.filter(id=bookID).delete()
# 重定向
return HttpResponseRedirect(reverse('detail'))
这个视图中,获取到 book 的 id,根据 id 来删除指定书籍。最后跟上面的添加书籍函数相同,使用重定位刷新页面。
打开 Web 服务访问链接查看效果:
删除后:
8.总结
本实验将带大家深入了解 Django 的 MTV 设计模式。
MTV
MVC
在说 MTV 模式之前,让我们来简单的说说著名的 MVC 模式。
MVC,是模型(Model)-视图(View)-控制器(Controller)的缩写。其具体定义如下:
- M:模型(Model),数据存取层,负责业务对象和数据库对象。
- V:视图(View),与用户的交互,负责显示与怎样显示。
- C:控制器(Controller),接受用户动作,调用模型,输出相应视图。
三者以一种插件似的,松耦合的方式连接在一起。
MTV
Django 的 MTV 设计模式是借鉴和遵循 MVC 的。
MTV 具体定义如下:
- M:模型(Model),负责业务对象和数据库的关系映射。
- T:模板(Template),负责如何把页面展示给用户。
- V:视图(View),负责业务逻辑,并在适当时候调用模型和模板。
URL 分发器
URL 分发器的作用是将页面请求分发给不同的视图(View)处理,视图再调用相应的模型(Model)和模板(Template)。
Django Web 框架:
一个误区是把 MVC 模式与 MTV 模式等价替换。其实这样是不对的。
在 MTV 模式中,MVC 中的 View 分成了视图 View(展现哪些数据)和模板 Template(如何展现)2 个部分,而控制器(Controller)这个要素由框架自己来实现了,我们需要做的就是把 URL 对应到视图 V 就可以了,通过这样的 URL 配置,系统将一个请求发送到一个合适的视图。
源码链接
https://github.com/Arrrrray/shiyanlou-code/tree/master/myProject