Skip to content

1.Django介绍

1.1. HTTP请求协议

HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于万维网(WWW:World Wide Web )服务器与本地浏览器之间传输超文本的传送协议。http协议是基于TCP/IP协议之上的应用层协议

请求协议:

image

请求方式: get与post请求  1. GET提交的数据会放在URL之后,以?分割URL和传输数据,参数之间以&相连,如EditBook?name=test1&id=123456. POST方法是把提交的数据放在HTTP包的请求体中.  2. GET提交的数据大小有限制(因为浏览器对URL的长度有限制),而POST方法提交的数据没有限制.  3. GET与POST请求在服务端获取请求数据方式不同

响应协议:

image

响应状态码

状态码的值 是当客户端向服务器端发送请求时, 返回的请求 结果。借助状态码,用户可以知道服务器端是正常 理了请求,还是出 现了 。

image

1.2. Web框架介绍

框架,即framework,特指为解决一个开放性问题而设计的具有一定约束性的支撑结构,使用框架可以快速帮你开发特定的系统。

Web框架是别人已经设定好的一个web网站模板,你学习它的规则,然后“填空”或“修改”成你需要的样子。简单说,就是你用别人搭建好的舞台来表演。

一般Web框架的架构这样的:

image

1.3. MVC/MTV介绍

**MVC百度百科:**全名Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。

通俗解释:一种文件的组织和管理形式!不要被缩写吓到了,这其实就是把不同类型的文件放到不同的目录下的一种方法,然后取了一个高大上的名字。当然,它带来的好处有很多,比如前后端分离,松耦合等,就不详细说明了。

模型(model):定义数据库相关的内容,一般放在Models.py文件中。

视图(view):定义HTML等静态网页文件相关,也就是那些HTML,CSS,JS等前端的东西。

控制器(Controller):定义业务逻辑相关,就是我们的主要代码。

MTV:Django的MTV模式本质上和MVC是一样的,也是为了各组件间保持松耦合关系,只是定义上有些许不同,Django的MTV分别是值:

  • M 代表模型(Model): 负责业务对象和数据库的关系映射(ORM);
  • T 代表模板 (Template):负责如何把页面展示给用户(html);
  • V 代表视图(View): 负责业务逻辑,并在适当时候调用Model和Template。

除了以上三层之外,还需要一个URL分发器,它的作用是将一个个URL的页面请求分发给不同的View处理,View再调用相应的Model和Template,MTV的响应模式如下所示:

image

一般是用户通过浏览器向我们的服务器发起一个请求(request),这个请求回去访问视图函数,(如果不涉及到数据调用,那么这个时候视图函数返回一个模板也就是一个网页给用户),视图函数调用模型,模型去数据库查找数据,然后逐级返回,视图函数把返回的数据填充到模板中空格中,最后返回网页给用户。

1.4. Hello Django

1.4.1. 创建Django项目

使用Pycharm图形化界面创建

这里推荐使用Pycharm,它功能强大,界面友好。

1,点击:file——》new project,出现下面的对话框。

image

选择Django栏目,输入项目名称,这里采用国际惯例的mysite。选择Python解释器版本,点击create创建。

2,Django将自动生成下面的目录结构:

image

3,与项目同名的目录中是配置文件,templates目录是html文件存放也就是MTV中T。manage.py是Django项目管理文件。

image

使用terminal终端创建

进入指定的项目保存目录,然后运行下面的命令:

pip3 install django
django-admin startproject mysite

这样就在目录下面生成一个mysite目录,也就是我们的Django项目的根目录。它包含了一系列自动生成的目录和文件,具备各自专有的用途。

注意:在给项目命名的时候必须避开Django和Python的保留关键字,比如“django”,“test”等,否则会引起冲突和莫名的错误。对于mysite的放置位置,不建议放在传统的/var/www目录下,它会具有一定的数据暴露危险,因此Django建议你将项目文件放在例如/home/mycode类似的位置。

一个新建的项目结果大概如下(上面已经给出了,在这里再次展示,主要解释目录意义)

mysite/
manage.py
mysite/
__init__.py
settings.py
urls.py
wsgi.py

各文件和目录解释:

  • 外层的mysite目录与Django无关,只是你项目的容器,可以任意命名。
  • manage.py:一个命令行工具,用于与Django进行不同方式的交互脚本,非常重要,也是Django的管理主程序。
  • 内层的mysite/ 目录是真正的项目文件包裹目录,他的名字是你引用内部文件的包明,例如:mysite.urls。
  • mysite/init.py : 一个定义包的空文件。
  • mysite/settings.py : 项目的主配置文件
  • mysite/urls.py : 路由文件,所有的任务都是从这里开始分配,相当于Django驱动站点的内容表格。
  • mysite.wsgi.py : 一个基于WSGI的web服务器进入点,提供底层的网络通信功能(通常不用关心)

1.4.2. 创建APP

在每个Django项目中可以包含多个APP,相当于一个大型项目中的分系统,子模块,功能部件等等,相互之间比较独立,但也有联系。

APP应用和project项目的区别

  • 一个APP实现某个功能,比如博客,公共档案数据库或者见到的投票系统
  • 一个project是配置文件和多个APP的集合,这里APP组合成整个站点
  • 一个project可以包含多个APP
  • 一个APP可以属于多个project

所有的APP共享项目资源。

APP的存放位置可以是任何地点,但是通常都将他们放在与manage.py脚本同级的目录下,这样方便导入文件。

在pycharm下面的terminal终端中输入命令(pycharm中没有可以创建APP的图形化按钮,需要在终端中输入命令):

python manage.py startapp cmdb

这样就创建了一个叫做cmdb的APP,Django自动生成“cmdb”文件夹。

image

1.4.3. 编写路由

路由都在urls文件里面,它将浏览器输入的url映射到相应的业务处理逻辑。

简单的urls编写方法如下图:

image

1.4.4. 编写业务处理逻辑

业务处理逻辑都在views.py文件里。

image

通过上面两个步骤,我们将index这个url指向了views里的index()函数,它接受用户请求,并返回一个“Hello world”字符串

1.4.5. 运行web服务

现在我们已经可以将web服务运行起来了。

命令行的运行方式为:

python manage.py runserver 127.0.0.1:8000

命令行的展示如下:

image

但是在Pycharm中,我们可以这样做:

1,在上步工具栏中找到下面图示的图标。

image

2,点击下拉箭头

image

3,点击edit Configurations

image

4,在host中输入:127.0.0.1 port中输入:8000.

image

5,OK之后,点击绿色的三角,web服务就运行起来了。

image

6,按图所示,自动跳转到浏览器程序页面。显示的却是下图中的404页面:

image

,7,修改一下,添加“/index”,就一切OK了

image

至此,一个最简单的Django编写的web服务器就启动成功了。

1.4.6. 返回HTML文件

上面我们返回给用户浏览器的是什么?一个字符串!实际上这肯定不行,通常我们都是将HTML文件返回给用户。

1,下面我们写这么一个index.html文件:

image

2,再修改一下views文件。

image

3,为了让Django知道我们的HTML文件在哪里,需要修改settings文件的相应内容。但是默认情况下,它正好适用,你无需修改。

image

接下来,我们可以重新启动web服务。在浏览器刷新一下,你会看到带有样式的“hello world”

注:这里有个小技巧,在多个频繁重启服务时,由于端口未释放的原因,容易启动不了服务,修改一下端口就OK了。

4,结果如下:

image

1.4.7. 使用静态文件

我们已经可以将HTML文件返回给用户了,但是还不够,前端三大块,HTML,CSS,JS还有各种插件,他们齐全才是一个完整的页面。在Django中,我们将这些文件统称为“静态文件”,因为这些文件的内容基本是固定不变的,不需要动态生成,一般将静态文件放在static目录中。

对于小项目,这些都不是问题,你可以将静态文件放在任何你的web服务器能够找到的地方。但是对于大型项目,尤其是那些包含多个APP在内的项目,处理那些由APP带来的多套不同的静态文件是个麻烦问题。当然了这也正是django.contrib,staticfiles的用途,它收集每个应用(和任何你指定的地方)的静态文件到一个统一指定的地方,并且易于访问。

在mysite中新建一个static目录。

image

你的CSS,JS和各种插件都可以放置在这个目录里。

为了让Django找到这个目录,依然需要对settings进行配置。

image

同样在index.html文件中,可以引入js文件:

image

重新启动web服务,刷新浏览器,查看结果。(因为导入js文件,并没有渲染,所以页面不变)

image

直接访问静态文件

实际上不管是在Django开发服务器上,还是在nginx+uwsgi+django部署的服务器上,都可以通过url访问静态文件,不需要在Django中专门为每个静态文件编写url路由和视图。

1.4.8. 接受用户发送的数据

上面,我们将一个要素齐全的HTML文件返还给了用户浏览器。但这还不够,因为web服务器和用户之间没有动态交互。

下面我们设计一个表单,让用户输入用户名和密码,提交给index这个url,服务器将接收到这些数据。

1,先修改index.html文件

image

2,修改views.py文件

image

3,此时,重启web服务时,会出错,因为Django有一个csrf跨站请求保护机制,我们暂时在settings文件中将其关闭,或者在form表单里添加一个{% csrf_token %} 标签。这里为了演示方便,我们采用临时关闭的方式。

报错页面结果如下:

image

命令行报错结果如下:

image

在setting中将其注释,如下:

image

再次进入浏览器,刷新页面

image

输入用户名和密码,我们可以在pycharm中看到相应的数据。

image

1.4.9. 返回动态页面

我们收到了用户的数据,但返回给用户的依然是个静态页面,通常我们会根据用户的数据,进行处理后再返回给用户。

这时候,Django采用自己的模板语言,类似jinja2,根据提供的数据,替换掉HTML中的相应部分,详细语法以后再学习。

1,先改造views.py文件

image

2,再改造index.html文件:

image

3,重启服务,刷新浏览器

image

可以看到,我们获得了用户实时输入的数据,并将它实时的展示在了用户页面上,这是一个不错的交互过程。

1.4.10. 使用数据库

流程走到这里,Django的MTV框架基本已经浮出水面了,只剩下最后的额数据库部分了。

上面我们虽然和用户交互的很好,但是没有保留任何数据,页面一旦关闭,或者服务器重启,一切都将回到原点。

使用数据库毫无疑问的,Django通过自带的ORM框架操作数据库,并且自带轻量级的sqlite3数据库,Django默认使用SQLite数据库,因为Python源生支持SQLite数据库,所以我们无需安装任何程序,就可以直接使用,当然,如果我们创建一个实际的项目,可以使用类似Mysql的数据库,避免以后数据库迁移的相关问题,下面我们先看一下。

1,首先是注册app(打开mysite/settings.py配置文件,这是整个Django项目的设置中心):

image

不注册它,你的数据库就不知道该给哪个APP创建表。

默认情况,INSTALLED_APPS中会自动包含下列条目,它们都是Django自动生成的:

  • django.contrib.admin:admin管理后台站点
  • django.contrib.auth:身份认证系统
  • django.contrib.contenttypes:内容类型框架
  • django.contrib.sessions:会话框架
  • django.contrib.messages:消息框架
  • django.contrib.staticfiles:静态文件管理框架

2,在settings中,配置数据库相关的参数,如果使用自带的sqlite,不需要修改。

image

如果你想使用其他的数据库,请先安装相应的数据库操作模块,并将settings文件中DATABASES位置的'default'的键值进行相应的修改,用于连接数据库。

  • ENGINE(引擎):可以是django.db.backends.sqlite3,django.db.backends.postgresql,django.db.backends.mysql,django.db.backends.oracle,当然其他的也行。
  • NAME(名称):类似MySQL数据库管理系统中用于保存项目内容的数据库的名字。如果你使用的是默认的SQLite,那么数据库将作为一个文件存在你的机器中,此时的NAME应该是这个文件的完整绝对路径,默认值是 os.path.join(BASE_DIR, ’db.sqlite3’) ,将把该文件存储在你的项目目录下。

如果你不是使用默认的SQLite数据库,那么一些诸如USER,PASSWORD和HOST的参数必须手动指定!下面给出一个基于pymysql操作Mysql数据库的例子。

# mysite/settings.py

# Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases


DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'mysite',
        'HOST': '192.168.1.1',
        'USER': 'root',
        'PASSWORD': 'pwd',
        'PORT': '3306',
    }
}

注意:

  • 在使用非SQLite的数据库时,请务必预先在数据库管理系统的提示符交互模式下创建数据库,你可以使用命令:“CREATE DATABASE database_name;”。Django不会自动帮你做这一步工作。
  • 确保你在settings文件中提供的数据库用户具有创建数据库表的权限,因为在接下来的教程中,我们需要自动创建一个test数据表。(在实际项目中也需要确认这一条要求。)
  • 如果你使用的是SQLite,那么你无需做任何预先配置,直接使用就可以了。

在修改settings文件时,请顺便将TIME_ZONE设置为国内所在的时区Asia/Shanghai

同时,请注意settings文件中顶部的INSTALLED_APPS设置项。它列出了所有的项目中被激活的Django应用(app)。你必须将你自定义的app注册在这里。每个应用可以被多个项目使用,并且可以打包和分发给其他人在他们的项目中使用。

3,编辑models.py,也就是MTV中的M

模型本质上就是数据库表的布局,再附加一些元数据。

Django通过自定义Python类的形式来定义具体的模型,每个模型的物理存在方式就是一个Python的类Class,每个模型代表数据库中的一张表,每个类的实例代表数据表中的一行数据,类中的每个变量代表数据表中的一列字段。Django通过模型,将Python代码和数据库操作结合起来,实现对SQL查询语言的封装。也就是说,你可以不会管理数据库,可以不会SQL语言,你同样能通过Python的代码进行数据库的操作。Django通过ORM对数据库进行操作,奉行代码优先的理念,将Python程序员和数据库管理员进行分工解耦。

image

这里我们创建了两个字段,分别保存用户的名称和密码

4,我们要在pycharm的teminal中通过命令创建数据库的表,有两条命令,分别是:

python manage.py makemigrations

通过运行makemigrations命令,相当于告诉Django你对模型有改动,并且你想把这些改动保存为一个“迁移(migration)”。

migrations是Django保存模型修改记录的文件,这些文件保存在磁盘上。在例子中,它就是polls/migrations/0001_initial.py,你可以打开它看看,里面保存的都是人类可读并且可编辑的内容,方便你随时手动修改。

接下来有一个叫做migrate的命令将对数据库执行真正的迁移动作。

image

再输入命令:

Python manage.py migrate

migrate命令对所有还未实施的迁移记录进行操作,本质上就是将你对模型的修改体现到数据库中具体的表上面。Django通过一张叫做django_migrations的表,记录并跟踪已经实施的migrate动作,通过对比获得哪些migrations尚未提交。

migrations的功能非常强大,允许你随时修改你的模型,而不需要删除或者新建你的数据库或数据表,在不丢失数据的同时,实时动态更新数据库。我们将在后面的章节对此进行深入的阐述,但是现在,只需要记住修改模型时的操作分三步

  • 在models.py中修改模型;
  • 运行python manage.py makemigrations为改动创建迁移记录;
  • 运行python manage.py migrate,将操作同步到数据库。

之所以要将创建和实施迁移的动作分成两个命令两步走是因为你也许要通过版本控制系统(例如github,svn)提交你的项目代码,如果没有一个中间过程的保存文件(migrations),那么github如何知道以及记录、同步、实施你所进行过的模型修改动作呢?毕竟,github不和数据库直接打交道,也没法和你本地的数据库通信。但是分开之后,你只需要将你的migration文件(例如上面的0001)上传到github,它就会知道一切。

命令行结果如下:image

migrate命令将遍历INSTALLED_APPS设置中的所有项目,在数据库中创建对应的表,并打印出每一条动作信息。如果你感兴趣,可以在你的数据库命令行下输入: SHOW TABLES;(MySQL)或 .schema(SQLite) 来列出 Django 所创建的表。

提示:对于极简主义者,你完全可以在INSTALLED_APPS内注释掉任何或者全部的Django提供的通用应用。这样,migrate也不会再创建对应的数据表。

5,修改views.py中的业务逻辑

image

重启Web服务后,刷新页面,之后和用户交互的数据都能保存到数据库中,任何时候都可以从数据库中读取数据,展示到页面上。

image

image

至此,一个要素齐全,主体框架展示清晰的Django项目完成了。

2.投票示例

1.1. Web投票示例

该应用包括以下两个部分:

  • 一个可以让公众用户进行投票和查看投票结果的站点
  • 一个可以进行增删改查的后台admin管理界面

在开始之前,我们可以查看安装的Django是什么版本,在命令行输入:

python -m django --version

image

如果没有安装,首先安装:

pip3 install django

1.1.1. 新建项目

进入我们指定的项目保存目录,然后运行下面的命令:

django-admin startproject mysite

这将在目录下生成一个mysite目录,也就是我们这个Django项目的根目录。它包含了一系列自动生成的目录和文件,具备各自专有的用途。

1.1.2. 启动开发服务器

进入mysite项目的根目录,输入下面的命令:

python manage.py runserver

你会看到下面的提示,这表明Django的开发

image

Django提供一个用于开发的web服务器,使你无需配置一个类似Ngnix的线上服务器,就能让站点运行起来。这是一个由Python编写的轻量级服务器,简易并且不安全,因此不要将它用于生产环境。

打开浏览器,访问http://127.0.0.1:8000/,你将看到Django的欢迎界面,一切OK!

Django的开发服务器(以后简称服务器)默认运行在内部的8000端口,如果你想指定端口,请在命令中显示给出:

python manage.py runserver 8080

如果你想修改服务器的IP地址,请按以下方式运行命令:

python manage.py runserver 0.0.0.0:8000

这时,Django将运行在8000端口,整个局域网内都可以访问站点,而不只是本机。

注意:Django的开发服务器具有自动重载功能,当你的代码有修改,每隔一段时间服务器将自动更新。但是,有一些例如增加文件的动作,不会触发服务器重载,这时候就需要你自己手动重启。

1.1.3. 创建投票APP

进入mysite项目根目录,确保与,manage.py文件处于同一级,输入下述命令:

python manage.py startapp polls

系统会自动生成polls应用的目录,其结构如下:

polls/
    __init__.py
    admin.py
    apps.py
    migrations/
        __init__.py
    models.py
    tests.py
    views.py

1.1.4. 编写第一个视图

在polls/views.py文件中,编写代码:

from django.http import HttpResponse

def index(request):
    return HttpResponse("Hello, world. You're at the polls index.")

1.1.5. URL管理

  • 1,创建好Project后,在全局配置文件中有一个urls.py这个模型,该模型主要管理本项目的全局url配置
  • 2,每个APP也应该创建一个urls.py模块来管理自己APP下的url集合

APP下的urls.py

为了调用该视图,我们还需要编写urlconf,也就是路由路径。现在在polls目录中新建一个文件,名字为urls.py,在其中输入代码如下:

  • 1,import django 中的url模块
  • 2,import app 中的views模块
  • 3,需要注意的是url时以正则表达式来配置管理,访问web页面的url = 全局url + APP url
from django.conf.urls import url
from . import views

urlpatterns = [
    url(r'^$',views.index, name='index'),
]

此时,目录的文件结构是这样的:

polls/
    __init__.py
    admin.py
    apps.py
    migrations/
        __init__.py
    models.py
    tests.py
    urls.py
    views.py

全局urls.py配置

  • 1,需要import include模块
  • 2,在urlpatterns中添加app下的urls模块,namespace参数可以防止不同app下的url 名字冲突。

接下来,我们在项目的主urls文件中添加urlpattern条目,指向我们刚才建立的polls这个APP独有的urls文件,这里需要导入include模块,打开mysite/urls.py文件,代码如下:

from django.conf.urls import include,url
from django.contrib import admin

urlpatterns = [
    url(r'^polls/',include('polls.urls')),
    url(r'^admin/',admin.site.urls),
]

include语法相当于多级路由,它把接收到的url地址去除前面的正则表达式,将剩下的字符串传递给下一级路由进行判断。

include的背后是一种即插即用的思想,项目根路由不关心具体APP的路由策略,只管往指定的二级路由转发,实现了应用解耦。APP所属的二级路由可以根据自己的需要随意编写,不会和其他的APP路由发生冲突。APP目录可以放置在任何位置,而不用修改路由。这是软件设计里很常见的一种模式。

建议:除了admin路由外,尽量给每个APP设计自己独立的二级路由。

好了,路由设置成功后,启动服务器,然后在浏览器中访问地址http://localhost:8000/polls/。一切正常的话,你将看到“Hello, world. You’re at the polls index.”

image

1.1.6. url方法

url()方法可以接受4个参数,其中2个是必须的:regex和view,以及2个可选的参数:kwargs和name。

regex

regex是正则表达式的通用缩写,它是一种匹配字符串或者url地址的语法。Django拿着用户请求的url地址,在url.py文件中对urlpatterns列表中的每一项条目从头开始进行逐一对比,一旦遇到匹配项,立即执行该条目映射的视图函数或者下级路由,其后的条目将不再继续匹配。因此,url路由的编写顺序非常重要!

需要注意的是,regex不会去匹配GET和POST参数或者域名,例如对于https://www.example.com/myapp/,regex只尝试匹配myapp/。对于https://www.example.com/myapp/?page=3,regex也只尝试匹配myapp/。

当URLconf模块加载的时候会预先编译正则表达式,因此他的匹配搜索速度非常快,你通常感觉不到。

view

view指的是处理当前url请求的视图函数,当正则表达式匹配到某个条目时,自动将封装的HTTPRequest对象作为第一个参数,正则表达式“捕获”到的值作为第二个参数,传递该条目指定的视图view。如果是简单捕获,那么捕获值将作为一个位置参数进行传递,如果是命名捕获,那么将作为关键字参数进行传递。

kwargs

任意数量的关键字参数可以作为一个字典传递给目标视图

name

对你的URL进行命名,让你能够在Django的任意处,尤其是模板内显式的引用它,这是一个非常强大的功能,相当于给URL取了一个全局变量名。不会将url匹配地址写死(后面说到)。

注意

  • 1,如果正则里面有()括号表示为分组,会自动将()括号里面的内容传到views.article_page视图中
  • 2,如果正则格式为:(?P<>)表示为命名分组,在View视图里面或者template调用的时候,可以直接使用命名去调用

在urls.py中我们可以使用正则表达式来匹配url

urlpatterns = [
    url(r'^$', views.index),
    url(r'^article/(?P<article_id>[0-9]+)$', views.article_page),
]

其中(?P...)子串匹配到的内容将可以用命名的name来提取url的值,组的name必须是有效的Python标识符,而且在本表达式内不重名。

1.2. 数据库安装

打开mysite/settings.py配置文件,这是整个Django项目的设置中心。Django默认使用SQLite数据库,因为Python源生支持SQLite数据库,所以我们无需安装任何程序,就可以直接使用它。当然,如果你是在创建一个实际的项目,可以使用类似MySql的数据库,避免以后数据库迁移的相关问题。

# Database
# https://docs.djangoproject.com/en/2.1/ref/settings/#databases

DATABASES = {
    'default': {
        # 这里可以指定使用的数据库类型,例如mysql
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

如果你想使用其他的数据库,请先安装相应的数据库操作模块,并将settings文件中DATABASES位置的'default' 的键值进行相应的修改,用于连接你的数据库。其中:

  • ENGINE(引擎):可以是django.db.backends.sqlite3django.db.backends.postgresqldjango.db.backends.mysqldjango.db.backends.oracle,当然其它的也行。
  • NAME(名称):类似Mysql数据库管理系统中用于保存项目内容的数据库的名字。如果你使用的是默认的SQLite,那么数据库将作为一个文件将存放在你的本地机器内,此时的NAME应该是这个文件的完整绝对路径包括文件名,默认值os.path.join(BASE_DIR, ’db.sqlite3’),将把该文件储存在你的项目目录下。

如果不是使用默认的SQLite数据库,那么一些诸如USER,PASSWORD和HOST的参数必须手动指定!下面给出一个基于pymysql操作MySQL数据库的例子。

# mysite/settings.py

# Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases


DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'mysite',
        'HOST': '192.168.1.1',
        'USER': 'root',
        'PASSWORD': 'pwd',
        'PORT': '3306',
    }
}

注意:

  • 在使用非SQLite的数据库时,请务必预先在数据库管理系统的提示符交互模式下创建数据库,你可以使用命令:‘CREATE DATABASE database_name;’。Django不会自动帮你做这一步工作。
  • 确保你在settings文件中提供的数据库用户具有创建数据库表的权限,因为在接下来的教程中,我们需要自动创建一个test数据表(在实际项目中也需要确认这一条要求)
  • 如果你使用的是SQLite,那么你无需做任何预先配置,直接使用即可。

