Django教程

小家电维修 / 2023-05-03 / 原文

1.前言

  复习该课程的时候已经2023年初了,2021年底发布了4.0,由于是复习,所以这里还是使用django3.X版本来进行练习。

  下面看一下django的版本演变过程。

  Django 是基于Python的Web 框架,依赖Python 环境,所以需要提前安装好Python 解释器。

  建议安装长期支持 LTS 版本的 Python。

  Django 官方版本查看

  • https://www.djangoproject.com/download
  • https://docs.djangoproject.com/en/4.0/faq/install/

  Django 各版本对Python 版本的依赖关系

  Django 本身的版本规划时间图如下所示

  未来版本的计划节点

  从上表中可以看出Django 的版本更新迭代非常快,大版本号几乎两年加一。

  实际上,对于非重度用户,Django 的版本差别并没有你想象中那么大,完全不必为版本的问题头疼。

 

2.Django 简介

2.1 基本介绍

  Django 是一个由 Python 编写的一个开放源代码的 Web 应用框架。

  使用 Django,只要很少的代码,Python 的程序开发人员就可以轻松地完成一个正式网站所需要的大部分内容,并进一步开发出全功能的 Web 服务 Django 本身基于 MVC 模型,即 Model(模型)+ View(视图)+ Controller(控制器)设计模式,MVC 模式使后续对程序的修改和扩展简化,并且使程序某一部分的重复利用成为可能。

  MVC 优势:

    • 低耦合
    • 开发快捷
    • 部署方便
    • 可重用性高
    • 维护成本低
    • ...

  Python 加 Django 是快速开发、设计、部署网站的最佳组合。

  特点

    • 强大的数据库功能
    • 自带强大的后台功能
    • 优雅的网址

 

2.2 MVC 与 MTV模型

  MVC 模型

  MVC 模式(Model–view–controller)是软件工程中的一种软件架构模式,把软件系统分为三个基本部分:模型(Model)、视图(View)和控制器(Controller)。

  MVC 以一种插件式的、松耦合的方式连接在一起。

    • 模型(M)- 编写程序应有的功能,负责业务对象与数据库的映射(ORM)。
    • 视图(V)- 图形界面,负责与用户的交互(页面)。
    • 控制器(C)- 负责转发请求,对请求进行处理。

  简易图:

  用户操作流程图:

 

  MTV 模型

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

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

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

  简易图:

  用户操作流程图:

  解析:

  用户通过浏览器向我们的服务器发起一个请求(request),这个请求会去访问视图函数:

    • 如果不涉及到数据调用,那么这个时候视图函数直接返回一个模板也就是一个网页给用户。
    • 如果涉及到数据调用,那么视图函数调用模型,模型去数据库查找数据,然后逐级返回。

  视图函数把返回的数据填充到模板中空格,最后返回网页给用户。

 

3.Django 安装

  在安装 Django 前,系统需要已经安装了Python的开发环境。接下来我们来具体看下不同系统下Django的安装。

 

3.1 Windows 下安装 Django

  如果你还未安装Python环境需要先下载Python安装包。

  1、Python 下载地址:https://www.python.org/downloads/

  2、Django 下载地址:https://www.djangoproject.com/download/

  注意:目前 Django 1.6.x 以上版本已经完全兼容 Python 3.x。

 

  第一种方式

  下载 Django 压缩包,解压并和 Python安装目录放在同一个根目录,进入 Django 目录,执行 python setup.py install,然后开始安装,Django 将要被安装到 Python 的 Lib下site-packages。

  然后是配置环境变量,将这几个目录添加到系统环境变量中: C:\Python33\Lib\site-packages\django;C:\Python33\Scripts。 添加完成后就可以使用Django的django-admin.py命令新建工程了。

 

  第二种方式,及其简单(推荐)

  验证

 

3.2 Linux 上安装 Django

  第一种方式

  安装 setuptools

  命令:

# Python3 安装
yum install python3-setuptools
# Python2 安装
yum install python2-setuptools

  完成之后,就可以使用 easy_install 命令安装 Django

easy_install django

  之后我们在 Python 解释器输入以下代码:

[root@solar django]# python
Python 3.7.4 (default, May 15 2014, 14:49:08)
[GCC 4.8.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import django
>>> django.VERSION
(3, 0, 6, 'final', 0)

  我们可以看到输出了Django的版本号,说明安装成功。

  

  第二种方式:pip 命令安装方法

sudo pip3 install Django -i https://pypi.tuna.tsinghua.edu.cn/simple

  -i https://pypi.tuna.tsinghua.edu.cn/simple 指定清华镜像源,下载速度更快。

  指定 Django 的下载版本(3.0.6 可以改成你要的版本):

sudo pip3 install Django==3.0.6 -i https://pypi.tuna.tsinghua.edu.cn/simple

  如果 pip < 1.4,安装方法如下:

pip install https://www.djangoproject.com/download/1.11a1/tarball/

 

  第三种方式:源码安装方法

  下载源码包:https://www.djangoproject.com/download/

  输入以下命令并安装:

tar xzvf Django-X.Y.tar.gz    # 解压下载包
cd Django-X.Y                 # 进入 Django 目录
python setup.py install       # 执行安装命令

  安装成功后 Django 位于 Python 安装目录的 site-packages 目录下。

 

3.3 Mac 下安装

  下载

  从这里下载最新的稳定版本:DJango-3.x.y.tar.gz,在页面右侧列表下载,如下图:

  记住是最新的官方版本哦。其中 x.y 是版本号。进入你下载该文件的文件夹目录,执行如下命令:(Mac下默认是/Users/xxx/Downloads,xxx是你的用户名)

$ tar zxvf Django-3.x.y.tar.gz

  你也可以从 Github 上下载最新版,地址:https://github.com/django/django:

git clone https://github.com/django/django.git

 

  安装

  进入解压后的目录:

cd Django-3.x.y
sudo python setup.py install

  安装成功后会输出以下信息:

……
Processing dependencies for Django==3.x.y
Finished processing dependencies for Django==3.x.y

  再进入我们的站点目录,创建 Django 项目:

$ django-admin.py startproject testdj

  启动服务:

cd testdj # 切换到我们创建的项目
$ python manage.py runserver
……
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

  以上信息说明,项目已启动,访问地址为http://127.0.0.1:8000/。

 

4.Django 创建第一个项目

  本章我们将介绍Django 管理工具及如何使用 Django 来创建项目,第一个项目我们以 HelloWorld 来命令项目。

  测试版本说明:

    • Python 3.10.X
    • Django 3.2.X

 

4.1 Django 管理工具

  安装 Django 之后,您现在应该已经有了可用的管理工具 django-admin,Windows 如果没有配置环境变量可以用 django-admin。

  我们可以来看下django-admin 的命令介绍:

$ django-admin

Type 'django-admin help <subcommand>' for help on a specific subcommand.

Available subcommands:

[django]
    check
    compilemessages
    createcachetable
    dbshell
    diffsettings
    dumpdata
    flush
    inspectdb
    loaddata
    makemessages
    makemigrations
    migrate
    runserver
    sendtestemail
    shell
    showmigrations
    sqlflush
    sqlmigrate
    sqlsequencereset
    squashmigrations
    startapp
    startproject
    test
    testserver
……省略部分……

 

4.2 创建第一个项目

  使用 django-admin 来创建 HelloWorld 项目:

django-admin startproject HelloWorld

  创建完成后我们可以查看下项目的目录结构:

$ cd HelloWorld/
$ tree
.
|-- HelloWorld
|   |-- __init__.py
|   |-- asgi.py
|   |-- settings.py
|   |-- urls.py
|   `-- wsgi.py
`-- manage.py

  目录说明:

  HelloWorld: 项目的容器。

    • manage.py: 一个实用的命令行工具,可让你以各种方式与该 Django 项目进行交互。
    • HelloWorld/__init__.py: 一个空文件,告诉 Python 该目录是一个 Python 包。
    • HelloWorld/asgi.py: 一个 ASGI 兼容的 Web 服务器的入口,以便运行你的项目。
    • HelloWorld/settings.py: 该 Django 项目的设置/配置。
    • HelloWorld/urls.py: 该 Django 项目的 URL 声明; 一份由 Django 驱动的网站"目录"。
    • HelloWorld/wsgi.py: 一个 WSGI 兼容的 Web 服务器的入口,以便运行你的项目。

  接下来我们进入 HelloWorld 目录输入以下命令,启动服务器:

python3 manage.py runserver 0.0.0.0:8000

  0.0.0.0 让其它电脑可连接到开发服务器,8000 为端口号。如果不说明,那么端口号默认为 8000。

  在浏览器输入你服务器的 ip(这里我们输入本机 IP 地址: 127.0.0.1:8000) 及端口号,如果正常启动,输出结果如下:

 

4.3 视图和 URL 配置

  在先前创建的 HelloWorld 目录下的 HelloWorld 目录新建一个 views.py 文件,并输入代码:

#HelloWorld/HelloWorld/views.py 
from django.http import HttpResponse
 
def hello(request):
    return HttpResponse("Hello world ! ")

  接着,绑定 URL 与视图函数。打开 urls.py 文件,删除原来代码,将以下代码复制粘贴到 urls.py 文件中:

#HelloWorld/HelloWorld/urls.py 
from django.conf.urls import url
 
from . import views
 
urlpatterns = [
    url(r'^$', views.hello),
]

  整个目录结构如下:

$ tree
.
|-- HelloWorld
|   |-- __init__.py
|   |-- __init__.pyc
|   |-- settings.py
|   |-- settings.pyc
|   |-- urls.py              # url 配置
|   |-- urls.pyc
|   |-- views.py              # 添加的视图文件
|   |-- views.pyc             # 编译后的视图文件
|   |-- wsgi.py
|   `-- wsgi.pyc
`-- manage.py

  完成后,启动 Django 开发服务器,并在浏览器访问打开浏览器并访问:

  我们也可以修改以下规则:

#HelloWorld/HelloWorld/urls.py 文件代码
from django.urls import path
 
from . import views
 
urlpatterns = [
    path('hello/', views.hello),
]

  通过浏览器打开 http://127.0.0.1:8000/hello,输出结果如下:

  注意:项目中如果代码有改动,服务器会自动监测代码的改动并自动重新载入,所以如果你已经启动了服务器则不需手动重启。

 

4.4 path() 函数

  Django path() 可以接收四个参数,分别是两个必选参数:route、view 和两个可选参数:kwargs、name。

  语法格式:

path(route, view, kwargs=None, name=None)
  • route: 字符串,表示 URL 规则,与之匹配的 URL 会执行对应的第二个参数 view。
  • view: 用于执行与正则表达式匹配的 URL 请求。
  • kwargs: 视图使用的字典类型的参数。
  • name: 用来反向获取 URL。

  Django2. 0中可以使用 re_path() 方法来兼容 1.x 版本中的 url() 方法,一些正则表达式的规则也可以通过 re_path() 来实现 。

from django.urls import include, re_path

urlpatterns = [
    re_path(r'^index/$', views.index, name='index'),
    re_path(r'^bio/(?P<username>\w+)/$', views.bio, name='bio'),
    re_path(r'^weblog/', include('blog.urls')),
    ...
]

 

5.Django 模板

  在上一章节中我们使用 django.http.HttpResponse() 来输出 "Hello World!"。该方式将数据与视图混合在一起,不符合 Django 的 MVC 思想。

  本章节我们将为大家详细介绍 Django 模板的应用,模板是一个文本,用于分离文档的表现形式和内容。

 

5.1 模板应用实例

  我们接着上一章节的项目将在 HelloWorld 目录底下创建 templates 目录并建立 lizexiong.html文件,整个目录结构如下:

HelloWorld/
|-- HelloWorld
|   |-- __init__.py
|   |-- __init__.pyc
|   |-- settings.py
|   |-- settings.pyc
|   |-- urls.py
|   |-- urls.pyc
|   |-- views.py
|   |-- views.pyc
|   |-- wsgi.py
|   `-- wsgi.pyc
|-- manage.py
`-- templates
    `-- lizexiong.html

  lizexiong.html 文件代码如下:

HelloWorld/templates/lizexiong.html 文件代码:
<h1>{{ hello }}</h1>

  从模板中我们知道变量使用了双括号。

  接下来我们需要向Django说明模板文件的路径,修改HelloWorld/settings.py,修改 TEMPLATES 中的 DIRS 为 [os.path.join(BASE_DIR, 'templates')],如下所示:

#HelloWorld/HelloWorld/settings.py 文件代码
...
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',
            ],
        },
    },
]
...

  我们现在修改 views.py,增加一个新的对象,用于向模板提交数据:

#HelloWorld/HelloWorld/views.py 文件代码
from django.shortcuts import render
 
def lizexiong(request):
    context          = {}
    context['hello'] = 'Hello World!'
    return render(request, 'lizexiong.html', context)

  HelloWorld/HelloWorld/urls.py 文件代码:

#HelloWorld/HelloWorld/urls.py 文件代码
from django.urls import path
 
from . import views
 
urlpatterns = [
    path('lizexiong/', views.lizexiong),
]

  可以看到,我们这里使用 render 来替代之前使用的 HttpResponse。render 还使用了一个字典 context 作为参数。

  context 字典中元素的键值 hello 对应了模板中的变量 {{ hello }}。

  再次访问 http://127.0.0.1:8000/lizexiong,可以看到页面:

  这样我们就完成了使用模板来输出数据,从而实现数据与视图分离。

  接下来我们将具体介绍模板中常用的语法规则。

 

5.2 Django 模板标签

5.2.1 变量

  模板语法:

view:{"HTML变量名" : "views变量名"}
HTML:{{变量名}}
#HelloWorld/HelloWorld/views.py 文件代码
from django.shortcuts import render

def lizexiong(request):
  views_name = "李泽雄"
  return  render(request,"lizexiong.html", {"name":views_name})

  templates 中的 lizexiong.html :

<p>{{ name }}</p>

  再次访问 http://127.0.0.1:8000/lizexiong,可以看到页面:

 

5.2.2 列表

  templates 中的 lizexiong.html中,可以用 . 索引下标取出对应的元素。

#HelloWorld/HelloWorld/views.py 文件代码
from django.shortcuts import render

def lizexiong(request):
    views_list = ["李泽雄1","吴鑫哲2","刘稳3"]
    return render(request, "lizexiong.html", {"views_list": views_list})
#HelloWorld/templates/lizexiong.html 文件代码:
<p>{{ views_list }}</p>   # 取出整个列表
<p>{{ views_list.0 }}</p> # 取出列表的第一个元素

  再次访问 http://127.0.0.1:8000/lizexiong,可以看到页面:

 

5.2.3 字典

  templates 中的 lizexiong.html中,可以用 .键 取出对应的值。

#HelloWorld/HelloWorld/views.py 文件代码:
from django.shortcuts import render

def lizexiong(request):
    views_dict = {"name":"李泽雄"}
    return render(request, "lizexiong.html", {"views_dict": views_dict})
HelloWorld/templates/lizexiong.html 文件代码:
<p>{{ views_dict }}</p>
<p>{{ views_dict.name }}</p>

  再次访问 http://127.0.0.1:8000/lizexiong,可以看到页面:

 

5.2.4 过滤器

  模板语法:

{{ 变量名 | 过滤器:可选参数 }}

  模板过滤器可以在变量被显示前修改它,过滤器使用管道字符,如下所示:

{{ name|lower }}

  {{ name }} 变量被过滤器 lower 处理后,文档大写转换文本为小写。

  过滤管道可以被* 套接* ,既是说,一个过滤器管道的输出又可以作为下一个管道的输入:

{{ my_list|first|upper }}

  以上实例将第一个元素并将其转化为大写。

  有些过滤器有参数。 过滤器的参数跟随冒号之后并且总是以双引号包含。 例如:

{{ bio|truncatewords:"30" }}

  这个将显示变量 bio 的前30个词。

  其他过滤器:

    • addslashes : 添加反斜杠到任何反斜杠、单引号或者双引号前面。
    • date : 按指定的格式字符串参数格式化 date 或者 datetime 对象,实例:
{{ bio|truncatewords:"30" }}
    • length : 返回变量的长度。

 

  default

  default 为变量提供一个默认值。

  如果 views 传的变量的布尔值是 false,则使用指定的默认值。

  以下值为 false:

0  0.0  False  0j  ""  []  ()  set()  {}  None
#HelloWorld/HelloWorld/views.py 文件代码:
from django.shortcuts import render

def lizexiong(request):
    name =0
    return render(request, "lizexiong.html", {"name": name})
#HelloWorld/templates/lizexiong.html 文件代码:
{{ name|default:"李泽雄666" }}

  再次访问 http://127.0.0.1:8000/lizexiong,可以看到页面:

 

  length

  返回对象的长度,适用于字符串和列表。

  字典返回的是键值对的数量,集合返回的是去重后的长度。

#HelloWorld/HelloWorld/views.py 文件代码:
from django.shortcuts import render

def lizexiong(request):
    name ="李泽雄-length"
    return render(request, "lizexiong.html", {"name": name})
HelloWorld/templates/lizexiong.html 文件代码:
{{ name|length}}

  再次访问 http://127.0.0.1:8000/lizexiong,可以看到页面:

 

  filesizeformat

  以更易读的方式显示文件的大小(即'13 KB', '4.1 MB', '102 bytes'等)。

  字典返回的是键值对的数量,集合返回的是去重后的长度。

#HelloWorld/HelloWorld/views.py 文件代码:
from django.shortcuts import render

def lizexiong(request):
    num=1024
    return render(request, "lizexiong.html", {"num": num})
#HelloWorld/templates/lizexiong.html 文件代码:
{{ num|filesizeformat}}

  再次访问 http://127.0.0.1:8000/lizexiong,可以看到页面:

 

  date

  根据给定格式对一个日期变量进行格式化。

  格式 Y-m-d H:i:s返回 年-月-日 小时:分钟:秒 的格式时间。

#HelloWorld/HelloWorld/views.py 文件代码:
from django.shortcuts import render

def lizexiong(request):
    import datetime
    now  =datetime.datetime.now()
    return render(request, "lizexiong.html", {"time": now})
HelloWorld/templates/lizexiong.html 文件代码:
{{ time|date:"Y-m-d" }}

  再次访问 http://127.0.0.1:8000/lizexiong,可以看到页面:

 

  truncatechars

  如果字符串包含的字符总个数多于指定的字符数量,那么会被截断掉后面的部分。

  截断的字符串将以 ... 结尾。

#HelloWorld/HelloWorld/views.py 文件代码:
from django.shortcuts import render

def lizexiong(request):
    views_str = "李泽雄"
    return render(request, "lizexiong.html", {"views_str": views_str})
HelloWorld/templates/lizexiong.html 文件代码:
{{ views_str|truncatechars:2}}

  再次访问 http://127.0.0.1:8000/lizexiong,可以看到页面:

 

  safe

  将字符串标记为安全,不需要转义。

  要保证 views.py 传过来的数据绝对安全,才能用 safe。

  和后端 views.py 的 mark_safe 效果相同。

  Django 会自动对 views.py 传到HTML文件中的标签语法进行转义,令其语义失效。加 safe 过滤器是告诉 Django 该数据是安全的,不必对其进行转义,可以让该数据语义生效。

#HelloWorld/HelloWorld/views.py 文件代码:
from django.shortcuts import render

def lizexiong(request):
    views_str = "<a href='https://www.baidu.com/'>点击跳转</a>"
    return render(request, "lizexiong.html", {"views_str": views_str})
#HelloWorld/templates/lizexiong.html 文件代码:
{{ views_str|safe }}

  再次访问 http://127.0.0.1:8000/lizexiong,可以看到页面:

 

5.2.5 if/else 标签

  基本语法格式如下:

{% if condition %}
     ... display
{% endif %}

  或者:

{% if condition1 %}
   ... display 1
{% elif condition2 %}
   ... display 2
{% else %}
   ... display 3
{% endif %}

  根据条件判断是否输出。if/else 支持嵌套。

  {% if %} 标签接受 and , or 或者 not 关键字来对多个变量做判断 ,或者对变量取反( not ),例如:

{% if athlete_list and coach_list %}
     athletes 和 coaches 变量都是可用的。
{% endif %}
#HelloWorld/HelloWorld/views.py 文件代码:
from django.shortcuts import render

def lizexiong(request):
    views_num = 88
    return render(request, "lizexiong.html", {"num": views_num})
#HelloWorld/templates/lizexiong.html 文件代码:
{%if num > 90 and num <= 100 %}
优秀
{% elif num > 60 and num <= 90 %}
合格
{% else %}
一边玩去~
{% endif %}

  再次访问 http://127.0.0.1:8000/lizexiong,可以看到页面:

 

5.2.6 for 标签

  {% for %} 允许我们在一个序列上迭代。

  与 Python 的 for 语句的情形类似,循环语法是 for X in Y ,Y 是要迭代的序列而 X 是在每一个特定的循环中使用的变量名称。

  每一次循环中,模板系统会渲染在 {% for %} 和 {% endfor %} 之间的所有内容。

  例如,给定一个运动员列表 athlete_list 变量,我们可以使用下面的代码来显示这个列表:

<ul>
{% for athlete in athlete_list %}
    <li>{{ athlete.name }}</li>
{% endfor %}
</ul>
#HelloWorld/HelloWorld/views.py 文件代码:
from django.shortcuts import render

def lizexiong(request):
    views_list = ["李泽雄","李泽雄1","吴鑫哲2","刘稳3",]
    return render(request, "lizexiong.html", {"views_list": views_list})
#HelloWorld/templates/lizexiong.html 文件代码:
{% for i in views_list %}
{{ i }}
{% endfor %}

  再次访问 http://127.0.0.1:8000/lizexiong,可以看到页面:

 

  给标签增加一个 reversed 使得该列表被反向迭代:

{% for athlete in athlete_list reversed %}
...
{% endfor %}
#HelloWorld/templates/lizexiong.html 文件代码:
{% for i in views_list  reversed%}
{{ i }}
{% endfor %}

  再次访问 http://127.0.0.1:8000/lizexiong,可以看到页面:

 

  遍历字典: 可以直接用字典 .items 方法,用变量的解包分别获取键和值。

#HelloWorld/HelloWorld/views.py 文件代码:
from django.shortcuts import render

def lizexiong(request):
    views_dict = {"name":"李泽雄","age":30}
    return render(request, "lizexiong.html", {"views_dict": views_dict})
#HelloWorld/templates/lizexiong.html 文件代码:
{% for i,j in views_dict.items %}
{{ i }}---{{ j }}
{% endfor %}

  再次访问 http://127.0.0.1:8000/lizexiong,可以看到页面:

  在 {% for %} 标签里可以通过 {{forloop}} 变量获取循环序号。

    • forloop.counter: 顺序获取循环序号,从 1 开始计算
    • forloop.counter0: 顺序获取循环序号,从 0 开始计算
    • forloop.revcounter: 倒序获取循环序号,结尾序号为 1
    • forloop.revcounter0: 倒序获取循环序号,结尾序号为 0
    • forloop.first(一般配合if标签使用): 第一条数据返回 True,其他数据返回 False
    • forloop.last(一般配合if标签使用): 最后一条数据返回 True,其他数据返回 False
#HelloWorld/HelloWorld/views.py 文件代码:
from django.shortcuts import render

def lizexiong(request):
     views_list = ["a", "b", "c", "d", "e"]
     return render(request, "lizexiong.html", {"listvar": views_list})
#HelloWorld/templates/lizexiong.html 文件代码:
{% for i in listvar %}
    {{ forloop.counter }}
    {{ forloop.counter0 }}
    {{ forloop.revcounter }}
    {{ forloop.revcounter0 }}
    {{ forloop.first }}
    {{ forloop.last }}
{% endfor %}

  再次访问 http://127.0.0.1:8000/lizexiong,可以看到页面:

  输出结果太多,这里就不演示了

 

  {% empty %}

  可选的 {% empty %} 从句:在循环为空的时候执行(即 in 后面的参数布尔值为 False )。

#HelloWorld/HelloWorld/views.py 文件代码:
from django.shortcuts import render

def lizexiong(request):
  views_list = []
  return render(request, "lizexiong.html", {"listvar": views_list})
#HelloWorld/templates/lizexiong.html 文件代码:
{% for i in listvar %}
    {{ forloop.counter0 }}
{% empty %}
    空空如也~
{% endfor %}

  再次访问 http://127.0.0.1:8000/lizexiong,可以看到页面:

  可以嵌套使用 {% for %} 标签:

{% for athlete in athlete_list %}
    <h1>{{ athlete.name }}</h1>
    <ul>
    {% for sport in athlete.sports_played %}
        <li>{{ sport }}</li>
    {% endfor %}
    </ul>
{% endfor %}

 

5.2.7 ifequal/ifnotequal 标签

  {% ifequal %} 标签比较两个值,当他们相等时,显示在 {% ifequal %} 和 {% endifequal %} 之中所有的值。

  下面的例子比较两个模板变量 user 和 currentuser :

{% ifequal user currentuser %}
    <h1>Welcome!</h1>
{% endifequal %}

  和 {% if %} 类似, {% ifequal %} 支持可选的 {% else%} 标签:8

{% ifequal section 'sitenews' %}
    <h1>Site News</h1>
{% else %}
    <h1>No News Here</h1>
{% endifequal %}

 

5.2.8 注释标签

  Django 注释使用 {# #}。

{# 这是一个注释 #}

 

5.2.9 include 标签

  {% include %} 标签允许在模板中包含其它的模板的内容。

  下面这个例子都包含了 nav.html 模板:

{% include "nav.html" %}

 

5.3 csrf_token

  csrf_token 用于form表单中,作用是跨站请求伪造保护。

  如果不用 {% csrf_token %} 标签,在用 form 表单时,要再次跳转页面会报 403 权限错误。

  用了{% csrf_token %} 标签,在 form 表单提交数据时,才会成功。

  解析:

  首先,向服务器发送请求,获取登录页面,此时中间件 csrf 会自动生成一个隐藏input标签,该标签里的 value 属性的值是一个随机的字符串,用户获取到登录页面的同时也获取到了这个隐藏的input标签。

  然后,等用户需要用到form表单提交数据的时候,会携带这个 input 标签一起提交给中间件 csrf,原因是 form 表单提交数据时,会包括所有的 input 标签,中间件 csrf 接收到数据时,会判断,这个随机字符串是不是第一次它发给用户的那个,如果是,则数据提交成功,如果不是,则返回403权限错误。

 

5.4 自定义标签和过滤器

  1、在应用目录下创建 templatetags 目录(与 templates 目录同级,目录名只能是 templatetags)。

HelloWorld/
|-- HelloWorld
|   |-- __init__.py
|   |-- __init__.pyc
|   |-- settings.py
...
|-- manage.py
`-- templatetags
`-- templates

  2、在 templatetags 目录下创建任意 py 文件,如:my_tags.py

  3、my_tags.py 文件代码如下:

from django import template

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

  修改 settings.py 文件的 TEMPLATES 选项配置,添加 libraries 配置:

...
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [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',
            ],
            "libraries":{                          # 添加这边三行配置
                'my_tags':'templatetags.my_tags'   # 添加这边三行配置        
            }                                      # 添加这边三行配置
        },
    },
]
...

  4、利用装饰器 @register.filter 自定义过滤器。

  注意:装饰器的参数最多只能有 2 个。

@register.filter
def my_filter(v1, v2):
    return v1 * v2

  5、利用装饰器 @register.simple_tag 自定义标签。

@register.simple_tag
def my_tag1(v1, v2, v3):
    return v1 * v2 * v3

  6、在使用自定义标签和过滤器前,要在 html 文件 body 的最上方中导入该 py 文件。

{% load my_tags %}

  7、在 HTML 中使用自定义过滤器。

{{ 11|my_filter:22 }}

  8、在 HTML 中使用自定义标签。

{% my_tag1 11 22 33 %}

  9、语义化标签

  在该 py 文件中导入 mark_safe。

from django.utils.safestring import mark_safe

  定义标签时,用上 mark_safe 方法,令标签语义化,相当于 jQuery 中的 html() 方法。

  和前端HTML文件中的过滤器 safe 效果一样。

@register.simple_tag
def my_html(v1, v2):
    temp_html = "<input type='text' id='%s' class='%s' />" %(v1, v2)
    return mark_safe(temp_html)

  在HTML中使用该自定义标签,在页面中动态创建标签。

{% my_html "zzz" "xxx" %}

 

5.5 配置静态文件

  1、在项目根目录下创建 statics 目录。

  2、在 settings 文件的最下方配置添加以下配置:

STATIC_URL = '/static/' # 别名 
STATICFILES_DIRS = [ 
    os.path.join(BASE_DIR, "statics"), 
]

  3、在 statics 目录下创建 css 目录,js 目录,images 目录,plugins 目录, 分别放 css文件,js文件,图片,插件。

  4、把 bootstrap 框架放入插件目录 plugins。

  5、在 HTML 文件的 head 标签中引入 bootstrap。

  注意:此时引用路径中的要用配置文件中的别名 static,而不是目录 statics。

<link rel="stylesheet" href="/static/plugins/bootstrap-3.3.7/dist/css/bootstrap.css">

  在模板中使用需要加入 {% load static %} 代码,以下实例我们从静态目录中引入图片。

#HelloWorld/HelloWorld/views.py 文件代码:
from django.shortcuts import render

def lizexiong(request):
    name ="李泽雄-statics"
    return render(request, "lizexiong.html", {"name": name})
#HelloWorld/templates/lizexiong.html 文件代码:
{% load static %}
{{name}}<img decoding="async" src="{% static 'images/lizexiong-logo.png' %}" alt="lizexiong-logo">

  再次访问 http://127.0.0.1:8000/lizexiong,可以看到页面:

 

5.6 模板继承

  模板可以用继承的方式来实现复用,减少冗余内容。

  网页的头部和尾部内容一般都是一致的,我们就可以通过模板继承来实现复用。

  父模板用于放置可重复利用的内容,子模板继承父模板的内容,并放置自己的内容。

 

5.6.1 父模板

  标签 block...endblock: 父模板中的预留区域,该区域留给子模板填充差异性的内容,不同预留区域名字不能相同。

{% block 名称 %} 
预留给子模板的区域,可以设置设置默认内容
{% endblock 名称 %}

 

5.6.2 子模板

  子模板使用标签 extends 继承父模板:

{% extends "父模板路径"%}

  子模板如果没有设置父模板预留区域的内容,则使用在父模板设置的默认内容,当然也可以都不设置,就为空。

  子模板设置父模板预留区域的内容:

{ % block 名称 % }
内容 
{% endblock 名称 %}

  接下来我们先创建之前项目的 templates 目录中添加 base.html 文件,代码如下:

#HelloWorld/templates/base.html 文件代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>李泽雄(lizexiong.com)</title>
</head>
<body>
    <h1>Hello World!</h1>
    <p>李泽雄 Django 测试。</p>
    {% block mainbody %}
       <p>original</p>
    {% endblock %}
</body>
</html>

  以上代码中,名为 mainbody 的 block 标签是可以被继承者们替换掉的部分。

  所有的 {% block %} 标签告诉模板引擎,子模板可以重载这些部分。

  lizexiong.html 中继承 base.html,并替换特定 block,lizexiong.html 修改后的代码如下:

#HelloWorld/templates/lizexiong.html 文件代码:
{%extends "base.html" %}
 
{% block mainbody %}
<p>继承了 base.html 文件</p>
{% endblock %}

  第一行代码说明 lizexiong.html 继承了 base.html 文件。可以看到,这里相同名字的 block 标签用以替换 base.html 的相应 block。

  重新访问地址 http://127.0.0.1:8000/lizexiong,输出结果如下:

 

6.Django 模型

  Django 对各种数据库提供了很好的支持,包括:PostgreSQL、MySQL、SQLite、Oracle。

  Django 为这些数据库提供了统一的调用API。 我们可以根据自己业务需求选择不同的数据库。

  MySQL 是 Web 应用中最常用的数据库。本章节我们将以 Mysql 作为实例进行介绍。

  如果你没安装 mysql 驱动,可以执行以下命令安装:

sudo pip3 install pymysql

 

6.1 Django ORM

  Django 模型使用自带的 ORM。

  对象关系映射(Object Relational Mapping,简称 ORM )用于实现面向对象编程语言里不同类型系统的数据之间的转换。

  ORM 在业务逻辑层和数据库层之间充当了桥梁的作用。

  ORM 是通过使用描述对象和数据库之间的映射的元数据,将程序中的对象自动持久化到数据库中。

  使用 ORM 的好处:

    • 提高开发效率。
    • 不同数据库可以平滑切换。

  使用 ORM 的缺点:

    • ORM 代码转换为 SQL 语句时,需要花费一定的时间,执行效率会有所降低。
    • 长期写 ORM 代码,会降低编写 SQL 语句的能力。

  ORM 解析过程:

    • ORM 会将 Python 代码转成为 SQL 语句。
    • SQL 语句通过 pymysql 传送到数据库服务端。
    • 在数据库中执行 SQL 语句并将结果返回。

  ORM 对应关系表:

 

6.2 数据库配置

  Django 如何使用 mysql 数据库

  创建 MySQL 数据库( ORM 无法操作到数据库级别,只能操作到数据表)语法:

create database 数据库名称 default charset=utf8; # 防止编码问题,指定为 utf8

  例如我们创建一个名为 lizexiong 数据库,编码指定为 utf8:

create database lizexiong default charset=utf8; 

  我们在项目的 settings.py 文件中找到 DATABASES 配置项,将其信息修改为:

#HelloWorld/HelloWorld/settings.py: 文件代码:
DATABASES = { 
    'default': 
    { 
        'ENGINE': 'django.db.backends.mysql',    # 数据库引擎
        'NAME': 'lizexiong', # 数据库名称
        'HOST': '127.0.0.1', # 数据库地址,本机 ip 地址 127.0.0.1 
        'PORT': 3306, # 端口 
        'USER': 'root',  # 数据库用户名
        'PASSWORD': '123456', # 数据库密码
    }  
}  

  如果你使用了 Python2.x 版本这里添加了中文注释,所以你需要在 HelloWorld/settings.py 文件头部添加 # -*- coding: UTF-8 -*-。

  上面包含数据库名称和用户的信息,它们与 MySQL 中对应数据库和用户的设置相同。Django 根据这一设置,与 MySQL 中相应的数据库和用户连接起来。

  接下来,告诉 Django 使用 pymysql 模块连接 mysql 数据库:

# 在与 settings.py 同级目录下的 __init__.py 中引入模块和进行配置
import pymysql
pymysql.install_as_MySQLdb()

 

6.3 定义模型

6.3.1 创建 APP

  Django 规定,如果要使用模型,必须要创建一个 app。我们使用以下命令创建一个 TestModel 的 app:

django-admin startapp TestModel

  目录结构如下:

HelloWorld
|-- HelloWorld
|-- manage.py
...
|-- TestModel
|   |-- __init__.py
|   |-- admin.py
|   |-- models.py
|   |-- tests.py
|   `-- views.py

  我们修改 TestModel/models.py 文件,代码如下:

#HelloWorld/TestModel/models.py: 文件代码:
# models.py
from django.db import models
 
class Test(models.Model):
    name = models.CharField(max_length=20)

  以上的类名代表了数据库表名,且继承了models.Model,类里面的字段代表数据表中的字段(name),数据类型则由CharField(相当于varchar)、DateField(相当于datetime), max_length 参数限定长度。

  接下来在 settings.py 中找到INSTALLED_APPS这一项,如下:

INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'TestModel',               # 添加此项
)

  在命令行中运行:

$ python3 manage.py migrate   # 创建表结构

$ python3 manage.py makemigrations TestModel  # 让 Django 知道我们在我们的模型有一些变更
$ python3 manage.py migrate TestModel   # 创建表结构

  看到几行 "Creating table…" 的字样,你的数据表就创建好了。

Creating tables ...
……
Creating table TestModel_test  #我们自定义的表
……

  表名组成结构为:应用名_类名(如:TestModel_test)。

  注意:尽管我们没有在 models 给表设置主键,但是 Django 会自动添加一个 id 作为主键。

 

6.3.2 常见报错信息

  如果执行以上命令时会出现如下报错信息:

  原因是 MySQLclient 目前只支持到 Python3.4,因此如果使用的更高版本的 python,需要修改如下:

  通过报错信息的文件路径找到 ...site-packages\Django-2.0-py3.6.egg\django\db\backends\mysql 这个路径里的 base.py 文件,把这两行代码注释掉(代码在文件开头部分):

if version < (1, 3, 13):
    raise ImproperlyConfigured('mysqlclient 1.3.13 or newer is required; you have %s.' % Database.__version__)

  一般点报错的代码文件路径信息,会自动跳转到报错文件中行数,此时我们在报错的代码行数注释掉。

  如果出现以下报错信息:

  我们点报错的代码文件路径,跳转到报错文件中行数,此时我们在报错的代码行数之前加上:

query = query.encode()

 

6.4 数据库操作

  接下来我们在 HelloWorld 目录中添加 testdb.py 文件(下面介绍),并修改 urls.py:

#HelloWorld/HelloWorld/urls.py: 文件代码:
from django.urls import path
 
from . import views,testdb
 
urlpatterns = [
    path('lizexiong/', views.lizexiong),
    path('testdb/', testdb.testdb),
]

 

6.4.1 添加数据

  添加数据需要先创建对象,然后再执行 save 函数,相当于SQL中的INSERT:

#HelloWorld/HelloWorld/testdb.py: 文件代码:
# -*- coding: utf-8 -*-
 
from django.http import HttpResponse
 
from TestModel.models import Test
 
# 数据库操作
def testdb(request):
    test1 = Test(name='lizexiong')
    test1.save()
    return HttpResponse("<p>数据添加成功!</p>")

  访问 http://127.0.0.1:8000/testdb 就可以看到数据添加成功的提示。

  输出结果如下:

 

6.4.2 获取数据

  Django提供了多种方式来获取数据库的内容,如下代码所示:

#HelloWorld/HelloWorld/testdb.py: 文件代码:
# -*- coding: utf-8 -*-
 
from django.http import HttpResponse
 
from TestModel.models import Test
 
# 数据库操作
def testdb(request):
    # 初始化
    response = ""
    response1 = ""
    
    
    # 通过objects这个模型管理器的all()获得所有数据行,相当于SQL中的SELECT * FROM
    list = Test.objects.all()
        
    # filter相当于SQL中的WHERE,可设置条件过滤结果
    response2 = Test.objects.filter(id=1) 
    
    # 获取单个对象
    response3 = Test.objects.get(id=1) 
    
    # 限制返回的数据 相当于 SQL 中的 OFFSET 0 LIMIT 2;
    Test.objects.order_by('name')[0:2]
    
    #数据排序
    Test.objects.order_by("id")
    
    # 上面的方法可以连锁使用
    Test.objects.filter(name="lizexiong").order_by("id")
    
    # 输出所有数据
    for var in list:
        response1 += var.name + " "
    response = response1
    return HttpResponse("<p style='color:red;'>" + response + "</p>")

 

6.4.3 更新数据

  修改数据可以使用 save() 或 update():

#HelloWorld/HelloWorld/testdb.py: 文件代码:
# -*- coding: utf-8 -*-
 
from django.http import HttpResponse
 
from TestModel.models import Test
 
# 数据库操作
def testdb(request):
    # 修改其中一个id=1的name字段,再save,相当于SQL中的UPDATE
    test1 = Test.objects.get(id=1)
    test1.name = 'Google'
    test1.save()
    
    # 另外一种方式
    #Test.objects.filter(id=1).update(name='Google')
    
    # 修改所有的列
    # Test.objects.all().update(name='Google')
    
    return HttpResponse("<p>修改成功</p>")

查看数据库

 

6.4.4 删除数据

  删除数据库中的对象只需调用该对象的delete()方法即可:

#HelloWorld/HelloWorld/testdb.py: 文件代码:
# -*- coding: utf-8 -*-
 
from django.http import HttpResponse
 
from TestModel.models import Test
 
# 数据库操作
def testdb(request):
    # 删除id=1的数据
    test1 = Test.objects.get(id=1)
    test1.delete()
    
    # 另外一种方式
    # Test.objects.filter(id=1).delete()
    
    # 删除所有数据
    # Test.objects.all().delete()
    
    return HttpResponse("<p>删除成功</p>")

  结果这里就不演示了。

 

6.5 另一个案例

  在用另一个模型更加详细的演示一下有哪些models,一对多,多对多的关系。案例来自w3cshcool

 

6.5.1 快速示例

  此示例模型定义了一个Person,其中包含first_name和 last_name:

from django.db import models

class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)

  first_name并且last_name是场模型。每个字段都指定为类属性,并且每个属性都映射到数据库列。

  上面的Person模型将创建一个数据库表,如下所示:

CREATE TABLE myapp_person (
    "id" serial NOT NULL PRIMARY KEY,
    "first_name" varchar(30) NOT NULL,
    "last_name" varchar(30) NOT NULL
);

  一些技术说明:

  • 表的名称myapp_person是自动从某些模型元数据派生而来的,但是可以被覆盖。
  • 一个id字段被自动添加,但这种行为可以被覆盖。
  • 在此示例中,SQL是使用PostgreSQL语法格式化的,但是值得注意的是Django使用了针对设置文件中指定的数据库后端定制的SQL 。CREATE TABLE

  详情参考官网: https://docs.djangoproject.com/en/3.0/topics/db/models/

 

6.5.2 定义模型

  Django根据属性的类型确定以下信息:

    • 当前选择的数据库支持字段的类型
    • 渲染管理表单时使用的默认html控件
    • 在管理站点最低限度的验证

  django会为表创建自动增长的主键列,每个模型只能有一个主键列,如果使用选项设置某属性为主键列后django不会再创建自动增长的主键列。

  默认创建的主键列属性为id,可以使用pk代替,pk全拼为primary key。

  pk是主键的别名,若主键名为id2,那么pk是id2的别名。

  属性命名限制:

    • 不能是python的保留关键字。
    • 不允许使用连续的下划线,这是由django的查询方式决定的,在第4节会详细讲解查询。
    • 定义属性时需要指定字段类型,通过字段类型的参数指定选项。

       具体语法如下:

       属性=models.字段类型(选项)

 

6.5.3 模型字段类表

 

6.5.4 字段选项

  • 通过字段选项,可以实现对字段的约束
  • 在字段对象时通过关键字参数指定
  • null:如果为True,Django 将空值以NULL 存储到数据库中,默认值是 False
  • blank:如果为True,则该字段允许为空白,默认值是 False
  • 对比:null是数据库范畴的概念,blank是表单验证证范畴的
  • db_column:字段的名称,如果未指定,则使用属性的名称
  • db_index:若值为 True, 则在表中会为此字段创建索引
  • default:默认值
  • primary_key:若为 True, 则该字段会成为模型的主键字段
  • unique:如果为 True, 这个字段在表中必须有唯一值

 

6.5.5 关联关系

6.5.5.1 多对一 (ForeignKey)

  Django提供了定义了几种最常见的数据库关联关系的方法:多对一,多对多,一对一。

  多对一关系,需要两个位置参数,一个是关联的模型,另一个是 on_delete 选项,外键要定义在多的一方,如一个汽车厂生产多种汽车,一辆汽车只有一个生产厂家

from django.db import models
class Manufacturer(models.Model):
    # ...
    pass
class Car(models.Model):
    manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)

 

6.5.5.2 多对多关系的额外字段:throuth

  例如:这样一个应用,它记录音乐家所属的音乐小组。 我们可以用一个ManyToManyField 表示小组和成员之间的多对多关系。 但是,有时你可能想知道更多成员关系的细节,比如成员是何时加入小组的。

  对于这些情况,Django 允许你指定一个中介模型来定义多对多关系。 你可以将其他字段放在中介模型里面。 源模型的ManyToManyField 字段将使用through 参数指向中介模型。 对于上面的音乐小组的例子,代码如下:

from django.db import models

class Person(models.Model):
    name = models.CharField(max_length=128)

    def __str__(self):              # __unicode__ on Python 2
        return self.name

class Group(models.Model):
    name = models.CharField(max_length=128)
    members = models.ManyToManyField(Person, through='Membership')

    def __str__(self):              # __unicode__ on Python 2
        return self.name

class Membership(models.Model):
    person = models.ForeignKey(Person, on_delete=models.CASCADE)
    group = models.ForeignKey(Group, on_delete=models.CASCADE)
    date_joined = models.DateField()
    invite_reason = models.CharField(max_length=64)

  您需要在设置中间模型的时候,显式地为多对多关系中涉及的中间模型指定外键。这种显式声明定义了这两个模型之间是如何关联的。

  在中间模型当中有一些限制条件:

    • 你的模型中间要么有且仅有一个指向源模型(我们例子当中的Group)的外键,你要么必须通过ManyToManyField.through_fields参数在多个外键当中手动选择一个外键,有如果外个多键盘没有用through_fields 参数选择一个的话,会出现验证错误。对于某个目标模型(我们示例当中的Person)的外键也有同样的限制。
    • 在一个有用的描述模型当中自己指向自己的多对多关系的中间模型当中,可以有两个指向同一个模型的外健,但这两个外健分表代表多对多关系(不同)的两端。如果外健的个数超过两个,你必须和上面一样的指定through_fields参数,要不然会出现验证错误。

  现在你已经通过中间模型完成你的ManyToManyField(示例中的Membership),可以开始创建一些多对多关系了。你通过实例化中间模型来创建关系:

>>> paul = Person.objects.create(name="Paul McCartney")
>>> beatles = Group.objects.create(name="The Beatles")
>>> m1 = Membership(person=ringo, group=beatles,
...     date_joined=date(1962, 8, 16),
...     invite_reason="Needed a new drummer.")
>>> m1.save()
>>> beatles.members.all()
<QuerySet [<Person: Ringo Starr>]>
>>> ringo.group_set.all()
<QuerySet [<Group: The Beatles>]>
>>> m2 = Membership.objects.create(person=paul, group=beatles,
...     date_joined=date(1960, 8, 1),
...     invite_reason="Wanted to form a band.")
>>> beatles.members.all()
<QuerySet [<Person: Ringo Starr>, <Person: Paul McCartney>]>

  你也可以使用add()、、create()或set()创建关系,只要你为任何必需的细分指定 through_defaults:

>>> beatles.members.add(john, through_defaults={'date_joined': date(1960, 8, 1)})
>>> beatles.members.create(name="George Harrison", through_defaults={'date_joined': date(1960, 8, 1)})
>>> beatles.members.set([john, paul, ringo, george], through_defaults={'date_joined': date(1960, 8, 1)})

  你可能更潜在直接创造中间模型。

  如果自定义中间模型没有强制对的唯一性,调用方法会删除所有中间模型的实例:(model1, model2)remove()

>>> Membership.objects.create(person=ringo, group=beatles,
...     date_joined=date(1968, 9, 4),
...     invite_reason="You've been gone for a month and we miss you.")
>>> beatles.members.all()
<QuerySet [<Person: Ringo Starr>, <Person: Paul McCartney>, <Person: Ringo Starr>]>
>>> # This deletes both of the intermediate model instances for Ringo Starr
>>> beatles.members.remove(ringo)
>>> beatles.members.all()
<QuerySet [<Person: Paul McCartney>]>

  方法clear()用于实例的所有多对多关系:

>>> # Beatles have broken up
>>> beatles.members.clear()
>>> # Note that this deletes the intermediate model instances
>>> Membership.objects.all()
<QuerySet []>

  一旦你建立了自定义多对多关联关系,就可以执行查询操作。和一般的多对多关联关系一样,你可以使用多对多关联模型的属性来查询:

# Find all the groups with a member whose name starts with 'Paul'
>>> Group.objects.filter(members__name__startswith='Paul')
<QuerySet [<Group: The Beatles>]>

  当你使用中间模型的时候,你也可以查询他的属性:

# Find all the members of the Beatles that joined after 1 Jan 1961
>>> Person.objects.filter(
...     group__name='The Beatles',
...     membership__date_joined__gt=date(1961,1,1))
<QuerySet [<Person: Ringo Starr]>

  如果你想访问一个关系的信息时你可以直接查询Membership模型:

>>> ringos_membership = Membership.objects.get(group=beatles, person=ringo)
>>> ringos_membership.date_joined
datetime.date(1962, 8, 16)
>>> ringos_membership.invite_reason
'Needed a new drummer.'

  另一种访问同样信息的方法是通过Person对象来查询多对多递归关系:

>>> ringos_membership = ringo.membership_set.get(group=beatles)
>>> ringos_membership.date_joined
datetime.date(1962, 8, 16)
>>> ringos_membership.invite_reason
'Needed a new drummer.'

 

6.5.5.3 一对一关联

  使用OneToOneField来定义一对一关系。就像使用其他类型的Field一样:在模型属性中包含它。

  当一个对象以某种方式“继承”另一个对象时,这那个对象的主键非常有用。

  OneToOneField需要一个位置参数:与模型相关的类。

  例如,当你要建立一个有关“位置”信息的数据库时,你可能会包含通常的地址,电话等分支。然后,如果你想接着建立一个关于关于餐厅的数据库,除了将位置数据库当中的一部分复制到Restaurant模型,你也可以将一个指向Place OneToOneField放到Restaurant当中(因为餐厅“是一个”地点);事实上,在处理这样的情况时最好使用模型继承,它隐含的包括了一个一对一关系。

  和 ForeignKey一样,可以创建自关联关系也可以创建与尚未定义的模型的关系。

  OneToOneField初步还接受一个可选的parent_link参数。

  OneToOneField类通常自动的成为模型的主键,这条规则现在不再使用了(而你可以手动指定primary_key参数)。因此,现在可以在其中的模型当中指定多个OneToOneField分段。

 

  详情参考官网:  https://www.khan.pub/django3.0/index.html

 

7.Django 表单

  HTML表单是网站交互性的经典方式。 本章将介绍如何用Django对用户提交的表单数据进行处理。

 

7.1 HTTP 请求

  HTTP协议以"请求-回复"的方式工作。客户发送请求时,可以在请求中附加数据。服务器通过解析请求,就可以获得客户传来的数据,并根据URL来提供特定的服务。

 

7.1.1 GET 方法

  我们在之前的项目中创建一个 search.py 文件,用于接收用户的请求:

#/HelloWorld/HelloWorld/search.py 文件代码:
from django.http import HttpResponse
from django.shortcuts import render
# 表单
def search_form(request):
    return render(request, 'search_form.html')
 
# 接收请求数据
def search(request):  
    request.encoding='utf-8'
    if 'q' in request.GET and request.GET['q']:
        message = '你搜索的内容为: ' + request.GET['q']
    else:
        message = '你提交了空表单'
    return HttpResponse(message)

  在模板目录 templates 中添加 search_form.html 表单:

#/HelloWorld/templates/search_form.html 文件代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>李泽雄</title>
</head>
<body>
    <form action="/search/" method="get">
        <input type="text" name="q">
        <input type="submit" value="搜索">
    </form>
</body>
</html>

  urls.py 规则修改为如下形式:

#/HelloWorld/HelloWorld/urls.py 文件代码:
from django.conf.urls import url
from . import views,testdb,search
 
urlpatterns = [
    url(r'^hello/$', views.lizexiong),
    url(r'^testdb/$', testdb.testdb),
    url(r'^search-form/$', search.search_form),
    url(r'^search/$', search.search),
]

  访问地址 http://127.0.0.1:8000/search-form/ 并搜索,结果如下所示:

  点击前

  提交点击后

 

7.1.2 POST 方法

  上面我们使用了 GET 方法,视图显示和请求处理分成两个函数处理。

  提交数据时更常用 POST 方法。我们下面使用该方法,并用一个URL和处理函数,同时显示视图和处理请求。

  我们在 templates 创建 post.html:

#/HelloWorld/templates/post.html 文件代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>李泽雄</title>
</head>
<body>
    <form action="/search-post/" method="post">
        {% csrf_token %}
        <input type="text" name="q">
        <input type="submit" value="搜索">
    </form>
 
    <p>{{ rlt }}</p>
</body>
</html>

  在模板的末尾,我们增加一个 rlt 记号,为表格处理结果预留位置。

  表格后面还有一个 {% csrf_token %} 的标签。csrf 全称是 Cross Site Request Forgery。这是 Django 提供的防止伪装提交请求的功能。POST 方法提交的表格,必须有此标签。

  在HelloWorld目录下新建 search2.py 文件并使用 search_post 函数来处理 POST 请求:

#/HelloWorld/HelloWorld/search2.py 文件代码:
# -*- coding: utf-8 -*-
 
from django.shortcuts import render
from django.views.decorators import csrf
 
# 接收POST请求数据
def search_post(request):
    ctx ={}
    if request.POST:
        ctx['rlt'] = request.POST['q']
    return render(request, "post.html", ctx)

  urls.py 规则修改为如下形式:

#/HelloWorld/HelloWorld/urls.py 文件代码:
from django.conf.urls import url
from . import views,testdb,search,search2
 
urlpatterns = [
    url(r'^hello/$', views.hello),
    url(r'^testdb/$', testdb.testdb),
    url(r'^search-form/$', search.search_form),
    url(r'^search/$', search.search),
    url(r'^search-post/$', search2.search_post),
]

  访问 http://127.0.0.1:8000/search-post/ 显示结果如下:

  完成以上实例后,我们的目录结构为:

HelloWorld
|-- HelloWorld
|   |-- __init__.py
|   |-- __init__.pyc
|   |-- search.py
|   |-- search.pyc
|   |-- search2.py
|   |-- search2.pyc
|   |-- settings.py
|   |-- settings.pyc
|   |-- testdb.py
|   |-- testdb.pyc
|   |-- urls.py
|   |-- urls.pyc
|   |-- views.py
|   |-- views.pyc
|   |-- wsgi.py
|   `-- wsgi.pyc
|-- TestModel
|   |-- __init__.py
|   |-- __init__.pyc
|   |-- admin.py
|   |-- admin.pyc
|   |-- apps.py
|   |-- migrations
|   |   |-- 0001_initial.py
|   |   |-- 0001_initial.pyc
|   |   |-- __init__.py
|   |   `-- __init__.pyc
|   |-- models.py
|   |-- models.pyc
|   |-- tests.py
|   `-- views.py
|-- db.sqlite3
|-- manage.py
`-- templates
    |-- base.html
    |-- hello.html
    |-- post.html
    `-- search_form.html

 

7.2 Request 对象

  每个视图函数的第一个参数是一个 HttpRequest 对象,就像下面这个 lizexiong() 函数:

from django.http import HttpResponse

def lizexiong(request):
    return HttpResponse("Hello world")

  HttpRequest对象包含当前请求URL的一些信息:

  Request对象也有一些有用的方法:

 

7.3 QueryDict对象

  在HttpRequest对象中, GET和POST属性是django.http.QueryDict类的实例。

  QueryDict类似字典的自定义类,用来处理单键对应多值的情况。

  QueryDict实现所有标准的词典方法。还包括一些特有的方法:

  此外, QueryDict也有一些方法,如下表:

 

8.Django 视图

  视图层

  一个视图函数,简称视图,是一个简单的 Python 函数,它接受 Web 请求并且返回 Web 响应。

  响应可以是一个 HTML 页面、一个 404 错误页面、重定向页面、XML 文档、或者一张图片...

  无论视图本身包含什么逻辑,都要返回响应。代码写在哪里都可以,只要在 Python 目录下面,一般放在项目的 views.py 文件中。

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

  视图层中有两个重要的对象:请求对象(request)与响应对象(HttpResponse)。

 

8.1 请求对象: HttpRequest 对象(简称 request 对象)

  以下介绍几个常用的 request 属性。

  1、GET

  数据类型是 QueryDict,一个类似于字典的对象,包含 HTTP GET 的所有参数。

  有相同的键,就把所有的值放到对应的列表里。

  取值格式:对象.方法。

  get():返回字符串,如果该键对应有多个值,取出该键的最后一个值。

#实例
def lizexiong(request):
    name = request.GET.get("name")
    return HttpResponse('姓名:{}'.format(name))

 

  2、POST

  数据类型是 QueryDict,一个类似于字典的对象,包含 HTTP POST 的所有参数。

  常用于 form 表单,form 表单里的标签 name 属性对应参数的键,value 属性对应参数的值。

  取值格式: 对象.方法。

  get():返回字符串,如果该键对应有多个值,取出该键的最后一个值。

#实例
def lizexiong(request):
    name = request.POST.get("name")
    return HttpResponse('姓名:{}'.format(name))

 

  3、body

  数据类型是二进制字节流,是原生请求体里的参数内容,在 HTTP 中用于 POST,因为 GET 没有请求体。

  在 HTTP 中不常用,而在处理非 HTTP 形式的报文时非常有用,例如:二进制图片、XML、Json 等。

#实例
def lizexiong(request):
    name = request.body
    print(name)
    return HttpResponse("李泽雄")

 

  4、path

  获取 URL 中的路径部分,数据类型是字符串。

#实例
def lizexiong(request):
    name = request.path
    print(name)
    return HttpResponse("李泽雄")

  会获取url路径/lizexiong/

 

  5、method

  获取当前请求的方式,数据类型是字符串,且结果为大写。

#实例
def lizexiong(request):
    name = request.method
    print(name)
    return HttpResponse("李泽雄")

  会打印当前提交方式是POST还是GET

 

8.2 响应对象:HttpResponse 对象

  响应对象主要有三种形式:HttpResponse()、render()、redirect()。

  HttpResponse(): 返回文本,参数为字符串,字符串中写文本内容。如果参数为字符串里含有 html 标签,也可以渲染。

def lizexiong(request):
    # return HttpResponse("李泽雄")
    return HttpResponse("<a href='https://www.lizexiong.com/'>李泽雄</a>")

  render(): 返回文本,第一个参数为 request,第二个参数为字符串(页面名称),第三个参数为字典(可选参数,向页面传递的参数:键为页面参数名,值为views参数名)。

def lizexiong(request):
    name ="李泽雄"
    return render(request,"lizexiong.html",{"name":name})

  redirect():重定向,跳转新页面。参数为字符串,字符串中填写页面路径。一般用于 form 表单提交后,跳转到新页面。

def lizexiong(request):
    return redirect("/index/")

  render 和 redirect 是在 HttpResponse 的基础上进行了封装:

  • render:底层返回的也是 HttpResponse 对象
  • redirect:底层继承的是 HttpResponse 对象

 

9.Django 路由

  路由简单的来说就是根据用户请求的 URL 链接来判断对应的处理程序,并返回处理结果,也就是 URL 与 Django 的视图建立映射关系。

  Django 路由在 urls.py 配置,urls.py 中的每一条配置对应相应的处理方法。

  Django 不同版本 urls.py 配置有点不一样:

  Django1.1.x 版本

  url() 方法:普通路径和正则路径均可使用,需要自己手动添加正则首位限制符号。

#实例
from django.conf.urls import url # 用 url 需要引入

urlpatterns = [
    url(r'^admin/$', admin.site.urls),
    url(r'^index/$', views.index), # 普通路径
    url(r'^articles/([0-9]{4})/$', views.articles), # 正则路径
]

 

  Django 2.2.x 之后的版本

  • path:用于普通路径,不需要自己手动添加正则首位限制符号,底层已经添加。
  • re_path:用于正则路径,需要自己手动添加正则首位限制符号。
#实例
from django.urls import re_path # 用re_path 需要引入
urlpatterns = [
    path('admin/', admin.site.urls),
    path('index/', views.index), # 普通路径
    re_path(r'^articles/([0-9]{4})/$', views.articles), # 正则路径
]

  总结:Django1.1.x 版本中的 url 和 Django 2.2.x 版本中的 re_path 用法相同。

 

9.1 正则路径中的分组

9.1.1 正则路径中的无名分组

  无名分组按位置传参,一一对应。

  views 中除了 request,其他形参的数量要与 urls 中的分组数量一致。

#urls.py
urlpatterns = [
    path('admin/', admin.site.urls),
    re_path("^index/([0-9]{4})/$", views.index),
]
#views.py
from django.shortcuts import HttpResponse

def index(request, year):
    print(year) # 一个形参代表路径中一个分组的内容,按顺序匹配
    return HttpResponse('李泽雄')

 

9.1.2 正则路径中的有名分组

  语法:

(?P<组名>正则表达式)

  有名分组按关键字传参,与位置顺序无关。

  views 中除了 request,其他形参的数量要与 urls 中的分组数量一致, 并且 views 中的形参名称要与 urls 中的组名对应。

#urls.py
urlpatterns = [
    path('admin/', admin.site.urls),
    re_path("^index/(?P[0-9]{4})/(?P[0-9]{2})/$", views.index),
]
#views.py
from django.shortcuts import HttpResponse
def index(request, year, month):
    print(year,month) # 一个形参代表路径中一个分组的内容,按关键字对应匹配
    return HttpResponse('李泽雄')

 

9.1.3 路由分发(include)

  存在问题:Django 项目里多个app目录共用一个 urls 容易造成混淆,后期维护也不方便。

  解决:使用路由分发(include),让每个app目录都单独拥有自己的 urls。

  步骤:

  1、在每个 app 目录里都创建一个 urls.py 文件。

  2、在项目名称目录下的 urls 文件里,统一将路径分发给各个 app 目录。

#实例
from django.contrib import admin
from django.urls import path,include # 从 django.urls 引入 include
urlpatterns = [
    path('admin/', admin.site.urls),
    path("app01/", include("app01.urls")),
    path("app02/", include("app02.urls")),
]

  在各自 app 目录下,写自己的 urls.py 文件,进行路径跳转。

  app01 目录:

from django.urls import path,re_path 
from app01 import views # 从自己的 app 目录引入 views 
urlpatterns = [ 
    re_path(r'^login/(?P<m>[0-9]{2})/$', views.index, ),
]

  app02 目录:

from django.urls import path,re_path
from app02 import views # 从自己的 app 目录引入views 
urlpatterns = [ 
    re_path("^xxx/(?P[0-9]{4})/$", views.xxx), 
]

  在各自 app 目录下的 views.py 文件中写各自的视图函数。

 

9.2 反向解析

  随着功能的增加,路由层的 url 发生变化,就需要去更改对应的视图层和模板层的 url,非常麻烦,不便维护。

  这时我们可以利用反向解析,当路由层 url 发生改变,在视图层和模板层动态反向解析出更改后的 url,免去修改的操作。

  反向解析一般用在模板中的超链接及视图中的重定向。

 

9.2.1 普通路径

  在 urls.py 中给路由起别名,name="路由别名"

path("login1/", views.login, name="login")

  在 views.py 中,从 django.urls 中引入 reverse,利用 reverse("路由别名") 反向解析:

return redirect(reverse("login"))

  在模板 templates 中的 HTML 文件中,利用 {% url "路由别名" %} 反向解析。

<form action="{% url 'login' %}" method="post">

 

9.2.2 正则路径(无名分组)

  在 urls.py 中给路由起别名,name="路由别名"

re_path(r"^login/([0-9]{2})/$", views.login, name="login")

  在 views.py 中,从 django.urls 中引入 reverse,利用 reverse("路由别名",args=(符合正则匹配的参数,)) 反向解析。

return redirect(reverse("login",args=(10,)))

  在模板 templates 中的 HTML 文件中利用 {% url "路由别名" 符合正则匹配的参数 %} 反向解析。

<form action="{% url 'login' 10 %}" method="post">

 

9.2.3 正则路径(有名分组)

  在 urls.py 中给路由起别名,name="路由别名"

re_path(r"^login/(?P<year>[0-9]{4})/$", views.login, name="login")

  在 views.py 中,从 django.urls 中引入 reverse,利用 reverse("路由别名",kwargs={"分组名":符合正则匹配的参数}) 反向解析。

return redirect(reverse("login",kwargs={"year":3333}))

  在模板 templates 中的 HTML 文件中,利用 {% url "路由别名" 分组名=符合正则匹配的参数 %} 反向解析。

<form action="{% url 'login' year=3333 %}" method="post">

 

9.3 命名空间

  命名空间(英语:Namespace)是表示标识符的可见范围。

  一个标识符可在多个命名空间中定义,它在不同命名空间中的含义是互不相干的。

  一个新的命名空间中可定义任何标识符,它们不会与任何重复的标识符发生冲突,因为重复的定义都处于其它命名空间中。

  存在问题:路由别名 name 没有作用域,Django 在反向解析 URL 时,会在项目全局顺序搜索,当查找到第一个路由别名 name 指定 URL 时,立即返回。当在不同的 app 目录下的urls 中定义相同的路由别名 name 时,可能会导致 URL 反向解析错误。

  解决:使用命名空间。

 

9.3.1 普通路径

  定义命名空间(include 里面是一个元组)格式如下:

include(("app名称:urls""app名称"))

  实例:

path("app01/", include(("app01.urls","app01")))
path("app02/", include(("app02.urls","app02")))

  在 app01/urls.py 中起相同的路由别名。

path("login/", views.login, name="login")

  在 views.py 中使用名称空间,语法格式如下:

reverse("app名称:路由别名")

  实例:

return redirect(reverse("app01:login")

  在 templates 模板的 HTML 文件中使用名称空间,语法格式如下:

{% url "app名称:路由别名" %}

  实例:

<form action="{% url 'app01:login' %}" method="post">

 

10.Django Admin 管理工具

  Django 提供了基于 web 的管理工具。

  Django 自动管理工具是 django.contrib 的一部分。你可以在项目的 settings.py 中的 INSTALLED_APPS 看到它:

#/HelloWorld/HelloWorld/settings.py 文件代码:
INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
)

  django.contrib是一套庞大的功能集,它是Django基本代码的组成部分。

 

10.1 激活管理工具

  通常我们在生成项目时会在 urls.py 中自动设置好,我们只需去掉注释即可。

  配置项如下所示:

#/HelloWorld/HelloWorld/urls.py 文件代码:
# urls.py
from django.conf.urls import url
from django.contrib import admin
 
urlpatterns = [
    url(r'^admin/', admin.site.urls),
]

  当这一切都配置好后,Django 管理工具就可以运行了。

 

10.2 使用管理工具

  启动开发服务器,然后在浏览器中访问 http://127.0.0.1:8000/admin/,得到如下界面:

  你可以通过命令 python manage.py createsuperuser 来创建超级用户,如下所示:

C:\Users\zexiong.li\HelloWorld>python manage.py createsuperuser
Username: admin
Email address:
Password:
Password (again):
Superuser created successfully.

  之后输入用户名密码登录,界面如下:

  为了让 admin 界面管理某个数据模型,我们需要先注册该数据模型到 admin。比如,我们之前在 TestModel 中已经创建了模型 Test 。修改 TestModel/admin.py:

#HelloWorld/TestModel/admin.py: 文件代码:
from django.contrib import admin
from TestModel.models import Test
 
# Register your models here.
admin.site.register(Test)

  刷新后可以看到Testmodel数据表:

 

10.3 复杂模型

  管理页面的功能强大,完全有能力处理更加复杂的数据模型。

  先在 TestModel/models.py 中增加一个更复杂的数据模型:

#HelloWorld/TestModel/models.py: 文件代码:
from django.db import models
 
# Create your models here.
class Test(models.Model):
    name = models.CharField(max_length=20)
 
class Contact(models.Model):
    name   = models.CharField(max_length=200)
    age    = models.IntegerField(default=0)
    email  = models.EmailField()
    def __unicode__(self):
        return self.name
 
class Tag(models.Model):
    contact = models.ForeignKey(Contact, on_delete=models.CASCADE,)
    name    = models.CharField(max_length=50)
    def __unicode__(self):
        return self.name

  这里有两个表。Tag 以 Contact 为外部键。一个 Contact 可以对应多个 Tag。

  我们还可以看到许多在之前没有见过的属性类型,比如 IntegerField 用于存储整数。

  在 TestModel/admin.py 注册多个模型并显示:

#HelloWorld/TestModel/admin.py: 文件代码:
from django.contrib import admin
from TestModel.models import Test,Contact,Tag
 
# Register your models here.
admin.site.register([Test, Contact, Tag])

  刷新管理页面,显示结果如下:

  在以上管理工具我们就能进行复杂模型操作。

  注意:

  如果你之前还未创建表结构,可使用以下命令创建:

$ python manage.py makemigrations TestModel  # 让 Django 知道我们在我们的模型有一些变更

$ python manage.py migrate TestModel   # 创建表结构

 

10.4 自定义表单

  我们可以自定义管理页面,来取代默认的页面。比如上面的 "add" 页面。我们想只显示 name 和 email 部分。修改 TestModel/admin.py:

#HelloWorld/TestModel/admin.py: 文件代码:
from django.contrib import admin
from TestModel.models import Test,Contact,Tag
 
# Register your models here.
class ContactAdmin(admin.ModelAdmin):
    fields = ('name', 'email')
 
admin.site.register(Contact, ContactAdmin)
admin.site.register([Test, Tag])

  以上代码定义了一个 ContactAdmin 类,用以说明管理页面的显示格式。

  里面的 fields 属性定义了要显示的字段。

  由于该类对应的是 Contact 数据模型,我们在注册的时候,需要将它们一起注册。显示效果如下:

  我们还可以将输入栏分块,每个栏也可以定义自己的格式。修改 TestModel/admin.py为:

#HelloWorld/TestModel/admin.py: 文件代码:
from django.contrib import admin
from TestModel.models import Test,Contact,Tag
 
# Register your models here.
class ContactAdmin(admin.ModelAdmin):
    fieldsets = (
        ['Main',{
            'fields':('name','email'),
        }],
        ['Advance',{
            'classes': ('collapse',), # CSS
            'fields': ('age',),
        }]
    )
 
admin.site.register(Contact, ContactAdmin)
admin.site.register([Test, Tag])

  上面的栏目分为了 Main 和 Advance 两部分。classes 说明它所在的部分的 CSS 格式。这里让 Advance 部分隐藏:

  Advance 部分旁边有一个 Show 按钮,用于展开,展开后可点击 Hide 将其隐藏,如下图所示:

 

10.5 内联(Inline)显示

  上面的 Contact 是 Tag 的外部键,所以有外部参考的关系。

  而在默认的页面显示中,将两者分离开来,无法体现出两者的从属关系。我们可以使用内联显示,让 Tag 附加在 Contact 的编辑页面上显示。

  修改TestModel/admin.py:

#HelloWorld/TestModel/admin.py: 文件代码:
from django.contrib import admin
from TestModel.models import Test,Contact,Tag
 
# Register your models here.
class TagInline(admin.TabularInline):
    model = Tag
 
class ContactAdmin(admin.ModelAdmin):
    inlines = [TagInline]  # Inline
    fieldsets = (
        ['Main',{
            'fields':('name','email'),
        }],
        ['Advance',{
            'classes': ('collapse',),
            'fields': ('age',),
        }]
 
    )
 
admin.site.register(Contact, ContactAdmin)
admin.site.register([Test])

  显示效果如下:

 

10.6 列表页的显示

  在 Contact 输入数条记录后,Contact 的列表页看起来如下:

  我们也可以自定义该页面的显示,比如在列表中显示更多的栏目,只需要在 ContactAdmin 中增加 list_display 属性:

#HelloWorld/TestModel/admin.py: 文件代码:
from django.contrib import admin
from TestModel.models import Test,Contact,Tag
 
# Register your models here.
class TagInline(admin.TabularInline):
    model = Tag
 
class ContactAdmin(admin.ModelAdmin):
    list_display = ('name','age', 'email') # list
    inlines = [TagInline]  # Inline
    fieldsets = (
        ['Main',{
            'fields':('name','email'),
        }],
        ['Advance',{
            'classes': ('collapse',),
            'fields': ('age',),
        }]
 
    )
 
admin.site.register(Contact, ContactAdmin)
admin.site.register([Test])

  刷新页面显示效果如下:

  搜索功能在管理大量记录时非常有,我们可以使用 search_fields 为该列表页增加搜索栏:

#HelloWorld/TestModel/admin.py: 文件代码:
from django.contrib import admin
from TestModel.models import Test,Contact,Tag
 
# Register your models here.
class TagInline(admin.TabularInline):
    model = Tag
 
class ContactAdmin(admin.ModelAdmin):
    list_display = ('name','age', 'email') # list
    search_fields = ('name',)
    inlines = [TagInline]  # Inline
    fieldsets = (
        ['Main',{
            'fields':('name','email'),
        }],
        ['Advance',{
            'classes': ('collapse',),
            'fields': ('age',),
        }]
 
    )
 
admin.site.register(Contact, ContactAdmin)
admin.site.register([Test])

  看搜索框

  Django Admin 管理工具还有非常多实用的功能,感兴趣的同学可以深入研究下。

 

11.Django ORM - 单表实例

  阅读本章节前你需要先阅读了 Django 模型 进行基础配置及了解常见问题的解决方案。

  接下来我们重新创建一个项目 app01(如果之前已创建过,忽略以下操作):

django-admin startproject app01

  接下来在 settings.py 中找到 INSTALLED_APPS 这一项,如下:

INSTALLED_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app01',               # 添加此项
)

  接下来,告诉 Django 使用 pymysql 模块连接 mysql 数据库:

# 在与 settings.py 同级目录下的 __init__.py 中引入模块和进行配置
import pymysql
pymysql.install_as_MySQLdb()

 

11.1 创建模型

  在项目中的 models.py 中添加以下类:

#app01/models.py
from django.db import models
class Book(models.Model):
    id = models.AutoField(primary_key=True) # id 会自动创建,可以手动写入
    title = models.CharField(max_length=32) # 书籍名称
    price = models.DecimalField(max_digits=5, decimal_places=2) # 书籍价格
    publish = models.CharField(max_length=32) # 出版社名称
    pub_date = models.DateField() # 出版时间

  然后在命令行执行以下命令:

$ python3 manage.py migrate   # 创建表结构

$ python3 manage.py makemigrations app01  # 让 Django 知道我们在我们的模型有一些变更
$ python3 manage.py migrate app01   # 创建表结构

 

11.2 常见报错信息

  如果执行以上命令时会出现如下报错信息:

  原因是 MySQLclient 目前只支持到 Python3.4,因此如果使用的更高版本的 python,需要修改如下:

  通过报错信息的文件路径找到 ...site-packages\Django-2.0-py3.6.egg\django\db\backends\mysql 这个路径里的 base.py 文件,把这两行代码注释掉(代码在文件开头部分):

if version < (1, 3, 13):
    raise ImproperlyConfigured('mysqlclient 1.3.13 or newer is required; you have %s.' % Database.__version__)

  一般点报错的代码文件路径信息,会自动跳转到报错文件中行数,此时我们在报错的代码行数注释掉。

  这时数据库 lizexiong 就会创建一个 app01_book 的表。

  接下来我们在app01 项目里添加 views.py 和 models.py 文件,app01 项目目录结构:

app01
|-- app01
|   |-- __init__.py
|   |-- __pycache__
|   |-- asgi.py
|   |-- migrations
|   |-- models.py
|   |-- settings.py
|   |-- urls.py
|   |-- views.py
|   `-- wsgi.py

 