修改时间

在修改settings文件时候,请顺便将TIME_ZONE设置为国内所在的时间Asia/Shanghai。

image

INSTALLED_APPS设置项的了解

请注意settings文件中顶部的INSTALLED_APPS设置项。它列出了所有的项目中被激活的Django应用(app)。你必须将你自定义的app注册在这里。每个应用可以被多个项目使用,并且可以打包和分发给其他人在他们的项目中使用。

默认情况,INSTALLED_APPS中会自动包含下列条目,它们都是Django自动生成的:

  • django.contrib.admin:admin管理后台站点
  • django.contrib.auth:身份认证系统
  • django.contrib.contenttypes:内容类型框架
  • django.contrib.sessions:会话框架
  • django.contrib.messages:消息框架
  • django.contrib.staticfiles:静态文件管理框架

上面的一些应用也需要建立一些数据库表,所以在使用它们之前我们要在数据库中创建这些表。使用下面的命令创建数据表:

Python manage.py migrate

migrate命令将遍历INSTALLED_APPS设置中的所有项目,在数据库中创建对应的表,并打印出每一条动作信息,如果你感兴趣,可以在你的数据库命令行下输入:SHOW TABLES (MySql);或者.schema(SQLite)来列出Django所创建的表。

1.3. 创建模型

现在我们来定义模型model,模型本质上就是数据库表的布局,再附加一些元数据。

Django通过自定义Python类的形式来定义具体的模型,每个模型的物理存在方式就是一个Python的类Class,每个类的实例代表数据表中的一行数据,类中的每个变量代表数据表中的一列字段。

Django通过模型,将Python代码和数据库操作结合起来,实现对SQL查询语言的封装。也就是说,你可以不会管理数据库,可以不会SQL语言,你同样能通过Python的代码进行数据库的操作。Django通过对ORM对数据库进行操作,奉行代码优先的理念,将Python程序员和数据库管理员进行分工解耦。

在这个简单的投票应用中,我们将创建两个模型:Question 和Choice。Question包含一个问题和一个发布日期。Choice包含两个字段:该选项的文本描述和该选项的投票数。每一条Choice都关联到一个Question。这些都是由Python的类来体现,编写的全是Python的代码,不接触任何SQL语句。现在,编辑polls/models.py文件,具体代码如下:

# polls/models.py

from django.db import models

class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')

class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)

上面的代码非常简单明了,每一个类都是Django.db.models.Model的子类。每一个字段都是Filed类的一个实例,例如用于保存字符数据的CharFiled和用于保存时间类型的DateTimeFiled,它部门告诉Django每一个字段保存的数据类型。

每一个Field实例的名字就是字段的名字(如:question_test或者pub_date)。在你的Python代码中会使用这个值,你的数据库也会将这个值作为表的列名。

你也可以在每个Field中使用一个可选的第一位置参数用于提供一个人类可读的字段名,让你的模型更友好,更易读,并且将被作为文档的一部分来增强代码的可读性。

一些Field类必须提供某些特定的参数,例如CharField需要你指定max_length,这不仅是数据库结构的需要,同样也用于数据验证功能。

有必填参数,当然就会有可选参数,比如在votes里我们将其默认值设置为0。

最后请注意,我们使用ForeignKey定义了一个外键关系。它告诉Django,每一个Choice关联到一个对应的Question(注意要将外键写在‘多’的一方)。Django支持通用的数据关系:一对一,多对一和多对多。

1.4. 启用模型

上面的代码看着有点少,其实包含了大量的信息,据此,Django会做下面两件事情:

  • 创建该APP对应的数据库表结构
  • 为Question和Choice对象创建基于Python的数据库访问API

但是,我们必须首先告诉Django项目,我们要使用投票APP。

要将应用添加到项目中,需要在INSTALLED_APPS设置中增加指向该应用的配置文件的链接。对于本例的投票应用,它的配置类文件是polls/apps.py,路径格式为polls.apps.PollsConfig。我们需要在INSTALLED_APPS中,将该路径添加进去:

# mysite/settings.py

INSTALLED_APPS = [
'polls.apps.PollsConfig',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]

实际上,在多数情况下,我们简写成‘polls’就可以了;

# mysite/settings.py

INSTALLED_APPS = [
'polls',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]

现在Django已经知道你的投票应用的存在了,并把它加入了项目大家庭。

我们需要再运行一下命令:

python manage.py makemigrations polls

你会看到类似下面的提示:

image

通过运行makemigrations命令,相当于告诉Django你对模型有改动,并且你想把这些改动保存为一个“迁移(migration)”。

migration 是Django保存模型修改记录的文件,这些文件保存在磁盘上。在例子中,它就是polls/migrations/0001_initial.py,你可以打开它看看,里面保存的都是人类可读并且可编辑的内容,方便你随时手动修改。

接下来有一个叫做migrate的命令将对数据库执行真正的迁移动作。但是在此之前,让我们先看看在migration的时候实际执行的SQL语句是什么。有一个叫做sqlmigrate的命令可以展示SQL语句,例如:

python manage.py sqlmigrate polls 0001

你将会看到如下类似的文本(经过适当的格式调整,方便阅读)

BEGIN;
--
-- Create model Choice
--
CREATE TABLE "polls_choice" (
    "id" serial NOT NULL PRIMARY KEY,
    "choice_text" varchar(200) NOT NULL,
    "votes" integer NOT NULL
);
--
-- Create model Question
--
CREATE TABLE "polls_question" (
    "id" serial NOT NULL PRIMARY KEY,
    "question_text" varchar(200) NOT NULL,
    "pub_date" timestamp with time zone NOT NULL
);
--
-- Add field question to choice
--
ALTER TABLE "polls_choice" ADD COLUMN "question_id" integer NOT NULL;
ALTER TABLE "polls_choice" ALTER COLUMN "question_id" DROP DEFAULT;
CREATE INDEX "polls_choice_7aa0f6ee" ON "polls_choice" ("question_id");
ALTER TABLE "polls_choice"
    ADD CONSTRAINT "polls_choice_question_id_246c99a640fbbd72_fk_polls_question_id"
    FOREIGN KEY ("question_id")
    REFERENCES "polls_question" ("id")
    DEFERRABLE INITIALLY DEFERRED;
COMMIT;

请注意:

  • 实际的输出内容将取决于您使用的数据库会有所不同,上面的是PostgreSQL的输出。
  • 表名是自动生成的,通过组合应用名(polls)和小写的模型名question和choice。(这两个模型名称是我们自己建立的,你也可以自己建立)
  • 主键(IDs)是自动添加的(我们也可以重写此行为)
  • 按照惯例,Django会在外键字段名称上附加“_id” (我们也可以重写此行为)
  • 生成的SQL语句时针对你所使用的数据库,会为你自动处理特定于数据库的字段,例如anto_increment(MySQL),serial(PostgreSQL),或者integer primary key(SQLite),在引用字段名时也是如此,比如使用双引号护着单引号。
  • 这些SQL命令并没有在你的数据库中实际运行,它只是在屏幕上显示出来,以便让你了解Django真正执行的是什么

你也可以运行下面命令,它将检查项目中的错误,并不实际进行迁移或者链接数据库的操作。

python manage.py check

image

现在我们可以运行migrate命令,在数据库中进行真正的表操作了。

image

migrate命令对所有还未实施的迁移记录进行操作,本质上就是将你对模型的修改体现到数据库中具体的表上面。

Django通过一个叫做Django_migrations的表,记录并跟踪已经实施的migrate动作,通过对比获得哪些migrations尚未提交。

migrations的功能非常强大,允许你随机修改你的模型,而不需要删除或者新建你的数据库或者数据表,在不丢失数据的同时,实时动态更新数据库,我们将在后面的章节对此进行深入的阐述,但是现在,只需要记住修改模型时的操作分为三步:

  • 在models.py中修改模型
  • 运行 python manage.py makemigrations 为改动创建迁移记录
  • 运行 python manage.py migrate ,将操作同步到数据库

之所以要将创建和实时迁移的动作分成两个命令两步走是因为你也许要通过版本控制系统(例如GitHub SVN)提交你的项目代码,如果没有一个中间过程的保存文件(migrations),那么GitHub如何知道以及记录,同步,实施你所进行过的模型修改动作呢?毕竟,GitHub不和数据库直接打交道,也没法和你本地的数据通信。但是分开之后。你只需要将你的migrate文件(例如上面的0001)上传到GitHub,它就知道一切。

1.5. 使用模型的API

下面,让我们进入Python的交互环境,尝试使用Django提供的数据库访问API,要进入Python的shell,请输入命令:

python manage.py shell

相比较直接输入“Python”命令的方式进入Python环境,调用manage.py参数能将DJANGO_SETTINGS_MODULE 环境变量导入,它将自动按照mysite/settings.py中的设置,配置好你的Python shell环境,这样你就可以导入和调用你项目内的模块了。

当你进入shell后,尝试一下下面的API把:

    >>> from polls.models import Question, Choice # 导入我们写的模型类
    # 现在系统内还没有questions对象
    >>> Question.objects.all()
    <QuerySet []>

    # 创建一个新的question对象
    # Django推荐使用timezone.now()代替python内置的datetime.datetime.now()
    # 这个timezone就来自于Django唯一的依赖库pytz
    from django.utils import timezone
    >>> q = Question(question_text="What's new?", pub_date=timezone.now())

    # 你必须显式的调用save()方法,才能将对象保存到数据库内
    >>> q.save()

    # 默认情况,你会自动获得一个自增的名为id的主键
    >>> q.id
    1

    # 通过python的属性调用方式,访问模型字段的值
    >>> q.question_text
    "What's new?"
    >>> q.pub_date
    datetime.datetime(2012, 2, 26, 13, 0, 0, 775217, tzinfo=<UTC>)

    # 通过修改属性来修改字段的值,然后显式的调用save方法进行保存。
    >>> q.question_text = "What's up?"
    >>> q.save()

    # objects.all() 用于查询数据库内的所有questions
    >>> Question.objects.all()
    <QuerySet [<Question: Question object>]>

这里等一下:上面 是一个不可读的内容展示,你无法从中获得任何直观的信息,为此我们需要一点小技巧,让Django在打印对象时显示一些我们制定的信息。

返回polls.models.py文件,修改一下question和Choice这两个类,代码如下:

from django.db import models
from django.utils.encoding import python_2_unicode_compatible

@python_2_unicode_compatible # 当你想支持python2版本的时候才需要这个装饰器
class Question(models.Model):
    # ...
    def __str__(self):   # 在python2版本中使用的是__unique__
        return self.question_text

@python_2_unicode_compatible
class Choice(models.Model):
    # ...
    def __str__(self):
        return self.choice_text

这个技巧,不但对我们打印对象时很有帮助,在我们使用Django的admin站点时也同样有帮助。

另外,这里我们自定义一个模型的方法,用于判断问卷是否最近时间段内发布的。

import datetime
from django.db import models
from django.utils import timezone

class Question(models.Model):
    # ...
    def was_published_recently(self):
        return self.pub_date >= timezone.now() - datetime.timedelta(days=1)

请注意上面分别导入了两个关于时间的模块,一个是Python内置的datatime模块,一个是Django工具包提供的timezone模块。

保存修改后,我们重新启动一个新的Python shell ,再来看看其他的API。

>>> from polls.models import Question, Choice