11.3 数据库添加

  规则配置:

#app01/urls.py: 文件代码:
from django.contrib import admin
from django.urls import path
from . import views
 
urlpatterns = [
    path('add_book/', views.add_book),
]

  方式一:模型类实例化对象

  需从 app 目录引入 models.py 文件:

from app 目录 import models

  并且实例化对象后要执行 对象.save() 才能在数据库中新增成功。

#app01/views.py: 文件代码:
from django.shortcuts import render,HttpResponse
from app01 import models 
def add_book(request):
    book = models.Book(title="李泽雄",price=300,publish="小家电维修",pub_date="2008-8-8") 
    book.save()
    return HttpResponse("<p>数据添加成功!</p>")

  查看数据库

 

  方式二:通过 ORM 提供的 objects 提供的方法 create 来实现(推荐)

#app01/views.py: 文件代码:
from django.shortcuts import render,HttpResponse
from app01 import models 
def add_book(request):
    books = models.Book.objects.create(title="如来神掌",price=200,publish="功夫出版社",pub_date="2010-10-10") 
    print(books, type(books)) # Book object (18) 
    return HttpResponse("<p>数据添加成功!</p>")

  查看数据库

 

11.4 查找

  使用 all() 方法来查询所有内容。

  返回的是 QuerySet 类型数据,类似于 list,里面放的是一个个模型类的对象,可用索引下标取出模型类的对象。

#app01/views.py: 文件代码:
from django.shortcuts import render,HttpResponse
from app01 import models 
def add_book(request):
    books = models.Book.objects.all() 
    print(books,type(books)) # QuerySet类型,类似于list,访问 url 时数据显示在命令行窗口中。
    return HttpResponse("<p>查找成功!</p>")

  查看终端

  也可以让内容显示出来

from django.shortcuts import render,HttpResponse
from app01 import models
def add_book(request):
    books = models.Book.objects.all()
    print(books,type(books)) # QuerySet类型,类似于list,访问 url 时数据显示在命令行窗口中。
    for i in books:
        print (i.title)
    return HttpResponse("<p>查找成功!</p>")

  查看终端

 

  filter() 方法用于查询符合条件的数据。

  返回的是 QuerySet 类型数据,类似于 list,里面放的是满足条件的模型类的对象,可用索引下标取出模型类的对象。

  pk=3 的意思是主键 primary key=3,相当于 id=3。

  因为 id 在 pycharm 里有特殊含义,是看内存地址的内置函数 id(),因此用 pk。

#app01/views.py: 文件代码:
from django.shortcuts import render,HttpResponse
from app01 import models
def add_book(request):
    books = models.Book.objects.filter(pk=5)
    print(books)
    print("//////////////////////////////////////")
    books = models.Book.objects.filter(publish='菜鸟出版社', price=300)
    print(books, type(books))  # QuerySet类型,类似于list。
    return HttpResponse("<p>查找成功!</p>")

  这里就不演示通过浏览器url访问了,直接贴出终端信息

 

  exclude() 方法用于查询不符合条件的数据。

  返回的是 QuerySet 类型数据,类似于 list,里面放的是不满足条件的模型类的对象,可用索引下标取出模型类的对象。

#app01/views.py: 文件代码:
from django.shortcuts import render,HttpResponse
from app01 import models
def add_book(request):
    books = models.Book.objects.exclude(pk=5)
    print(books)
    print("//////////////////////////////////////")
    books = models.Book.objects.exclude(publish='小家电维修', price=300)
    print(books, type(books))  # QuerySet类型,类似于list。
    for i in books:
        print (i.title)
    return HttpResponse("<p>查找成功!</p>")

  这里就不演示通过浏览器url访问了,直接贴出终端信息

 

  get() 方法用于查询符合条件的返回模型类的对象符合条件的对象只能为一个,如果符合筛选条件的对象超过了一个或者没有一个都会抛出错误。

#app01/views.py: 文件代码:
from django.shortcuts import render,HttpResponse
from app01 import models 
def add_book(request):
    books = models.Book.objects.get(pk=2)
    books = models.Book.objects.get(pk=18)  # 报错,没有符合条件的对象
    books = models.Book.objects.get(price=200)  # 报错,符合条件的对象超过一个
    print(books, type(books))  # 模型类的对象
    return HttpResponse("<p>查找成功!</p>")

 

  order_by() 方法用于对查询结果进行排序。

  返回的是 QuerySet类型数据,类似于list,里面放的是排序后的模型类的对象,可用索引下标取出模型类的对象。

  注意:

  • 参数的字段名要加引号。
  • 降序为在字段前面加个负号 -。
#app01/views.py: 文件代码:
from django.shortcuts import render,HttpResponse
from app01 import models
def add_book(request):
    books = models.Book.objects.order_by("price") # 查询所有,按照价格升序排列
    for i in books:
        print (i.title)
    books = models.Book.objects.order_by("-price") # 查询所有,按照价格降序排列
    for i in books:
        print (i.title)
    return HttpResponse("<p>查找成功!</p>")

 

  reverse() 方法用于对查询结果进行反转。

  返回的是 QuerySet类型数据,类似于 list,里面放的是反转后的模型类的对象,可用索引下标取出模型类的对象。

#app01/views.py: 文件代码:
from django.shortcuts import render,HttpResponse
from app01 import models
def add_book(request):
    # 按照价格升序排列:降序再反转
    books = models.Book.objects.order_by("-price").reverse()
    for i in books:
        print (i.title)
    return HttpResponse("<p>查找成功!</p>")

 

  count() 方法用于查询数据的数量返回的数据是整数。

#app01/views.py: 文件代码:
from django.shortcuts import render,HttpResponse
from app01 import models
def add_book(request):
    books = models.Book.objects.count() # 查询所有数据的数量
    print ("all_books",books)
    books = models.Book.objects.filter(price=200).count() # 查询符合条件数据的数量
    print ("book=200",books)
    return HttpResponse("<p>查找成功!</p>")

 

  first() 方法返回第一条数据返回的数据是模型类的对象也可以用索引下标 [0]。

#app01/views.py: 文件代码:
from django.shortcuts import render,HttpResponse
from app01 import models
def add_book(request):
    books = models.Book.objects.first() # 返回所有数据的第一条数据
    print (books.title)
    return HttpResponse("<p>查找成功!</p>")

 

  last() 方法返回最后一条数据返回的数据是模型类的对象不能用索引下标 [-1],ORM 没有逆序索引。

#app01/views.py: 文件代码:
from django.shortcuts import render,HttpResponse
from app01 import models
def add_book(request):
    books = models.Book.objects.last() # 返回所有数据的最后一条数据
    print (books.title)
    return HttpResponse("<p>查找成功!</p>")

 

  exists() 方法用于判断查询的结果 QuerySet 列表里是否有数据。

  返回的数据类型是布尔,有为 true,没有为 false。

  注意:判断的数据类型只能为 QuerySet 类型数据,不能为整型和模型类的对象。

#app01/views.py: 文件代码:
from django.shortcuts import render,HttpResponse
from app01 import models
def add_book(request):
    books = models.Book.objects.exists()
    print ("1:",books)
    # # 报错,判断的数据类型只能为QuerySet类型数据,不能为整型
    # books = models.Book.objects.count().exists()
    # print ("2:",books)
    # # 报错,判断的数据类型只能为QuerySet类型数据,不能为模型类对象
    # books = models.Book.objects.first().exists()
    # print ("3:",books)
    return HttpResponse("<p>查找成功!</p>")

  如果返回正确如下:

  其余的报错连网页都会报错

 

  values() 方法用于查询部分字段的数据。

  返回的是 QuerySet 类型数据,类似于 list,里面不是模型类的对象,而是一个可迭代的字典序列,字典里的键是字段,值是数据。

  注意:

    • 参数的字段名要加引号
    • 想要字段名和数据用 values
#app01/views.py: 文件代码:
from django.shortcuts import render,HttpResponse
from app01 import models
def add_book(request):
    # 查询所有的id字段和price字段的数据
    books = models.Book.objects.values("pk","price")
    print(books[0]["price"],type(books)) # 得到的是第一条记录的price字段的数据
    return HttpResponse("<p>查找成功!</p>")

 

  values_list() 方法用于查询部分字段的数据。

  返回的是 QuerySet 类型数据,类似于 list,里面不是模型类的对象,而是一个个元组,元组里放的是查询字段对应的数据。

  注意:

    • 参数的字段名要加引号
    • 只想要数据用 values_list
#app01/views.py: 文件代码:
from django.shortcuts import render,HttpResponse
from app01 import models
def add_book(request):
    # 查询所有的price字段和publish字段的数据
    books = models.Book.objects.values_list("price","publish")
    print(books)
    print(books[0][0],type(books)) # 得到的是第一条记录的price字段的数据
    return HttpResponse("<p>查找成功!</p>")

 

  distinct() 方法用于对数据进行去重。

  返回的是 QuerySet 类型数据。

  注意:

    • 对模型类的对象去重没有意义,因为每个对象都是一个不一样的存在。
    • distinct() 一般是联合 values 或者 values_list 使用。
#app01/views.py: 文件代码:
from django.shortcuts import render,HttpResponse
from app01 import models
def add_book(request):
    # 查询一共有多少个出版社
    books = models.Book.objects.values_list("publish").distinct() # 对模型类的对象去重没有意义,因为每个对象都是一个不一样的存在。
    books = models.Book.objects.distinct()
    print (books)
    return HttpResponse("<p>查找成功!</p>")

 

  filter() 方法基于双下划线的模糊查询(exclude 同理)。

  注意:filter 中运算符号只能使用等于号 = ,不能使用大于号 > ,小于号 < ,等等其他符号。

  __in 用于读取区间,= 号后面为列表 。

#app01/views.py: 文件代码:
from django.shortcuts import render,HttpResponse
from app01 import models
def add_book(request):
    # 查询价格为200或者300的数据
    books = models.Book.objects.filter(price__in=[200,300])
    print (books)
    return HttpResponse("<p>查找成功!</p>")

 

  __gt 大于号 ,= 号后面为数字。

# 查询价格大于200的数据
books = models.Book.objects.filter(price__gt=200)

  __gte 大于等于,= 号后面为数字。

# 查询价格大于等于200的数据
books = models.Book.objects.filter(price__gte=200)

  __lt 小于,=号后面为数字。

# 查询价格小于300的数据
books=models.Book.objects.filter(price__lt=300)

  __lte 小于等于,= 号后面为数字。

# 查询价格小于等于300的数据
books=models.Book.objects.filter(price__lte=300)

  __range 在 ... 之间,左闭右闭区间,= 号后面为两个元素的列表。

books=models.Book.objects.filter(price__range=[200,300])

  __contains 包含,= 号后面为字符串。

books=models.Book.objects.filter(title__contains="")

  __icontains 不区分大小写的包含,= 号后面为字符串

books=models.Book.objects.filter(title__icontains="python") # 不区分大小写

  __startswith 以指定字符开头,= 号后面为字符串。

books=models.Book.objects.filter(title__startswith="")

  __endswith 以指定字符结尾,= 号后面为字符串。

books=models.Book.objects.filter(title__endswith="")

  __year 是 DateField 数据类型的年份,= 号后面为数字。