# 先看看__str__()的效果,直观多了吧?
>>> Question.objects.all()
<QuerySet [<Question: What's up?>]>

# Django提供了大量的关键字参数查询API
>>> Question.objects.filter(id=1)
<QuerySet [<Question: What's up?>]>
>>> Question.objects.filter(question_text__startswith='What')
<QuerySet [<Question: What's up?>]>

# 获取今年发布的问卷
>>> from django.utils import timezone
>>> current_year = timezone.now().year
>>> Question.objects.get(pub_date__year=current_year)
<Question: What's up?>

# 查询一个不存在的ID,会弹出异常
>>> Question.objects.get(id=2)
Traceback (most recent call last):
...
DoesNotExist: Question matching query does not exist.

# Django为主键查询提供了一个缩写:pk。下面的语句和Question.objects.get(id=1)效果一样.
>>> Question.objects.get(pk=1)
<Question: What's up?>

# 看看我们自定义的方法用起来怎么样
>>> q = Question.objects.get(pk=1)
>>> q.was_published_recently()
True

# 让我们试试主键查询
>>> q = Question.objects.get(pk=1)

# 显示所有与q对象有关系的choice集合,目前是空的,还没有任何关联对象。
>>> q.choice_set.all()
<QuerySet []>

# 创建3个choices.
>>> q.choice_set.create(choice_text='Not much', votes=0)
<Choice: Not much>
>>> q.choice_set.create(choice_text='The sky', votes=0)
<Choice: The sky>
>>> c = q.choice_set.create(choice_text='Just hacking again', votes=0)

# Choice对象可通过API访问和他们关联的Question对象
>>> c.question
<Question: What's up?>

# 同样的,Question对象也可通过API访问关联的Choice对象
>>> q.choice_set.all()
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>
>>> q.choice_set.count()
3

# API会自动进行连表操作,通过双下划线分割关系对象。连表操作可以无限多级,一层一层的连接。
# 下面是查询所有的Choices,它所对应的Question的发布日期是今年。(重用了上面的current_year结果)
>>> Choice.objects.filter(question__pub_date__year=current_year)
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>

# 使用delete方法删除对象
>>> c = q.choice_set.filter(choice_text__startswith='Just hacking')
>>> c.delete()

关于模型的使用就暂时先介绍这么多,这部分内容是Django项目的核心,也会动态网站与数据库交互的核心。

1.6. admin后台管理站点

很多时候,我们不光要开发针对客户使用的前端页面,还要给后台管理人员提供相应的管理界面。但是大多数时候为你的团队或者客户编写用于增加,修改和删除内容的后台管理站点是一件非常乏味的工作并且没有多少创造性,而且也需要花费不少的时间和精力。Django最大的优点之一就是体贴的为你提供了一个基于项目model创建的一个后台管理站点admin。这个界面只给站点管理员使用,并不对大众开放,虽然admin的界面可能不是那么美观,功能不是那么强大,内容不一定符合你的要求,但是它是免费的,现成的,并且还是可制定的,有完善的帮助文档,所以。。

1.6.1. 创建管理员用户

首先,我们需要通过下面的命令,创建一个可以登录的admin站点的用户:

python manage.py createsuperuser

输入用户名:

Username: admin

输入邮箱地址:

Email address: xxx@xxx.xxx

输入密码:

Password: **********
Password (again): *********
Superuser created successfully.

注意:Django1.10版本后,超级用户的密码强制要求有一定的复杂性。

image

1.6.2. 启动开发服务器

服务器启动后,在浏览器访问http://127.0.0.1:8000/admin/。我们就能看到admin的登录界面了:

image

在实际环境中,为了站点的安全性,我们不能将管理后台的url随便暴露给他人,不能用/admin/这么简单的路径。

打开跟url路由文件mysite/url.py,修改其中admin.site.urls对应的正则表达式,比如:

from django.conf.urls import url
from django.contrib import admin

urlpatterns = [
    url(r'^my/set/', admin.site.urls),
]

这样,我们必须访问http://127.0.0.1:8000/my/set/才能进入admin界面。

1.6.3. 进入admin站点

利用刚才建立的admin账户,登录admin,你将看到如下的界面:

image

当前只有两个可编辑的的内容,groups和users,他们是django.contrib.auth模块提供的身份认证框架。

1.6.4. 在admin中注册投票应用

现在还无法看到投票应用,必须先在admin中注册,告诉admin站点,请将polls的模型加入站点内,接受站点的管理。

打开polls.admin.py文件,加入下面的内容:

from django.contrib import admin
from .models import Question

admin.site.register(Question)

1.6.5. admin的站点的主要功能

注册question模型后,刷新admin页面就能看到Question栏目了。

image

点击“Question”,进入questions的修改列表页面。这个页面会显示所有的数据库内的questions对象,你可以在这里对他们进行修改,看到下面的“what 's up?”了吗?它就是我们之前创建的一个question对象,并且通过str方法的帮助,显示了较为直观的信息,而不是一个冷冷的对象类型名称。

image

下面点击 what's up ? 进入编辑页面:

image

这里需要注意的是:

  • 页面中的表单是由Question模型自动生成的
  • 不同的模型字段类型(DateTimeField , CharField)会表现为不同的HTML input 框类型。
  • 每一个DateTimeField都会自动生成一个可点击链接,日期是Today,并且有一个日历弹出框;时间是Now,并且有一个通用的时间输入列表框。

在页面的底部,则是一些可选项按钮:

  • delete 弹出一个删除确认页面

  • save and add another : 保存当前修改,并加载一个新的空白的当前类型对象的表单

  • save and continue aditing :保存当前修改,并重新加载该对象的编辑页面

  • save :保存修改,返回当前对象类型的列表页面

    如果Date published字段的值和你在前面创建它的时候不一致,可能你没有正确的配置TIME_ZONE ,在国内,通常是8个小时的时间差别。修改TIME_ZONE 配置并重新加载页面,就能显示正确的时间了。

在页面的右上角,点击History按钮,你会看到你对当前对象的所有修改操作都在这有记录,包括修改时间和操作人员,如下图所示:

image

1.6.6. admin后台管理用户密码修改

方法一

在Terminal中执行下面代码:

python manage.py changepassword your_name

(其中“your_name”为你要修改密码的用户名),根据提示内容修改即可。

方法二

进入shell环境,执行:

from django.contrib.auth.models import User
u = User.objects.get(username='your_name')
u.set_password('new_password')
u.save()

1.7. 视图的学习

1.7.1. 视图概述

一个视图就是一个页面,通常提供特定的功能,使用特定的模板。例如:在一个博客应用中,你可能会看到下列视图:

  • 博客主页:显示最新发布的一些内容
  • 每篇博客的详细页面:博客的永久链接
  • 基于年的博客页面:显示指定年内的所有博客
  • 基于月的博客页面:显示指定月内的所有博客
  • 基于日的博客页面:显示指定日内的所有博客
  • 发布评论:处理针对某篇博客发布的评论

在我们的投票应用中,我们将建立下面的视图:

  • 问卷“index”页:显示最新的一些问卷
  • 问卷“detail”页面:显示一个问卷的详细文本内容,没有调查结果但是有一个投票或调查表单。
  • 问卷“results”页面:显示某个问卷的投票或调查结果。
  • 投票动作页面:处理针对某个问卷的某个选项的投票动作。

在Django中,网页和其他的一些内容都是通过视图来处理的,视图其实就是一个简单的Python函数(在基于类的视图中称为方法)。Django通过对比请求的URL地址来选择对应的视图。

1.7.2. 编写视图

下面,打开polls.view.py文件,输入下列代码:

def detail(request,qiestion_id):
    return HttpResponse("you are lokking at quetion %s."%question_id)

def results(request,question_id):
    respose = "You're looking at the results of question %s."
    return HttpResponse(respose % question_id)

def vote(request,question_id):
    return HttpResponse("You're voting on question %s."%question_id)

然后在polls/urls.py文件中加入下面的url模式,将其映射到我们上面新增的视图。

urlpatterns = [
    # url(r'^polls/',include('polls.urls')),
    # ex :/polls/
    url(r'^$',views.index, name='index'),
    # ex :/polls/5/results/
    url(r'^(?P<question_id>[0-9]+)/$',views.detail,name='detail'),
    # ex:/polls/5/results/
    url(r'^(?P<question_id>[0-9]+)/results/$',views.results,name='results'),
    # ex: /polls/5/vote
    url(r'^(P<question_id>[0-9]+)/vote/$',views.vote,name='vote'),

]

现在去浏览器中访问/polls/34/(注意:这里省略了域名。另外,使用了二级路由后,url中都要添加polls部分,参考前面的章节),它将调用detail()函数,然后在页面中显示你在url里提供的ID。访问/polls/34/results//polls/34/vote/,将分别显示预定义的伪结果和投票页面。

image

image

image

上面访问的路由过程如下:当有人访问/polls/34/地址时,Django将首先加载mysite.urls模块,因为它是settings文件里设置的根URL配置文件。在该文件里,Django发现了urlpatterns变量,于是在其内按顺序进行匹配。当它匹配上了^polls/,就裁去url中匹配的文本polls/,然后将剩下的文本“34/”,传递给polls.urls进行下一步的处理。在polls.urls中,又匹配到了r’^(?P<question_id>[0-9]+)/$’,最终结果就是调用该模式对应的detail()视图,也就是下面的函数:

detail(request=<HttpRequest object>, question_id='34')

函数中的question_id=’34’参数,是由(?P<question_id>[0-9]+)而来。在正则表达式中通过一个双圆括号,Django会捕获它匹配到的值并传递给对应的视图,作为视图的位置参数之一,而?P<question_id>则表示我要给这个捕获的值指定一个特殊的变量名,在视图中可以通过question_id这个变量名随意的引用它,形成一个关键字参数,不用考虑参数的位置。至于[0-9]+则是一个很简单的原生正则表达式,用于匹配一系列连续的数字,它匹配到的值也就是具体要传递的参数值。

所有的URL模式都是正则表达式,Django不限制你在url模式中的书写方式。但是,你真的没必要书写一个如下的较为愚蠢的包含.html的模式,它显然是没必要,不够简练的:

url(r'^polls/latest\.html$', views.index),

你完全可以用下面的模式代替上面的:

url(r'^polls/latest$', views.index),

1.7.3. 写一个实际的视图

每一个视图至少做两件事之一:返回一个包含请求页面的HTTPResponse对象或者弹出一个类似Http404的异常,其他的则随便你,你爱干什么干什么。

下面是一个Http404异常界面:

image

下面是一个新的index()视图,用于替代先前无用的index。它会根据发布日期显示最近的5个投票问卷。

from django.http import HttpResponse

from .models import Question

def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    output = ', '.join([q.question_text for q in latest_question_list])
    return HttpResponse(output)

# 下面是那些没改动过的视图(detail, results, vote)

这里有一个非常重要的问题:在当前视图中的HTML页面是硬编码的。如果你想改变页面的显示内容,就必须修改这里的Python代码。为了解决这个问题,需要使用Django提供的模板系统,解耦视图和模板之间的硬链接。

首先,在polls目录下创建一个新的templates目录,Django会在它里面查找模板文件。在templates目录中,再创建一个新的子目录叫polls,进入该子目录,创建一个新的index.html。换句话说你的模板文件应该是polls/template/polls/index.html。可以在Django中直接使用polls/index/html引用该文件。

注意:在Pycharm中,templates文件夹通常已经帮你创建好了!

image

模板命名空间

你也许会想,为什么不把模板文件直接放在polls/templates目录下,而是费劲的再建一个子目录呢?设想这么个情况,有另外一个APP,它也有一个名为Index.html的文件,当Django在搜索模板时,有可能找到它,然后退出搜索,这就命中了错误的目录,不是我们想要的结果。解决了这个问题的最好办法就是在templates目录下再建立一个与APP同名的子目录,将自己所属的模板都放在里面,从而达到 独立命名空间的作用,不会再出现引用错误。

现在,将下面代码写入文件polls/templates/polls/index.html:

{% if latest_question_list %}
    <ul>
    {% for question in latest_question_list %}
    <li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
    {% endfor %}
    </ul>
{% else %}
    <p>No polls are available.</p>
{% endif %}

同时,修改视图文件中polls/views.py,让新的index.html文件生效。

from django.http import HttpResponse
from django.template import loader
from .models import Question

def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    template = loader.get_template('polls/index.html')
    context = {
    'latest_question_list': latest_question_list,
    }
    return HttpResponse(template.render(context, request))

上面的代码会加载polls/index.html文件,并传递给它一个参数,这个参数是一个字典,包含了模板变量名和Python对象之间的映射关系。

在浏览器中通过访问/polls/,你可以看到一个列表,包含“what's up” 的问卷,以及连接到其对应详细内容页面的链接点。

如果你显示的是下面的页面,说明你前面没有添加Question对象。没关系,我们可以手动添加以下即可。

image

进入admin界面,选择Questions,点击右上角的Add question,如下操作。

image

image

image

快捷方式:render()

在实际运用中,加载模板,传递参数,返回HTTPResponse对象时一整套再常用不过的操作了,为了节省力气,Django提供了一个快捷方式,render函数,一步到位,看如下代码:

polls/views.py

from django.shortcuts import render
from .models import Question
def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    context = {'latest_question_list': latest_question_list}
    return render(request, 'polls/index.html', context)

render()函数的第一个位置参数是请求对象(就是view函数的第一个参数),第二个位置参数是模板,还可以有一个可选择的第三参数,一个字典,包含需要传递给模板的数据,最后render函数返回一个经过字典数据渲染过的模板封装而成的HTTPResponse对象。

HTTPResponse对象

对于HTTPRequest对象来说,是由Django自动创建的,但是HttpResponse对象就必须由我们自己创建,每个view请求处理方法必须返回一个HTTPResponse对象。

HTTPResponse类在Django.http.HttpResponse

在HTTPResponse对象上扩展的常用方法:

  • 页面渲染:render()
  • 页面跳转:redirect(" path")
  • locals() :可以直接将函数中所有的变量传给模板

总结:render和redirect的区别

1,if render的页面需要模板语言渲染,需要的将数据库的数据加载到HTML,那么所有的这一部分除了写在视图函数中,必须还要写在login中,没有解耦。

2,render是渲染变量到模板中,而redirect是HTTP中的1个跳转的函数,一般会生成302状态码。

1.7.4. 返回404错误

现在让我们来编写具体问卷文本内容的视图polls/views.py

from django.http import Http404
from django.shortcuts import render
from .models import Question
# ...
def detail(request, question_id):
    try:
        question = Question.objects.get(pk=question_id)
    except Question.DoesNotExist:
        raise Http404("Question does not exist")
    return render(request, 'polls/detail.html', {'question': question})

所以,访问的话,如果请求的问卷ID不存在,那么会弹出一个Http404错误。

新建polls/detail.html文件,暂时写入下面的代码:

{{ question }}

快捷方式:get_object_or_404()

就像render函数一样,Django同样为你提供了一个偷懒的方式,替代上面的多行代码,那就是get_object_or_404()方法,参考下面的代码:

polls/view.py

from django.shortcuts import get_object_or_404, render
from .models import Question
# ...
def detail(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, 'polls/detail.html', {'question': question})

别说我没提醒你,和render一样,也需要从Django内置的快捷方式模块中导出get_object_or_404()

get_object_or_404()方法将一个Django模型作为第一个位置参数,后面可以跟上任意个数的关键字参数,如果对象不存在则弹出Http404错误。

同样,还有一个get_list_or_404()方法,和上面的get_object_or_404()类似,只不过是用来替代filter()函数,当查询列表为空时弹出404错误。(filter是模型API中用来过滤查询结果的函数,它的结果是一个列表集。而get则是查询一个结果的方法,和filter是一个和多个的区别!)

1.7.5. 模板的学习

detail()视图会将上下文变量question传递给对应的polls/templates/polls/detail/html 模板,修改该模板的内容,如下所示:

<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
    <li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>

在模板系统中 圆点. 是万能的魔法师,你可以用它访问对象的属性,在例子 中,Django首先会在Question对象中尝试查找一个字典,如果失败,则尝试查找属性,如果再失败,则尝试作为列表的索引进行查询。

在{ % for %} 循环中的方法调用——poll.choice_set.all其实就是Python的代码poll.choice_set.all() ,它将返回一组可迭代的Choice对象,并用在{& for %}标签中。

这里我们对Django模板语言有个简单的印象就好。

1.7.6. 删除模板中硬编码的URLs

在polls/index.html文件中,还有一部分硬编码存在,也就是href里面的“/polls/”部分:

<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>

它对于代码修改非常不利,设想如果你在urls.py文件里修改了正则表达式,那么你所有的模板对这个url的引用都需要修改,这是无法接受的!

我们前面给urls定义了一个name的别名,可以用它来解决这个问题,具体代码如下:

<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>

Django会在polls.urls文件中查找name ='detail’ 的url,具体的就是下面这行:

url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),

举个例子,如果你想要将polls的detail视图的URL更换为polls/specifices/12/,那么你不需要在模板中重新修改url地址了,仅仅只需要在polls/urls.py文件中,将对应的正则表达式改成下面这样的就好了,所有模板中对它的引用都会自动修改成新的链接:

# 添加新的单词'specifics'
url(r'^specifices/(?P<question_id>[0-9]+)/$', views.detail, name='detail'),

1.7.7. URL names的命名空间

在本文中,只有一个APP也就是polls,但是在现实中很显然会有5个,10个,更多的app同时在一个项目中。Django是如何区分这些APP之间的URL name呢?

使用URLconf的命名空间,在polls.urls.py文件的开头部分,添加一个app_name的变量来指定该应用的命名空间:

from django.conf.urls import url
from . import views

app_name = 'polls'  # 关键是这行
urlpatterns = [
    url(r'^$', views.index, name='index'),
    url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
    url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'),
    url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
]

现在我们将代码修改的严谨一些,将polls/templates/polls/index.html中的

<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>

修改为:

<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>

注意引用方法是冒号而不是原点也不是斜杠!!!

1.7.8. 视图函数

一个视图函数,简称视图,是一个简单的Python函数,它接受Web请求并且返回Web响应。响应可以是一张网页的HTML内容,一个重定向,一个404错误,一个XML文档,或者一张图片....是任何东西都可以。无论视图本身包含什么逻辑,都要返回响应。为了将代码放在某处,约定是将视图方式在项目或者应用程序目录中的名为view.py的文件中。下面是一个返回当前日期和时间作为HTML文档的视图:

from django.shortcuts import render, HttpResponse, HttpResponseRedirect, redirect
import datetime

def current_datetime(request):
    now = datetime.datetime.now()
    html = "<html><body>It is now %s.</body></html>" % now
    return HttpResponse(html)

让我们逐行解析上面代码:

1,首先,我们从django.shortcuts导入了HttpRespons类,以及Python的datetime库。

2,我们定义了current_datetime函数,它就是视图函数。每个视图函数都使用HttpRequest对象作为第一个参数,并且通常称之为request。

(注意:视图函数的名称并不重要;不需要用一个统一的命名方式来命名,以便让Django识别它,我们将其命名为current_datetime,是因为这个名称能够精准地反映出它的功能)。

3,这个视图会返回一个HttpResponse对象,其中包含生成的响应。每个视图函数都负责返回一个HTTPResponse对象。

详细可参考下图:

image

所以视图层,熟练掌握两个对象即可:请求对象(request)和响应对象(HttpResponse)。

1.7.9. 表单form

为了接收用户的投票选择,我们需要在前段页面显示一个投票界面,让我们重写之前的polls/detail.html文件,代码如下:

<h1>{{ question.question_text }}</h1>

{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}

<form action="{% url 'polls:vote' question.id %}" method="post">
{% csrf_token %}
{% for choice in question.choice_set.all %}
    <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" />
    <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br />
{% endfor %}
<input type="submit" value="Vote" />
</form>

简要说明:

上面的模板显示一系列单选按钮,按钮的值时选项的ID,按钮的名字是字符串“choice”。这意味着,当你选择了其中某个按钮,并提交表单,一个包含数据choice=# 的POST请求将被发送到指定的url,#是被选择的选项的ID,这就是HTML表单的基本概念。

如果你有一定的前端知识,那么form标签的action属性和method属性应该很清楚其含义,action表示你要发送的目的url,method表示提交数据的方式,一般分为POST和GET。

forloop.counter是Django模板系统专门提供的一个变量,用来表示你当前循环的次数,一般用来给循环项目添加有序数标。

由于我们发送了一个POST请求,就必须考虑一个跨扎请求伪造的安全问题,简称CSRF,Django为你提供了一个简单的方法来避免这个困扰,那就是在form表单内添加一条{ % csrf_token %} 标签,标签名不可更改,固定格式,位置任意,只要是在form表单内。这个方法对form表单的提交方式方便好使,但如果是用ajax的方式提交数据,就不能用这个方法了。

现在,让我们创建一个处理提交过来的数据视图,前面我们已经写了一个“占坑”的vote视图的url(polls/urls.py)

url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote')

以及占坑的vote视图函数(polls.views.py),我们把坑填起来:

from django.shortcuts import get_object_or_404, render
from django.http import HttpResponseRedirect, HttpResponse
from django.urls import reverse
from .models import Choice, Question
# ...

def vote(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    try:
        selected_choice = question.choice_set.get(pk=request.POST['choice'])
    except (KeyError, Choice.DoesNotExist):
        # 发生choice未找到异常时,重新返回表单页面,并给出提示信息
        return render(request, 'polls/detail.html', {
        'question': question,
        'error_message': "You didn't select a choice.",
        })
    else:
        selected_choice.votes += 1
        selected_choice.save()
        # 成功处理数据后,自动跳转到结果页面,防止用户连续多次提交。
        return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))

有些新的东西,我们要解释一下:

  • request.POST是一个类似字典的对象,允许你通过键名访问提交的数据,本例中,request.POST['choice'] 返回被选择选项的ID,并且值的类型永远是string字符串,哪怕它看起来像数字!同样的,你也可以用类似的手段获取GET请求发送过来的数据,一个道理。
  • request.POST['choice'] 有可能触发一个KeyError异常,如果你的POST数据里没有提供choice键值,在这种情况下,上面的代码会返回表单页面并给出错误提示,PS:通常我们会给个默认值,防止这种异常的产生,例如---- request.POST['choice',None],一个None解决所有问题。
  • 在选择计数器加一后,返回的是一个HttpResponseRedirect而不是先前我们常用的HTTPResponse,HttpResponseRedirect需要一个参数:重定向的URL。这里有一个建议,当你成功处理POST数据后,应当保持一个良好的习惯,始终返回一个HttpResponseRedirect。这里不仅仅是对Django而言,它是一个良好的WEB开发习惯。
  • 我们再上面HttpResponseRedirect的构造器中使用了一个reverse()函数,它能帮助我们避免在视图函数中硬编码URL。它首先需要一个我们再URLconf中指定的name,然后是传递的数据。例如'polls/3/results/’ ,其中的3是某个question.id的值,重定向后将进入polls:results对应的视图,并将question.id传递给它,白话来讲,就是把活扔给另一个路由对应的视图去干。

当有人对某个问题投票后,vote()视图重定向到了问卷的结果显示页面,下面我们来写这个处理结果页面的视图(polls/views.py):

from django.shortcuts import get_object_or_404, render

def results(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, 'polls/results.html', {'question': question})

同样,还需要写个模板polls/templates/polls/results.html (路由,视图,模板,模型都是这个套路)

<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
    <li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
{% endfor %}
</ul>
<a href="% url 'polls:detail' question.id %}">Vote again?</a>

现在你可以到浏览器中访问/polls/1/ 了 ,投票了。你会看到一个结果页面,每投一次,它的内容就会更新一次。如果你提交的时候没有选择项目,则会得到一个错误提示。

如果你在前面漏掉了一部分操作没做,比如没有创建choice选项对象,那么可以按下面的操作,补充一下:

D:\Django\mysite>python  manage.py shell
Python 3.7.1 (v3.7.1:260ec2c36a, Oct 20 2018, 14:57:15) [MSC v.1915 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from polls.models import Question
>>> q = Question.objects.get(pk=1)
>>> q.choice_set.create(choice_text = 'Not much',votes=0)
<Choice: Not much>
>>> q.choice_set.create(choice_text = 'The sky',votes=0)
<Choice: The sky>
>>> q.choice_set.create(choice_text = 'Just hacking again',votes=0)
<Choice: Just hacking again>

3.Django路由

目录结构:

image 路由分发---无名分组、有名分组&分发 urls.py

"""路由控制 URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/2.0/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path,re_path,include

from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),

    # 分发:全局分发器;用于把路径分发到不同的应用(application)里面
    re_path(r"^app01/",include("app01.urls"))
    # include()是分发函数;如果用户访问的是 app01, include里面的 app01.urls这个py文件就能分发下去;此时用户输入的url中需要有 /app01/ ,e.g. http://127.0.0.1:8000/app01/articles/2003/10/123/
    # 如果想把路径中的 /app01/ 去掉,如下:
    # re_path(r"^",include("app01.urls"))
]

app01/urls.py

from django.contrib import admin
from django.urls import path,re_path

from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),

    # 路由配置:路径 ----> 视图函数
    re_path(r'^articles/2003/$', views.special_case_2003),
    # ^articles/2003/$ :正则匹配;匹配以articles/2003/开头、以articles/2003/结尾的路径;唯一匹配

    re_path(r'^articles/([0-9]{4})/$', views.year_archive),
    # ([0-9]{4}) 是一个分组匹配(加了括号);匹配到路径后,request会传入 year_archive 函数的第一个参数,分组匹配结果会以位置参数传入到year_archive函数的第二个参数, e.g. year_archive(request,1999);so year_archive函数需要有两个参数
    # 从上到下执行,所以如果匹配到了2003,会走第一个路径,下面的不再执行
    # 匹配分组之后,视图函数一定要传入相应的位置参数

    re_path(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive),
    # 同理, month_archive需要有三个参数

    re_path(r'^articles/(?P<y>[0-9]{4})/(?P<m>[0-9]{2})/(?P<c>[0-9]+)/$', views.article_detail),
    # (?P<名字>):这是有名分组(就是给每个组取了个名字,用的比较多),有名分组利用的是关键字传参;
    # 有名分组取的名字一定要和后面函数的形参相同;有名分组传参不依赖于位置顺序
]

views.py

from django.shortcuts import render,HttpResponse

# Create your views here.

def special_case_2003(request):  # request指请求对象

    return HttpResponse("special_case_2003")
    # HttpResponse 指响应对象;参数是字符串,响应体的内容

def year_archive(request,year):
    return HttpResponse("year_archive_%s"%year)

def month_archive(request,year,month):
    return HttpResponse("month_archive_%s_%s"%(year,month))

def article_detail(request,c,m,y):  # 形参位置顺序无所谓 # y,m,c都是字符串;通过re_path(url)传过来的参数都是字符串格式
    return HttpResponse(y+"-"+m+"-"+c)

反向解析:

反向解析一:在模板中 urls.py

"""路由控制之反向解析 URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/2.0/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path

from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path("login/",views.login,name="log")  # name=log 是这个url的别名(反向解析); path()的第一个参数是接口,用户需要知道
]

app01/views.py

from django.shortcuts import render,HttpResponse

# Create your views here.

def login(request):
    # request 是请求对象;所有的请求信息都在request里面
    print(request.method)
    # request.method 表示获取 请求方式(GET/POST)

    if request.method == "GET":
        print(request.GET)
        # request.GET 表示获取所有的 GET请求数据;字典的形式
        # <QueryDict: {}>
        return render(request, "login.html")
        # 反向解析的过程:用户通过 /login/ 这个接口 到达urls.py,然后通过 path("login/",views.login,name="log") 到达 views.py;
        # render(request, "login.html") 方法在渲染 login.html 这个页面的时候,会在 action="{% url 'log' %}" 这一步 找到别名为 "log" 的url,并将 {% url 'log' %} 替换为该url
    else:
        print(request.POST)
        # request.PSOT 表示获取所有的 POST请求数据;字典的形式
        # < QueryDict: {'username': ['123'], 'psw': ['123']} >

        # 获取请求体(POST)
        user = request.POST.get("username")
        psw = request.POST.get("psw")

        if user == "abc123" and psw == "abc123":
            # 每次请求一定要有 返回值
            return HttpResponse("成功")
        else:
            return HttpResponse("用户名密码错误")

login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
{#action如果不写IP和Port,默认是发送到当前的IP端口#}
{#<form action="http://127.0.0.1:8000/login/" method="post">#}

{#{% url "log" %} 反向解析的语法#}
{#通过这种反向解析方法,不管 urls.py 中的路径接口如何改变,action中的路径能始终自动与其保持一致#}
<form action="{% url 'log' %}" method="post">
    用户名:<input type="text" name="username">
    密码:<input type="password" name="psw">
    <input type="submit">
</form>

</body>
</html>

反向解析二:在python脚本的视图函数中(views.py) 以“1. 无名分组、有名分组&分发”中的代码为例: app01/urls.py

from django.contrib import admin
from django.urls import path,re_path

from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),

    # 路由配置:路径 ----> 视图函数
    re_path(r'^articles/2003/$', views.special_case_2003,name="s_c_2003"),
    # ^articles/2003/$ :正则匹配;匹配以articles/2003/开头、以articles/2003/结尾的路径;唯一匹配

    re_path(r'^articles/([0-9]{4})/$', views.year_archive,name="y_a"), # 通过 name="y_a" 给这个url起一个别名
]

app01/views.py

from django.shortcuts import render,HttpResponse

# Create your views here.

from django.urls import reverse

def special_case_2003(request):  # request指请求对象

    # 在视图函数中也可以反向解析获取其他的url
    url1 = reverse("s_c_2003")  # 反向解析
    url2 = reverse("y_a",args=("1999",))
    # 由于别名为 "y_a" 的那个url含有正则匹配的分组,而我们指向解析出来的应该是一个具体的路径,而不是一个匹配规则,所以在reverse("y_a",args=("1999",)) 中需要手动添加一个元组如args=("1999",),元组中的元素得满足 该url的正则匹配规则

    print(url1)
    print(url2)


    return HttpResponse("special_case_2003")
    # HttpResponse 指响应对象;参数是字符串,响应体的内容

def year_archive(request,year):
    return HttpResponse("year_archive_%s"%year)

django2.0版的path

一个简单的基本事例: app01/urls.py

from django.contrib import admin
from django.urls import path,re_path

from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('articles/<greet>/', views.special_case_2004),
]

app01/views.py

from django.shortcuts import render,HttpResponse

def special_case_2004(request,greet):  # request指请求对象
    return HttpResponse(greet)

使用尖括号(<>)从url中捕获值;<>就代表一个分组;

捕获值中可以包含一个转化器类型(converter type),比如使用 捕获一个整数变量。若果没有转化器,将匹配任何字符串,当然也包括了 / 字符;

无需添加前导斜杠。

path转化器:Django默认支持以下5个转化器:

str,匹配除了路径分隔符(/)之外的非空字符串,这是默认的形式 int,匹配正整数,包含0。 slug,匹配字母、数字以及横杠、下划线组成的字符串 path,匹配任何非空字符串,包含了路径分隔符 uuid,匹配格式化的uuid,如 075194d3-6885-417e-a8a8-6c931e272f00。

4.Django视图与模板

视图函数

一个视图函数,简称视图,是一个简单的Python 函数,它接受Web请求并且返回Web响应。响应可以是一张网页的HTML内容,一个重定向,一个404错误,一个XML文档,或者一张图片. . . 是任何东西都可以。无论视图本身包含什么逻辑,都要返回响应。代码写在哪里也无所谓,只要它在你的Python目录下面;为了将代码放在某处,约定是将视图放置在项目或应用程序目录中的名为views.py的文件中。

视图请求对象

request对象:包含所有的请求信息(浏览器给服务器发过来的是一堆字符串)

1. 属性:
1.1 请求方式:
request.method:获取请求方式(GET/POST) # 地址栏发过来的请求默认都是GET请求;<form mehtod="post">为POST请求
<form action="" method="post"></form>中的action如果为空,默认是往当前页面的url发送请求;如果只写了路径(如 /login/),默认是往当前页面的IP和端口拼接该路径发送请求

1.2 请求数据:
request.GET:获取所有的GET请求信息(字典形式)
request.POST:获取所有的POST请求信息(字典形式)


1.3 http://127.0.0.1:8000/index/?name=1&age=2  GET请求数据中某个值的获取:
request.GET.get("name")
request.GET.get("age")
request.POST.get("xxx") 同理

1.4 request.path:获取请求路径(如:/login/)
"""
url组成:
协议(http):IP:port/路径?get请求数据
如:http://127.0.0.1:8000/index/?name=1&age=2

"""
# 127.0.0.1:8000 也有访问路径,根目录、根路径(/)
# 在url控制器中添加根路径: re_path(r"^$",views.index)
# url控制器中 路径和函数是一对一或多对一的关系


request.get_full_path():获取路径和后面的get请求数据

视图响应对象

响应对象主要有三种形式:

  • HttpResponse()

  • render()

  • redirect()

HttpResponse()括号内直接跟一个具体的字符串作为响应体。(html标签也能渲染出来)

render用法:render(request,template_name[, context]) 如:render(request,"index.html",{"timer":ctime})

render()方法:结合一个给定的模板和一个给定的上下文字典,并返回一个渲染后的 HttpResponse 对象。

参数:

  • request: 用于生成响应的请求对象。
  • template_name:模板文件;要使用的模板的完整名称
  • context:可选参数;添加到模板上下文的一个字典。默认是一个空字典。

注:不含正则时也可使用 path;有正则需要用re_path

redirect()用法:

1.传递要重定向的一个硬编码的URL:

def my_view(request):
    ...
    return redirect('/some/url/')
  1. 也可以是一个完整的URL:
def my_view(request):
    ...
    return redirect('http://example.com/')

模板

templates文件夹下面的文件都叫模板文件;只不过有的包含模板语法(这种情况下,在把html页面发送给客户端之前,会先有一个解析模板语法的过程),有的不包含

模板语法之变量:

project的urls.py

from django.contrib import admin
from django.urls import path

from app01 import views
urlpatterns = [
    path('admin/', admin.site.urls),
    path(r"index/",views.index)
]

app01/views.py

from django.shortcuts import render

# Create your views here.

def index(request):
    """
    模板语法只有两个:
    (1)、{{}}  : 用于渲染变量
        1. 深度查询:句点符
        2. 过虑器
    (2)、 {% %} : 用于渲染标签
    :param request:
    :return:
    """


    name = "neo"
    i = 10
    l = [1,2,"a"]
    info = {"name":"neo","age":22}
    b = True

    class Person(object):
        def __init__(self,name,age):
            self.name = name
            self.age = age
    alex = Person("alex",33)
    egon = Person("egon",22)
    person_list = [alex,egon]


    # return render(request,"index.html",{"name":name})
    # 如果变量特别多,可利用 locals():作用就是把所有的局部变量按照字典的形式传到 render()的第三个形参位置;传的方式是按照 {"name":name,"i":i,...}这种方式
    return render(request,"index.html",locals())

templates/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<p>{{ name }}</p>
<p>{{ i }}</p>
<p>{{ l }}</p>
<p>{{ b }}</p>
<p>{{ alex }}</p>
<p>{{ person_list }}</p>
<p>{{ info }}</p>
<hr>

深度查询:利用点来完成
{# 想要列表l的第二个元素: l.1 #}
<p>{{ l.1 }}</p>

{# 获取字典info中name那个key对应的value #}
<p>{{ info.name }}</p>

{# 获取对象alex的name属性值 #}
<p>{{ alex.name }}</p>

{# 获取person_list列表中第2个对象的age属性值 #}
<p>{{ person_list.1.age }}</p>

</body>
</html>

模板之过虑器:

project的urls.py

from django.contrib import admin
from django.urls import path

from app01 import views
urlpatterns = [
    path('admin/', admin.site.urls),
    path(r"index/",views.index),
    path(r"login/",views.login)
]

app01/views.py

from django.shortcuts import render,HttpResponse

# Create your views here.

def index(request):
    """
    模板语法只有两个:
    (1)、{{}}  : 用于渲染变量
        1. 深度查询:句点符
        2. 过虑器: {{val | filter_name: 参数}}
    (2)、 {% %} : 用于渲染标签
    :param request:
    :return:
    """

    # 深度查询:句点符
    name = "neo"
    i = 10
    l = [1,2,"a"]
    info = {"name":"neo","age":22}
    b = True

    class Person(object):
        def __init__(self,name,age):
            self.name = name
            self.age = age
    alex = Person("alex",33)
    egon = Person("egon",22)
    person_list = [alex,egon]

    # 过虑器
    import datetime
    now = datetime.datetime.now()

    empty_list = []
    file_size = 1235467
    str = "hello world"
    text = "Django is a high-level Python Web framework that encourages rapid development and clean, pragmatic design. Built by experienced developers, it takes care of much of the hassle of Web development, so you can focus on writing your app without needing to reinvent the wheel. It’s free and open source."
    link = "<a href='#'>click</a>"

    # 模板之标签
    num = 90

    # return render(request,"index.html",{"name":name})
    # 如果变量特别多,可利用 locals():作用就是把所有的局部变量按照字典的形式传到 render()的第三个形参位置;传的方式是按照 {"name":name,"i":i,...}这种方式
    return render(request,"index.html",locals())

def login(request):
    if request.method == "POST":
        return HttpResponse("OK")
    return render(request,"login.html")  # login.html中含有模板语法,因为有{% csrf_token %};所以发送给客户端之前会先渲染模板语法

templates/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<p>{{ name }}</p>
<p>{{ i }}</p>
<p>{{ l }}</p>
<p>{{ b }}</p>
<p>{{ alex }}</p>
<p>{{ person_list }}</p>
<p>{{ info }}</p>
<hr>

{#深度查询:利用点来完成#}
{# 想要列表l的第二个元素: l.1 #}
<p>{{ l.1 }}</p>

{# 获取字典info中name那个key对应的value #}
<p>{{ info.name }}</p>

{# 获取对象alex的name属性值 #}
<p>{{ alex.name }}</p>

{# 获取person_list列表中第2个对象的age属性值 #}
<p>{{ person_list.1.age }}</p>
<hr>

<h3>过虑器</h3>
{# 时间类型会自动给你渲染好格式,如:June 6, 2018, 4:32 p.m. #}
{{ now }}
{# 把上述时间类型通过过虑器转化格式;date是Django提供的过虑器名字,固定的 #}
{{ now|date:"Y-m-d" }}

{#过虑器#}
{# default :如果一个变量是false或者为空,使用给定的默认值。否则,使用变量的值。#}
{{ empty_list|default:"数据为空" }}
{#length:返回值的长度。它对字符串和列表都起作用。#}
{{ person_list|length }}
{#filesizeformat:将值格式化为一个 “人类可读的” 文件尺寸 (例如 '13 KB', '4.1 MB', '102 bytes', 等等)。#}
<p>{{ file_size|filesizeformat }}</p>
{#date:如果 value=datetime.datetime.now(),同上#}
{#slice:把字符串按照切片的功能进行切割显示(顾头不顾尾)#}
{{ str|slice:"2:-1" }}
{#truncatechars:如果字符串字符多于指定的字符数量,那么会被截断。截断的字符串将以可翻译的省略号序列(“...”)结尾(...也占3个字符)。 参数:要截断的字符数#}
<p>{{ text|truncatechars:20 }}</p>
{#truncatewords:按照单词截取#}
<p>{{ text|truncatewords:6 }}</p>

{#safe:Django的模板中会对HTML标签和JS等语法标签进行自动转义;为了在Django中关闭HTML的自动转义有两种方式,如果是一个单独的变量我们可以通过过滤器“|safe”的方式告诉Django这段代码是安全的不必转义。#}
<p>{{ link }}</p>
{#浏览器效果:<a href='#'>click</a>#}
<p>{{ link|safe }}</p>

{#add:加法;如下:让其加100#}
<p>{{ l.0|add:100 }}</p>
{#upper:转成大写#}
<p>{{ name|upper }}</p>
<hr>

{#标签#}
{#for标签:遍历每一个元素#}
{#i就是l列表中的元素#}
{% for i in l %}
<p>{{ i }}</p>
{% endfor %}
{#可以利用{% for obj in list reversed %}反向完成循环。#}

{#i是info字典中的每个key#}
{% for i in info %}
<p>{{ i }}</p>
{% endfor %}
{#遍历一个字典#}
{% for key,val in info.items %}
<p>{{ key }}:{{ val }}</p>
{% endfor %}

{% for person in person_list %}
<p>{{ person.name }}    {{ person.age }}</p>
{% endfor %}

{#循环序号可以通过{{forloop}}显示;{{forloop}}必须要写在循环内部;forloop.counter是从1开始,forloop.counter0是从0开始 #}
{% for i in l %}
<p>{{ forloop.counter}} {{ i }}</p>
{% endfor %}

{#for...empty:for 标签带有一个可选的{% empty %} 从句,以便在给出的组是空的或者没有被找到时,可以有所操作。#}
{% for i in empty_list %}
<p>{{ i }}</p>
    {% empty %}
    <p>内容为空</p>
{% endfor %}
{#如果empty_list为空,就会显示{% empty %}后面的 <p>内容为空</p> 标签#}
<hr>
{#if标签:{% if %}会对一个变量求值,如果它的值是“True”(存在、不为空、且不是boolean类型的false值),对应的内容块会输出。#}
{% if num > 100 or num < 0 %}
    <p>无效</p>
    {% elif num > 80 and num < 100 %}
    <p>优秀</p>
    {% else %}
    <p>凑合吧</p>

{% endif %}

{#with:使用一个简单地名字缓存一个复杂的变量,当你需要使用一个“昂贵的”方法(比如访问数据库)很多次的时候是非常有用的#}
{% with person_list.1.age as p_age %}
{# 此时 p_age 就代表 person_list.1.age   #}
    {{ p_age }}
    {{ p_age }}
{% endwith %}

{#csrf_token:这个标签用于跨站请求伪造保护#}

</body>
</html>

templates/login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="" method="post">
{#  csrf_token:这个标签用于跨站请求伪造保护;(可以给当前页面发送POST请求)  #}
    {% csrf_token %}
    <input type="text" name="user">
    <input type="submit">
</form>

</body>
</html>

模板语法之自定义标签和过滤器

步骤:

  1. 在settings中的INSTALLED_APPS配置当前app,不然django无法找到自定义的simple_tag.
  2. 在app中创建templatetags模块(模块名只能是templatetags)
  3. 创建任意 .py 文件,如:my_tags.py;在其中定义自己的过滤器和标签
  4. 在使用自定义过滤器的html文件中导入之前创建的 my_tags.py
  5. 使用自定义的过滤器或标签

目录结构:

![image.png]](http://jshand.fulfill.com.cn/staticpft/docs/imgs/python/django/04/01.png)

app01/views.py

from django.shortcuts import render

# Create your views here.

def index(request):
    i = 10
    return render(request,"index.html",locals())

app01/templatetags/my_tags_filters.py

from django import template

register = template.Library()   # register的名字是固定的,不可改变

@register.filter
def multi_filter(x,y):
    return x*y
# 一个函数加上装饰器 register.filter 就变成了一个自定义的过滤器;上述过滤器实现的功能是两个数相乘
# 自定义的过滤器要在模板语法中使用
# 自定义过滤器的局限性:第一个形参是被修饰的变量,第二个形参是过滤器中的参数;过滤器最多只能放两个参数
# 自定义过滤器的优势:进行逻辑判断时,只能用过滤器

# 自定义标签
@register.simple_tag
def multi_tag(x,y):
    return x*y
# 自定义标签无参数个数的限制

templates/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h3>自定义过滤器,标签</h3>
{#在使用自定义的过滤器的html文件中导入之前创建的 my_tags_filters.py#}
{% load my_tags_filters %}
<p>{{ i|multi_filter:20 }}</p>
{#函数multi_filter中有两个形参,所有过滤器multi_filter中要给一个参数,如上面的20#}

{#调用自定义标签#}
<p>{% multi_tag 7 9 %}</p>

{#自定义过滤器的优势#}
{#变量乘以20如果大于100就显示100,否则就显示它本身#}
{% if i|multi_filter:20 > 100 %}
    <p>100</p>
    {% else %}
    <p>{{ i }}</p>
{% endif %}
{#自定义标签不能放到流程控制判断中#}

</body>
</html>

模板继承(extends)

模版继承可以让你创建一个基本的“骨架”模版,它包含您站点中的全部元素,并且可以定义能够被子模版覆盖的 blocks 。

目录结构:

![image.png]](http://jshand.fulfill.com.cn/staticpft/docs/imgs/python/django/04/02.png)

urls.py

from django.contrib import admin
from django.urls import path

from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path(r'index/',views.index),

    path(r"orders/",views.orders)
]

views.py

from django.shortcuts import render

# Create your views here.

def index(request):
    i = 10
    return render(request,"index.html",locals())

def orders(request):
    return render(request,"orders.html")

base.html (需要引入的模板样式;extends 语法)

<!DOCTYPE html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">

    {#再放一个盒子用于重新写title标签;可留多个盒子用于以后扩写内容#}
    {% block title %}
        <title>base</title>
    {% endblock %}
    {# block区域里面本身也可以写内容,如上面的block写入 <title>base</base> ,这样如果引用这个模板时不重写 title这个block,就会默认会使用 block中原有的内容   #}
    <link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
    <style>
        * {
            padding: 0;
            margin: 0;
        }

        .header {
            width: 100%;
            height: 50px;
            background-color: blue;
        }
    </style>
</head>
<body>
{#模板语法之继承#}
<div class="header"></div>

<div class="container">
    <div class="row">
        <div class="col-md-3">
{#            想在这个地方调用advertise.html,就用include语法#}
            {% include "advertise.html" %}

        </div>
        <div class="col-md-9">
            {# 继承:col-md-9 中的内容都删除#}
            {# 在这个地方写一个 block,意味着在这留了一个区域等待别人来重新写 #}
            {% block con %}

            {% endblock %}
        </div>

    </div>
</div>


</body>
</html>

orders.html(在其中引入 base.html 这个模板)

{#extends需要放在第一行#}
{#extends标签:用于继承;把 base.html 的样式全都拿过来(头部和左侧栏样式),然后 col-md-9中的内容自己再重新写#}
{% extends "base.html" %}
{#引入过来 base.html的样式之后,需要在 block中填充内容#}
{% block con %}
    {# 重新写自己的内容   #}
    <h4>订单</h4>

{% endblock %}

{#重新写title;放一个HTML的title标签#}
{% block title %}
    <title>orders</title>
{% endblock %}

{#过程:先通过 extends 来继承样式,再通过 block 来填充内容#}
{#继承用于解决HTML代码的复用性#}

index.html(include语法)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
    <style>
        * {
            padding: 0;
            margin: 0;
        }

        .header {
            width: 100%;
            height: 50px;
            background-color: blue;
        }
    </style>
</head>
<body>
{#模板语法之继承#}
<div class="header"></div>

<div class="container">
    <div class="row">
        <div class="col-md-3">
       {# 想在这个地方调用advertise.html,就用include语法#}
            {% include "advertise.html" %}

        </div>
        <div class="col-md-9">
            <h3>自定义过滤器,标签</h3>
            {#在使用自定义的过滤器的html文件中导入之前创建的 my_tags_filters.py#}
            {% load my_tags_filters %}
            <p>{{ i|multi_filter:20 }}</p>
            {#函数multi_filter中有两个形参,所有过滤器multi_filter中要给一个参数,如上面的20#}

            {#调用自定义标签#}
            <p>{% multi_tag 7 9 %}</p>

            {#自定义过滤器的优势#}
            {#变量乘以20如果大于100就显示100,否则就显示它本身#}
            {% if i|multi_filter:20 > 100 %}
                <p>100</p>
            {% else %}
                <p>{{ i }}</p>
            {% endif %}
            {#自定义标签不能放到流程控制判断中#}
        </div>

    </div>
</div>


</body>
</html>

advertise.html(被 index.html引入的内容)

<div class="action">
    <div class="panel panel-danger">
        <div class="panel-heading">Panel heading without title</div>
        <div class="panel-body">
            111
        </div>
    </div>
    <div class="panel panel-warning">
        <div class="panel-heading">Panel heading without title</div>
        <div class="panel-body">
            222
        </div>
    </div>
    <div class="panel panel-success">
        <div class="panel-heading">Panel heading without title</div>
        <div class="panel-body">
            333
        </div>
    </div>
</div>

注意:

- 如果你在模版中使用 `{% extends %}` 标签,它必须是模版中的第一个标签。其他的任何情况下,模版继承都将无法工作。
- 在base模版中设置越多的 `{% block %}` 标签越好。子模版不必定义全部父模版中的blocks,所以,你可以在大多数blocks中填充合理的默认内容,然后,只定义你需要的那一个。多一点钩子总比少一点好。
- 如果你发现你自己在大量的模版中复制内容,那可能意味着你应该把内容移动到父模版中的一个 `{% block %}` 中。
- 为了更好的可读性,你也可以给你的 `{% endblock %}` 标签一个 *名字* 。例如:
{% block content %}
...
{% endblock content %}

在大型模版中,这个方法帮你清楚的看到哪一个  {% block %} 标签被关闭了

  • 不能在一个模版中定义多个相同名字的 block 标签。

5.Django之连接数据库

一,使用Django自带的sqlite3数据库  Django通过自带的ORM框架可以操作数据库,并且自带轻量级的sqlite3数据库,Django默认使用SQLite数据库,因为Python源生支持SQLite数据库,所以我们无需安装任何程式,就可以直接使用,下面我们先练习一下。

1.1 在settings中,配置数据库相关参数  因为是自带的sqlite,所以无需修改,这里我们看一下:

# Database
# https://docs.djangoproject.com/en/2.1/ref/settings/#databases

DATABASES = {
    'default': {
        # 这里可以指定使用的数据库类型,例如mysql
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

1.2 编译models.py  Django通过自定义python类的形式来定义具体的模型,每个模型的物理存在方式就是一个python的Class,每个模型代表数据库中的一张表,每个类的实例代表数据库中的一行数据,类中的每个变量代表数据库中的一列字段。

Django通过模型,将python代码和数据库操作结合起来,实现对SQL查询语言的封装。也就是说,你可以不会管理数据库,可以不会SQL语言,你同样能通过python的代码进行数据库的操作。DJango通过ORM对数据库进行操作,下面直接看代码:

from django.db import models

# Create your models here.
class UserInfo(models.Model):
    '''
        创建两个字段,最大长度是32,类型是char
        '''
    user = models.CharField(max_length= 32)
    pwd = models.CharField(max_length= 32)

这里我们创建了两个字段,分别保存用户的名称和密码。

上面的代码,相当于下面的原生SQL语句。

CREATE TABLE UserInfo (
    "id" serial NOT NULL PRIMARY KEY,
    "user" varchar(30) NOT NULL,
    "pwd" varchar(30) NOT NULL
);

注意:

Django默认自动创建自增主键ID,当然也可以自己指定主键。 上面的SQL语法基于PostgreSQL 更多字段和参数

每个字段有一些特有的参数,例如,CharField 需要 max_length 参数来指定VARCHAR 数据库字段的大小。还有一些适用于所有字段的通用参数。这些参数在文档中有详细定义,这里我们学习一下最常用的:

<1> CharField
       字符串字段, 用于较短的字符串.
       CharField 要求必须有一个参数 maxlength, 用于从数据库层和Django校验层
限制该字段所允许的最大字符数.


<2> IntegerField
      #用于保存一个整数.


<3> FloatField
       一个浮点数. 必须 提供两个参数:

       参数    描述
       max_digits    总位数(不包括小数点和符号)
       decimal_places    小数位数
               举例来说, 要保存最大值为 999 (小数点后保存2位),你要这样定义字段:

               models.FloatField(..., max_digits=5, decimal_places=2)
               要保存最大值一百万(小数点后保存10位)的话,你要这样定义:

               models.FloatField(..., max_digits=19, decimal_places=10)
               admin 用一个文本框(<input type="text">)表示该字段保存的数据.


<4> AutoField
       一个 IntegerField, 添加记录时它会自动增长. 你通常不需要直接使用这个字段;
       自定义一个主键:my_id=models.AutoField(primary_key=True)
       如果你不指定主键的话,系统会自动添加一个主键字段到你的 model.


<5> BooleanField
       A true/false field. admin 用 checkbox 来表示此类字段.


<6> TextField
       一个容量很大的文本字段.
       admin 用一个 <textarea> (文本区域)表示该字段数据.(一个多行编辑框).


<7> EmailField
       一个带有检查Email合法性的 CharField,不接受 maxlength 参数.


<8> DateField
       一个日期字段. 共有下列额外的可选参数:
       Argument    描述
       auto_now    当对象被保存时,自动将该字段的值设置为当前时间.通常用于表
示 "last-modified" 时间戳.
       auto_now_add    当对象首次被创建时,自动将该字段的值设置为当前时间.
通常用于表示对象创建时间.
       (仅仅在admin中有意义...)


<9> DateTimeField
        一个日期时间字段. 类似 DateField 支持同样的附加选项.

更多参数

(1)null

如果为True,Django 将用NULL 来在数据库中存储空值。 默认值是 False.

(1)blank

如果为True,该字段允许不填。默认为False。
要注意,这与 null 不同。null纯粹是数据库范畴的,而 blank 是数据验证范畴的。
如果一个字段的blank=True,表单的验证将允许该字段是空值。如果字段的blank=False,该字段就是必填的。

(2)default

字段的默认值。可以是一个值或者可调用对象。如果可调用 ,每有新对象被创建它都会被调用。

(3)primary_key

如果为True,那么这个字段就是模型的主键。如果你没有指定任何一个字段的primary_key=True,
Django 就会自动添加一个IntegerField字段做为主键,所以除非你想覆盖默认的主键行为,
否则没必要设置任何一个字段的primary_key=True。

(4)unique

如果该值设置为 True, 这个数据字段的值在整张表中必须是唯一的

(5)choices
由二元组组成的一个可迭代对象(例如,列表或元组),用来给字段提供选择项。
 如果设置了choices ,默认的表单将是一个选择框而不是标准的文本框,而且这
个选择框的选项就是choices 中的选项。

1.3 在终端创建表  我们需要在pycharm的teminal中通过命令创建数据库的表,有两条命令,分别是:

生成同步数据库的脚本代码

python manage.py makemigrations

通过运行makemigrations 命令,相当于告诉Django,你对模型有改动,并且你想把这些改动保存为一个“迁移”(migration)。

运行后,结果如下:

Migrations for 'mysql01':
  mysql01\migrations\0001_initial.py
    - Create model UserInfo

migrations是Django保存模型修改记录的文件,这些文件保存在磁盘上,从上面可以看到,它就是 mysql01\migrations\0001_initial.py 。我们可以打开这个文件看一下,里面保存的都是可读并且可编译的内容,方便我们随时手动修改。

下面看一下0001_initial.py内容:

# Generated by Django 2.1.7 on 2019-03-12 01:45

from django.db import migrations, models


class Migration(migrations.Migration):

    initial = True

    dependencies = [
    ]

    operations = [
        migrations.CreateModel(
            name='UserInfo',
            fields=[
                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                ('user', models.CharField(max_length=32)),
                ('pwd', models.CharField(max_length=32)),
            ],
        ),
    ]

同步数据库(也就是对数据库执行真正的迁移动作)脚本代码

python manage.py migrate

migrate命令对所有还未实施的迁移记录进行操作,本质上就是将你对模型的修改体现到数据库中具体的表上面。Django通过一张叫做django_migrations的表,记录并跟踪已经实施的migrate动作,通过对比获得哪些migrations尚未提交。

运行后,结果如下:

Operations to perform:
  Apply all migrations: admin, auth, contenttypes, mysql01, sessions
Running migrations:
  Applying mysql01.0001_initial... OK

注意:在开发过程中,数据库同步误操作之后,难免会遇到后面不能同步成功的情况,解决这个问题的一个简单粗暴方法是把migrations目录下的脚本(除init.py之外)全部删掉,再把数据库删掉之后创建一个新的数据库,数据库同步操作再重新做一遍。

设置好用户名和密码后便可登录啦!

二,使用MySQL数据库  下面学习MySQL数据库连接Django。

1,打开MySQL命令对话框,创建数据库

create database djangomysql default charset=utf8;

在使用非SQLite的数据库时候,请务必预先在数据库管理系统的提示符交互模式下创建数据库,我们可以使用命令:“CREATE DATABASE database_name;”。Django不会自动帮你做这一步工作。

2,创建一个APP

python manage.py startapp mysql02

3,修改Django中settings.py的相关参数,配置MySQL  添加mysql02这个APP

INSTALLED_APPS = [
    'django.contrib.admin',  #admin后台管理站点
    'django.contrib.auth',  # 身份认证系统
    'django.contrib.contenttypes', # 内容类型框架
    'django.contrib.sessions', # 回话框架
    'django.contrib.messages',  # 消息框架
    'django.contrib.staticfiles', # 静态文件管理框架
    'mysql02',
]

注释SQLite,配置MySQL

DATABASES = {
    'default': {
        # 这里可以指定使用的数据库类型,例如mysql
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'djangomysql',
        'USER':'root',
        'PASSWORD':'******',
        'HOST':'localhost',
        'PORT':'3306',
    }
}

4,安装MySQL-python

必须先按照MySQLdb,不然会报如下的错误:

ModuleNotFoundError: No module named 'MySQLdb'

Django连接mysql默认驱动是MySQLdb,MySQLdb没有支持python3的版本,如果使用python3.x版本的时候,Django连接mysql的方法是使用pymysql替换MySQLdb。

在配置文件同目录下的init.py文件中加入以下代码(本文就是mysite下的init.py):

import MySQLdb

使用myslqclient代替MySQLdb,mysqlclient项目在github上的地址为 https://github.com/PyMySQL/mysqlclient-python,该项目fork MySQLdb,加入了对python3的支持。

安装如下:

pip install mysqlclient

没有提示MySQLdb模块找不到,说明安装OK。

5,编译models.py

Django通过自定义python类的形式定义具体的模型,每个模型代表数据库中的一张表,每个类的实例代表数据表中的一行数据,类中的每个变量代表数据表中的一列字段。

from django.db import models

# Create your models here.
class Userinfo(models.Model):
    '''
        创建两个字段,最大长度为32,类型是char
        '''
    user = models.CharField(max_length= 32)
    pwd = models.CharField(max_length= 32)

6,在终端创建数据库的表

我们需要先生成同步数据库的脚本代码

python manage.py makemigrations

执行结果如下:

Migrations for 'mysql02':
  mysql02\migrations\0001_initial.py
    - Create model Userinfo

然后同步数据库(也就是对数据库执行真正的迁移动作)

python manage.py migrate

执行结果如下:

Operations to perform:
  Apply all migrations: admin, auth, 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 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 sessions.0001_initial... OK

7,查看mysql数据库

在MySQL中,进入创建的库中,查看我们在Django创建的表:

image

6.Django之ORM单表操作

MVC或者MVC框架中包括一个重要的部分,就是ORM,它实现了数据模型与数据库的解耦,即数据模型的设计不需要依赖于特定的数据库,通过简单的配置就可以轻松更换数据库,这极大的减轻了开发人员的工作量,不需要面对因数据库变更而导致的无效劳动

ORM是“对象-关系-映射”的简称。

![image.png]](http://jshand.fulfill.com.cn/staticpft/docs/imgs/python/django/06/01.png)

Mysql中的表对应python中的类,表的字段对应类的属性,表的记录对应类的实例化的对象

单表操作

创建表

  1. 创建模型

![image.png]](http://jshand.fulfill.com.cn/staticpft/docs/imgs/python/django/06/02.png)

创建名为app01的app,在app01下的models.py中创建模型:

from django.db import models

# Create your models here.

class Book(models.Model):  # 类名可以随便起,但一定得继承 models.Model
    id = models.AutoField(primary_key=True)  # AutoField表示自增字段; primary_key=True 表示是主键
    title = models.CharField(max_length=32)
    # state = models.BooleanField()
    pub_date = models.DateField()
    price = models.DecimalField(max_digits=8,decimal_places=2)
    publish = models.CharField(max_length=32)
  1. 更多字段和参数

每个字段有一些特有的参数,例如,CharField需要max_length参数来指定VARCHAR数据库字段的大小。还有一些适用于所有字段的通用参数。

  1. settings配置

若想将模型转为mysql数据库中的表,需要在settings中配置:

DATABASES = {
    'default':{
        'ENGINE':'django.db.backends.mysql',
        'NAME':'orm', # 要连接的数据库,连接前需要先创建好
        'USER':'root',  # 连接数据库的用户名
        'PASSWORD':'root',  # 连接数据库的密码
        'HOST':'127.0.0.1',  # 连接主机
        'PORT':3306  # 端口
    }
}

注意1:NAME即数据库的名字,在mysql连接前该数据库必须已经创建(ORM只能处理到表这一层,数据库操作不了),而上面的sqlite数据库下的db.sqlite3则是项目自动创建 USER和PASSWORD分别是数据库的用户名和密码。设置完后,再启动我们的Django项目前,我们需要激活我们的mysql。然后,启动项目,会报错:no module named MySQLdb 。这是因为django默认你导入的驱动是MySQLdb,可是MySQLdb 对于py3有很大问题,所以我们需要的驱动是PyMySQL 所以,我们只需要找到项目名(project)文件下的init(ORM/init.py),在里面写入:

import MySQLdb

最后通过两条数据库迁移命令即可在指定的数据库中创建表 :

python manage.py makemigrations
python manage.py migrate
# makemigrations 后并没有在数据库生成表,而是在对应的 migrationsns 文件夹下生成了 py 文件
# migrate 时会执行 migrations文件夹下的 py文件(至于执行哪个py文件,程序会先去 django自带的 django_migrations 这张表中去查,如果migrationsns 文件夹下人某个py文件在 django_migrations 这张表已经存在,则 migrate时则会跳过这个py文件不执行,即已经执行过的py文件会存放在 django_migrations表中)
# 修改数据库的时候,尽量用 makemigrations 和 migrate,不要直接在Navicat 中修改

注意2:确保配置文件中的INSTALLED_APPS中写入我们创建的app名称

# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app01.apps.App01Config',
]

注意: 如果想打印orm转换过程中的sql,需要在settings中进行如下配置:

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console':{
            'level':'DEBUG',
            'class':'logging.StreamHandler',
        },
    },
    'loggers': {
        'django.db.backends': {
            'handlers': ['console'],
            'propagate': True,
            'level':'DEBUG',
        },
    }
}

添加表记录

urls.py

from django.contrib import admin
from django.urls import path

from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path(r'index/',views.index)
]

app01/models.py

from django.db import models

# Create your models here.

class Book(models.Model):  # 类名可以随便起,但一定得继承 models.Model
    id = models.AutoField(primary_key=True)  # AutoField表示自增字段; primary_key=True 表示是主键
    title = models.CharField(max_length=32)
    # state = models.BooleanField()
    pub_date = models.DateField()
    price = models.DecimalField(max_digits=8,decimal_places=2)
    publish = models.CharField(max_length=32)

app01/views.py

from django.shortcuts import render,HttpResponse

# Create your views here.

# 先把模型导入进来
from app01.models import Book

def index(request):
    # 添加表记录

    # 方式一:实例化Book对象
    book_obj = Book(id=1,title="python全栈开发",pub_date="2018-6-7",price=100,publish="IT出版社")   # pub_date是一个日期类型,一定得按照"年-月-日"的格式
    book_obj.save()  # save()之后记录才会生成 # 表里面的一条记录就是类的一个对象

    # 方式二:用 objects去调用create; create方法有返回值,返回值就是生成的记录对象
    book_obj2 = Book.objects.create(title="linux运维",pub_date="2015-6-7",price=100,publish="IT出版社")  # id是自增的,所以无需再写
    # 这种方式不需要 save();.objects.create(kwargs) 直接就在数据库中生成了一条记录(对象),并把这个对象返回给 book_obj2(我们也就可以打印book_obj2中的属性)
    print(book_obj2.pub_date)


    return HttpResponse("ok")

单表查询:

models.py

from django.db import models

# Create your models here.

class Book(models.Model):  # 类名可以随便起,但一定得继承 models.Model
    id = models.AutoField(primary_key=True)  # AutoField表示自增字段; primary_key=True 表示是主键
    title = models.CharField(max_length=32)
    # state = models.BooleanField()
    pub_date = models.DateField()
    price = models.DecimalField(max_digits=8,decimal_places=2)
    publish = models.CharField(max_length=32)

    def __str__(self):  # 只是控制了对象的打印形式
        return self.title

views.py

from django.shortcuts import render,HttpResponse

# Create your views here.

# 先把模型导入进来
from app01.models import Book

def index(request):

    # #######################查询表结构API#######################
    # 注意:要知道每个方法的返回值是什么、以及每个方法是谁来调用的

    # 1. all() :查询所有结果;返回值是一个QuerySet数据类型(Django自定义的数据类型),调用者是 objects
    all_books = Book.objects.all()
    print(all_books)
    # Book类没有 __str()__方法时的打印结果:
    # <QuerySet [<Book: Book object (1)>, <Book: Book object (2)>]>
    # Book有 __str()__ 方法时的打印结果:
    # <QuerySet [<Book: python全栈开发>, <Book: linux运维>]>
    # QuerySet数据类型:相当于 [obj1,obj2,...],可用列表的方式去处理它

    for obj in all_books:  # 支持遍历
        print(obj.title,obj.pub_date)

    print(all_books[1].publish)  # 支持索引

    # 2. first(),last()方法;返回值是一个model(模型)对象,调用者是 QuerySet对象
    book1 = Book.objects.all().first()  # 相当于 Book.objects.all()[0]
    book2 = Book.objects.all().last()

    print(book1,book2)

    # 3. filter()方法:返回值:QuerySet对象,调用者:管理器(objects)
    books = Book.objects.filter(title="python全栈开发",price=100)  # filter()的作用相当于sql语句的where;# 返回值:[obj1,obj2,....];多个过滤条件用逗号分隔
    print(books)
    # filter()方法也可以调用 first(),last()方法
    print(books.first())

    # 4. get()方法:有且只有一个查询结果时才有意义;如果有多个查询结果或者没有查询结果,报错;所以,返回值:model对象
    book_get = Book.objects.get(title="python全栈开发")
    print(book_get.price)

    # 5. exclude():排除条件的过滤,对应filter();返回QuerySet
    ret = Book.objects.exclude(title="python全栈开发")
    print(ret)

    # 6. order_by():按照某种条件排序(默认是按照id);返回值:QuerySet,调用者:QuerySet
    book_order_asc = Book.objects.all().order_by("id")
    print(book_order_asc)
    book_order_desc = Book.objects.all().order_by("-id")  # 按照降序排列
    print(book_order_desc)
    book_price_desc = Book.objects.all().order_by("-price")  # 按照价格降序排列
    print(book_price_desc)
    # Book.objects.all().order_by("-price","id") # 先按照价格降序排列,再按照id升序
    # 要熟悉orm的链式操作

    # 7. reverse() :对查询结果反向排序

    # 8. count():计数;返回值:int,调用者:QuerySet
    Book.objects.all().count()  # 计数里面有多少个元素

    # 9. exists():如果QuerySet包含数据,就返回True,否则返回False
    ret_exists = Book.objects.all().exists()  # 判断 Book.objects.all() 里面是否有数据

    # 10. values(*field):field代表字段;values()具有遍历作用,返回一个QuerySet --- 一个特殊的QuerySet,运行后得到的并不是一系列model的实例化对象,而是一个可迭代的字典序列;返回值:QuerySet(列表里面放字典),调用者:QuerySet
    # values()方法很重要
    ret_values = Book.objects.all().values("price","title")
    print(ret_values)
    # <QuerySet [{'price': Decimal('100.00'), 'title': 'python全栈开发'}, {'price': Decimal('100.00'), 'title': 'linux运维'}]>
    print(ret_values[0].get("price"))
    # 100.00

    # 11. values_list(*field):它与values()非常相似,它返回的QuerySet里面是一个元组序列,values返回的是一个字典序列
    ret_values_list = Book.objects.all().values_list("price", "title")
    print(ret_values_list)
    # <QuerySet [(Decimal('100.00'), 'python全栈开发'), (Decimal('100.00'), 'linux运维')]>

    # 12. distinct():从返回结果中剔除重复纪录(通常配合values,values_list一起使用)
    ret_distinct = Book.objects.all().values("price").distinct()
    print(ret_distinct)
    # <QuerySet [{'price': Decimal('100.00')}]>
    # 注: Book.objects.all().distinct() 这种写法没有任何意义

    # #######################查询表结构之模糊查询(都是双下划线)#######################
    # 1. __gt :大于;__lt:小于;返回值:QuerySet
    price__gt = Book.objects.filter(price__gt=50,price__lt=200)
    print("__gt",price__gt)

    # 2. __startswith:以什么开头;返回值:QuerySet
    obj_start = Book.objects.filter(title__startswith="py")
    print(obj_start)

    # 3. __contains:包含什么;返回值:QuerySet
    # 4. __icontains:包含某些元素(不区分大小写)
    obj_contains = Book.objects.filter(title__contains="x")
    print(obj_contains)

    # 5. __in = [] :是列表中的一个;返回值:QuerySet
    obj_in = Book.objects.filter(price__in=[100,150,200])
    print(obj_in)

    # 6. __year : 某一年的(只有date类型有);返回值:QuerySet
    obj_year = Book.objects.filter(pub_date__year=2018)
    print(obj_year)

    # 7. __range = [] : 在某个区间(包含两端)
    obj_range = Book.objects.filter(price__range=[50,100])
    print("range",obj_range)

    return HttpResponse("ok")

补充:例如上面的4.get() 方法,如果值不存在,可以捕获到这个异常

from django.core.exceptions import ObjectDoesNotExist  # ObjectDoesNotExist 值不存在的异常类型

单表之删除和编辑

views.py

from django.shortcuts import render,HttpResponse

# Create your views here.

# 先把模型导入进来
from app01.models import Book

def index(request):
    # #######################删除、修改表记录#######################
    # delete() : 删除;调用者:QuerySet对象 或者 model对象;返回值是删除元素的一些信息
    # Book.objects.filter(pub_date__year=2018).delete()  # 需要都把记录查询出来才能删除;调用者:QuerySet对象
    # Book.objects.filter(pub_date__year=2015).first().delete()  # 调用者:model对象

    # update():编辑记录; 调用者:QuerySet;返回值:更新的条数
    Book.objects.filter(title__contains="linux").update(title="linux运营维护")


    return HttpResponse("ok")

图书管理系统

![image.png]](http://jshand.fulfill.com.cn/staticpft/docs/imgs/python/django/06/03.png)

settings.py

"""
Django settings for bookms project.

Generated by 'django-admin startproject' using Django 2.0.1.

For more information on this file, see
https://docs.djangoproject.com/en/2.0/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.0/ref/settings/
"""

import os

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'm6=&s25aszxks#m(5f57mdpi)hc%v7#&e0$kmak48@80xr7t0h'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []


# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app01.apps.App01Config',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'bookms.urls'

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',
            ],
        },
    },
]

WSGI_APPLICATION = 'bookms.wsgi.application'


# Database
# https://docs.djangoproject.com/en/2.0/ref/settings/#databases

# DATABASES = {
#     'default': {
#         'ENGINE': 'django.db.backends.sqlite3',
#         'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
#     }
# }
DATABASES = {
    'default':{
        'ENGINE':'django.db.backends.mysql',
        'NAME':'bookms', # 要连接的数据库,连接前需要先创建好
        'USER':'root',  # 连接数据库的用户名
        'PASSWORD':'tj037778',  # 连接数据库的密码
        'HOST':'127.0.0.1',  # 连接主机
        'PORT':3306  # 端口
    }
}

# Password validation
# https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


# Internationalization
# https://docs.djangoproject.com/en/2.0/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.0/howto/static-files/

STATIC_URL = '/static/'  # 若存放静态文件的static目录在app目录下,则该句生效,无需定义下面的
# STATICFILES_DIRS = [
#     os.path.join(BASE_DIR,"static"),
# ]    # 若存放静态文件的static目录在project目录下,则用该定义


LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console':{
            'level':'DEBUG',
            'class':'logging.StreamHandler',
        },
    },
    'loggers': {
        'django.db.backends': {
            'handlers': ['console'],
            'propagate': True,
            'level':'DEBUG',
        },
    }
}

urls.py

"""bookms URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/2.0/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path,re_path

from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path(r"addbook/",views.addbook),
    path(r"books/",views.books),

    # (\d+)用于匹配获取表记录的主键(书的id)
    re_path(r"books/(\d+)/delete",views.bookdel),
    re_path(r"books/(\d+)/edit",views.bookedit)
]

views.py

from django.shortcuts import render,HttpResponse,redirect

# Create your views here.

# 把模型表引入
from app01.models import Book

def addbook(request):
    # 由于templates/addbook.html 中 form标签的 action没写,所以还是提交到了当前页面index.html
    if request.method == "POST":
        title = request.POST.get("title")
        price = request.POST.get("price")
        date = request.POST.get("date")
        press = request.POST.get("press")

        # 添加记录
        book_obj = Book.objects.create(title=title,price=price,pub_date=date,publish=press)

        return redirect("/books/")

    return render(request,"addbook.html")

def books(request):
    books_list = Book.objects.all()

    return render(request,"books.html",locals())

def bookdel(request,pk):
    # 把对应ID的书删除
    Book.objects.filter(id=pk).delete()

    # redirect()的作用就是让浏览器往重定向的路径再发一次请求
    return redirect("/books/")

def bookedit(request,id):
    # 编辑对应ID的书

    # 先获取对应书对象
    book_obj = Book.objects.filter(id=id).first()

    # 由于编辑后的内容还是提交到当前页面,所以在这个函数里面要判断请求是否就POST
    if request.method == "POST":
        # 获取编辑后的内容
        title = request.POST.get("title")
        price = request.POST.get("price")
        date = request.POST.get("date")
        press = request.POST.get("press")

        # 把编辑后的内容更新到表中
        Book.objects.filter(id=id).update(title=title,price=price,pub_date=date,publish=press)
        # 更新完后重定向到 books.html 页面
        return redirect("/books/")

    return render(request,"editbook.html",{"book_obj":book_obj})

models.py

from django.db import models

# Create your models here.

class Book(models.Model):
    id = models.AutoField(primary_key=True)
    title = models.CharField(max_length=32)
    pub_date = models.DateField()
    price = models.DecimalField(max_digits=8, decimal_places=2)
    publish = models.CharField(max_length=32)

books.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>addbook</title>
    <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.min.css">

    <style>
        .container{
            margin-top: 70px;
        }
        .btn{
            margin-bottom: 10px;
        }
    </style>

</head>
<body>
<h3>查看书籍</h3>
<div class="container">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <a href="/addbook/" class="btn btn-primary">添加书籍</a>
            {# .table-striped 类可以给 <tbody> 之内的每一行增加斑马条纹样式;.table-bordered 类为表格和其中的每个单元格增加边框。#}
            <table class="table table-striped table-bordered">
                <thead>
                    <tr>
                        <th>书籍名称</th>
                        <th>价格</th>
                        <th>出版日期</th>
                        <th>出版社</th>
                        <th>编辑</th>
                        <th>删除</th>
                    </tr>
                </thead>
                <tbody>
                    {# 遍历 books_list是的每一个对象(表记录),把每条表记录添加到 table的一行中 #}
                    {% for book in books_list %}
                        <tr>
                            <td>{{ book.title }}</td>
                            <td>{{ book.price }}</td>
                            {# 利用date过滤器格式化时间样式 #}
                            <td>{{ book.pub_date|date:"Y-m-d" }}</td>
                            <td>{{ book.publish }}</td>
                            {# 动态的为每个a标签添加索引 #}
                            <td><a href="/books/{{ book.pk }}/edit" class="btn btn-info">编辑</a></td>
                            {# 把主键id添加到a标签的路径中;.pk表示model对象的主键 #}
                            <td><a href="/books/{{ book.pk }}/delete" class="btn btn-danger">删除</a></td>
                        </tr>

                    {% endfor %}

                </tbody>
            </table>
        </div>
    </div>
</div>

</body>
</html>

addbook.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>addbook</title>
    <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.min.css">

    <style>
        .container{
            margin-top: 70px;
        }
        .btn{
            margin-top: 10px;
        }
    </style>

</head>
<body>
<h3>添加书籍</h3>
<div class="container">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <form action="" method="post">
                {% csrf_token %}
                <div>
                    <label for="">书籍名称</label>
                    {# form-controlform-control控件,可以为input元素添加CSS定制样式#}
                    <input type="text" class="form-control" name="title">
                </div>
                <div>
                    <label for="">价格</label>
                    <input type="text" class="form-control" name="price">
                </div>
                <div>
                    <label for="">出版日期</label>
                    {# date类型:能够下拉选择日期 #}
                    <input type="date" class="form-control" name="date">
                </div>
                <div>
                    <label for="">出版社</label>
                    <input type="text" class="form-control" name="press">
                </div>
                {# btn btn-success:绿色按钮;pull-right:右移 #}
                <input type="submit" class="btn btn-success pull-right">
            </form>
        </div>
    </div>
</div>

</body>
</html>

editbook.html

html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>addbook</title>
    <link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.min.css">

    <style>
        .container{
            margin-top: 70px;
        }
        .btn{
            margin-top: 10px;
        }
    </style>

</head>
<body>
<h3>编辑书籍</h3>
<div class="container">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            {# form标签的action没写,所以默认还是会提交到当前页面 #}
            <form action="" method="post">
                {% csrf_token %}
                <div>
                    <label for="">书籍名称</label>
                    {# 编辑时书名这一栏的内容是该对象原先的title;其它栏同理 #}
                    <input type="text" class="form-control" name="title" value="{{ book_obj.title }}">
                </div>
                <div>
                    <label for="">价格</label>
                    <input type="text" class="form-control" name="price" value="{{ book_obj.price }}">
                </div>
                <div>
                    <label for="">出版日期</label>
                    {# 此处的日期也需要用date过滤器格式化,要不然显示不出来 #}
                    <input type="date" class="form-control" name="date" value="{{ book_obj.pub_date|date:'Y-m-d' }}">
                </div>
                <div>
                    <label for="">出版社</label>
                    <input type="text" class="form-control" name="press" value="{{ book_obj.publish }}">
                </div>
                {# btn btn-success:绿色按钮;pull-right:右移 #}
                <input type="submit" class="btn btn-success pull-right">
            </form>
        </div>
    </div>
</div>

</body>
</html>

7.Django之ORM多表操作

表关系总结:

一对多:在多的表中建立关联字段

多对多:创建第三张表(关联表):id 和 两个关联字段

一对一:在两张表中的任意一张表中建立关联字段(关联字段一定要加 unique 约束)

创建模型:

from django.db import models

# Create your models here.
class AuthorDetail(models.Model):
    nid = models.AutoField(primary_key=True)
    birthday = models.DateField()
    telephone = models.BigIntegerField()
    addr = models.CharField(max_length=64)

class Author(models.Model):
    nid = models.AutoField(primary_key=True)
    name = models.CharField(max_length=32)
    age = models.IntegerField()
    # 一对一:在任意一张表中添加约束
    authordetail = models.OneToOneField(to="AuthorDetail",to_field="nid",on_delete=models.CASCADE) # authordetail也会被自动添加 _id
    # 对于Django2.0版本,一对多(models.ForeignKey)和一对一(models.OneToOneField)要加上 on_delete=models.CASCADE 这个属性

class Publish(models.Model):
    nid = models.AutoField(primary_key=True)
    name = models.CharField(max_length=32)
    city = models.CharField(max_length=32)
    email = models.EmailField()



class Book(models.Model):

    nid = models.AutoField(primary_key=True)
    title = models.CharField( max_length=32)
    publishDate=models.DateField()
    price=models.DecimalField(max_digits=5,decimal_places=2)
    # 一对多:在多的表中添加关联字段
    publish = models.ForeignKey(to="Publish",to_field="nid",on_delete=models.CASCADE)  # 这句代码做了两件事:1. 生成表的时候在 publish前面自动加了 _id,并在表中生成了一个字段 publish_id;2. 把 publish_id 作为外键关联到了 Publish表的 nid 字段;具体作用用SQL语句表示如下:
    """
        publish_id int,
        foreign key(publish_id) references publish(id)
    """

    # 多对多:生成第三张表来存多对多对应关系
    authors = models.ManyToManyField(to="Author")  # 这句代码的作用是创建第三张表来存多对多关系,而不是在该表中创建一个 authors_id 的字段;ManyToManyField可以建在两个模型中的任意一个,自动创建第三张表;作用用SQL语句表示如下:
    """
    create table book_authors(
        id int primary key auto_increment,
        book_id int,
        author_id int,
        foreign key(book_id) references book(id),
        foreign key(author_id) references author(id)
        );
    """
    # ManyToManyField可以建在两个模型中的任意一个,自动创建第三张表

![image.png]](http://jshand.fulfill.com.cn/staticpft/docs/imgs/python/django/07/01.png)

注意:

  • 表的名称myapp_modelName,是根据 模型中的元数据自动生成的,也可以覆写为别的名称
  • id 字段是自动添加的
  • 对于外键字段,Django 会在字段名上添加"_id" 来创建数据库中的列名
  • Django 会根据settings 中指定的数据库类型来使用相应的SQL 语句
  • 修改配置文件中的INSTALL_APPSZ中设置,在其中添加models.py所在应用的名称
  • 外键字段 ForeignKey 有一个 null=True 的设置(它允许外键接受空值 NULL),你可以赋给它空值 None 。

添加表记录

author
authordetail

添加2条记录

urls.py

from django.contrib import admin
from django.urls import path

from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path(r"add/",views.add)
]

views.py

from django.shortcuts import render,HttpResponse

# Create your views here.

from app01.models import *

def add(request):
    # 单表插入记录:(单表:没有关联字段的表)
    pub = Publish.objects.create(name="人民出版社",email="123@qq.com",city="北京")

    # #######################邦定一对多关系####################

    # 为一对多有关联字段的表添加记录
    # 为book添加出版社;book是多
    # 方式一:直接为 publish_id 这个字段添加 出版社的id
    book_obj1 = Book.objects.create(title="红楼梦",price=200,publishDate="2016-8-8",publish_id=1)
    # Book中的 publish会在数据库迁移的时候添加 _id 变成 publish_id 字段
    print(book_obj1.title)

    # 方式二:为 publish 赋值,此时赋的值为 Publish的实例模型对象
    pub_obj = Publish.objects.filter(nid=1).first()  # nid为1的出版社对象
    book_obj2 = Book.objects.create(title="西游记", price=200, publishDate="2016-8-8", publish=pub_obj)  # publish等于一个 Publish对象
    # create操作时,会把 publish翻译成 publish_id,把 pub_obj的主键值取出来,并把 pub_obj的主键值赋值给 publish_id
    print(book_obj2.price)  # book_obj2的价格
    print(book_obj2.publish_id)  # book_obj2的出版社id
    print(book_obj2.publish)  # 不管是方式一还是方式二,book_obj2.publish 都表示 pub_obj 这个model对象(重点)
    # book_obj2.publish:与这本书籍关联的出版社对象;所以 book_obj2.publish 后面还能用点语法,如:
    print(book_obj2.publish.email)

    # 一对多的查询:查询“西游记”对应出版社的邮箱
    book_obj3 = Book.objects.filter(title="西游记").first()  # 西游记这本书对象
    email = book_obj3.publish.email  # book_obj3.publish:西游记这本书对应的出版社对象
    print(email)

    # #################邦定多对多关系############################
    book_obj4 = Book.objects.create(title="python全栈开发",price=100,publishDate="2017-8-8",publish_id=1)

    alex = Author.objects.get(nid=1)
    egon = Author.objects.get(nid=2)
    # 为多对多关系表添加记录的接口(添加的前提是先create一条记录)
    book_obj4.authors.add(alex,egon)  # 给python全栈开发这本书籍对象添加作者,作者为alex和egon;邦定多对多关系的API
    """
    book_obj4.authors.add(alex,egon)执行的操作:
    找到 book_obj4的主键,找到alex这个作者对象的主键,然后在 Book和authors的关系表中添加一条记录;
    找到 book_obj4的主键,找到egon这个作者对象的主键,然后在 Book和authors的关系表中添加一条记录;
    所以book_obj4.authors.add(alex,egon)会在 app01_book_authors 这张表中添加两条记录
    """
    # 另外一种写法:add中直接写对应作者的主键(id),而不是写作者的model对象
    book_obj4.authors.add(1,2)
    # 或者:
    book_obj4.authors.add(*[1,2])

    # 解除多对多关系(解除的前提是先查出来)
    book_obj5 = Book.objects.filter(nid=3).first()
    book_obj5.authors.remove(2)  # 把 book_id为book_obj4的主键、author_id为2的那条记录从 app01_book_authors 这张关系表中删除
    # book_obj5.authors.remove(1,2)
    # book_obj5.authors.remove(*[1,2])

    # 删除所有: clear()
    book_obj5.authors.clear()

    # book_obj5.authors.all() :表示与这本书关联的所有作者对象的集合(QuerySet)(重点)
    print(book_obj5.authors.all())
    # 查询所有作者的名字
    ret = book_obj5.authors.all().values("name")

    return HttpResponse("ok")

基于对象的跨表查询

还以上面的表为例: views.py

def add(request):
    # #################基于对象的跨表查询(基于子查询)############################
    # #########1. 一对多
    # 查询主键为1的书籍的出版社所在的城市
    # 正向查询;正向查询,你需要先知道关联字段在哪个表中,如这个例子中,publish在Book中,通过publish去查找Publish就是正向查询;同理,通过Publish查找Book就是反向查询
    # 正向查询号按字段(如Book中的publish),反向查询按表名(如下面的 book_set.all();book就是表名,set就是集合的意思;集合的形式:QuerySet)
    book_obj = Book.objects.filter(nid=1).first()  # 对应书籍对象
    press_city = book_obj.publish.city  # book_obj.publish:对应出版社对象
    print(press_city)
    # 查询人民出版社出版的所有书籍的名称
    publish_obj = Publish.objects.filter(name="人民出版社").first()  # 出版社对象
    book_objs = publish_obj.book_set.all()  # 该出版社对应的所有书籍对象;QuerySet;[book_obj1,book_obj2,....]
    print(book_objs)

    # 打印对应所有书籍的名称
    for obj in publish_obj.book_set.all():
        print(obj.title)


    # #######2. 一对一
    # 正向查询:查询alex的手机号
    alex = Author.objects.filter(name="alex").first()
    phoneno = alex.authordetail.telephone  # 正向查询按字段
    print(phoneno)

    # 反向查询:查询号手机号为911的作者名字
    authordetail_obj = AuthorDetail.objects.filter(telephone=911).first()
    authorname = authordetail_obj.author.name  # authordetail_obj.author为对应的作者对象
    print(authorname)


    # 查询所有住址在北京的作者的姓名
    authorDetail_list = AuthorDetail.objects.filter(addr="北京")
    for obj in authorDetail_list:
        print(obj.author.name)

    # #######3. 多对多
    # 正向查询:查询“python全栈开发”所有作者的名字
    book = Book.objects.filter(title="python全栈开发").first()
    author_book = book.authors.all()  # 表示与这本书关联的所有作者对象的集合(QuerySet)

    # 反向查询:查询alex出版过的所有书籍的名称
    author_obj = Author.objects.filter(name="alex").first()
    books_set = author_obj.book_set.all()  # 所有书籍对象
    print(books_set)

    for obj in books_set:
        print(obj.title)
  
  return HttpResponse("ok")
""" # 一对多: 
   正向按字段:publish 
book ------------------> publish
   <----------------- 
   反向按表名:book_set.all() 

# 一对一:(反向查询直接按表名,因为本来就是一一对应的关系,不需要 _set.all()) 
    正向按字段:authordetail 
author ------------------> authordetail 
    <----------------- 
     反向按表名:author  

# 多对多: 
    正向按字段:authors.all()
book ------------------> author 
    <----------------- 
   反向按表名:book_set.all() 

"""

django 默认每个主表的对象都有一个是外键的属性,可以通过它来查询到所有属于主表的子表的信息。这个属性的名称默认是以子表的名称小写加上_set()来表示(上面默认以 book_set 访问),默认返回的是一个querydict对象。

基于双下划线的跨表查询:(正向查询按字段,反向查询按表名小写用来告诉ORM引擎join哪张表)

def add(request):
    # #################基于双下划线(QuerySet)的跨表查询(基于join)############################
    # 关键点:正向查询按字段,反向查询按表名

    # 一对一查询的查询:查询alex的手机号
    # 正向查询:需求:通过Author表join与其关联的AuthorDetail表,属于正向查询;按字段authordetail通知ORM引擎join AuthorDetail表
    Author.objects.filter(name="alex").values("authordetail__telephone")

    # 反向查询:需求:通过AuthorDetail表join与其关联的Author表,属于反向查询;按表名小写author通知ORM引擎join AuthorD表
    AuthorDetail.objects.filter(author__name="alex").values("telephone")

    # 查询人民出版社出版过的所有书籍的名字与价格(一对多)
    ret1 = Book.objects.filter(publish__name="人民出版社").values("title","price")  # 正向查询:Book ----> Publish, publish是Book中的字段,publish__name是所关联的Publish的name
    print(ret1)
    ret2 = Publish.objects.filter(name="人民出版社").values("book__title","book__price")  # 反向查询:Publish ----> Book, book__title中book是表名,book__title是Book中的title字段
    print(ret2)
    """
    上述两种方式实现的效果一样,区别在于基表的不同;双下划线的查询方式能自动确认 SQL JOIN 联系
    """

    # 查询alex出版过的所有书籍的名字(多对多)(可以把双下划线理解成“的”)
    alex_book1 = Author.objects.filter(name="alex").values("book__title")  # 反向查询:Author---->Book 按表名 book__
    print(alex_book1)
    alex_book2 = Book.objects.filter(authors__name="alex").values("title")  # 正向查询:Book---->Author 按字段 authors__
    print(alex_book2)

    ##### 混合使用
    # 查询人民出版社出版过的所有书籍的名字以及作者的姓名
    mix1 = Book.objects.filter(publish__name="人民出版社").values("title","authors__name")  # 以book为基表
    print(mix1)
    mix2 = Publish.objects.filter(name="人民出版社").values("book__title","book__authors__name")  # 以 publish 为基表
    print(mix2)

    # 手机号以11开头的作者出版过的所有书籍名称以及出版社名称
    mix3 = Book.objects.filter(authors__authordetail__telephone__startswith=11).values("title","publish__name")
    print("mix3",mix3)
    mix4 = Author.objects.filter(authordetail__telephone__startswith="11").values("book__title","book__publish__name")
    print(mix4)

  return HttpResponse("ok")

聚合查询和分组查询

def add(request):
    # ###################聚合查询和分组查询##################
    # 聚合函数:aggregate
    from django.db.models import Avg
    price_avg = Book.objects.all().aggregate(Avg("price"))  # 所有书籍的平均价格;字典的形式
    print(price_avg)
    # {'price__avg': 166.666667}
    price_avg2 = Book.objects.all().aggregate(c=Avg("price"))
    print(price_avg2)
    # {'c': 166.666667}

    # 分组函数:annotate()
    # 统计每本书的作者个数
    from django.db.models import Count
    author_num = Book.objects.all().annotate(c=Count("authors__name"))  # authors__name表示author表中的name字段(正向查询);annotate的作用是给前面所有的对象添加一个独立的“注释”(相当于给前面的所有对象添加了一个新的属性)
    print(author_num)
    for obj in author_num:
        print(obj.title,obj.c)

    # 统计每个作者出版书籍的最高价格
    from django.db.models import Max
    max_price = Author.objects.all().annotate(hp=Max("book__price")).values("name","hp")  # book__price:反向查询;.values("name","hp") 链式操作
    print(max_price)
  
  return HttpResponse("ok")

aggregate(*args,**kwargs)QuerySet 的一个终止子句,意思是说,它返回一个包含一些键值对的字典。键的名称是聚合值的标识符,值是计算出来的聚合值。键的名称是按照字段和聚合函数的名称自动生成出来的。如果你想要为聚合值指定一个名称,可以向聚合子句提供它(kwargs的形式)。

而annotate()函数可以为QuerySet中的每个对象生成一个独立的摘要,输出的结果仍然是一个QuerySet对象,能够调用filter()、order_by()甚至annotate()

总结 :跨表分组查询本质就是将关联表join成一张表,再按单表的思路进行分组查询。

分组查询:

  1. 单表的分组查询:

models.py

from django.db import models

# Create your models here.
class Emp(models.Model):
    name = models.CharField(max_length=32)
    age = models.IntegerField()
    salary = models.DecimalField(max_digits=8,decimal_places=2)
    dep = models.CharField(max_length=32)
    province = models.CharField(max_length=32)

views.py

from django.shortcuts import render,HttpResponse

# Create your views here.
from app01.models import *
from django.db.models import Avg,Max,Min,Count

def add(request):
    # 单表分组查询:
    # 查询每个部门的名称以及平均薪水
    ret1 = Emp.objects.values("dep")
    print(ret1)  # <QuerySet [{'dep': '教学部'}, {'dep': '保安部'}, {'dep': '教学部'}]>
    ret = Emp.objects.values("dep").annotate(avg_salary=Avg("salary"))  # sql: select dep,Avg(salary) from emp group by dep
    print(ret)  # <QuerySet [{'dep': '保安部', 'avg_salary': 5000.0}, {'dep': '教学部', 'avg_salary': 6000.0}]>

    # 单表分组查询的ORM语法: 单表模型.objects.values("group by的字段").annotate(聚合函数("要统计的字段"))  # annotate()前面 select 的是哪个字段,就是以哪个字段分组
    """
    # 补充:
    Emp.objects,all() === SQL: select * from emp
    Emp.objects.all().values("name") === Emp.objects.values("name") === SQL: select name from emp
    所以 .values("xxx") 就相当于 SQL的 select xxx
    Emp.objects.annotate(avg_salary=Avg("salary")) 就是获取所有salary的平均值

    在单表下,按照主键进行group by分组是没有意义的;所以单表分组时不要加 .all() 
    """

    return HttpResponse("ok")
  1. 多表的分组查询:

views.py

from django.shortcuts import render,HttpResponse

# Create your views here.

from app01.models import *

def add(request):
    # ##############多表(跨表)分组查询:##############
    # 查询每个出版社的名称以及出版的书籍个数(先join再进行分组)
    # sql:select publish.name,Count(book.id) from Publish inner join Book on book.publish_id=publish.id
    #           group by publish.id    # (与下面的联表方式不等同)
    # 用ORM语法进行跨表分组查询
    # 方式1. 跨表查询时可利用主键进行 .values(主键)
    # ret = Publish.objects.values("nid").annotate(c=Count("book__title"))
    # print(ret)

    # 方式2
    # ret = Publish.objects.values("name").annotate(c=Count("book__title"))
    # print(ret)

    # 方式3(重点):Publish.objects.values("nid").annotate(c=Count("book__title")):这是一个特殊的QuerySet,里面只有两对键值;如果这两对键值不够用,可以再通过 .values(字段)的方式去获取想要的字段值,因为其基表是 Publish;不加 ,value()就是默认显示默认的那两个字段"nid"和"c"
    ret = Publish.objects.values("nid").annotate(c=Count("book__title")).values("name","c")  # 推荐这种方式
    print(ret)
    # < QuerySet[{'name': '人民出版社', 'c': 3}] >
    # 注意:ret = Publish.objects.values("nid").annotate(c=Count("book__title")):返回结果里面存的仍然是对象,只不过显示的是字典的格式,所以其后面还能继续 .values(Publish的其它字段或者 annotate的字段)

    # 查询每一个作者的名字以及出版过的书籍的最高价格
    # sql: select app01_author.name,Max(app01_book.price) from app01_book inner join app01_book_authors
    #           on app01.book.nid = app01_book_authors.book_id
    #       inner join app01_author on app01_author.nid = app01_book_authors.author_id
    #       group by app01_author.nid
    #       按照作者表的主键nid进行 group by
    ret = Author.objects.values("pk").annotate(max_price=Max("book__price")).values("name","max_price")   # pk代表主键
    print(ret)

    # 示例: 查询每一个书籍名称以及对应的作者个数
    ret = Book.objects.values("pk").values(author_num=Count("authors__name")).values("title","author_num")
    print(ret)

    # #################### 跨表分组查询扩展 #########################
    # 查询每个出版社的名称以及出版的书籍个数
    ret3 = Publish.objects.all().annotate(c=Count("book__title")).values("name","c")  # 这种方式也可以,因为join之后 group by publish.nid 和 group by publish.nid,publish.name,publish.city,publish.email没有任何区别
    print(ret3)  # <QuerySet [{'name': '人民出版社', 'c': 3}]>
    ret4 = Publish.objects.all().annotate(c=Count("book__title"))
    print(ret4)  # <QuerySet [<Publish: Publish object (1)>]>
    ret5 = Publish.objects.annotate(c=Count("book__title"))
    print(ret5)  # <QuerySet [<Publish: Publish object (1)>]>

    """
    跨表的分组查询模型总结:
        “每一个”的表模型.objects.values("pk").annotate(聚合函数(关联表__统计的字段)).values(需要显示的字段)
        “每一个”的表模型.objects.annotate(聚合函数(关联表__统计的字段)).values(需要显示的字段)
    """

    # #################练习#############
    # 1.统计每一本以py开头的书籍的作者个数
    ret6 = Book.objects.filter(title__startswith="py").values("pk").annotate(author_num=Count("authors__name")).values("title","author_num")  # 先过滤出来符合条件的对象再进行分组 # 此 filter 相当于 sql语句的 where过滤方法
    print(ret6)

    # 2. 统计不只一个作者的书籍:这个过滤条件可分解为:统计每本书的作者个数,然后过滤出作者个数大于1的书籍
    ret7 = Book.objects.values("pk").annotate(author_num=Count("authors__name")).filter(author_num__gt=1).values("title","author_num")  # 先进行(分组)统计,再过滤  # 此filter相当于 sql语句的 having过滤方法(分组之后再过滤)
    print(ret7)

    return HttpResponse("ok")

F查询和Q查询

views.py

from django.shortcuts import render,HttpResponse

# Create your views here.

from app01.models import *

def add(request):
    # ###########F查询和Q查询#################
    # F查询:更新数据库字段

    from django.db.models import F,Q

    # 所有书籍的价格提升10元钱
    Book.objects.all().update(price=F("price")+10)
    # 当 = 右边用到字段的值时,就利用 F("字段") 的方法

    # Q查询:构造复杂条件
    # 查询名字为红楼梦且价格为210的书籍
    ret9 = Book.objects.filter(title="红楼梦",price=210)  # 这句代码的含义为: 名字为红楼梦 且 价格为210; filter()里面的 逗号 是 “且”
    print(ret9)
    # 查询名字为红楼梦或价格为210的书籍
    ret10 = Book.objects.filter(Q(title="红楼梦")|Q(price=210))  # 一个Q()就表示一个条件,Q和Q之间的“与或非”关系用 &、|、~ 来表示;多个Q可以放在一个括号 () 内当做一个整体,再去的其它的Q进行逻辑组合
    print(ret10)

    # 名字不为红楼梦的书籍
    ret11 = Book.objects.filter(~Q(title="红楼梦"))
    print(ret11)
    # 注意:如果 filter()中既有Q 又有键值对,那么一定要先在filter()中放入Q,再放入键值对

    return HttpResponse("ok")

基于多表的图书管理系统

目录结构图:

![image.png]](http://jshand.fulfill.com.cn/staticpft/docs/imgs/python/django/07/02.png)

urls.py

from django.contrib import admin
from django.urls import path,re_path

from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),
    re_path(r"book/add/$",views.add_book),
    re_path(r"books/$",views.books),
    re_path(r"books/(\d+)/edit/$",views.edit_book),
    re_path(r"books/(\d+)/delete/$",views.delete_book)
]

views.py

from django.shortcuts import render,HttpResponse,redirect

from app01.models import *

# Create your views here.
def add_book(request):

    if request.method == "POST":
        title = request.POST.get("title")
        price = request.POST.get("price")
        pub_date = request.POST.get("pub_date")
        publish_id = request.POST.get("publish_id")
        # 对于 type="checkbox"的<input> 和 多选的 <select multiple>,前端提交到后端的是一个列表;想要获取到这个列表,需要用 request.POST.getlist() 的方法( getlist())
        authors_id_list = request.POST.getlist("authors_id_list")
        print(authors_id_list) # ['1', '2', '3']

        # 为Book表添加记录
        book_obj = Book.objects.create(title=title,price=price,publishDate=pub_date,publish_id=publish_id)
        print(book_obj) # Book object (1)

        # 对于添加多对多表的数据,由于邦定多对多关系的表是Django为我们自动生成的(如:app01_book_author表),这个表我们不能直接用;所以我们需要调用一个接口去添加多对多的数据:多对多的基表的对象.多对多的字段.add(),add()中可以是一个个另外一张表(表2)中的对象,也可以是一个列表中包含的多个表2 中的对象(前面要加*),也可以是一个列表中包含表2的主键(前面也要加*)
        book_obj.authors.add(*authors_id_list)  # 通过这个接口可以在邦定多对多关系表(app01_book_author)中添加记录

        return redirect("/books/")

    # 获取Publish表和Author表中的全部对象列表;用于动态生成相应的 <option>
    publish_list = Publish.objects.all()
    author_list = Author.objects.all()

    return render(request,"addbook.html",{"publish_list":publish_list,"author_list":author_list})

def books(request):

    book_list = Book.objects.all()

    return render(request,"books.html",{"book_list":book_list})

def edit_book(request,edit_book_id):
    # 获取到相应的书籍对象
    edit_book_obj = Book.objects.filter(pk=edit_book_id).first()
    if request.method == "POST":
        title = request.POST.get("title")
        price = request.POST.get("price")
        pub_date = request.POST.get("pub_date")
        publish_id = request.POST.get("publish_id")
        authors_id_list = request.POST.getlist("authors_id_list")

        # 先更新Book表(此处没有更新 Book.authors 字段)
        Book.objects.filter(pk=edit_book_id).update(title=title,price=price,publishDate=pub_date,publish_id=publish_id)
        # 更新作者信息(app01_book_author表)
        """
        方式一:app01_book_author表 先clear()再add()
        # 先清除和这本书有关的作者记录
        edit_book_obj.authors.clear()
        # 然后添加和这本书关联的作者记录
        edit_book_obj.authors.add(*authors_id_list)
        """
        # 方式二:set()方法:set()方法会先执行 clear()操作, 再执行add()操作  # .add()是绑定关系,.set()是先清空关系(.clear()),再绑定关系(.add())
        edit_book_obj.authors.set(authors_id_list)  # set()中的参数放一个列表即可,无需再加*

        return redirect("/books/")

    publish_list = Publish.objects.all()
    author_list = Author.objects.all()

    return render(request,"edit_book.html",{"edit_book_obj":edit_book_obj,"publish_list":publish_list,"author_list":author_list})

def delete_book(request,delete_book_id):

    Book.objects.filter(pk=delete_book_id).delete()

    return redirect("/books/")

models.py

from django.db import models

# Create your models here.
class Author(models.Model):
    nid = models.AutoField(primary_key=True)
    name = models.CharField(max_length=32)
    age = models.IntegerField()


class Publish(models.Model):
    nid = models.AutoField(primary_key=True)
    name = models.CharField(max_length=32)
    city = models.CharField(max_length=32)
    email = models.EmailField()

class Book(models.Model):

    nid = models.AutoField(primary_key=True)
    title = models.CharField( max_length=32)
    publishDate=models.DateField()
    price=models.DecimalField(max_digits=5,decimal_places=2)  # 最大为: 999.99

    publish = models.ForeignKey(to="Publish",to_field="nid",on_delete=models.CASCADE)
    authors = models.ManyToManyField(to="Author")

books.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="/static/bs/css/bootstrap.min.css">
</head>
<body>
<h3>添加书籍</h3>
<div class="container">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <a href="/book/add/" class="btn btn-primary">添加书籍</a>
            <table class="table table-bordered table-hover table-striped">
                <thead>
                    <tr>
                        <th>编号</th>
                        <th>书籍名称</th>
                        <th>价格</th>
                        <th>出版时间</th>
                        <th>出版社</th>
                        <th>作者</th>
                        <th>编辑操作</th>
                        <th>删除操作</th>
                    </tr>
                </thead>
                <tbody>
                    {% for book in book_list %}
                        <tr>
                            {# forloop.counter表示从1开始计数 #}
                            <td>{{ forloop.counter }}</td>
                            <td>{{ book.title }}</td>
                            <td>{{ book.price }}</td>
                            {# 获取时间时需要利用date做一个时间过滤 #}
                            <td>{{ book.publishDate|date:"Y-m-d" }}</td>
                            {# 利用book查询publish:正向查询按字段 #}
                            <td>{{ book.publish.name }}</td>
                            <td>
                                {# 通过 book.authors.all 能获取到所有的多对多关系的 author对象 #}
                                {# 但不能直接把对象显示到前端,需要用 author.name的方式去显示 #}
                                {% for author in book.authors.all %}
                                    {# forloop.last表示循环的最后一次  #}
                                    {% if forloop.last %}
                                    <span>{{ author.name }}</span>
                                        {% else %}
                                        <span>{{ author.name }}</span>,
                                    {% endif %}
                                {% endfor %}
                            </td>
                            <td>
                                {# 把每个book对象的主键添加到对应编辑、删除链接的路径中 #}
                                <a href="/books/{{ book.pk }}/edit/" class="btn btn-warning">编辑</a>
                            </td>
                            <td><a href="/books/{{ book.pk }}/delete/" class="btn btn-danger">删除</a></td>
                        </tr>
                    {% endfor %}

                </tbody>
            </table>
        </div>
    </div>
</div>

</body>
</html>

addbook.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="/static/bs/css/bootstrap.min.css">
</head>
<body>
<h3>添加书籍</h3>
<div class="container">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <form action="" method="post">
                {% csrf_token %}
                <div class="form-group">
                    <label for="">书籍名称</label>
                    {# 如果没写 value属性,则按输入框中的数据作为其 value值;如果写了value属性,value的值就是该value属性的值 #}
                    <input type="text" name="title" class="form-control">
                </div>
                <div class="form-group">
                    <label for="">价格</label>
                    <input type="text" name="price" class="form-control">
                </div>
                <div class="form-group">
                    <label for="">出版日期</label>
                    <input type="date" name="pub_date" class="form-control">
                </div>
                <div class="form-group">
                    {# 出版社为单选下拉列表 #}
                    <label for="">出版社</label>
                    <select name="publish_id" id="" class="form-control">
                        {# 此处需要动态的去生成 <option>;根据 Publish表中有多少个对象,就生成多少个<option> #}
                        {% for publish in publish_list %}
                            {# 前端显示为 Publish的name,但提交时为 Publish的主键 #}
                            <option value="{{ publish.pk }}">{{ publish.name }}</option>
                        {% endfor %}

                    </select>
                </div>
                <div class="form-group">
                    <label for="">作者</label>
                    {# 作者为多选下拉列表 #}
                    <select name="authors_id_list" id="" class="form-control" multiple>
                        {% for author in author_list %}
                            <option value="{{ author.pk }}">{{ author.name }}</option>
                        {% endfor %}
                    </select>
                </div>
                <input type="submit" class="btn btn-default">
            </form>
        </div>
    </div>
</div>

</body>
</html>

edit_book.html

html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="/static/bs/css/bootstrap.min.css">
</head>
<body>
<h3>添加书籍</h3>
<div class="container">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <form action="" method="post">
                {% csrf_token %}
                <div class="form-group">
                    <label for="">书籍名称</label>
                    {# 把书籍对象的title赋值给 value属性 #}
                    <input type="text" name="title" class="form-control" value="{{ edit_book_obj.title }}">
                </div>
                <div class="form-group">
                    <label for="">价格</label>
                    <input type="text" name="price" class="form-control" value="{{ edit_book_obj.price }}">
                </div>
                <div class="form-group">
                    <label for="">出版日期</label>
                    <input type="date" name="pub_date" class="form-control" value="{{ edit_book_obj.publishDate|date:"Y-m-d" }}">
                </div>
                <div class="form-group">
                    <label for="">出版社</label>
                    <select name="publish_id" id="" class="form-control">
                        {# publish为Publish表中的一个publish对象 #}
                        {% for publish in publish_list %}
                            {# 如果正在编辑的这本书籍对象的publish和循环到的publish对象相同,就为这个 <option>标签添加 selected属性 #}
                            {% if edit_book_obj.publish == publish %}
                                <option value="{{ publish.pk }}" selected>{{ publish.name }}</option>
                                {% else %}
                                <option value="{{ publish.pk }}">{{ publish.name }}</option>
                            {% endif %}
                        {% endfor %}

                    </select>
                </div>
                <div class="form-group">
                    <label for="">作者</label>
                    <select name="authors_id_list" id="" class="form-control" multiple>
                        {% for author in author_list %}
                            {# 如果循环到的这个author对象,在这本书籍关联的所有作者对象的集合中(QuerySet),那就为这个 <option>添加 selected #}
                            {% if author in edit_book_obj.authors.all %}
                                <option value="{{ author.pk }}" selected>{{ author.name }}</option>
                                {% else %}
                                <option value="{{ author.pk }}">{{ author.name }}</option>
                            {% endif %}

                        {% endfor %}
                    </select>
                </div>
                <input type="submit" class="btn btn-default">
            </form>
        </div>
    </div>
</div>

</body>
</html>

8.Django之ajax

向服务器发送请求的途径:

  1. 浏览器地址栏,默认get请求
  2. form表单:

get请求;

post请求

  1. a标签,默认get请求
  2. Ajax:get请求;post请求

Ajax的特点(记住):

(1) 异步请求

(2)局部刷新

AJAX(Asynchronous Javascript And XML)翻译成中文就是“异步Javascript和XML”。即使用Javascript语言与服务器进行异步交互,传输的数据为XML(传输的数据不只是XML,现在更多使用json数据)。

  • 同步交互:客户端发出一个请求后,需要等待服务器响应结束后,才能发出第二个请求;
  • 异步交互:客户端发出一个请求后,无需等待服务器响应结束,就可以发出第二个请求。

优点:

  • AJAX使用Javascript技术向服务器发送异步请求
  • AJAX无须刷新整个页面

基于Jquery的Ajax实现

目录结构:

![image.png]](http://jshand.fulfill.com.cn/staticpft/docs/imgs/python/django/08/01.png)

urls.py

from django.contrib import admin
from django.urls import path

from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path(r"index/",views.index),
    path(r"test_ajax/",views.test_ajax) # ajax的路径需要有流程线(如,路由分发的路径,视图函数,页面等)
]

views.py

from django.shortcuts import render,HttpResponse

# Create your views here.

def index(request):

    return render(request,"index.html")

def test_ajax(request):
    return HttpResponse("hello world")

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
</head>
<body>
<h2>this is Index</h2>
<button class="ajax">Ajax</button>
<p class="content"></p>
</body>
<script>
$(".ajax").click(function () {
    $.ajax({
        {# ajax请求的url;IP和端口没写就默认为当前的 #}
        url:"/test_ajax/",
        {# 请求方式;默认get #}
        type:"get",
        {#回调函数#}
        success:function (data) {
            $(".content").html(data)
        }
    })
})
{# 上述Ajax流程:点击button按钮,通过ajax向 特定的url发送请求;服务器返回字符串 "hello world"并传给回调函数的形参 data;回调函数决定怎么在页面上展示data   #}
</script>
</html>

Ajax传递数据:通过 data:{ } 的形式 发送数据

urls.py

from django.contrib import admin
from django.urls import path

from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path(r"index/",views.index),
    path(r"plus/",views.plus)
]

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
</head>
<body>

<input type="text" class="val1">+<input type="text" class="val2">=<input type="text" class="val3"> <button class="cal">计算</button>

</body>
<script>

$(".cal").click(function () {
    $.ajax({
        url:"/plus/",
        type:"post",
        {#ajax的input中不需要添加 name 属性,只需要 class或id 能找到它就行;但form表单需要利用 name属性去取值#}
        data:{
            {# input框中的值是字符串格式 #}
            "val1":$(".val1").val(),
            "val2":$(".val2").val()
        },
        success:function (data) {
            $(".val3").val(data)
        }
    })
})


</script>
</html>

views.py

from django.shortcuts import render,HttpResponse

def test_ajax(request):
    return render(request,"index.html")

def plus(request):
    print(request.POST)
    val1 = request.POST.get("val1")
    val2 = request.POST.get("val2")

    val3 = int(val1) + int(val2)

    return HttpResponse(val3)

注:settings.py MIDDLEWARE 中的 'django.middleware.csrf.CsrfViewMiddleware' 需要注释掉

基于Ajax的登陆验证

目录结构同上;settings.py 也注释掉 csrf models.py

# Create your models here.
class UserInfo(models.Model):
    '''
        创建两个字段,最大长度是32,类型是char
        '''
    user = models.CharField(max_length= 32)
    pwd = models.CharField(max_length= 32)

urls.py

from django.contrib import admin
from django.urls import path

from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path(r"index/",views.index),

    path(r"login/",views.login)
]

views.py

from django.shortcuts import render,HttpResponse

from app01.models import User

def test_ajax(request):
    return render(request,"index.html")

def login(request):
    print(request.POST)
    user = request.POST.get("user")
    psw = request.POST.get("psw")

    user_obj = UserInfo.objects.filter(user=user,pwd=psw).first()
    res = {"user":None,"msg":None}

    if user_obj:  # 能在数据库中匹配出来
        res["user"] = user_obj.user
    else:
        res["msg"] = "用户名密码错误"

    # 字典格式的数据类型不能直接发送,需要先转化为字符串格式
    import json
    # 利用json.dumps()序列化
    return HttpResponse(json.dumps(res))

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
</head>
<body>

{#form表单的action也没必要写,其实form标签也没必要;只是习惯上把form控件标签放到form中#}
<form>
    用户名 <input type="text" class="user">
    密码 <input type="password" class="psw">
    {# 用ajax发请求时 input的type用"button",不要用"submit",否则就变成了form表单发送请求 #}
    <input type="button" value="submit" class="login_btn"><span class="error"></span>
</form>

</body>
<script>

{# 登陆验证 #}
$(".login_btn").click(function () {
    {# 把ajax内置在某个事件中 #}
    $.ajax({
        url:"/login/",
        type:"post",
        data:{
            "user":$(".user").val(),
            "psw":$(".psw").val()
        },
        success:function (data) {
            console.log(data)
            {# 此时data为json字符串格式 #}
            console.log(typeof data)

            {# 此时data这个字符串交给JS去处理了;就需要用JS的反序列化方法 #}
            {# 只要该语言支持json接口,它就能反解成自己支持的数据类型:python的字典会反解成JS的对象({}),python的列表会反解成JS的数组([])#}
            {# JSON.parse()是JS的反序列化方法 #}
            var new_data = JSON.parse(data)
            console.log(new_data)
            console.log(typeof new_data)

            if (new_data.user){
                {# location.href= 表示前端跳转 #}
                location.href="https://www.baidu.com"
            }else {
                $(".error").html(new_data.msg).css({"color":"red","margin-left":"10px"})
            }

        }
    })
})


</script>
</html>

文件上传:

请求头ContentType:

ContentType指的是消息主体的编码类型,常见的类型共有3种:

1. application/x-www-form-urlencoded:这应该是最常见的 POST 提交数据的方式了。浏览器的原生

表单,如果不设置 enctype 属性,那么最终就会以 application/x-www-form-urlencoded 方式提交数据。请求类似于下面这样

POST http://www.example.com HTTP/1.1
Content-Type: application/x-www-form-urlencoded;charset=utf-8

user=yuan&age=22

2. multipart/form-datamultipart/form-data:这又是一个常见的 POST 数据提交的方式。我们使用表单上传文件时,必须让

表单的 enctype 等于 multipart/form-data

3. application/json:Json格式的字符串作为请求头

基于form表单的文件上传

urls.py

from django.contrib import admin
from django.urls import path

from app01 import views

urlpatterns = [
    path(r"file_put/",views.file_put)
]

views.py

from django.shortcuts import render,HttpResponse

def file_put(request):
    if request.method == "POST":


        # 1. 基于form表单的文件上传
        print(request.POST)
        # request.POST 只有在 contentType = urlencoded 时候才有数据

        # 注意:上传成功的文件放在 request.FILEs 这个属性里面
        print(request.FILES)

        # 下载所上传的文件
        file_obj = request.FILES.get("avatar")
        # 文件对象有一个属性 .name 表示文件名
        with open(file_obj.name,"wb") as f:
            for line in file_obj:
                f.write(line)

        return HttpResponse("ok")

    return render(request,"file_put.html")

file_put.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>

</head>
<body>
<h3>基于form表单的文件上传</h3>

{# 注意:上传文件的form表单中要写上 enctype="multipart/form-data" #}
<form action="" method="post" enctype="multipart/form-data">
    用户名 <input type="text" name="user">
    {# 上传文件 input的type属性值是 "file" #}
    头像 <input type="file" name="avatar">
    <input type="submit">
</form>
</body>
</html>

利用Ajax上传普通数据

views.py

from django.shortcuts import render,HttpResponse

def file_put(request):
    if request.method == "POST":
         # 3. Ajax传递Json数据
        print("request.body",request.body)   # request.body:请求报文中的请求体(请求体的源数据);
        # request.body b'{"a":1,"b":2}'   # 此数据可通过python的json.dumps()方法获取
        print("request.POST",request.POST)  # 此时 request.POST 中没有数据
        # request.POST <QueryDict: {}>

         return HttpResponse("ok")

    return render(request,"file_put.html")

file_put.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>

</head>
<body>


<h3>基于Ajax的文件上传</h3>

{# 利用Ajax上传普通数据  #}
<form action="" method="post">
    用户名 <input type="text" name="user">
    <input type="button" class="btn" value="Ajax">
</form>

</body>
<script>
    {#利用Ajax上传普通数据  #}
    $(".btn").click(function () {
        $.ajax({
            {#url不写默认为当前路径#}
            url: "",
            type: "post",
            {#不指定enctype,默认用application/x-www-form-urlencoded #}
            data: {
                a: 1,
                b: 2
            },
            success: function (data) {
                console.log(data)
            }
        })
    })
 {#不论是form表单还是Ajax都有一个默认的请求头 application/x-www-form-urlencoded  #}

</script>
</html>

Ajax传递Json数据

views.py

from django.shortcuts import render,HttpResponse

def file_put(request):
    if request.method == "POST":
         # 3. Ajax传递Json数据
        print("request.body",request.body)   # request.body:请求报文中的请求体(请求体的源数据);
        # request.body b'{"a":1,"b":2}'   # 此数据可通过python的json.dumps()方法获取
        print("request.POST",request.POST)  # 此时 request.POST 中没有数据
        # request.POST <QueryDict: {}>

         return HttpResponse("ok")

    return render(request,"file_put.html")

file_put.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>

</head>
<body>
{#Ajax传递Json数据#}
<form action="" method="post">
    用户名 <input type="text" name="user">
    <input type="button" class="btn" value="Ajax">
</form>
</body>
<script>

{#Ajax传递Json数据#}
$(".btn").click(function () {
        $.ajax({
            {#url不写默认为当前路径#}
            url:"",
            type:"post",
            {#告诉服务器编码类型为json数据#}
            contentType:"application/json",
             {#然后需要用JS的方法把数据变成Json数据类型:JSON.stringify():序列化 #}
             {#然后请求体中的数据就是 {"a":"1","b":"2"} 类型的json字符串 #}
            data:JSON.stringify({
                a:1,
                b:2
            }),
            success:function (data) {
                console.log(data)
            }
        })
    })

</script>
</html>

基于Ajax的文件上传

views.py

from django.shortcuts import render,HttpResponse

def file_put(request):
    if request.method == "POST":
        # 4. 基于Ajax的文件上传
        print("request.body", request.body)
        print("request.POST", request.POST)  # request.POST <QueryDict: {'user': ['neo']}>
        print(request.FILES)  # <MultiValueDict: {'avatar': [<InMemoryUploadedFile: default.jpg (image/jpeg)>]}>


        # 下载所上传的文件
        file_obj = request.FILES.get("avatar")
        # file_obj = request.FILES.get("avatar")
        # 文件对象有一个属性 .name 表示文件名
        with open(file_obj.name,"wb") as f:
            for line in file_obj:
                f.write(line)

        return HttpResponse("ok")

    return render(request,"file_put.html")

file_put.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>

</head>
<body>

{# 基于Ajax的文件上传 #}
<form action="" method="post">
    用户名 <input type="text" class="user">
    {# 上传文件 input的type属性值是 "file" #}
    头像 <input type="file" class="avatar">
    <input type="button" class="btn" value="Ajax">
</form>


</body>
<script>

{#  基于Ajax的文件上传  #}
    $(".btn").click(function () {
         {#涉及到文件上传,一定要用 FormData 创建一个新的对象(formdata编码);固定格式 #}
        var formdata = new FormData();
        {#然后给创建的 formdata对象添加键值:append(key,value)方法 #}
        formdata.append("user",$(".user").val());
        formdata.append("avatar",$(".avatar")[0].files[0]);
         {#$(".avatar")[0]是对应的 input 标签,DOM元素;取DOM元素中包含的文件对象: .files[0],固定语法 #}

        $.ajax({
            {#url不写默认为当前路径#}
            url:"",
            type:"post",
            {#传formdata的时候一定要加上 contentType:false,processData:false, 这两句代码 #}
            {# contentType:false  表示不做编码处理 #}
            contentType:false,
            {#processData:false表示不对数据做预处理#}
            processData:false,
            {#把formdata赋值给data#}
            data:formdata,
            success:function (data) {
                console.log(data)
            }
        })
    })

</script>
</html>

9.Django表单和分页

Django组件:分页器

目录结构:

![image.png]](http://jshand.fulfill.com.cn/staticpft/docs/imgs/python/django/09/01.png)

urls.py

from django.contrib import admin
from django.urls import path

from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path(r"index/",views.index)
]

models.py

from django.db import models

# Create your models here.

class Book(models.Model):
    nid = models.AutoField(primary_key=True)
    title = models.CharField(max_length=32)
    price = models.DecimalField(decimal_places=2,max_digits=8)

views.py

from django.shortcuts import render

# Create your views here.

from app01.models import Book

# 导入分页器
from django.core.paginator import Paginator,EmptyPage

def index(request):

    """
    # 批量插入数据
    # for i in range(100):
    #     Book.objects.create(title="book_%s"%i,price=i*i)
    # 但上面的批量插入方式效率低,因为需要往数据库中insert100次;不如把100条记录先准备好,只insert一次, insert ... values(),(),...

    book_list = []
    for i in range(100):  # 这个过程中和数据库无关,只是实例化出了100个Book对象(因为没有 save() )
        book_obj = Book(title="book_%s"%i,price=i*i)
        book_list.append(book_obj)
    # bulk_create():批量插入数据
    Book.objects.bulk_create(book_list)  # 效果就是:一条insert后面有100条数据

    :param request:
    :return:
    """

    book_list = Book.objects.all()  # .objects.all():相当于一个 生成器,并不是一次性把数据从数据库全部取了出来

    # Paginator(object_list,per_page):第一个参数是对象列表,第二个是每页显示多少条数据
    paginator = Paginator(book_list,3)  # 得到一个Paginator分页器对象

    # paginator的属性
    # print("count:",paginator.count)   # paginator.count:统计Book_list里面有多少条数据
    # print("num_pages:",paginator.num_pages)  # paginator.num_pages:总共有多少页
    # print("page_range:",paginator.page_range)  # paginator.page_range:页码的列表

    # count: 100
    # num_pages: 13
    # page_range: range(1, 14)   # range(1,14) 是因为range顾头不顾尾

    """
    # 显示某一页具体数据的两种方式
    page1 = paginator.page(1)  # 第一页的page对象;page1可进行迭代处理
    print("objects_list",page1.object_list)  # page1.object_list:这一页所有的model记录对象;QuerySet类型

    # page1也可进行遍历
    for i in page1:
        print(i)

    # 所以想要显示某一页的具体数据可以通过 page1.object_list 或者 遍历 page1 这两种方法
    """

    # 为了不把页码写死,需要动态的传入页码
    current_page_num = int(request.GET.get("page", 1))  # get中的1表示默认获取到的值

    # page_range:用于在模板中渲染多少个的页码的变量
    # 固定显示的最大页码数为11页
    if paginator.num_pages > 11:
        # 如果总页码数大于11,则固定显示11页

        # 如果当前页为最前面的5个, 则要让 page_range 固定为 range(1,12)
        if current_page_num - 5 < 1:
            page_range = range(1,12)
        # 如果当前页为最后面的5个, 则要让 page_range 固定为 range(paginator.num_pages-10,paginator.num_pages+1) (即最后面的11个页码)
        elif current_page_num + 5 > paginator.num_pages:
            page_range = range(paginator.num_pages-10,paginator.num_pages+1)
        else: # 中间的情况
            page_range = range(current_page_num-5,current_page_num+6)
    else:
        # 如果小于11页,就显示全部页码
        page_range = paginator.page_range

    # 如果页码错误(current_page_num不在有效页码范围之内),就让其显示第一页
    try:

        current_page = paginator.page(current_page_num)  # 当前页

        """
        # page对象的方法:
        print(current_page.has_next())  # page.has_next():是否有下一页
        print(current_page.next_page_number())  # page.next_page_number(): 下一页的页码
        print(current_page.has_previous())  # 是否有上一页
        print(current_page.previous_page_number())  # 上一页的页码
        """

    except EmptyPage as e:
        current_page = paginator.page(1)

    return render(request,"index.html",locals())

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body>
<ul>
    {#此时不要循环所有数据(book_list),而是只循环当前页的数据 #}
    {% for book in current_page %}
        <li>{{ book.title }}:{{ book.price }}</li>
    {% endfor %}

</ul>

<nav aria-label="Page navigation">
  <ul class="pagination">
      {# 另一种方法:直接利用 current_page.has_previous 和 current_page.previous_page_number #}
      {# 先判断它有没有上一页   #}
    {% if current_page.has_previous %}
        <li>
            {# 通过过滤器让 current_page_num 减1(加 -1);这是一种方法 #}
            {# <a href="?page={{ current_page_num|add:-1 }}" aria-label="Previous">#}

            <a href="?page={{ current_page.previous_page_number }}" aria-label="Previous">
            <span aria-hidden="true">上一页</span>
            </a>
        </li>
        {# 如果没有上一页,则让该li标签 disabled #}
        {% else %}
        <li class="disabled">
            <a href="" aria-label="Previous">
            <span aria-hidden="true">上一页</span>
            </a>
        </li>
    {% endif %}

    {# 循环页码总数,添加分页器的页数 #}
    {# 但为了只显示固定数量的页码数,我们应该只循环page_range这个变量,而不是所有的页码 #}
{#    {% for item in paginator.page_range %}#}
      {% for item in page_range %}
        {% if current_page_num == item %}
            {# a标签中的 herf 如果只有请求数据,那么默认的IP、端口和路径就是当前页面的 #}
            <li class="active" ><a href="?page={{ item }}">{{ item }}</a></li>
            {# 点击分页器中的某一页码,其颜色就会发生变化:在li标签中添加 class="active" #}
            {% else %}
            <li><a href="?page={{ item }}">{{ item }}</a></li>
        {% endif %}
    {% endfor %}


{#    <li><a href="?page={{ current_page_num|add:1 }}" aria-label="Next"><span aria-hidden="true">下一页</span></a></li>#}
    {# 下一页同理: .has_next 和 next_page_num #}
    {% if current_page.has_next %}
        <li><a href="?page={{ current_page.next_page_number }}" aria-label="Next"><span aria-hidden="true">下一页</span></a></li>
        {% else %}
        <li class="disabled"><a href="" aria-label="Next"><span aria-hidden="true">下一页</span></a></li>
    {% endif %}
  </ul>
</nav>

</body>
</html>

django组件 ---- forms组件

(1) forms组件之校验字段功能

urls.py

from django.contrib import admin
from django.urls import path

from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path(r"reg/",views.reg)
]

views.py

from django.shortcuts import render,HttpResponse

# Create your views here.

# 导入forms组件
from django import forms
class UserForm(forms.Form):  # 类名可以自己定义,但需要继承 forms.Form
    # 字段名称 = 校验规则
    # 校验规则都有一个 非空 规则
    name = forms.CharField(min_length=4)  # name这个字段必须是最少为4位的字符串
    psw = forms.CharField(min_length=4)
    r_psw = forms.CharField(min_length=4)
    email = forms.EmailField()  # forms.EmailField():邮箱校验规则
    tel = forms.CharField()


def reg(request):
    if request.method == "POST":

        """
        # forms组件的应用:
        form = UserForm({"name":"neo","email":"123@qq.com","xx":"666naosid"})  # 把待校验的信息以字典的形式(字典的键值必须和类的字段一致)放入 UserForm中实例化一个对象;该对象有 is_valid() 的方法进行校验是否符合定义的校验规则
        #  如果字典中的某个key在UserForm中的所有字段中不存在,且UserForm中的【所有】字段都校验正确,则这些不存在的key-value 不会对校验结果(form.is_valid())产生影响;但如果UserForm中不是【所有的】字段都校验正确,则 form.is_valid()返回 False
        # 如: UserForm({"name":"neo123","email":"123@qq.com","xxx":"123465"}) 返回True,因为 name 和 email都校验正确(UserForm所有的字段);但 UserForm({"name":"neo123","xxx":"123465"}) 会返回 False
        print(form.is_valid())  # is_valid() 返回Bool值

        # UserForm中的字段如果没有被全部校验(如:字典是没有 "name"= "xxx" 等),则返回False,因为 为空匹配
        if form.is_valid():
            print(form.cleaned_data)  # 如果 所有的字段都校验成功,则 form会有一个 cleaned_data 的属性, form.cleaned_data 是一个包含所有正确信息的字典(不包含不存在的字段,即不包含与UserForm无关的字段)
        else: # 没有符合全部字段的校验规则

            print(form.cleaned_data) # 把 form.is_valid() 在校验过程中发现的符合校验规则的key-value放入 form.cleaned_data 这个字典中
            print(form.errors)      # 把 form.is_valid() 在校验过程中发现的不符合校验规则的key-value(即错误信息)放入 form.errors 这个字典中;其数据类型可理解为一个字典,字典时的value是一个列表(django.forms.utils.ErrorList)放错误信息

        """

        # forms组件的校验功能
        print(request.POST)
        form = UserForm(request.POST)  # request.POST(来自前端的form表单的name属性)本身就是一个字典,而且它的key和 UserForm的字段名一致(在reg.html中自己设置的);虽然 request.POST中有 csrf,但并不会影响校验结果(原因如上,只是多了一个无关的key-value而已)

        if form.is_valid():
            print(form.cleaned_data)
        else:
            print(form.cleaned_data)
            print(form.errors)
            print(type(form.errors))  # <class 'django.forms.utils.ErrorDict'>  # 所以能用字典的方法去处理 form.errors

        return HttpResponse("ok")

    return render(request,"reg.html")

reg.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<form action="" method="post">
    {% csrf_token %}
    {# 为了form表单校验方便,表单控件是 name属性的值应该和forms组件(UserForm)中的字段一致   #}
    <p>用户名 <input type="text" name="name"></p>
    <p>密码 <input type="password" name="psw"></p>
    <p>确认密码 <input type="password" name="r_psw"></p>
    <p>邮箱 <input type="text" name="email"></p>
    <p>电话 <input type="text" name="tel"></p>
    <input type="submit">

</form>

</body>
</html>

forms组件的渲染标签功能1:

还以上面的project为例

views.py

from django.shortcuts import render,HttpResponse

# Create your views here.

# 导入forms组件
from django import forms
class UserForm(forms.Form):  # 类名可以自己定义,但需要继承 forms.Form
    # 字段名称 = 校验规则
    # 校验规则都有一个 非空 规则
    name = forms.CharField(min_length=4)  # CharField()可以渲染input标签
    psw = forms.CharField(min_length=4)
    r_psw = forms.CharField(min_length=4)
    email = forms.EmailField()  # EmailField() 也可以渲染input标签
    tel = forms.CharField()


def reg(request):
    if request.method == "POST":

        if form.is_valid():
            print(form.cleaned_data)
        else:
            print(form.cleaned_data)
            print(form.errors)
            print(type(form.errors)) 

        return HttpResponse("ok")

    # forms组件渲染方式1
    # 假如你不想自己去渲染 reg.html 中的form表单控件,你可以先用 UserForm 实例化一个对象 form,然后把这个 form 对象传到 render 中,最后在reg.html中利用 form对象去渲染 form表单控件
    form1 = UserForm()  # get请求方式时传入 render() reg.html 中去渲染 form 表单控件
    return render(request,"reg.html",locals())

reg.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<h3>forms组件渲染方式1</h3>

<form action="" method="post">
    {% csrf_token %}

    <p>用户名
  {# 通过传进来的 UserForm 的对象 form1.字段的形式,可直接渲染出 input标签,并且 input标签的name属性值为 form1这个对象的 字段名#}
        {{ form1.name }}
    </p>
    <p>密码 {{ form1.psw }}</p>
    <p>确认密码 {{ form1.r_psw }}</p>
    <p>邮箱 {{ form1.email }}</p>
    <p>电话 {{ form1.tel }}</p>
{#     CharField 校验规则能渲染 type="text"的input标签;EmailField 校验规则能渲染 type="email"的input标签#}
{#     字段非常多的时候可以用这种方式#}
    <input type="submit">
</form>

</body>
</html>

浏览器效果如下:

![image.png]](http://jshand.fulfill.com.cn/staticpft/docs/imgs/python/django/09/02.png)

注意:forms组件的校验字段功能和渲染标签功能是两套功能;GET请求时进行渲染标签,POST时进行校验字段

forms组件的渲染标签功能2和3:

还以上面的project为例,views.py是要为UserForm这个类添加 label 属性

reg.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<h3>forms组件渲染方式2(推荐使用这种)</h3>

<form action="" method="post">
    {% csrf_token %}

    {# 也可以 for 循环 views.py 传进来的 form1这个对象,for循环这个form1对象时得到的是 每一个 field对象(字段对象,如渲染方式1中的 form1.name,form1.psw等)#}
    {% for field in form1 %}
        <p>
            {# 字段对象有一个固有的属性 label,如 form1.psw.lable; 字段对象.label 在前端页面的展示效果会动态的跟随字段的名字而变化,如 form1.psw.label 在前端显示的input前的文字为 psw #}
            {# 如果想让 label 属性显示中文,需要在 UserForm 中为每个字段添加 lable属性值 #}
            <label for="">{{ field.label }}</label>
            {# field.label的作用是让input标签前添加label标签(中文描述);field就是 form1.字段名 #}
            {# 此时 field就是form1中的第一个字段对象 #}
            {{ field }}
        </p>
    {% endfor %}
    <input type="submit">

<h3>forms组件渲染方式方法3</h3>
    <form action="" method="post">
        {# 直接只写一个 form1.as_p #}
        {{ form1.as_p }}
        {# 不推荐这种写法,这种渲染方式得到的form表单控件的结构是固定的:<p><label></label><input></p>,直接就写死了 #}
    </form>

</form>

</body>
</html>

views.py 中的UserForm:

# 导入forms组件
from django import forms
class UserForm(forms.Form):  # 类名可以自己定义,但需要继承 forms.Form
    # 字段名称 = 校验规则
    # 校验规则都有一个 非空 规则
    name = forms.CharField(min_length=4,label="用户名")   # 为每个 字段添加 label属性
    psw = forms.CharField(min_length=4,label="密码")
    r_psw = forms.CharField(min_length=4,label="确认密码")
    email = forms.EmailField(label="邮箱") 
    tel = forms.CharField(label="电话")

浏览器效果如下:

![image.png]](http://jshand.fulfill.com.cn/staticpft/docs/imgs/python/django/09/03.png)

forms组件渲染错误信息

views.py

from django.shortcuts import render,HttpResponse

# Create your views here.

# 导入forms组件
from django import forms
class UserForm(forms.Form):  
    name = forms.CharField(min_length=4,label="用户名")  
    psw = forms.CharField(min_length=4,label="密码")
    r_psw = forms.CharField(min_length=4,label="确认密码")
    email = forms.EmailField(label="邮箱")  
    tel = forms.CharField(label="电话")

def reg(request):
    if request.method == "POST":
         print(request.POST)
        form1 = UserForm(request.POST)  

        if form1.is_valid():
            print(form1.cleaned_data)
        else:
            print(form1.cleaned_data)
            print(form1.errors)
            print(type(form1.errors))

            # forms 组件渲染错误信息
            # GET请求时的form1对象和POST请求时的form1对象是不一样的;GET的form1没有传数据(未邦定数据的forms组件对象),POST的form1传了数据(邦定数据的forms组件对象)
            # 有错误信息时,依然把 reg.html 返回给前端;此时已输入的正确信息会在输入框中保留
            return render(request,"reg.html",locals())

    form1 = UserForm()  # get请求方式时传入 render() reg.html 中去渲染 form 表单控件
    return render(request,"reg.html",locals())

reg.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<h3>forms组件渲染错误信息</h3>

{#form表单控件的 novalidate 表示:当提交表单时不对表单数据(输入)进行验证#}
<form action="" method="post" novalidate>
    {% csrf_token %}
    <p>
        {{ form1.name.label }}
        {# form1.字段不但能渲染出一个input标签,还能把 POST请求数据 在views.py 中过滤出来的正确信息 赋值给该 input 标签的value属性值;通过这种方式 在前端刷新页面时 已输入的正确信息能保留在输入框中 #}
        {{ form1.name }} <span>{{ form1.name.errors.0 }}</span>
        {# 每个字段对象都有一个 errors的属性;可通过 .errors.0 的方式 获取该错误信息 #}
        {# GET请求时不会显示错误信息,因为此时没有 errors, .errors.0 找不到信息就在前端页面上显示为空 #}
    </p>
    <p>{{ form1.psw.label }} {{ form1.psw }}<span>{{ form1.psw.errors.0 }}</span></p>
    <p>{{ form1.r_psw.label }} {{ form1.r_psw }}<span>{{ form1.r_psw.errors.0 }}</span></p>
    <p>{{ form1.email.label }} {{ form1.email }}<span>{{ form1.email.errors.0 }}</span></p>
    <p>{{ form1.tel.label }} {{ form1.tel }}<span>{{ form1.tel.errors.0 }}</span></p>

    <input type="submit">
</form>

</form>

</body>
</html>

forms组件的参数配置

forms组件的参数是在 views.py中的 UserForm中进行配置的,如:

# forms组件的参数配置
from django import forms
from django.forms import widgets
class UserForm(forms.Form):

    name = forms.CharField(min_length=4,label="用户名",error_messages={"required":"该字段不能为空"},
                           widget=widgets.TextInput(attrs={"class":"form-control"}))  # name这个字段必须是最少为4位的字符串 # 为每个 字段添加 label属性
    psw = forms.CharField(min_length=4,label="密码",error_messages={"required":"该字段不能为空"},
                          widget=widgets.PasswordInput(attrs={"class":"form-control"}))
    r_psw = forms.CharField(min_length=4,label="确认密码",error_messages={"required":"该字段不能为空"},
                            widget=widgets.PasswordInput(attrs={"class":"form-control"}))
    email = forms.EmailField(label="邮箱",widget=widgets.EmailInput(attrs={"class":"form-control"}),
                             error_messages={"required": "该字段不能为空", "invalid": "邮箱格式错误"})  # forms.EmailField():邮箱校验规则
    tel = forms.CharField(label="电话",widget=widgets.TextInput(attrs={"class":"form-control"}),error_messages={"required":"该字段不能为空"})

    """
    1. widget = widgets.TextInput():通过 添加属性 widget 能够控制渲染出来的input标签的 type 属性值,如: widget = widgets.TextInput() 表示渲染出 type="text"的input标签(默认值), widget= widgets.PasswordInput():text="passwrod" 的 input 标签,widget=widgets.EmailInput(): text="email" 的 input 标签
    2. widget = widgets.TextInput(attrs={"class":"form-control"}):widget = widgets.TextInput()等参数中添加 attrs={"属性名":"属性值"} 能为渲染出来的input标签 添加特定的属性,如:widget = widgets.TextInput(attrs={"class":"form-control"}) 表示为渲染出来的 input标签添加 class="form-control"
    3. error_messages={}:表示控制错误信息,如 error_messages={"required":"该字段不能为空","invalid":"格式错误"},"required"是“非空错误”的key,"invalid"是“格式错误”的key 
    """

reg.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body>

<div class="container">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <h3>forms组件的参数配置</h3>

            <form action="" method="post" novalidate>
                {% csrf_token %}
                <p>
                    {{ form1.name.label }}
                    {{ form1.name }} <span>{{ form1.name.errors.0 }}</span>
                </p>
                <p>{{ form1.psw.label }} {{ form1.psw }}<span>{{ form1.psw.errors.0 }}</span></p>
                <p>{{ form1.r_psw.label }} {{ form1.r_psw }}<span>{{ form1.r_psw.errors.0 }}</span></p>
                <p>{{ form1.email.label }} {{ form1.email }}<span>{{ form1.email.errors.0 }}</span></p>
                <p>{{ form1.tel.label }} {{ form1.tel }}<span>{{ form1.tel.errors.0 }}</span></p>

                <input type="submit" class="btn btn-default">
            </form>
        </div>
    </div>
</div>

</body>
</html>

局部钩子和全局钩子:

views.py

from django.shortcuts import render

# forms组件的参数配置
from django import forms
# 配置参数需要导入 widgets
from django.forms import widgets

# 钩子需要导入 ValidationError
from django.core.exceptions import NON_FIELD_ERRORS,ValidationError

from app01.models import UserInfo

class UserForm(forms.Form):

    name = forms.CharField(min_length=4,label="用户名",error_messages={"required":"该字段不能为空"},
                           widget=widgets.TextInput(attrs={"class":"form-control"}))  # name这个字段必须是最少为4位的字符串 # 为每个 字段添加 label属性
    psw = forms.CharField(min_length=4,label="密码",error_messages={"required":"该字段不能为空"},
                          widget=widgets.PasswordInput(attrs={"class":"form-control"}))
    r_psw = forms.CharField(min_length=4,label="确认密码",error_messages={"required":"该字段不能为空"},
                            widget=widgets.PasswordInput(attrs={"class":"form-control"}))
    email = forms.EmailField(label="邮箱",widget=widgets.EmailInput(attrs={"class":"form-control"}),
                             error_messages={"required": "该字段不能为空", "invalid": "邮箱格式错误"})  # forms.EmailField():邮箱校验规则
    tel = forms.CharField(label="电话",widget=widgets.TextInput(attrs={"class":"form-control"}),error_messages={"required":"该字段不能为空"})
    # 按照局部钩子校验字段
    def clean_name(self):  # 只能叫 clean_字段名
        # 根据源码可知, 能执行 clean_字段 方法,说明已经通过了上面的 字段=校验规则 的校验

        # 校验用户名是否重名
        val = self.cleaned_data.get("name")
        # 从数据库中读取数据
        ret = UserInfo.objects.filter(name=val)

        if not ret:
            return val  # 根据源码可知, return的 val 依然是 name 字段对应的值
        else:
            raise ValidationError("用户名已注册")  # 抛出的异常名必须是 ValidationError;抛出的错误会放到 错误字典中

    def clean_tel(self):
        # 校验手机号是否为11位
        val = self.cleaned_data.get("tel")
        print(val)
        if len(val) == 11:
            return val  # 如果手机号为11位,通过校验,则把手机号原封不动返回
        else:
            raise ValidationError("手机号必须为11位")

    # 一次校验里面用到多个校验的字段,就不能再用 上面的局部钩子方法,因为局部钩子方法一次只能校验一个字段;此时应该用全局钩子
    # clean():全局钩子;其它所有的校验规则校验完之后,才会走 clean()
    def clean(self):
        # 校验 psw 和 r_psw 是否一致
        psw = self.cleaned_data.get("psw")
        r_psw = self.cleaned_data.get("r_psw")

        # 先判断字段是否通过 字段校验和局部钩子,如果没有,则不进行全局钩子校验
        if psw and r_psw:
            if psw == r_psw:
                return self.cleaned_data  # 通过全局钩子校验,则把 cleaned_data 原封返回
            else:
                raise ValidationError("两次密码不一致!")  # 全局钩子的错误字典的形式:{"__all__":[....]},其key是 "__all__";这个异常类型不能通过 字段 获取到

        else: # 没通过字段校验和局部钩子,则把 干净的数据 clean_data 原封不动返回
            return self.cleaned_data

def reg(request):
    if request.method == "POST":
        print(request.POST)
        form1 = UserForm(request.POST)  

        if form1.is_valid():
            print(form1.cleaned_data)
        else:
            print(form1.cleaned_data)
            print(form1.errors)
            print(type(form1.errors))

         # 先把全局钩子的异常类型获取到
            err = form1.errors.get("__all__")  # 获取全局钩子的错误信息
            # 同时要在 reg.html 中专门把此错误信息放到 r_psw后面
            return render(request,"reg.html",locals())
    form1 = UserForm() 
    return render(request,"reg.html",locals())

reg.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body>

<div class="container">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <h3>forms组件的参数配置</h3>

            <form action="" method="post" novalidate>
                {% csrf_token %}
                <p>
                    {{ form1.name.label }}
                    {{ form1.name }} <span>{{ form1.name.errors.0 }}</span>
                </p>
                <p>{{ form1.psw.label }} {{ form1.psw }}<span>{{ form1.psw.errors.0 }}</span></p>
                <p>{{ form1.r_psw.label }}
                    {{ form1.r_psw }}
                    <span>{{ form1.r_psw.errors.0 }}</span>
                    {# err.0 表示获取第一个错误信息;err:列表的形式;不在 views.py中直接 form1.errors.get("__all__")[0] 是防止报错 #}
                    <span>{{ err.0 }}</span></p>
                <p>{{ form1.email.label }} {{ form1.email }}<span>{{ form1.email.errors.0 }}</span></p>
                <p>{{ form1.tel.label }} {{ form1.tel }}<span>{{ form1.tel.errors.0 }}</span></p>

                <input type="submit" class="btn btn-default">
            </form>
        </div>
    </div>
</div>

</body>
</html>

models.py

python
class UserInfo(models.Model):
    nid = models.AutoField(primary_key=True)
    name = models.CharField(max_length=32)

10.Django之cookie-和-session

会话:会话可理解为客户端与服务器之间的一次会晤,在一次会晤中可能会包含多次请求和响应;在JavaWeb中,客户向某一服务器发出第一个请求开始,会话就开始了,直到客户关闭了浏览器会话结束。

在一个会话的多个请求中共享数据,就是会话跟踪技术

HTTP协议是无状态协议,也就是说每个请求都是独立的!无法记录前一次请求的状态。但HTTP协议中可以使用Cookie来完成会话跟踪!在Web开发中,使用session来完成会话跟踪,session底层依赖Cookie技术。

Cookie:

cookie:具体一个浏览器针对一个服务器存储的 key-value(可理解为python的字典)

在HTTP中cookie表示服务器送给客户端浏览器的key-value结构的数据,类似于一个python中的字典。随着服务器端的响应发送给客户端浏览器,然后客户端浏览器会把Cookie保存起来,当下一次再访问服务器时把Cookie再发送给服务器。 Cookie是由服务器创建,然后通过响应发送给客户端的一个键值对。客户端会保存Cookie,并会标注出Cookie的来源(哪个服务器的Cookie)。当客户端向服务器发出请求时会把所有这个服务器Cookie包含在请求中发送给服务器,这样服务器就可以识别客户端了!

cookie代码示例:

目录结构:

![image.png]](http://jshand.fulfill.com.cn/staticpft/docs/imgs/python/django/10/01.png)

models.py

class UserInfo(models.Model):
    nid = models.AutoField(primary_key=True)
    user = models.CharField(max_length=32)
    psw = models.CharField(max_length=32,default='')

urls.py

from django.contrib import admin
from django.urls import path

from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path(r"login/",views.login),
    path(r"index/",views.index),
    path(r"test_cookie_path",views.test_cookie_path)
]

views.py

from django.shortcuts import render,HttpResponse,redirect

# Create your views here.

from app01.models import UserInfo

# 登陆页面
def login(request):

    if request.method == "POST":
        user = request.POST.get("user")
        psw = request.POST.get("psw")

        user_obj = UserInfo.objects.filter(user=user,psw=psw).first()

        if user_obj:  # 验证成功
            # 响应体有:HttpResponse,render和redirect;这三个的本质都是返回了 HttpResponse
            # 设置 cookie 时一定要用响应体去进行设置

            response = HttpResponse("登陆成功")  # 响应体
            # 用响应体去设置 cookie
            response.set_cookie("is_login",True)  # "is_login" 是 cookie的 key,True是cookie的value  # 只有在这一步都有 cookie的设置,GET请求时没有设置cookie

            # 设置cookie的超时参数:max_age 和 expires
            """
            max_age=None:超长时间;
                          cookie需要延续的时间(以秒为单位);
                          如果参数是\ None`` ,这个cookie会延续到浏览器关闭为止;
                          max_age 表示 隔了多长时间后失效(IE浏览器不支持)
            expires=None,:超长时间;
                  expires默认None ,cookie失效的实际日期/时间;
                           expires现在记一种数据类型:datetime.datetime;
                           expire 表示在某一个 时刻 cookie 失效
            """
            # response.set_cookie("is_login", True,max_age=15)  # 15秒后这个cookie失效

            # response.set_cookie("username",user_obj.user)  # 这两句代码每走一次,就会把上次的cookie覆盖掉

            """
            import datetime
            date = datetime.datetime(year=2018,month=6,day=17,hour=4,minute=20,second=1)   # UTC格式的时间;需要自己在 datetime.datetime的参数中+8转为北京时间
            response.set_cookie("username", user_obj.user,expires=date)    # 在 date 这个时刻该cookie失效
            """

            """
            cookie的有效路径:path='/'
            path='/' :Cookie生效的路径,
                       浏览器只会把cookie回传给带有该路径的页面(只有该路径对应视图函数能调用这个cookie),这样可以避免将cookie传给站点中的其他的应用。
                       / 表示根路径,特殊的:根路径的cookie可以被任何url的页面(该路径对应的视图函数)访问
            """

            response.set_cookie("username",user_obj.user,path="/index/")  # {"username":user_obj.user} 这个 cookie 只有 /index/ 路径对应的视图函数 views.index 能够获取到

            # 删除 cookie
            # response.delete_cookie("username",path="/",domain=name)  # 放入 cookie的key即可

            return response

    return render(request,"login.html")

# 首页
def index(request):
    # 所有的请求信息都在 request 中,包括 cookie
    print(request.COOKIES)  # request.COOKIES:获取 cookie; request.COOKIES中包含cookies所有的key-value(字典的形式)
    is_login = request.COOKIES.get("is_login")  # 获取cookie 中 is_login对应的 value
    username = request.COOKIES.get("username")

    if is_login:
        return render(request,"index.html",{"username":username})
    else:
        return redirect("/login/")

# 测试 cookie的path
def test_cookie_path(request):
    print("test_cookie_path",request.COOKIES)

    return HttpResponse("test_cookie_path")

login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="" method="post">
    {% csrf_token %}
    用户名 <input type="text" name="user">
    密码 <input type="password" name="psw">
    <input type="submit">
</form>

</body>
</html>

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h3>hello {{ username }}</h3>

</body>
</html>

利用cookie显示上次访问时间

还以上面的project为例:

其它的代码不变,只修改 views.py 中的 index() 视图函数

# 首页
def index(request):
    # 所有的请求信息都在 request 中,包括 cookie
    print(request.COOKIES)  # request.COOKIES:获取 cookie; request.COOKIES中包含cookies所有的key-value(字典的形式)
    is_login = request.COOKIES.get("is_login")  # 获取cookie 中 is_login对应的 value
    username = request.COOKIES.get("username")

    if is_login:

        # 每次用户登陆时,记录下这个时间
        import datetime
        now = datetime.datetime.now().strftime("%Y-%m-%D %H:%M:%S")  # 当前登陆时间;把settings.py中的 TIME_ZONE 改为 'Asia/Shanghai'
        last_time = request.COOKIES.get("last_visit_time","") # 获取cookie中 "last_visit_time"对应的 value;如果没有取到,则默认为空

        # 利用 render() 设置上次登陆时间这个 cookie
        response = render(request,"index.html",{"username":username,"last_time":last_time})
        response.set_cookie("last_visit_time",now)  # 把这次登陆的当前时间设置为下次登陆的 last_visit_time

        return response
    else:
        return redirect("/login/")

session:

Session是服务器端技术,利用这个技术,服务器在运行时可以 为每一个用户的浏览器创建一个其独享的session对象,由于 session为用户浏览器独享,所以用户在访问服务器的web资源时 ,可以把各自的数据放在各自的session中,当用户再去访问该服务器中的其它web资源时,其它web资源再从用户各自的session中 取出数据为用户服务。

session流程图:

![image.png]](http://jshand.fulfill.com.cn/staticpft/docs/imgs/python/django/10/02.png)

示例代码:

还以上面的project为例

urls.py

from django.contrib import admin
from django.urls import path

from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path(r"login/",views.login),
    path(r"index/",views.index),

    path(r"login_session/",views.login_session),
    path(r"index_session/",views.index_session),
    path(r"logout/",views.logout)
]

views.py(login() 和 index()同上)

from django.shortcuts import render,HttpResponse,redirect

# Create your views here.

from app01.models import UserInfo

def login_session(request):

    if request.method == "POST":
        user = request.POST.get("user")
        psw = request.POST.get("psw")

        user_obj = UserInfo.objects.filter(user=user,psw=psw).first()
        if user_obj:
            # 设置 session
            request.session["is_login"] = True
            request.session["username"] = user_obj.user

            # 记录上次登陆时间
            import datetime
            now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            request.session["last_time"] = now

            print(request.session["username"])
            """
            if request.COOKIE.get("sessionid"):
                更新 session:
                    在 django_session 表(session-key,session-data)中更新一条记录:(此时sessionid(session_key)不变,session_data更新为最新的)
                            session-key                     session-data
                        {"nmqjb074n0zr653huahezadrjd3t87p7":最新的数据}

            else:
                设置 session 时 Django会自动执行三步操作:
                1. 生成随机字符串, 如:nmqjb074n0zr653huahezadrjd3t87p7;
                2. 给响应体设置 cookie: response.set_cookie("sessionid","nmqjb074n0zr653huahezadrjd3t87p7");cookie的key为 sessionid,value为第一步生成的随机字符串;
                3. 在 django_session 表(session-key,session-data)中创建一条记录:
                            session-key                              session-data
                        {"nmqjb074n0zr653huahezadrjd3t87p7":{"is_login":True,"username":user_obj.user}}

            即:登陆时,如果能从 cookie中 读出 "sessionid",则将 session 在 django_session表中 更新;否则 在 django_session表中创建一条 session 记录
            """

            return HttpResponse("登陆成功")

    return render(request, "login.html")

def index_session(request):

    # 读取 session
    print(request.session)
    print(request.session.get("is_login"))

    """
    读取 session时 Django也会自动执行以下三步:
    1. 读取 cookie 中的 sessionid: request.COOKIE.get("sessionid"),然后获取到那个随机字符串:nmqjb074n0zr653huahezadrjd3t87p7 
    2. 在 django_session 表中过滤记录:根据 session-key(那个随机字符串)进行过滤 session-data
                    session-key                         session-data
            nmqjb074n0zr653huahezadrjd3t87p7    {"is_login":True,"username":user_obj.user}

            作用即相当于:obj = django_session.objects.filter(session_key="nmqjb074n0zr653huahezadrjd3t87p7").first();获取到一个对象(记录)        
    3. 获取 session-data中的值: obj.session_data.get("is_login")
    """
    # 删除session的键值:
    # del request.session["is_login"]  # 只是删除了 session_data 中的 "is_login":True

    is_login = request.session.get("is_login")
    if is_login:
        username = request.session.get("username")

        last_time = request.session.get("last_time")

        return render(request, "index.html",{"username":username,"last_time":last_time})
        # return render(request, "index.html", {"username": username})
    else:
        return redirect("/login/")

def logout(request):
    # del request.session["is_login"]  # 注销推荐这种方式

    # 注销推荐使用 flush()
    # flush() : 删除当前的会话数据并删除会话的 cookie;用于确保前面的会话数据不可以再次被用户的浏览器 访问(把 django_session 表中的整条记录删除)
    request.session.flush()
    """
    flush()执行的操作:
    1. random_str = request.COOKIE.get("sessionid") # 先通过 sessionid 获取 那个随机字符串(session-key)
    2. django_session.objects.filter(session_key=random_str).delete()  # 删除整条记录
    3. response.delete_cookie("sessionid")   # 把 cookie 中 sessionid 也清除掉 (把 cookie 中的那个随机字符串删掉)
    """

    return redirect("/login/")

index.html(login.html同上)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h3>hello {{ username }}</h3>
<p>上次登陆时间:{{ last_time }}</p>
<a href="/logout/">注销</a>

</body>
</html>

session语法:

1、设置Sessions值
          request.session['session_name'] ="admin"
2、获取Sessions值
          session_name = request.session["session_name"]
3、删除Sessions值
          del request.session["session_name"]
4、flush()
     删除当前的会话数据并删除会话的Cookie。
     这用于确保前面的会话数据不可以再次被用户的浏览器访问

5、get(key, default=None)

fav_color = request.session.get('fav_color', 'red')

6、pop(key)

fav_color = request.session.pop('fav_color')

7、keys()

8、items()

9、setdefault()


10 用户session的随机字符串
        request.session.session_key

        # 将所有Session失效日期小于当前日期的数据删除
        request.session.clear_expired()

        # 检查 用户session的随机字符串 在数据库中是否
        request.session.exists("session_key")

        # 删除当前用户的所有Session数据
        request.session.delete("session_key")

        request.session.set_expiry(value)
            * 如果value是个整数,session会在些秒数后失效。
            * 如果value是个datatime或timedelta,session就会在这个时间后失效。
            * 如果value是0,用户关闭浏览器session就会失效。
            * 如果value是None,session会依赖全局session失效策略。

session配置:

Django默认支持Session,并且默认是将Session数据存储在数据库中,即:django_session 表中。

a. 配置 settings.py

    SESSION_ENGINE = 'django.contrib.sessions.backends.db'   # 引擎(默认)

    SESSION_COOKIE_NAME = "sessionid"                       # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串(默认)
    SESSION_COOKIE_PATH = "/"                               # Session的cookie保存的路径(默认)
    SESSION_COOKIE_DOMAIN = None                             # Session的cookie保存的域名(默认)
    SESSION_COOKIE_SECURE = False                            # 是否Https传输cookie(默认)
    SESSION_COOKIE_HTTPONLY = True                           # 是否Session的cookie只支持http传输(默认)
    SESSION_COOKIE_AGE = 1209600                             # Session的cookie失效日期(2周)(默认)
    SESSION_EXPIRE_AT_BROWSER_CLOSE = False                  # 是否关闭浏览器使得Session过期(默认)
    SESSION_SAVE_EVERY_REQUEST = False                       # 是否每次请求都保存Session,默认修改之后才保存(默认)

Released under the MIT License.