books=models.Book.objects.filter(pub_date__year=2008)

  __month 是DateField 数据类型的月份,= 号后面为数字。

books=models.Book.objects.filter(pub_date__month=10)

  __day 是DateField 数据类型的天数,= 号后面为数字。

books=models.Book.objects.filter(pub_date__day=01)

 

11.5 删除

  方式一:使用模型类的 对象.delete()。

  返回值:元组,第一个元素为受影响的行数

books=models.Book.objects.filter(pk=8).first().delete()

 

  方式二:使用 QuerySet 类型数据.delete()(推荐)

  返回值:元组,第一个元素为受影响的行数。

books=models.Book.objects.filter(pk__in=[1,2]).delete()

  

  注意:

  • Django 删除数据时,会模仿 SQL约束 ON DELETE CASCADE 的行为,也就是删除一个对象时也会删除与它相关联的外键对象。
  • delete() 方法是 QuerySet 数据类型的方法,但并不适用于 Manager 本身。也就是想要删除所有数据,不能不写 all。
books=models.Book.objects.delete()  # 报错
books=models.Book.objects.all().delete()   # 删除成功

 

11.6 修改

  方式一:

模型类的对象.属性 = 更改的属性值
模型类的对象.save()

  返回值:编辑的模型类的对象。

books = models.Book.objects.filter(pk=7).first() 
books.price = 400 
books.save()

 

  方式二:QuerySet 类型数据.update(字段名=更改的数据)(推荐)

  返回值:整数,受影响的行数

from django.shortcuts import render,HttpResponse
from app01 import models
def add_book(request):
    books = models.Book.objects.filter(pk__in=[7,8]).update(price=888)
    return HttpResponse(books)

 

12.Django ORM – 多表实例

  表与表之间的关系可分为以下三种:

    • 一对一: 一个人对应一个身份证号码,数据字段设置 unique。
    • 一对多: 一个家庭有多个人,一般通过外键来实现。
    • 多对多: 一个学生有多门课程,一个课程有很多学生,一般通过第三个表来实现关联。

 

12.1 准备

12.1.1 创建模型

class Book(models.Model):
    title = models.CharField(max_length=32)
    price = models.DecimalField(max_digits=5, decimal_places=2)
    pub_date = models.DateField()
    publish = models.ForeignKey("Publish", on_delete=models.CASCADE)
    authors = models.ManyToManyField("Author")


class Publish(models.Model):
    name = models.CharField(max_length=32)
    city = models.CharField(max_length=64)
    email = models.EmailField()


class Author(models.Model):
    name = models.CharField(max_length=32)
    age = models.SmallIntegerField()
    au_detail = models.OneToOneField("AuthorDetail", on_delete=models.CASCADE)


class AuthorDetail(models.Model):
    gender_choices = (
        (0, ""),
        (1, ""),
        (2, "保密"),
    )
    gender = models.SmallIntegerField(choices=gender_choices)
    tel = models.CharField(max_length=32)
    addr = models.CharField(max_length=64)
    birthday = models.DateField()

说明:

1、EmailField 数据类型是邮箱格式,底层继承 CharField,进行了封装,相当于 MySQL 中的 varchar。

2、Django1.1 版本不需要联级删除:on_delete=models.CASCADE,Django2.2 需要。

3、一般不需要设置联级更新.

4、外键在一对多的多中设置:models.ForeignKey("关联类名", on_delete=models.CASCADE)。

5、OneToOneField = ForeignKey(...,unique=True)设置一对一。

6、若有模型类存在外键,创建数据时,要先创建外键关联的模型类的数据,不然创建包含外键的模型类的数据时,外键的关联模型类的数据会找不到。

 

12.1.2 表结构

  书籍表 Book:title 、 price 、 pub_date 、 publish(外键,多对一) 、 authors(多对多)

  出版社表 Publish:name 、 city 、 email

  作者表 Author:name 、 age 、 au_detail(一对一)

  作者详情表 AuthorDetail:gender 、 tel 、 addr 、 birthday

  以下是表格关联说明:

 

12.1.3 插入数据

  我们在 MySQL 中执行以下 SQL 插入操作:

insert into app01_publish(name,city,email) values ("华山出版社", "华山", "hs@163.com"), ("明教出版社", "黑木崖", "mj@163.com")
 
# 先插入 authordetail 表中多数据
insert into app01_authordetail(gender,tel,addr,birthday) values (1,13432335433,"华山","1994-5-23"), (1,13943454554,"黑木崖","1961-8-13"), (0,13878934322,"黑木崖","1996-5-20") 

# 再将数据插入 author,这样 author 才能找到 authordetail 
insert into app01_author(name,age,au_detail_id) values ("令狐冲",25,1), ("任我行",58,2), ("任盈盈",23,3)

 

12.2 ORM - 添加数据

12.2.1 一对多(外键 ForeignKey)

  方式一: 传对象的形式,返回值的数据类型是对象,书籍对象。

  步骤:

  a. 获取出版社对象

  b. 给书籍的出版社属性 pulish 传出版社对象

#app01/views.py 文件代码:
def add_book(request):
    #  获取出版社对象
    pub_obj = models.Publish.objects.filter(pk=1).first()
    #  给书籍的出版社属性publish传出版社对象
    book = models.Book.objects.create(title="李泽雄", price=200, pub_date="2010-10-10", publish=pub_obj)
    print(book, type(book))
    return HttpResponse(book)

 

  方式二: 传对象 id 的形式(由于传过来的数据一般是 id,所以传对象 id 是常用的)。

  一对多中,设置外键属性的类(多的表)中,MySQL 中显示的字段名是:外键属性名_id。

  返回值的数据类型是对象,书籍对象。

  步骤:

    • 获取出版社对象的 id
    • 给书籍的关联出版社字段 pulish_id 传出版社对象的 id
#app01/views.py 文件代码:
def add_book(request):
    #  获取出版社对象
    pub_obj = models.Publish.objects.filter(pk=1).first()
    #  获取出版社对象的id
    pk = pub_obj.pk
    #  给书籍的关联出版社字段 publish_id 传出版社对象的id
    book = models.Book.objects.create(title="冲灵剑法", price=100, pub_date="2004-04-04", publish_id=pk)
    print(book, type(book))
    return HttpResponse(book)

 

12.2.2 多对多(ManyToManyField):在第三张关系表中新增数据

  方式一: 传对象形式,无返回值。

  步骤:

    • 获取作者对象
    • 获取书籍对象
    • 给书籍对象的 authors 属性用 add 方法传作者对象
#app01/views.py 文件代码:
def add_book(request):
    #  获取作者对象
    chong = models.Author.objects.filter(name="令狐冲").first()
    ying = models.Author.objects.filter(name="任盈盈").first()
    #  获取书籍对象
    book = models.Book.objects.filter(title="李泽雄").first()
    #  给书籍对象的 authors 属性用 add 方法传作者对象
    book.authors.add(chong, ying)
    return HttpResponse(book)

 

  方式二: 传对象id形式,无返回值。

  步骤:

    • 获取作者对象的 id
    • 获取书籍对象
    • 给书籍对象的 authors 属性用 add 方法传作者对象的 id
#app01/views.py 文件代码:
def add_book(request):
    #  获取作者对象
    chong = models.Author.objects.filter(name="令狐冲").first()
    #  获取作者对象的id
    pk = chong.pk
    #  获取书籍对象
    book = models.Book.objects.filter(title="冲灵剑法").first()
    #  给书籍对象的 authors 属性用 add 方法传作者对象的id
    book.authors.add(pk)

 

12.2.3 关联管理器(对象调用)

  前提:

    • 多对多(双向均有关联管理器)
    • 一对多(只有多的那个类的对象有关联管理器,即反向才有)

  语法格式:

正向:属性名
反向:小写类名加 _set

  注意:一对多只能反向

  常用方法:

  add()用于多对多,把指定的模型对象添加到关联对象集(关系表)中。

  注意:add() 在一对多(即外键)中,只能传对象( *QuerySet数据类型),不能传 id(*[id表])。

  *[ ] 的使用:

# 方式一:传对象
#这个例子在上面演示中已经关联了,就不演示了
book_obj = models.Book.objects.get(id=1)
author_list = models.Author.objects.filter(id__gt=2)
book_obj.authors.add(*author_list)  # 将 id 大于2的作者对象添加到这本书的作者集合中
# 方式二:传对象 id
book_obj.authors.add(*[1,3]) # 将 id=1 和 id=3 的作者对象添加到这本书的作者集合中
return HttpResponse("ok")

  反向:小写表名_set

ying = models.Author.objects.filter(name="任盈盈").first()
book = models.Book.objects.filter(title="冲灵剑法").first()
ying.book_set.add(book)
return HttpResponse("ok")

 

  create():创建一个新的对象,并同时将它添加到关联对象集之中。

  返回新创建的对象。

pub = models.Publish.objects.filter(name="明教出版社").first()
wo = models.Author.objects.filter(name="任我行").first()
book = wo.book_set.create(title="吸星大法", price=300, pub_date="1999-9-19", publish=pub)
print(book, type(book))
return HttpResponse("ok")

 

  remove()从关联对象集中移除执行的模型对象。

  对于 ForeignKey 对象,这个方法仅在 null=True(可以为空)时存在,无返回值。

author_obj =models.Author.objects.get(id=1)
book_obj = models.Book.objects.get(id=11)
author_obj.book_set.remove(book_obj)
return HttpResponse("ok")

 

  clear()从关联对象集中移除一切对象,删除关联,不会删除对象。

  对于 ForeignKey 对象,这个方法仅在 null=True(可以为空)时存在。

  无返回值。

#  清空独孤九剑关联的所有作者
book = models.Book.objects.filter(title="李泽雄").first()
book.authors.clear()

 

12.3 ORM 查询

  基于对象的跨表查询。

正向:属性名称
反向:小写类名_set

 

12.3.1 一对多

  查询主键为 1 的书籍的出版社所在的城市(正向)。

book = models.Book.objects.filter(pk=1).first()
res = book.publish.city
print(res, type(res))
return HttpResponse("ok")

 

  查询明教出版社出版的书籍名(反向)。

  反向:对象.小写类名_set(pub.book_set) 可以跳转到关联的表(书籍表)。

  pub.book_set.all()取出书籍表的所有书籍对象,在一个 QuerySet 里,遍历取出一个个书籍对象。

pub = models.Publish.objects.filter(name="明教出版社").first()
res = pub.book_set.all()
for i in res:
    print(i.title)
return HttpResponse("ok")

 

12.3.2 一对一

  查询令狐冲的电话(正向)

  正向:对象.属性 (author.au_detail) 可以跳转到关联的表(作者详情表)

author = models.Author.objects.filter(name="令狐冲").first()
res = author.au_detail.tel
print(res, type(res))
return HttpResponse("ok")

 

  查询所有住址在黑木崖的作者的姓名(反向)。

  一对一的反向,用 对象.小写类名 即可,不用加 _set。

  反向:对象.小写类名(addr.author)可以跳转到关联的表(作者表)。

addr = models.AuthorDetail.objects.filter(addr="黑木崖").first()
res = addr.author.name
print(res, type(res))
return HttpResponse("ok")

 

12.3.3 多对多

  李泽雄所有作者的名字以及手机号(正向)。

  正向:对象.属性(book.authors)可以跳转到关联的表(作者表)。

  作者表里没有作者电话,因此再次通过对象.属性(i.au_detail)跳转到关联的表(作者详情表)。

book = models.Book.objects.filter(title="李泽雄").first()
res = book.authors.all()
for i in res:
    print(i.name, i.au_detail.tel)
return HttpResponse("ok")

 

  查询任我行出过的所有书籍的名字(反向)。

author = models.Author.objects.filter(name="任我行").first()
res = author.book_set.all()
for i in res:
    print(i.title)
return HttpResponse("ok")

 

12.4 基于双下划线的跨表查询

  正向:属性名称__跨表的属性名称 反向:小写类名__跨表的属性名称

 

12.4.1 一对多

  查询明教出版社出版过的所有书籍的名字与价格。

res = models.Book.objects.filter(publish__name="明教出版社").values_list("title", "price")

  反向:通过 小写类名__跨表的属性名称(book__titlebook__price 跨表获取数据。

res = models.Publish.objects.filter(name="明教出版社").values_list("book__title","book__price")
return HttpResponse("ok")

 

12.4.2 多对多

  查询任我行出过的所有书籍的名字。

  正向:通过 属性名称__跨表的属性名称(authors__name) 跨表获取数据:

res = models.Book.objects.filter(authors__name="任我行").values_list("title")

  反向:通过 小写类名__跨表的属性名称(book__title) 跨表获取数据:

res = models.Author.objects.filter(name="任我行").values_list("book__title")

 

12.4.3 一对一

  查询任我行的手机号。

  正向:通过 属性名称__跨表的属性名称(au_detail__tel) 跨表获取数据。

res = models.Author.objects.filter(name="任我行").values_list("au_detail__tel")

  反向:通过 小写类名__跨表的属性名称(author__name) 跨表获取数据。

res = models.AuthorDetail.objects.filter(author__name="任我行").values_list("tel")

 

13.Django ORM – 多表实例(聚合与分组查询)

13.1 聚合查询(aggregate)

  聚合查询函数是对一组值执行计算,并返回单个值。

  Django 使用聚合查询前要先从 django.db.models 引入 Avg、Max、Min、Count、Sum(首字母大写)。

from django.db.models import Avg,Max,Min,Count,Sum  #   引入函数

  聚合查询返回值的数据类型是字典。

  聚合函数 aggregate() 是 QuerySet 的一个终止子句, 生成的一个汇总值,相当于 count()。

  使用 aggregate() 后,数据类型就变为字典,不能再使用 QuerySet 数据类型的一些 API 了。

  日期数据类型(DateField)可以用 Max 和 Min。

  返回的字典中:键的名称默认是(属性名称加上__聚合函数名),值是计算出来的聚合值。

  如果要自定义返回字典的键的名称,可以起别名:

aggregate(别名 = 聚合函数名("属性名称"))

  计算所有图书的平均价格:

from django.db.models import Avg,Max,Min,Count,Sum  #   引入函数
...
res = models.Book.objects.aggregate(Avg("price"))
print(res, type(res))
...

  计算所有图书的数量、最贵价格和最便宜价格:

res=models.Book.objects.aggregate(c=Count("id"),max=Max("price"),min=Min("price"))
print(res,type(res))

 

13.2 分组查询(annotate)

  分组查询一般会用到聚合函数,所以使用前要先从 django.db.models 引入 Avg,Max,Min,Count,Sum(首字母大写)。

from django.db.models import Avg,Max,Min,Count,Sum  #   引入函数

  返回值:

    • 分组后,用 values 取值,则返回值是 QuerySet 数据类型里面为一个个字典;
    • 分组后,用 values_list 取值,则返回值是 QuerySet 数据类型里面为一个个元组。

  MySQL 中的 limit 相当于 ORM 中的 QuerySet 数据类型的切片。

  注意

  annotate 里面放聚合函数。

    • values 或者 values_list 放在 annotate 前面:values 或者 values_list 是声明以什么字段分组,annotate 执行分组。
    • values 或者 values_list 放在annotate后面: annotate 表示直接以当前表的pk执行分组,values 或者 values_list 表示查询哪些字段, 并且要将 annotate 里的聚合函数起别名,在 values 或者 values_list 里写其别名。

 

13.2.1 准备数据和创建模型

#models.py
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)

class Emps(models.Model):
    name = models.CharField(max_length=32)
    age = models.IntegerField()
    salary =     models.DecimalField(max_digits=8, decimal_places=2)
    dep = models.ForeignKey("Dep", on_delete=models.CASCADE)
    province = models.CharField(max_length=32)
class Dep(models.Model):
    title = models.CharField(max_length=32)

  数据:

INSERT INTO `app01_emp` (`id`, `name`, `age`, `salary`, `dep`, `province`) VALUES ('1', '令狐冲', '24', '6000.00', '销售部', '河南'); INSERT INTO `app01_emp` (`id`, `name`, `age`, `salary`, `dep`, `province`) VALUES ('2', '任盈盈', '18', '8000.00', '关公部', '广东'); INSERT INTO `app01_emp` (`id`, `name`, `age`, `salary`, `dep`, `province`) VALUES ('3', '任我行', '56', '10000.00', '销售部', '广东'); INSERT INTO `app01_emp` (`id`, `name`, `age`, `salary`, `dep`, `province`) VALUES ('4', '岳灵珊', '19', '6000.00', '关公部', '河南'); INSERT INTO `app01_emp` (`id`, `name`, `age`, `salary`, `dep`, `province`) VALUES ('5', '小龙女', '20', '8000.00', '关公部', '河北'); INSERT INTO `app01_dep` (`id`, `title`) VALUES ('1', '销售部'); 
INSERT INTO `app01_dep` (`id`, `title`) VALUES ('2', '关公部'); 
INSERT INTO `app01_emps` (`id`, `name`, `age`, `salary`, `province`, `dep_id`) VALUES ('2', '令狐冲', '24', '8000.00', '河南', '1'); 
INSERT INTO `app01_emps` (`id`, `name`, `age`, `salary`, `province`, `dep_id`) VALUES ('3', '任盈盈', '18', '9000.00', '广东', '2'); 
INSERT INTO `app01_emps` (`id`, `name`, `age`, `salary`, `province`, `dep_id`) VALUES ('4', '任我行', '57', '10000.00', '广东', '1');
INSERT INTO `app01_emps` (`id`, `name`, `age`, `salary`, `province`, `dep_id`) VALUES ('5', '岳灵珊', '19', '6000.00', '河南', '2');
INSERT INTO `app01_emps` (`id`, `name`, `age`, `salary`, `province`, `dep_id`) VALUES ('6', '小龙女', '20', '8000.00', '河北', '2');

 

  统计每一个出版社的最便宜的书的价格:

res = models.Publish.objects.values("name").annotate(in_price = Min("book__price"))
print(res)

  命令行中可以看到以下输出:

<QuerySet [{'name': '华山出版社', 'in_price': Decimal('100.00')}, {'name': '明教出版社', 'in_price': Decimal('300.00')}]>

 

  统计每一本书的作者个数:

res = models.Book.objects.annotate(c = Count("authors__name")).values("title","c")
print(res)

  命令行中可以看到以下输出:

<QuerySet [{'title': '李泽雄', 'c': 2}, {'title': '冲灵剑法', 'c': 2}, {'title': '吸星大法', 'c': 1}]>

 

  统计每一本以"李"开头的书籍的作者个数:

res = models.Book.objects.filter(title__startswith="").annotate(c = Count("authors__name")).values("title","c")
print(res)

 

  统计不止一个作者的图书名称:

res = models.Book.objects.annotate(c = Count("authors__name")).filter(c__gt=0).values("title","c")
print(res)

 

  根据一本图书作者数量的多少对查询集 QuerySet 进行降序排序:

res = models.Book.objects.annotate(c = Count("authors__name")).order_by("-c").values("title","c")
print(res)

 

  查询各个作者出的书的总价格:

res = models.Author.objects.annotate(all = Sum("book__price")).values("name","all")
print(res)

 

13.2.2 F() 查询

  F() 的实例可以在查询中引用字段,来比较同一个 model 实例中两个不同字段的值。

  之前构造的过滤器都只是将字段值与某个常量做比较,如果想要对两个字段的值做比较,就需要用到 F()。

  使用前要先从 django.db.models 引入 F:

from django.db.models import F

  用法:

F("字段名称")

  F 动态获取对象字段的值,可以进行运算。

  Django 支持 F() 对象之间以及 F() 对象和常数之间的加减乘除和取余的操作。

  修改操作(update)也可以使用 F() 函数。

 

  查询工资大于年龄的人:

from django.db.models import F
...
book=models.Emp.objects.filter(salary__gt=F("age")).values("name","age")
...

  将每一本书的价格提高100元:

res = models.Book.objects.update(price=F("price")+100)
print(res)

  执行前

  执行后

 

13.2.3 Q() 查询

  使用前要先从 django.db.models 引入 Q:

from django.db.models import Q

  用法:

Q(条件判断)

  例如:

Q(title__startswith="")

  之前构造的过滤器里的多个条件的关系都是 and,如果需要执行更复杂的查询(例如 or 语句),就可以使用 Q 。

  Q 对象可以使用 & | ~ (与 或 非)操作符进行组合。

  优先级从高到低:~ & |。

  可以混合使用 Q 对象和关键字参数,Q 对象和关键字参数是用"and"拼在一起的(即将逗号看成 and ),但是 Q 对象必须位于所有关键字参数的前面。

 

  查询价格大于 350 或者名称以菜开头的书籍的名称和价格。

  from django.db.models import Q

...
res=models.Book.objects.filter(Q(price__gt=350)|Q(title__startswith="")).values("title","price")
print(res)
...

 

  查询以"雄"结尾或者不是 2010 年 10 月份的书籍:

res = models.Book.objects.filter(Q(title__endswith="") | ~Q(Q(pub_date__year=2010) & Q(pub_date__month=10)))
print(res) 

 

  查询出版日期是 2014 或者 1999 年,并且书名中包含有"泽"的书籍。

  Q 对象和关键字混合使用,Q 对象要在所有关键字的前面:

res = models.Book.objects.filter(Q(pub_date__year=2014) | Q(pub_date__year=1999), title__contains="")
print(res) 

 

14.Django Form 组件

  Django Form 组件用于对页面进行初始化,生成 HTML 标签,此外还可以对用户提交的数据进行校验(显示错误信息)。

  报错信息显示顺序:

    • 先显示字段属性中的错误信息,然后再显示局部钩子的错误信息。
    • 若显示了字段属性的错误信息,就不会显示局部钩子的错误信息。
    • 若有全局钩子,则全局钩子是等所有的数据都校验完,才开始进行校验,并且全局钩子的错误信息一定会显示。

  使用 Form 组件,需要先导入 forms:

from django import forms

  接下来我们在 app01 目录下创建一个 My_forms.py:

#app01/My_forms.py
from django import forms
from django.core.exceptions import ValidationError
from app01 import models

class EmpForm(forms.Form):
    name = forms.CharField(min_length=4, label="姓名", error_messages={"min_length": "你太短了", "required": "该字段不能为空!"})
    age = forms.IntegerField(label="年龄")
    salary = forms.DecimalField(label="工资")

  字段属性:

    • label:输入框前面的文本信息。
    • error_message:自定义显示的错误信息,属性值是字典, 其中 required 为设置不能为空时显示的错误信息的 key。
#app01/views.py
from django.shortcuts import render, HttpResponse
from app01.My_Forms import EmpForm
from app01 import models
from django.core.exceptions import ValidationError
# Create your views here.


def add_emp(request):
    if request.method == "GET":
        form = EmpForm()
        return render(request, "add_emp.html", {"form": form})
    else:
        form = EmpForm(request.POST)
        if form.is_valid():  # 进行数据校验
            # 校验成功
            data = form.cleaned_data  # 校验成功的值,会放在cleaned_data里。
            data.pop('r_salary')
            print(data)

            models.Emp.objects.create(**data)
            return HttpResponse(
                'ok'
            )
            # return render(request, "add_emp.html", {"form": form})
        else:
            print(form.errors)    # 打印错误信息
            clean_errors = form.errors.get("__all__")
            print(222, clean_errors)
        return render(request, "add_emp.html", {"form": form, "clean_errors": clean_errors})

  app01/urls.py 文件添加以下规则:

path('add_emp/', views.add_emp)

  HTML 模版:

#app01/add_emp.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
 
<h3>添加员工</h3>
 
{#1、自己手动写HTML页面#}
<form action="" method="post">
    <p>姓名:<input type="text" name="name"></p>
    <p>年龄:<input type="text" name="age"></p>
    <p>工资:<input type="text" name="salary"></p>
    <input type="submit">
</form>
 
{#2、通过form对象的as_p方法实现#}
{#<form action="" method="post" novalidate>#}
{#    {% csrf_token %}#}
{#    {{ form.as_p }}#}
{#    <input type="submit">#}
{#</form>#}
 
{#3、手动获取form对象的字段#}
{#<form action="" method="post" novalidate>#}
{#    {% csrf_token %}#}
{#    <div>#}
{#        <label for="id_{{ form.name.name }}">姓名</label>#}
{#        {{ form.name }} <span>{{ form.name.errors.0 }}</span>#}
{#    </div>#}
{#    <div>#}
{#        <label for="id_{{ form.age.name }}">年龄</label>#}
{#        {{ form.age }} <span>{{ form.age.errors.0 }}</span>#}
{#    </div>#}
{#    <div>#}
{#        <label for="id_salary">工资</label>#}
{#        {{ form.salary }} <span>{{ form.salary.errors.0 }}</span>#}
{#    </div>#}
{#    <input type="submit">#}
{#</form>#}
 
 
{#4、用for循环展示所有字段#}
{#<form action="" method="post" novalidate>#}
{#    {% csrf_token %}#}
{#    {% for field in form %}#}
{#        <div>#}
{#            <label for="id_{{ field.name }}">{{ field.label }}</label>#}
{#            {{ field }} <span>{{ field.errors.0 }}</span>#}
{#        </div>#}
{#    {% endfor %}#}
{#    <input type="submit">#}
{#</form>#}
 
</body>
</html>

  这里就不进行演示了,自己根据注释一个一个进行测试

 

14.1 局部钩子和全局钩子

  定义 Form 类:

#app01/My_forms.py
from django import forms
from django.core.exceptions import ValidationError
from app01 import models
class EmpForm(forms.Form):
    name = forms.CharField(min_length=5, label="姓名", error_messages={"required": "该字段不能为空!",
                                                                     "min_length": "用户名太短。"})
    age = forms.IntegerField(label="年龄")
    salary = forms.DecimalField(max_digits=5, decimal_places=2, label="工资")
    r_salary = forms.DecimalField(max_digits=5, decimal_places=2, label="请再输入工资")


    def clean_name(self):  # 局部钩子
        val = self.cleaned_data.get("name")


        if val.isdigit():
            raise ValidationError("用户名不能是纯数字")
        elif models.Emp.objects.filter(name=val):
            raise ValidationError("用户名已存在!")
        else:
            return val

    def clean(self):  # 全局钩子 确认两次输入的工资是否一致。
        val = self.cleaned_data.get("salary")
        r_val = self.cleaned_data.get("r_salary")


        if val == r_val:
            return self.cleaned_data
        else:
            raise ValidationError("请确认工资是否一致。")

  views.py 文件代码:

#app01/views.py
def add_emp(request):
    if request.method == "GET":
        form = EmpForm()  # 初始化form对象
        return render(request, "add_emp.html", {"form":form})
    else:
        form = EmpForm(request.POST)  # 将数据传给form对象
        if form.is_valid():  # 进行校验
            data = form.cleaned_data
            data.pop("r_salary")
            models.Emp.objects.create(**data)
            return redirect("/index/")
        else:  # 校验失败
            clear_errors = form.errors.get("__all__")  # 获取全局钩子错误信息
            return render(request, "add_emp.html", {"form": form, "clear_errors": clear_errors})

  模板文件代码如下:

#app01/add_emp.html
<form action="" method="post" novalidate>
    {% csrf_token %}
    <div>
        <label for="id_{{ form.name.name }}">姓名</label>
        {{ form.name }} <span>{{ form.name.errors.0 }}</span>
    </div>
    <div>
        <label for="id_{{ form.age.name }}">年龄</label>
        {{ form.age }} <span>{{ form.age.errors.0 }}</span>
    </div>
    <div>
        <label for="id_salary">工资</label>
        {{ form.salary }} <span>{{ form.salary.errors.0 }}{{ clear_errors.0 }}</span>
    </div>
    <div>
        <label for="id_r_salary">请再输入工资</label>
        {{ form.r_salary }} <span>{{ form.r_salary.errors.0 }}{{ clear_errors.0 }}</span>
    </div>
    <input type="submit">
</form>

  运行结果如下图所示:

 

15.Django 用户认证(Auth)组件

  Django 用户认证(Auth)组件一般用在用户的登录注册上,用于判断当前的用户是否合法,并跳转到登陆成功或失败页面。

  Django 用户认证(Auth)组件需要导入 auth 模块:

# 认证模块
from django.contrib import auth

# 对应数据库
from django.contrib.auth.models import User

  返回值是用户对象。

  创建用户对象的三种方法:

    • create():创建一个普通用户,密码是明文的。
    • create_user():创建一个普通用户,密码是密文的。
    • create_superuser():创建一个超级用户,密码是密文的,要多传一个邮箱 email 参数。

  参数:                             

    • username: 用户名。
    • password:密码。
    • email:邮箱 (create_superuser 方法要多加一个 email)。
from django.contrib.auth.models import User 
User.objects.create(username='lizexiong',password='123')
from django.contrib.auth.models import User 
User.objects.create_user(username='lizexiong',password='123')
from django.contrib.auth.models import User 
User.objects.create_superuser(username='lizexiong',password='123',email='lizexiong@163.com')

  验证用户的用户名和密码使用 authenticate() 方法,从需要 auth_user 表中过滤出用户对象。

  使用前要导入:

from django.contrib import auth

  参数:

    • username:用户名
    • password:密码

  返回值:如果验证成功,就返回用户对象,反之,返回 None。

def login(request):
    if request.method == "GET":
        return render(request, "login.html")
    username = request.POST.get("username")
    password = request.POST.get("pwd")
    valid_num = request.POST.get("valid_num")
    keep_str = request.session.get("keep_str")
    if keep_str.upper() == valid_num.upper():
        user_obj = auth.authenticate(username=username, password=password)
        print(user_obj.username)

  给验证成功的用户加 session,将 request.user 赋值为用户对象。

  登陆使用 login() 方法。

  使用前要导入:

from django.contrib import auth

  参数:

    • request:用户对象

  返回值:None

def login(request):
    if request.method == "GET":
        return render(request, "login.html")
    username = request.POST.get("username")
    password = request.POST.get("pwd")
    valid_num = request.POST.get("valid_num")
    keep_str = request.session.get("keep_str")
    if keep_str.upper() == valid_num.upper():
        user_obj = auth.authenticate(username=username, password=password)
        print(user_obj.username)
        if not user_obj:
            return redirect("/login/")
        else:

            auth.login(request, user_obj)
            path = request.GET.get("next") or "/index/"
            print(path)
            return redirect(path)
    else:
        return redirect("/login/")

  注销用户使用 logout() 方法,需要清空 session 信息,将 request.user 赋值为匿名用户。

  使用前要导入:

from django.contrib import auth

  参数:

  request:用户对象

    • 返回值:None
def logout(request):
    ppp = auth.logout(request)
    print(ppp) # None
    return redirect("/login/")

  设置装饰器,给需要登录成功后才能访问的页面统一加装饰器。

  使用前要导入:

from django.contrib.auth.decorators import login_required
from django.contrib.auth.decorators import login_required 
@login_required
def index(request):
  return HttpResponse("index页面。。。")

  设置从哪个页面访问,登录成功后就返回哪个页面。

  解析:

  django 在用户访问页面时,如果用户是未登录的状态,就给用户返回登录页面。

  此时,该登录页面的 URL 后面有参数:next=用户访问的页面的 URL。

  因此,设置在用户登录成功后重定向的 URL 为 next 参数的值。

  但是,若用户一开始就输入登录页面 logi,request.GET.get("next") 就取不到值,所以在后面加 or,可以设置自定义返回的页面。

# 如果直接输入 login、get() 就取不到值,path 可以自定义设置返回的页面
path = request.GET.get("next") or "/index/"
return redirect(path)

 

16.Django cookie 与 session

  Cookie 是存储在客户端计算机上的文本文件,并保留了各种跟踪信息。

  识别返回用户包括三个步骤:

    • 服务器脚本向浏览器发送一组 Cookie。例如:姓名、年龄或识别号码等。
    • 浏览器将这些信息存储在本地计算机上,以备将来使用。
    • 当下一次浏览器向 Web 服务器发送任何请求时,浏览器会把这些 Cookie 信息发送到服务器,服务器将使用这些信息来识别用户。

  HTTP 是一种"无状态"协议,这意味着每次客户端检索网页时,客户端打开一个单独的连接到 Web 服务器,服务器会自动不保留之前客户端请求的任何记录。

  但是仍然有以下三种方式来维持 Web 客户端和 Web 服务器之间的 session 会话:

 

16.1 Cookies

  一个 Web 服务器可以分配一个唯一的 session 会话 ID 作为每个 Web 客户端的 cookie,对于客户端的后续请求可以使用接收到的 cookie 来识别。

  在Web开发中,使用 session 来完成会话跟踪,session 底层依赖 Cookie 技术。

 

16.1.1 Django 中 Cookie 的语法

  设置 cookie:

rep.set_cookie(key,value,...)
rep.set_signed_cookie(key,value,salt='加密盐',...)

  获取 cookie:

request.COOKIES.get(key)

  删除 cookie:

rep =HttpResponse || render || redirect
rep.delete_cookie(key)

 

16.1.2 创建应用和模型

#models.py
class UserInfo(models.Model):
    username = models.CharField(max_length=32)
password = models.CharField(max_length=64)
#表创建后,自己手动去插入一条用户名和密码
insert into cookie_userinfo values ('1','lizexiong','123');

  这里别忘了在settings里面把配置加进入重新生成数据库,不然,找不到表会报错。

#urls.py
from django.contrib import admin
from django.urls import path
from cookie import views
urlpatterns = [
    path('admin/', admin.site.urls),
    path('login/', views.login),
    path('index/', views.index),
    path('logout/', views.logout),
path('order/', views.order)
]

#views.py
def login(request):
    if request.method == "GET":
        return render(request, "login.html")
    username = request.POST.get("username")
    password = request.POST.get("pwd")

    user_obj = models.UserInfo.objects.filter(username=username, password=password).first()
    print(user_obj.username)

    if not user_obj:
        return redirect("/login/")
    else:
        rep = redirect("/index/")
        rep.set_cookie("is_login", True)
        return rep
       
def index(request):
    print(request.COOKIES.get('is_login'))
    status = request.COOKIES.get('is_login') # 收到浏览器的再次请求,判断浏览器携带的cookie是不是登录成功的时候响应的 cookie
    if not status:
        return redirect('/login/')
    return render(request, "index.html")


def logout(request):
    rep = redirect('/login/')
    rep.delete_cookie("is_login")
    return rep # 点击注销后执行,删除cookie,不再保存用户状态,并弹到登录页面
   
def order(request):
    print(request.COOKIES.get('is_login'))
    status = request.COOKIES.get('is_login')
    if not status:
        return redirect('/login/')
    return render(request, "order.html")

  以下创建三个模板文件:login.html、index.html、order.html。

#login.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
 
<h3>用户登录</h3>
<form action="" method="post">
    {% csrf_token %}
    <p>用户名: <input type="text" name="username"></p>
    <p>密码: <input type="password" name="pwd"></p>
    <input type="submit">
</form>
 
 
</body>
</html>
#index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
 
 
<h2>index 页面。。。</h2>
 
 
<a href="/logout/">注销</a>
 
</body>
</html>
#order.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
 
 
<h2>order 页面。。。</h2>
 
 
<a href="/logout/">注销</a>
 
</body>
</html>

  运行结果如下图所示:

  只要有了is_login=true,可以在我们这几个网页之间来回跳转,自己可以尝试,如果点击了注销,机会删除is_login这个标志,登录需要验证的网页都会跳转回login

 

16.2 Session(保存在服务端的键值对)

  服务器在运行时可以为每一个用户的浏览器创建一个其独享的 session 对象,由于 session 为用户浏览器独享,所以用户在访问服务器的 web 资源时,可以把各自的数据放在各自的 session 中,当用户再去访问该服务器中的其它 web 资源时,其它 web 资源再从用户各自的 session 中取出数据为用户服务。

 

16.2.1 工作原理

  • 浏览器第一次请求获取登录页面 login。
  • 浏览器输入账号密码第二次请求,若输入正确,服务器响应浏览器一个 index 页面和一个键为 sessionid,值为随机字符串的 cookie,即 set_cookie ("sessionid",随机字符串)。
  • 服务器内部在 django.session 表中记录一条数据。

  django.session 表中有三个字段。

    • session_key:存的是随机字符串,即响应给浏览器的 cookie 的 sessionid 键对应的值。
    • session_data:存的是用户的信息,即多个 request.session["key"]=value,且是密文。
    • expire_date:存的是该条记录的过期时间(默认14天)
  • d. 浏览器第三次请求其他资源时,携带 cookie :{sessionid:随机字符串},服务器从 django.session 表中根据该随机字符串取出该用户的数据,供其使用(即保存状态)。

  注意: django.session 表中保存的是浏览器的信息,而不是每一个用户的信息。 因此, 同一浏览器多个用户请求只保存一条记录(后面覆盖前面),多个浏览器请求才保存多条记录。

  cookie 弥补了 http 无状态的不足,让服务器知道来的人是"谁",但是 cookie 以文本的形式保存在浏览器端,安全性较差,且最大只支持 4096 字节,所以只通过 cookie 识别不同的用户,然后,在对应的 session 里保存私密的信息以及超过 4096 字节的文本。

  session 设置:

request.session["key"] = value

  执行步骤:

    • 生成随机字符串
    • 把随机字符串和设置的键值对保存到 django_session 表的 session_key 和 session_data 里
    • 设置 cookie:set_cookie("sessionid",随机字符串) 响应给浏览器

  session 获取:

request.session.get('key')

  执行步骤:

    • 从 cookie 中获取 sessionid 键的值,即随机字符串。
    • 根据随机字符串从 django_session 表过滤出记录。
    • 取出 session_data 字段的数据。

  session 删除,删除整条记录(包括 session_key、session_data、expire_date 三个字段):

request.session.flush()

  删除 session_data 里的其中一组键值对:

del request.session["key"]

  执行步骤:

    • 从 cookie 中获取 sessionid 键的值,即随机字符串
    • 根据随机字符串从 django_session 表过滤出记录
    • 删除过滤出来的记录

 

16.2.2 实例

  创建路由:

#urls.py
from session import views as session_views

urlpatterns = [
    path('session_login/', session_views.login),
    path('s_index/', session_views.s_index),
    path('s_logout/', session_views.s_logout),
]

  创建视图函数:

#views.py
def login(request):
    if request.method == "GET":
        return render(request, "login.html")
    username = request.POST.get("username")
    password = request.POST.get("pwd")

    user_obj = models.UserInfo.objects.filter(username=username, password=password).first()
    print(user_obj.username)

    if not user_obj:
        return redirect("/session_login/")
    else:
        request.session['is_login'] = True
        request.session['user1'] = username
        return redirect("/s_index/")


def s_index(request):
    status = request.session.get('is_login')
    if not status:
        return redirect('/session_login/')
    return render(request, "s_index.html")


def s_logout(request):
   # del request.session["is_login"] # 删除session_data里的一组键值对
    request.session.flush() # 删除一条记录包括(session_key session_data expire_date)三个字段
    return redirect('/session_login/')

  模板文件:

#s_index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h2>session_index 页面。。。{{ request.session.user1 }}</h2>
<a href="/s_logout/">注销</a>
</body>
</html>

  只有登录成功才有session的信息显示,注销之后就没有了

 

17.Django 中间件

  Django 中间件是修改 Django request 或者 response 对象的钩子,可以理解为是介于 HttpRequest 与 HttpResponse 处理之间的一道处理过程。

  浏览器从请求到响应的过程中,Django 需要通过很多中间件来处理,可以看如下图所示:

  Django 中间件作用:

  • 修改请求,即传送到 view 中的 HttpRequest 对象。
  • 修改响应,即 view 返回的 HttpResponse 对象。

  中间件组件配置在 settings.py 文件的 MIDDLEWARE 选项列表中。

  配置中的每个字符串选项都是一个类,也就是一个中间件。

  Django 默认的中间件配置:

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

 

17.1 自定义中间件

  中间件可以定义四个方法,分别是:

process_request(self,request)
process_view(self, request, view_func, view_args, view_kwargs)
process_exception(self, request, exception)
process_response(self, request, response)

  自定义中间的步骤:

  在 app 目录下新建一个 py 文件,名字自定义,并在该 py 文件中导入 MiddlewareMixin:

from django.utils.deprecation import MiddlewareMixin

  自定义的中间件类,要继承父类 MiddlewareMixin:

class MD1(MiddlewareMixin):
    pass

  在 settings.py 中的 MIDDLEWARE 里注册自定义的中间件类:

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',
   
    'app01.middlewares.MD1',
]

 

17.2 自定义中间件类的方法

  自定义中间件类的方法有:process_request 和 process_response。

 

17.2.1 process_request 方法

  process_request 方法有一个参数 request,这个 request 和视图函数中的 request 是一样的。

  process_request 方法的返回值可以是 None 也可以是 HttpResponse 对象。

    • 返回值是 None 的话,按正常流程继续走,交给下一个中间件处理。
    • 返回值是 HttpResponse 对象,Django 将不执行后续视图函数之前执行的方法以及视图函数,直接以该中间件为起点,倒序执行中间件,且执行的是视图函数之后执行的方法。

  process_request 方法是在视图函数之前执行的。

  当配置多个中间件时,会按照 MIDDLEWARE中 的注册顺序,也就是列表的索引值,顺序执行。

  不同中间件之间传递的 request 参数都是同一个请求对象。

from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import render, HttpResponse
class MD1(MiddlewareMixin):
    def process_request(self, request):
       print("md1  process_request 方法。", id(request)) #在视图之前执行

 

17.2.2 process_response

  process_response 方法有两个参数,一个是 request,一个是 response,request 是请求对象,response 是视图函数返回的 HttpResponse 对象,该方法必须要有返回值,且必须是response。

  process_response 方法是在视图函数之后执行的。

  当配置多个中间件时,会按照 MIDDLEWARE 中的注册顺序,也就是列表的索引值,倒序执行。

class MD1(MiddlewareMixin):
    def process_request(self, request):
        print("md1  process_request 方法。", id(request)) #在视图之前执行


    def process_response(self,request, response): :#基于请求响应
        print("md1  process_response 方法!", id(request)) #在视图之后
        return response

  从下图看,正常的情况下按照绿色的路线进行执行,假设中间件1有返回值,则按照红色的路线走,直接执行该类下的 process_response 方法返回,后面的其他中间件就不会执行。

 

17.2.3 process_view

  process_view 方法格式如下:

process_view(request, view_func, view_args, view_kwargs)

  process_view 方法有四个参数:

    • request 是 HttpRequest 对象。
    • view_func 是 Django 即将使用的视图函数。
    • view_args 是将传递给视图的位置参数的列表。
    • view_kwargs 是将传递给视图的关键字参数的字典。

  view_args 和 view_kwargs 都不包含第一个视图参数(request)。

  process_view 方法是在视图函数之前,process_request 方法之后执行的。

  返回值可以是 None、view_func(request) 或 HttpResponse 对象。

    • 返回值是 None 的话,按正常流程继续走,交给下一个中间件处理。
    • 返回值是 HttpResponse 对象,Django 将不执行后续视图函数之前执行的方法以及视图函数,直接以该中间件为起点,倒序执行中间件,且执行的是视图函数之后执行的方法。
    • c.返回值是 view_func(request),Django 将不执行后续视图函数之前执行的方法,提前执行视图函数,然后再倒序执行视图函数之后执行的方法。
    • 当最后一个中间件的 process_request 到达路由关系映射之后,返回到第一个中间件 process_view,然后依次往下,到达视图函数。
class MD1(MiddlewareMixin):
    def process_request(self, request):
        print("md1  process_request 方法。", id(request)) #在视图之前执行


    def process_response(self,request, response): :#基于请求响应
        print("md1  process_response 方法!", id(request)) #在视图之后
        return response


    def process_view(self,request, view_func, view_args, view_kwargs):
        print("md1  process_view 方法!") #在视图之前执行 顺序执行
        #return view_func(request)

 

17.2.4 process_exception

  process_exception 方法如下:

process_exception(request, exception)

  参数说明:

    • request 是 HttpRequest 对象。
    • exception 是视图函数异常产生的 Exception 对象。

  process_exception 方法只有在视图函数中出现异常了才执行,按照 settings 的注册倒序执行。

  在视图函数之后,在 process_response 方法之前执行。

  process_exception 方法的返回值可以是一个 None 也可以是一个 HttpResponse 对象。

  返回值是 None,页面会报 500 状态码错误,视图函数不会执行。

  process_exception 方法倒序执行,然后再倒序执行 process_response 方法。

  返回值是 HttpResponse 对象,页面不会报错,返回状态码为 200。

  视图函数不执行,该中间件后续的 process_exception 方法也不执行,直接从最后一个中间件的 process_response 方法倒序开始执行。

  若是 process_view 方法返回视图函数,提前执行了视图函数,且视图函数报错,则无论 process_exception 方法的返回值是什么,页面都会报错, 且视图函数和 process_exception 方法都不执行。

  直接从最后一个中间件的 process_response 方法开始倒序执行:

class MD1(MiddlewareMixin):
    def process_request(self, request):
        print("md1  process_request 方法。", id(request)) #在视图之前执行

    def process_response(self,request, response): :#基于请求响应
        print("md1  process_response 方法!", id(request)) #在视图之后
        return response

    def process_view(self,request, view_func, view_args, view_kwargs):
        print("md1  process_view 方法!") #在视图之前执行 顺序执行
        #return view_func(request)


    def process_exception(self, request, exception):#引发错误 才会触发这个方法
        print("md1  process_exception 方法!")
        # return HttpResponse(exception) #返回错误信息

 

18.Django 视图 - FBV 与 CBV

  FBV(function base views) 基于函数的视图,就是在视图里使用函数处理请求。

  CBV(class base views) 基于类的视图,就是在视图里使用类处理请求。

 

18.1 FBV

  基于函数的视图其实我们前面章节一直在使用,就是使用了函数来处理用户的请求,查看以下实例:

  路由配置:

#urls.py 文件
urlpatterns = [
    path("login/", views.login),
]
#views.py 文件
from django.shortcuts import render,HttpResponse

def login(request):
    if request.method == "GET":
        return HttpResponse("GET 方法")
    if request.method == "POST":
        user = request.POST.get("user")
        pwd = request.POST.get("pwd")
        if user == "lizexiong" and pwd == "123456":
            return HttpResponse("POST 方法")
        else:
            return HttpResponse("POST 方法1")

  如果我们在浏览器中直接访问 http://127.0.0.1:8000/login/ ,输出结果为:

GET 方法

 

18.2 CBV

  基于类的视图,就是使用了类来处理用户的请求,不同的请求我们可以在类中使用不同方法来处理,这样大大的提高了代码的可读性。

  定义的类要继承父类 View,所以需要先引入库:

from django.views import View

  执行对应请求的方法前会优先执行 dispatch 方法(在get/post/put...方法前执行),dispatch() 方法会根据请求的不同调用相应的方法来处理。

  其实,在我们前面学到的知识都知道 Django 的 url 是将一个请求分配给可调用的函数的,而不是一个类,那是如何实现基于类的视图的呢? 主要还是通过父类 View 提供的一个静态方法 as_view() ,as_view 方法是基于类的外部接口, 他返回一个视图函数,调用后请求会传递给 dispatch 方法,dispatch 方法再根据不同请求来处理不同的方法。

  路由配置:

#urls.py 文件
urlpatterns = [
    path("login/", views.Login.as_view()),
]
#views.py 文件
from django.shortcuts import render,HttpResponse
from django.views import View

class Login(View):
    def get(self,request):
        return HttpResponse("GET 方法")

    def post(self,request):
        user = request.POST.get("user")
        pwd = request.POST.get("pwd")
        if user == "lizexiong" and pwd == "123456":
            return HttpResponse("POST 方法")
        else:
            return HttpResponse("POST 方法 1")

  如果我们在浏览器中直接访问 http://127.0.0.1:8000/login/ ,输出结果为:

GET 方法

 

19.Django Nginx+uwsgi 安装配置

  在前面的章节中我们使用 python manage.py runserver 来运行服务器。这只适用测试环境中使用。

  正式发布的服务,我们需要一个可以稳定而持续的服务器,比如apache, Nginx, lighttpd等,本文将以 Nginx 为例。

 

19.1 安装基础开发包

  Centos 下安装步骤如下:

yum groupinstall "Development tools"
yum install zlib-devel bzip2-devel pcre-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel

  我们可以再安装Python2.7.5或者其它版本,由于这里是测试,我们就按照2.7来也可以:

cd ~
wget http://python.org/ftp/python/2.7.5/Python-2.7.5.tar.bz2
tar xvf Python-2.7.5.tar.bz2
cd Python-2.7.5
./configure --prefix=/usr/local
make && make altinstall

 

19.1.1 安装Python包管理

  easy_install 包 https://pypi.python.org/pypi/distribute

  安装步骤:

cd ~
wget https://pypi.python.org/packages/source/d/distribute/distribute-0.6.49.tar.gz
tar xf distribute-0.6.49.tar.gz
cd distribute-0.6.49
python2.7 setup.py install
easy_install --version

  pip 包: https://pypi.python.org/pypi/pip

  安装 pip 的好处是可以用 pip list、pip uninstall 管理 Python 包, easy_install 没有这个功能,只有 uninstall。

 

19.1.2 安装 uwsgi

  uwsgi:https://pypi.python.org/pypi/uWSGI

  uwsgi 参数详解:http://uwsgi-docs.readthedocs.org/en/latest/Options.html

pip install uwsgi
uwsgi --version    # 查看 uwsgi 版本

  测试 uwsgi 是否正常:

  新建 test.py 文件,内容如下:

def application(env, start_response):
    start_response('200 OK', [('Content-Type','text/html')])
    return "Hello World"

  然后在终端运行:

uwsgi --http :8001 --wsgi-file test.py

  在浏览器内输入:http://127.0.0.1:8001,查看是否有"Hello World"输出,若没有输出,请检查你的安装过程。

 

19.2 安装 Django

pip install django

  测试 django 是否正常,运行:

django-admin.py startproject demosite
cd demosite
python2.7 manage.py runserver 0.0.0.0:8002

  在浏览器内输入:http://127.0.0.1:8002,检查django是否运行正常。

 

19.3 安装 Nginx

  这个就不演示了,建议用yum,源码太麻烦

 

19.4 uwsgi 配置

  uwsgi支持ini、xml等多种配置方式,本文以 ini 为例, 在/etc/目录下新建uwsgi

  9090.ini,添加如下配置:

[uwsgi]
socket = 127.0.0.1:9090
master = true         //主进程
vhost = true          //多站模式
no-site = true        //多站模式时不设置入口模块和文件
workers = 2           //子进程数
reload-mercy = 10     
vacuum = true         //退出、重启时清理文件
max-requests = 1000   
limit-as = 512
buffer-size = 30000
pidfile = /var/run/uwsgi9090.pid    //pid文件,用于下面的脚本启动、停止该进程
daemonize = /website/uwsgi9090.log

 

19.5 Nginx 配置

  找到nginx的安装目录(如:/usr/local/nginx/),打开conf/nginx.conf文件,修

  改server配置:

server {
        listen       80;
        server_name  localhost;
        
        location / {            
            include  uwsgi_params;
            uwsgi_pass  127.0.0.1:9090;              //必须和uwsgi中的设置一致
            uwsgi_param UWSGI_SCRIPT demosite.wsgi;  //入口文件,即wsgi.py相对于项目根目录的位置,“.”相当于一层目录
            uwsgi_param UWSGI_CHDIR /demosite;       //项目根目录
            index  index.html index.htm;
            client_max_body_size 35m;
        }
    }

  设置完成后,在终端运行:

uwsgi --ini /etc/uwsgi9090.ini &
/usr/local/nginx/sbin/nginx

  在浏览器输入:http://127.0.0.1,你就可以看到 django 的 "It work" 了。