Hello Django【学习Django】

发布于:2024-08-14 ⋅ 阅读:(126) ⋅ 点赞:(0)

Django学习,主要参考参考菜鸟教程上的教程
网站地址:https://www.runoob.com/django/django-intro.html

Django概述

Django是一个开放源代码的Web应用框架,由Python写成。采用了MTV的框架模式,即模型(Model),视图(View)和模版(Template)。

层次 职责
模型(Model),即数据存取层 处理与数据相关的所有事务:如何存取、如何验证有效性、包含哪些行为以及数据之间的关系等。
模板(Template),即表现层 处理与表现相关的决定:如何在页面或其他类型文档中进行显示。
视图(View),即业务逻辑层 存取模型及调取恰当模板的相关逻辑。模型与模板的桥梁。

Django 本身基于 MVC 模型,即 Model(模型)+ View(视图)+ Controller(控制器)设计模式,MVC 模式使后续对程序的修改和扩展简化,并且使程序某一部分的重复利用成为可能。

MVC 优势:

  1. 分离关注点:

    • Model :负责应用程序的数据和业务逻辑。通过将数据和逻辑从用户界面分离出来,使得模型可以独立于用户界面进行测试和修改。
    • View :负责显示用户界面,但通常没有直接访问应用程序的数据。这使得可以更容易地更改应用程序的外观而不影响数据处理。
    • Controller:处理用户输入、更新模型和调整视图。通过将用户输入和应用程序逻辑分离,可以更容易地更改用户界面的交互方式而不影响数据和业务逻辑。
  2. 可维护性:

    分离关注点使得每个组件都可以独立开发、测试和维护。这种分离降低了代码的耦合性,使得在一个组件中的修改不太可能导致对其他组件的影响。

  3. 可扩展性:

    由于每个组件都是相对独立的,因此可以更容易地添加新功能或进行修改,而不会影响应用程序的其他部分。

  4. 可重用性:

    模型、视图和控制器之间的分离允许在不同的上下文中重用这些组件。例如,可以更换视图以改变应用程序的外观,而不影响其余的结构。

  5. 可测试性:

    模型、视图和控制器的分离使得单元测试更加容易。可以分别测试每个组件,确保其功能正确,而无需整个应用程序的上下文。

  6. 团队协作:

    因为MVC模式清晰地定义了各个组件的角色和责任,所以在团队协作中更容易分工协作,不同开发人员可以专注于不同部分的开发。

特点

  • ORM(对象关系映射):Django 提供了一个强大的 ORM,允许开发者通过 Python 代码来定义和操作数据库模型,而无需直接使用 SQL。这使得数据库操作更加抽象和易于管理。
  • MVC 架构 :Django 遵循 MVC(模型-视图-控制器)的软件设计模式,但它使用了稍微不同的术语。在 Django 中,模型(Model)表示数据结构,视图(View)负责呈现用户界面,而控制器(Controller)的职责被称为视图(View)。
  • 模板引擎:Django 使用模板引擎来生成 HTML,这使得前端和后端的代码分离更加容易。Django 的模板语言允许开发者在模板中嵌入动态内容。
  • 自动化 admin 界面:Django 自动生成管理后台,使得管理和操作数据库的过程变得非常简单。开发者可以轻松地创建、修改和删除数据库记录,而无需编写自定义的管理界面。
  • 表单处理:Django 提供了强大的表单处理工具,使得用户输入的验证和处理变得更加简单。这对于开发 Web 表单和处理用户提交的数据非常有用。
  • 安全性:Django 内置了一些安全性功能,例如防止常见的 Web 攻击(如 CSRF 攻击),并提供了方便的用户身份验证和授权系统。
  • 可扩展性:Django 的组件是松耦合的,允许开发者使用现有的组件或编写自己的应用程序来扩展框架功能。
  • 社区支持:Django 拥有庞大的社区支持,提供了大量的文档、教程和第三方包,使得学习和使用 Django 变得更加容易。

项目环境搭建

使用清华镜像源搭建Django环境

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

HelloDjango

安装了Django之后,就可以使用Django的管理工具: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

创建一个名称为hello_django的Django项目,命令如下:

django-admin startproject hello_django

目结构文件含义如下:

  • 外层的hello_django目录:是项目的容器,Django不关心它的名字,可以将它重命名为任何我们喜欢的名字
  • 里面的hello_django目录:它是一个纯python包。为项目的名称,不能随意重命名
  • init.py:一个空文件,表示该目录是一个 Python 包
  • asgi.py:异步网关协议接口,能够处理多种通用的协议类型,包括 HTTP,HTTP2和WebSocket,可以看成ASGI是WSGI的扩展
  • settings.py :Django 项目的配置文件。包含了项目的默认设置,包括数据库信息,调试标志以及其他一些工作的变量
  • urls.py :Django 项目的URL路由声明,负责把URL模式映射到应用程序
  • wsgi.py:Web服务器网关接口(Python Web Server Gateway Interface的缩写),Python应用和Web服务器之间的一种接口,可以看成是一种协议、规范。它是基于Http协议的,不支持WebSoket
  • manage.py :它是Django的一个非常重要的工具,通过它可以调用 django shell 和数据库等,如:创建app应用程序、创建数据库表、清空数据、启动项目等操作

项目启动

python manage.py runserver 0.0.0.0:8000

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

正常启动,输出结果如下:

在 pychram 中也可以通过编辑配置的方式,便捷启动

视图和 URL 配置

在先前创建的 hello_django软件包中新建一个 views.py 文件,并输入代码:

from django.http import HttpResponse
def hello(request):
    return HttpResponse("Hello world !")

接着,绑定 URL 与视图函数。修改 urls.py 文件代码如下:

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

urlpatterns = [
    path("admin/", admin.site.urls),
    path("", views.hello, name="hello"),
]

整个目录结构如下:

$ tree
.
|-- hello_django
|   |-- __init__.py
|   |-- asgi.py
|   |-- settings.py
|   |-- urls.py              # url 配置
|   |-- views.py              # 添加的视图文件
|   |-- wsgi.py
`-- manage.py

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

也可以修改urlpattern,以RestFul风格调整路由地址

urlpatterns = [
    ...,
    path('hello/', views.hello),
]

此时需要访问 http://127.0.0.1:8000/hello

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

path() 函数

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

语法格式:

path(route, view, kwargs=None, name=None)
  • route: 字符串,定义 URL 的路径部分。可以包含变量,例如 <int:my_variable>,以从 URL 中捕获参数并将其传递给视图函数。
  • view: 视图函数,处理与给定路由匹配的请求。可以是一个函数或一个基于类的视图。
  • kwargs(可选): 一个字典,包含传递给视图函数的额外关键字参数。
  • name(可选): 为 URL 路由指定一个唯一的名称,以便在代码的其他地方引用它。这对于在模板中生成 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')),
    ...
]

Django 模板

使用 django.http.HttpResponse() 来输出 “Hello Django!”。该方式将数据与视图混合在一起,不符合 Django 的 MVC 思想。

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

Template应用实例

在 HelloDjango 目录底下创建 templates 目录并建立 index.html文件,整个目录结构如下:

hello_django/
|-- hello_django
|   |-- __init__.py
|   |-- asgi.py
|   |-- settings.py
|   |-- urls.py
|   |-- views.py
|   |-- wsgi.py
|-- manage.py
`-- templates
    `-- index.html

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>{{ replace_text }}</h1>
</body>
</html>

模板中,变量使用了双括号。

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

...
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],       # 修改位置
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]
...

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

from django.shortcuts import render
 
def runoob(request):
    context = {'replace_text': 'Hello Django!'}
    return render(request, 'index.html', context)

里使用 render 来替代之前使用的 HttpResponse。render 还使用了一个字典 context 作为参数。

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

修改 urls.py 的路由地址:

urlpatterns = [
    ...
    path("", views.index, name="index"),
]

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

Django 模板标签

变量

模板语法(具体用法参考上面小节):

view:{"HTML变量名" : "views变量名"}
HTML:{{变量名}}
列表

可以用 . 索引下标取出对应的元素

views.py修改如下:

from django.shortcuts import render

def index(request):
    views_list = ["1 元素", "2 Python", "3 Django"]
    context = {
        'replace_text': 'Hello Django!!',
        'replace_list': views_list,
    }

    return render(request, 'index.html', context)

index.html修改如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>{{ replace_text }}</h1>
<p>{{ replace_list }}</p>
<ul>
    <li>{{ replace_list.0 }}</li>
    <li>{{ replace_list.1 }}</li>
    <li>{{ replace_list.2 }}</li>
</ul>
</body>
</html>
字典

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

过滤器

模板语法:

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

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

{{ name|lower }}  <!-- name 变量被过滤器 lower 处理后,文档大写转换文本为小写 -->

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

{{ my_list|first|upper }} <!--将第一个元素并将其转化为大写-->

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

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

截断的字符串将以 结尾。

{{ bio|truncatewords:"30" }}  <!-- 将显示变量 bio 的前30个词 -->

其他过滤器:

  • addslashes : 添加反斜杠到任何反斜杠、单引号或者双引号前面。

  • date : 按指定的格式字符串参数格式化 date 或者 datetime 对象,实例:

    {{ pub_date|date:"F j, Y" }}
    
  • length : 返回变量的长度。

default

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

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

以下值为 false:

0  0.0  False  0j  ""  []  ()  set()  {}  None

length

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

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

index.html加入代码:

<p>{{ replace_list | length }}</p>

filesizeformat

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

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

date

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

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

views.py修改如下:

def index(request):
    import datetime
    now = datetime.datetime.now()

    views_list = ["1 元素", "2 Python", "3 Django"]
    context = {
        'replace_text': 'Hello Django!!',
        'replace_list': views_list,
        "time": now,
    }

    return render(request, 'index.html', context)

index.html加入代码:

<p>Time: {{ time }} </p>
<p> Date: {{ time | date:"Y-m-d" }} </p>

safe

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

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

和后端 views.pymark_safe 效果相同。

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

使用示例(views.py添加代码):

safe_str = "<a href='https://www.runoob.com/'>点击跳转-菜鸟教程</a>"

context = {
        ...
        "safe_str": safe_str,
    }

index.html加入代码:

{{ safe_str }}<br>
{{ safe_str | safe }}

if/else 标签

基本语法格式如下:

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

<!-- or -->
{% 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 %}

用法示例(views.py 添加代码):

if_else_num = 88

context = {
        ...
        "if_else_num": if_else_num,
    }

index.html加入代码:

<p>if-else (Whether pass or not?) </p>
{% if if_else_num > 90 and if_else_num <= 100 %}
<h3>Excellent!</h3>
{% elif if_else_num > 60 and if_else_num <= 90 %}
<h3>Good!</h3>
{% else %}
<h3>SO BAD!</h3>
{% endif %}
for 标签

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

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

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

例如,对于上文的 replace_list 变量,可以使用下面的代码来显示这个列表:

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

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

    {% for i,j in views_dict.items %}
    {{ i }}---{{ j }}
    {% endfor %}
    
<ul>
{% for li_txt in replace_list [reversed] %}
    <li>{{ li_txt }}</li>
    <p>forloop.counter: {{ forloop.counter }}</p>
    <p>forloop.counter0: {{ forloop.counter0 }}</p>
    <p>forloop.revcounter: {{ forloop.revcounter }}</p>
    <p>forloop.revcounter0: {{ forloop.revcounter0 }}</p>
    <p>forloop.first: {{ forloop.first }}</p>
    <p>forloop.last: {{ forloop.last }}</p>
    <hr>
{% endfor %}
</ul>

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

  • forloop.counter:顺序获取循环序号,从 1 开始计算
  • forloop.counter0:顺序获取循环序号,从 0 开始计算
  • forloop.revcounter:倒序获取循环序号,结尾序号为 1
  • forloop.revcounter0:倒序获取循环序号,结尾序号为 0
  • forloop.first(一般配合if标签使用):第一条数据返回 True,其他数据返回 False
  • forloop.last(一般配合if标签使用):最后一条数据返回 True,其他数据返回 False

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

{% for i in listvar %}
    {{ forloop.counter0 }}
{% empty %}
    空空如也~
{% endfor %}
ifequal/ifnotequal 标签

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

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

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

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

{% ifequal section 'sitenews' %}
    <h1>Site News</h1>
{% else %}
    <h1>No News Here</h1>
{% endifequal %}
注释和include标签

Django 注释使用 {# #}

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

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

{% include "nav.html" %}
csrf_token

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

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

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

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

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

自定义标签和过滤器

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

  2. templatetags 目录下创建文件,如:my_tags.py

    from django import template
    
    register = template.Library()   # register的名字是固定的,不可改变
    
  3. 修改 settings.py 文件的 TEMPLATES 选项配置,添加 libraries 配置:

    TEMPLATES = [
        {
            "BACKEND": "django.template.backends.django.DjangoTemplates",
            "DIRS": [os.path.join(BASE_DIR, "templates")],  # 添加模板根目录
            "APP_DIRS": True,
            "OPTIONS": {
                ...
                "libraries":{  # ned added
                    "my_tags": "templatetags.my_tags",
                }
            },
        },
    ]
    
  4. my_tags.py中自定义过滤器和标签。

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

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

      @register.filter
      def my_filter(v1, v2):
          return v1 + v2
      
    • 利用装饰器 @register.simple_tag 自定义标签。

      @register.simple_tag
      def my_simple_tag(v1, v2, v3):
          return v1 + v2 + v3
      
  5. 在使用自定义标签和过滤器前,要在 html 文件 <body> 的最上方中导入该 py 文件。

    <body>
    {% load my_tags %}
       ...
    </body>
    
  6. 在 HTML 中使用自定义过滤器和自定义标签。

    <p> Custom filter : </p>
    {{ 11 | my_filter:22 }}
    
    <p> Custom tag : </p>
    {% my_simple_tag 11 22 33 %}
    

  7. 语义化标签(在my_tags.py 文件中导入 mark_safe

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

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

    from django.utils.safestring import mark_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" %}
    

配置静态文件

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

  2. settings.py 文件的最下方配置添加以下配置:

    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-5.3.3-dist/css/bootstrap.css">
    

    在模板中使用需要加入 {% load static %} 代码,创建新网页文件show_img.html,从静态目录中引入图片。

    <!DOCTYPE html>
    <html lang="en">
    {% load static %}
    <link rel="stylesheet" href="/static/plugins/bootstrap-5.3.3-dist/css/bootstrap.css">
    <head>
        <meta charset="UTF-8">
        <title>show IMG</title>
    </head>
    <body>
    {{name}}<img src="{% static 'images/profile.jpg' %}" alt=profile-img">
    </body>
    </html>
    

    views.py添加:

    def show_img(request):
        name = "profile IMG"
        return render(request, 'show_img.html', {"name": name})
    

    urls.py添加:

    urlpatterns = [
        ...
        path("show_img", views.show_img, name="show_img"),
    ]
    
    

模板继承

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

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

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

  • 父模板

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

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

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

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

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

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

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

示例(对上面显示图片的 show_img.html进行重写):

  1. templates 目录中添加 base.html 文件,代码如下:

    <!DOCTYPE html>
    <html lang="en">
    <link rel="stylesheet" href="/static/plugins/bootstrap-5.3.3-dist/css/bootstrap.css">
    <head>
        <meta charset="UTF-8">
        <title>show IMG</title>
    </head>
    <body>
    <h1>{{name}}</h1>
        {% block mainbody %}
        <p>  original text </p>
        {% endblock %}
    </body>
    </html>
    

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

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

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

    不需要把 “base.html” 写进views.pyurls.py文件中

    {%extends "base.html" %}  <!-- 必须放在第一行 -->
    
    {% load static %}
    
    {% block mainbody %}
    <p>extended FILE 'base.html'</p>
    <img src="{% static 'images/profile.jpg' %}" alt=profile-img">
    {% endblock %}
    

Django 模型

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

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

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

安装 mysql 驱动:pip3 install pymysql

Django ORM

Django 模型使用自带的 ORM(对象关系映射,Object Relational Mapping)。

  • 用于实现面向对象编程语言里不同类型系统的数据之间的转换。
  • ORM 在业务逻辑层和数据库层之间充当了桥梁的作用。
  • ORM 是通过使用描述对象和数据库之间的映射的元数据,将程序中的对象自动持久化到数据库中。

使用 ORM 的好处:

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

使用 ORM 的缺点:

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

ORM 解析过程:

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

ORM(Models类)—— 数据库(数据表)

对象实例 —— 一条记录

属性 —— 字段

数据库配置

  1. 创建 MySQL 数据库( ORM 无法操作到数据库级别,只能操作到数据表)语法:create database 数据库名称 default charset=utf8; # 防止编码问题,指定为 utf8

    也可以在Navicat 等GUI工具中手动创建。

    例如:创建一个名为 hello_django 数据库:

  2. 在项目的 settings.py 文件中找到 DATABASES 配置项,修改为:

    DATABASES = { 
        'default': 
        { 
            'ENGINE': 'django.db.backends.mysql',
            'NAME': 'hello_django', # 数据库名称
            'HOST': '127.0.0.1',
            'PORT': 3306,
            'USER': 'root',
            'PASSWORD': '******',
        }  
    }
    

    Python2.x 版本这里添加了中文注释,需要在 settings.py 文件头部添加 # -\*- coding: UTF-8 -\*-

  3. 在与 settings.py 同级目录下的 __init__.py 中引入模块和进行配置,使用 pymysql 模块连接 mysql 数据库:

    注意:pymysql 版本需要大于等于 1.4.6 pip install --upgrade pymysql -i https://pypi.tuna.tsinghua.edu.cn/simple/

    import pymysql
    pymysql.install_as_MySQLdb()
    

定义模型

  1. 创建 APP

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

    django-admin startapp TestModel

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

    from django.db import models
     
    class Test(models.Model):
        name = models.CharField(max_length=20)
        date = models.DateField()
    

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

  3. 接下来在 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',               # 添加此项
    )
    
  4. 在命令行中运行:

    需要运行 python .\manage.py migrate,否则auth_table不会被创建,/admin/后台无法登录

    (base) PS D:\Python\py_files\LearnDjango\hello_django> python .\manage.py migrate                 
    Operations to perform:
      Apply all migrations: TestModel, admin, auth, contenttypes, sessions
    Running migrations:
      Applying contenttypes.0001_initial... OK
      Applying auth.0001_initial... OK
      Applying admin.0001_initial... OK
      Applying admin.0002_logentry_remove_auto_add... OK
      Applying admin.0003_logentry_add_action_flag_choices... OK
      Applying contenttypes.0002_remove_content_type_name... OK
      Applying auth.0002_alter_permission_name_max_length... OK
      Applying auth.0003_alter_user_email_max_length... OK
      Applying auth.0004_alter_user_username_opts... OK
      Applying auth.0005_alter_user_last_login_null... OK
      Applying auth.0006_require_contenttypes_0002... OK
      Applying auth.0007_alter_validators_add_error_messages... OK
      Applying auth.0008_alter_user_username_max_length... OK
      Applying auth.0009_alter_user_last_name_max_length... OK
      Applying auth.0010_alter_group_name_max_length... OK
      Applying auth.0011_update_proxy_permissions... OK
      Applying auth.0012_alter_user_first_name_max_length... OK
      Applying sessions.0001_initial... OK
    
    (base) PS D:\Python\py_files\LearnDjango\hello_django> python manage.py makemigrations TestModel
    Migrations for 'TestModel':
      TestModel\migrations\0001_initial.py
        - Create model Test
    (base) PS D:\Python\py_files\LearnDjango\hello_django> python manage.py migrate TestModel
    Operations to perform:
      Apply all migrations: TestModel
    Running migrations:
      Applying TestModel.0001_initial... OK
    

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

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

数据库操作

hello_django 目录中添加 testdb.py 文件(下面介绍),并修改 urls.py

...
from . import views, testdb

urlpatterns = [
    ...
    path("test_add/", testdb.test_add, name="test_add"),
]
  • 添加数据(写在testdb.py中)

    import time
    from django.http import HttpResponse
    from TestModel.models import Test
    
    
    def test_add(request):
        current_time = time.strftime("%Y-%m-%d", time.localtime())
        test1 = Test(name='moonjay', date=current_time)
        test1.save()
        # Test.objects.create(name="python",data=current_time) 
        return HttpResponse("<p>数据添加成功!</p>")
    

  • 获取数据

    Django提供了多种方式来获取数据库的内容,如下代码(testdb.py中添加)所示:

    def test_get(request):
        # 初始化
        response = ""
        response1 = ""
    
        # 通过objects这个模型管理器的all()获得所有数据行,相当于SQL中的SELECT * FROM
        listTest = Test.objects.all()
    
        # filter相当于SQL中的WHERE,可设置条件过滤结果
        response2 = Test.objects.filter(id=1)
        # Test.objects.filter(pk=1)  # pk == primary key
        # filter() 方法还可以基于双下划线的模糊查询(exclude 同理)
        Test.objects.filter(pk__in=[1,3])  # __in 用于读取区间,= 号后面为列表(只能使用'='号)
        Test.objects.filter(pk__gt=2)  # 查询pk大于2的数据 
        # __gte 大于等于;__lt 小于;__lte 小于等于;__range 在 ... 之间,左闭右闭区间
        Test.objects.filter(name__contains="moon")  # 查询name中包含“moon”的数据 
        # __icontains 不区分大小写的包含;__startswith 以指定字符开头;__endswith 以指定字符结尾
        Test.objects.filter(pub_date__year=2024)  # __year 是 DateField 数据类型的年份
        # __month 是DateField 数据类型的月份;__day 是DateField 数据类型的天数
        
        # exclude 用于查询不符合条件的数据。
        # 返回的是 QuerySet 类型数据,类似于 list,可用索引下标取出模型类的对象。
        Test.objects.exclude(id=1)
    
        # 获取单个对象
        response3 = Test.objects.get(id=1)
    
        # 限制返回的数据 相当于 SQL 中的 OFFSET 0 LIMIT 2;
        Test.objects.order_by('name')[0:2]
        Test.objects.order_by('-name')  # 降序 加‘-’
        Test.objects.order_by('-name').reverse()  # 降序后再反转(按名字升序)
    
        # 数据排序
        Test.objects.order_by("id")
        
        Test.objects.count() # 查询所有数据的数量 
    
        # 上面的方法可以连锁使用
        Test.objects.filter(name="moonjay").order_by("id")
        
        Test.objects.first() # 返回所有数据的第一条数据
        Test.objects.last() # 返回所有数据的最后一条数据
        
        Test.objects.exists() # return bool值,用于判断查询的结果 QuerySet 列表里是否有数据
        
        # 查询所有的id字段和name字段的数据
        tests = Test.objects.values("pk","name")  # 返回字典(键是字段,值是数据)
        tests[0]["name"]  # 得到第一条记录的name字段的数据
        
        Test.objects.values_list("pk","name")  # 返回元组(元组里放的是查询字段对应的数据)
        
        # distinct() 方法用于对数据进行去重。返回的是 QuerySet 类型数据。
        # 对模型类的对象去重没有意义,因为每个对象都是一个不一样的存在。
        # distinct() 一般是联合 values 或者 values_list 使用。
        Test.objects.values_list("name").distinct()  # 查询一共有多少不重名的人
    
        # 输出所有数据
        for var in listTest:
            response1 += var.name + " "
        response = response1
        return HttpResponse("<p>" + response + "</p>")
    

    修改 urls.py

    urlpatterns = [
        ...
        path("test_add/", testdb.test_add, name="test_add"),
        path("test_get/", testdb.test_get, name="test_get"),
    ]
    
  • 更新数据

    testdb.py中添加)修改数据可以使用 save()update()

    def test_update(request):
        # 修改其中一个id=1的name字段,再save,相当于SQL中的UPDATE
        test1 = Test.objects.get(id=1)
        test1.name = 'moonjay_py'
        test1.save()
        
        # 另外一种方式
        #Test.objects.filter(id=1).update(name='Google')
        
        # 修改所有的列
        # Test.objects.all().update(name='Google')
        
        return HttpResponse("<p>修改成功</p>")
    

    同样地,修改 urls.py

    urlpatterns = [
        ...
        path("test_add/", testdb.test_add, name="test_add"),
        path("test_get/", testdb.test_get, name="test_get"),
        path("test_update/", testdb.test_update, name="test_update"),
    ]
    
  • 删除数据

    testdb.py中添加)调用该对象的delete()方法:

    def test_del(request):
        # 删除id=1的数据
        Test.objects.get(id=1).delete() # 对象.delete()
        
        # Test.objects.filter(id=1).delete()  # 类型数据.delete()
        
        # 删除所有数据
        # Test.objects.all().delete()
        
        return HttpResponse("<p>删除成功</p>")
    
    • Django 删除数据时,会模仿 SQL约束 ON DELETE CASCADE 的行为,也就是删除一个对象时也会删除与它相关联的外键对象。
    • delete() 方法是 QuerySet 数据类型的方法,但并不适用于 Manager 本身。也就是想要删除所有数据,不能不写 all。
    books=models.Book.objects.delete()  # 报错
    books=models.Book.objects.all().delete()   # 删除成功
    
  • 同样地,修改 urls.py

    urlpatterns = [
        ...
        path("test_add/", testdb.test_add, name="test_add"),
        path("test_get/", testdb.test_get, name="test_get"),
        path("test_update/", testdb.test_update, name="test_update"),
        path("test_del/", testdb.test_del, name="test_del"),
    ]
    

Django 表单

使用Django对用户提交的表单数据进行处理。

HTTP 请求

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

  • GET 方法

    在之前的项目中创建一个 form.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

    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>search form</title>
    </head>
    <body>
        <form action="/search/" method="get">  <!-- 注意这里跳转地址 -->
            <input type="text" name="q">
            <input type="submit" value="搜索">
        </form>
    </body>
    </html>
    

    urls.py 规则添加路由地址:

    from django.conf.urls import url
    from . import views,testdb,form
     
    urlpatterns = [
        ...
        url('search-form/', form.search_form),
        url('search/', form.search),
    ]
    
  • POST 方法

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

    提交数据时更常用 POST 方法,并用一个URL和处理函数,同时显示视图和处理请求

    templates 创建 post_form.html

    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>post form</title>
    </head>
    <body>
        <form action="/post_form/" 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 方法提交的表格,必须有此标签

    修改 form.py 文件并使用 post_form 函数来处理 POST 请求:

    from django.shortcuts import render
    from django.views.decorators import csrf
     
    
    def post_form(request):
        ctx ={}
        if request.POST:
            ctx['rlt'] = request.POST['q']
        return render(request, "post_form.html", ctx)
    

    urls.py 添加路由:

    urlpatterns = [
        ...
        url('post_form/', form.post_form),
    ]
    

Request 对象

每个视图(如views.py或者上面提到的form.py)函数的第一个参数是一个 HttpRequest 对象。

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

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

属性 描述
path 请求页面的全路径,不包括域名。例如:"/hello/"
method 请求中使用的HTTP方法的字符串表示。全大写表示。例如:
if request.method == 'POST': do_something()
GET 包含所有HTTP GET参数的类字典对象。参见QueryDict 文档。
POST 包含所有HTTP POST参数的类字典对象。参见QueryDict 文档。服务器收到空的POST请求的情况也是有可能发生的。也就是说,表单form通过HTTP POST方法提交请求,但是表单中可以没有数据
因此,不能使用语句if request.POST来判断是否使用HTTP POST方法;
应该使用if request.method == "POST" (参见本表的method属性)。
注意: POST不包括file-upload信息。参见FILES属性。
REQUEST 为了方便,该属性是POST和GET属性的集合体,但是有特殊性,先查找POST属性,然后再查找GET属性。借鉴PHP’s $_REQUEST
例如,如果GET = {"name": "john"}POST = {"age": '34'},则 REQUEST["name"] 的值是"john", REQUEST["age"]的值是"34"。
强烈建议使用GET and POST,因为这两个属性更加显式化,写出的代码也更易理解。
COOKIES 包含所有cookies的标准Python字典对象。Keys和values都是字符串。
FILES 包含所有上传文件的类字典对象。FILES中的每个Key都是<input type="file" name="" />标签中name属性的值。FILES中的每个value 同时也是一个标准Python字典对象,包含下面三个Keys:
① filename: 上传文件名,用Python字符串表示
② content-type: 上传文件的Content type
③ content: 上传文件的原始内容
注意:只有在请求方法是POST,并且请求页面中<form>enctype="multipart/form-data"属性时FILES才拥有数据。否则,FILES 是一个空字典。
META 包含所有可用HTTP头部信息的字典。 例如:
CONTENT_LENGTH
CONTENT_TYPE
QUERY_STRING: 未解析的原始查询字符串
REMOTE_ADDR: 客户端IP地址
REMOTE_HOST: 客户端主机名
SERVER_NAME: 服务器主机名
SERVER_PORT: 服务器端口
META 中这些头加上前缀 HTTP_ 为Key,冒号(:)后面的为 Value, 例如:
HTTP_ACCEPT_ENCODING
HTTP_ACCEPT_LANGUAGE
HTTP_HOST: 客户发送的HTTP主机头信息
HTTP_REFERER: referring页
HTTP_USER_AGENT: 客户端的user-agent字符串
HTTP_X_BENDER: X-Bender头信息
user 是一个django.contrib.auth.models.User 对象,代表当前登录的用户。如果访问用户当前没有登录,user将被初始化为django.contrib.auth.models.AnonymousUser的实例。
可以通过user的is_authenticated()方法来辨别用户是否登录:
if request.user.is_authenticated(): # Do something for logged-in users.
else: # Do something for anonymous users.
只有激活Django中的AuthenticationMiddleware时该属性才可用
session 唯一可读写的属性,代表当前会话的字典对象。只有激活Django中的session支持时该属性才可用。
raw_post_data 原始HTTP POST数据,未解析过。 高级处理时会有用处。

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

方法 描述
__getitem__(key) 返回GET/POST的键值,先取POST,后取GET。
如果键不存在抛出 KeyError。 此时可以使用字典语法访问HttpRequest对象。
例如:request["foo"]等同于先request.POST["foo"] 然后request.GET["foo"]的操作。
has_key() 检查request.GET or request.POST中是否包含参数指定的Key。
get_full_path() 返回包含查询字符串的请求路径。例如, “/music/bands/the_beatles/?print=true”
is_secure() 如果请求是安全的,返回True,就是说,发出的是HTTPS请求。

QueryDict对象

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

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

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

方法 描述
__getitem__ 和标准字典的处理有一点不同。如果Key对应多个Value,__getitem__()返回最后一个value。
__setitem__ 设置参数指定key的value列表(一个Python list)。注意:它只能在一个mutable QueryDict 对象上被调用(就是通过copy()产生的一个QueryDict对象的拷贝)
get() 如果key对应多个value,get()返回最后一个value。
update() 参数可以是QueryDict,也可以是标准字典。和标准字典的update方法不同,该方法添加字典 items,而不是替换它们:
>>> q = QueryDict('a=1')
>>> q = q.copy() # to make it mutable
>>> q.update({'a': '2'})
>>> q.getlist('a')
['1', '2']
>>> q['a'] # returns the last
['2']
items() 和标准字典的items()方法有一点不同,该方法使用单值逻辑的__getitem__()
>>> q = QueryDict('a=1&a=2&a=3')
>>> q.items()
[('a', '3')]
values() 和标准字典的values()方法有一点不同,该方法使用单值逻辑的__getitem__()

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

方法 描述
copy() 返回对象的拷贝,内部实现是用Python标准库的copy.deepcopy()。该拷贝是mutable(可更改的)。
getlist(key) 返回和参数key对应的所有值,作为一个Python list返回。如果key不存在,则返回空list。
It’s guaranteed to return a list of some sort.
setlist(key,list_) 设置key的值为list_ (unlike __setitem__()).
appendlist(key,item) 添加item到和key关联的内部list.
setlistdefault(key,list) 和setdefault有一点不同,它接受list而不是单个value作为参数。
lists() 和items()有一点不同, 它会返回key的所有值,作为一个list, 例如:
>>> q = QueryDict('a=1&a=2&a=3')
>>> q.lists()
[('a', ['1', '2', '3'])]
urlencode() 返回一个以查询字符串格式进行格式化后的字符串。

Django 视图

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

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

无论视图本身包含什么逻辑,都要返回响应。

代码一般放在项目的 views.py 文件中。也可以新建py文件进行分类管理。

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

视图层中有两个重要的对象

  • 请求对象(request)
  • 响应对象(HttpResponse)

HttpRequest 对象(request)

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

  1. GET

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

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

    取值格式:对象.方法

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

    def get_object(request):
        name = request.GET.get("name")
        return HttpResponse('姓名:{}'.format(name))
    

  2. POST

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

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

    取值格式: 对象.方法

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

    def post_object(request):
        name = request.POST.get("name")
        return HttpResponse('姓名:{}'.format(name))
    

    post 请求返回 403

    1. 解决:

    导入模块:

    from django.views.decorators.csrf import csrf_exempt
    

    在函数前面添加修饰器:@csrf_exempt

    2. 原因:

    当采用客户端象 django 的服务器提交 post 请求时,会得到403,权限异常。

    因为 django 针对提交的请求,有校验。所以会如此。客户端提交的 post 如果不加这段,会出现 403 error。

    @csrf_exempt
    def post_object(request):
        name = request.POST.get("name")
    ...
    
  3. body

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

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

    def body_object(request):
        name = request.body
        print(name)  # 输出结果为 b'----...' 格式的二进制编码
        return HttpResponse("hello")
    
  4. path

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

    def path_object(request):
        name = request.path
        print(name)  # /路由名/
        return HttpResponse("hello")
    
  5. method

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

    def method_object(request):
        name = request.method
        print(name)  # POST
        return HttpResponse("hello")
    

HttpResponse 对象

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

  • HttpResponse():返回文本,参数为字符串,字符串中写文本内容。如果参数为字符串里含有 html 标签,也可以渲染。
  • render():返回文本,第一个参数为 request,第二个参数为字符串(页面名称),第三个参数为字典(可选参数,向页面传递的参数:键为页面参数名,值为views参数名)。
  • redirect():重定向,跳转新页面。参数为字符串,字符串中填写页面路径。一般用于 form 表单提交后,跳转到新页面

render(底层返回的是 HttpResponse 对象) 和 redirect(底层继承的是 HttpResponse 对象) 是在 HttpResponse 的基础上进行了封装。

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), # 正则路径 
    ]
    

正则路径中的分组

  • 无名分组

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

    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('hello')
    
  • 有名分组

    语法:(?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('hello')
    

    路由分发(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")), 
        ]
        
      • 3、在各自 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), 
        ]
        
      • 4、在各自 app 目录下的 views.py 文件中写各自的视图函数。

反向解析

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

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

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

  • 普通路径

    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"> 
    
  • 正则路径(无名分组)

    urls.py 中给路由起别名,name=“路由别名”

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

    views.py 中,利用 reverse(“路由别名”,args=(符合正则匹配的参数,)) 反向解析。

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

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

    <form action="{% url 'login' 10 %}" method="post"> 
    
  • 正则路径(有名分组)

    urls.py 中给路由起别名,name=“路由别名”

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

    views.py 中,利用 reverse(“路由别名”,kwargs={“分组名”:符合正则匹配的参数}) 反向解析。

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

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

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

命名空间

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

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

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

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

解决:使用命名空间。

  • 普通路径

    定义命名空间(include 里面是一个元组)格式如下:include(("app名称.urls", "app名称"))

    例如,对于上文包含app01和app02的项目:

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

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

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

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

    return redirect(reverse("app01:login")  # reverse("app名称:路由别名")
    

    templates 模板的 HTML 文件中使用名称空间,语法格式如下:{% url "app名称:路由别名" %}

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

注:其它路径同上

Django Admin

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

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

  • 激活管理工具

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

    配置项如下所示(urls.py):

    from django.contrib import admin
    from django.urls import path
    
    urlpatterns = [
        path("admin/", admin.site.urls),
        ...
    ]
    
    
  • 通过命令 python manage.py createsuperuser 来创建超级用户,如下所示:

    # python manage.py createsuperuser
    Username (leave blank to use 'root'): admin
    Email address: admin@runoob.com
    Password:
    Password (again):
    Superuser created successfully.
    

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

from django.contrib import admin
from TestModel.models import Test
 
# Register your models here.
admin.site.register(Test)

刷新后即可看到 Testmodel 数据表(访问 http://localhost:8000/admin )

复杂模型

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

先在 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

TagContact 为外部键。一个 Contact 可以对应多个 Tag

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

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   # 创建表结构

自定义表单

可以自定义管理页面,来取代默认的页面。比如上面 Contacts的"add" 页面。想只显示 name 和 email 部分。修改 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])

还可以将输入栏分块,每个栏也可以定义自己的格式。

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

修改 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])

列表页的显示

自定义,比如在列表中显示更多的栏目,只需要在 ContactAdmin 中增加 list_display 属性:

...
class ContactAdmin(admin.ModelAdmin):
    list_display = ('name','age', 'email') # list
    ...
...

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

...
class ContactAdmin(admin.ModelAdmin):
    list_display = ('name','age', 'email') # list
    search_fields = ('name',)
    ...
...

Django ORM - 多表实例

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

  • 一对一:一个人对应一个身份证号码,数据字段设置 unique。
  • 一对多:一个家庭有多个人,一般通过外键来实现。
  • 多对多:一个学生有多门课程,一个课程有很多学生,一般通过第三个表来实现关联(涉及范式,拆解为一对多和多对一的关系)。

表结构

  • 书籍表 Book:title 、 price 、 pub_date 、 publish(外键,多对一) 、 authors(多对多)
  • 出版社表 Publish:name 、 city 、 email
  • 作者表 Author:name 、 age 、 au_detail(一对一)
  • 作者详情表 AuthorDetail:gender 、 tel 、 addr 、 birthday

创建模型

创建APP

(base) PS D:\Python\py_files\LearnDjango> cd .\hello_django\                         
(base) PS D:\Python\py_files\LearnDjango\hello_django> django-admin startapp BookModel

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

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.ForeignKey("Publish", on_delete=models.CASCADE) # 出版社名称 
    pub_date = models.DateField() # 出版时间
    authors = models.ManyToManyField("Author")  # 会自动生成 book_authors 表


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、若有模型类存在外键,创建数据时,要先创建外键关联的模型类的数据,不然创建包含外键的模型类的数据时,外键的关联模型类的数据会找不到。

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',               # 添加此项
    'BookModel',               # 添加此项
)

执行以下命令:

$ python manage.py makemigrations BookModel  # 让 Django 知道我们在我们的模型有一些变更
Migrations for 'BookModel':
  BookModel\migrations\0001_initial.py
    - Create model AuthorDetail
    - Create model Book
    - Create model Publish
    - Create model Author
    - Add field authors to book
$ python manage.py migrate BookModel   # 创建表结构
Operations to perform:
  Apply all migrations: BookModel
Running migrations:
  Applying BookModel.0001_initial... OK

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

from django.contrib import admin
from BookModel.models import Book, Publish, Author, AuthorDetail

# Register your models here.
admin.site.register([Book, Publish, Author, AuthorDetail])

插入数据

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

insert into bookmodel_publish(name,city,email) values ("华山出版社", "华山", "hs@163.com"), ("明教出版社", "黑木崖", "mj@163.com")
 
# 先插入 authordetail 表中多数据
insert into bookmodel_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 bookmodel_author(name,age,au_detail_id) values ("令狐冲",25,1), ("任我行",58,2), ("任盈盈",23,3)

ORM - 添加

一对多(ForeignKey)

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

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

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

步骤:

  • a. 获取出版社对象的 id
  • b. 给书籍的关联出版社字段 pulish_id 传出版社对象的 id

BookModel/views.py

from django.shortcuts import render
from BookModel import models
from django.http import HttpResponse


# Create your views here.
def add_book(request):
    #  获取出版社对象
    pub_obj = models.Publish.objects.filter(pk=1).first()
    #  给书籍的出版社属性publish传出版社对象
    book = models.Book.objects.create(title="Python", price=200, pub_date="2024-7-13", publish=pub_obj.id)  # 等价于使用 pub_obj.pk
    return HttpResponse("<p>" + str(book) + ", 数据添加成功!</p>")

hello_django/urls.py中添加

from BookModel import views as book_views

urlpatterns = [
    ...
    path("add_book/", book_views.add_book, name="add_book"),
]
多对多(ManyToManyField)

在第三张关系表中新增数据

步骤:

  • a. 获取作者(Author)对象
  • b. 获取书籍(Book)对象
  • c. 向“第三张表”中添加(本例是添加author_id和book_id)

views.py添加代码:

def add_b2a(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("<p>成功添加</p>")

按照常规方式(不正确):

需要先在models添加class Book2Author

class Book2Author(models.Model):
    # 这里变量不要命名为author_id,因为设置ForeignKey会在生成表时默认变为author_id
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
    book = models.ForeignKey(Book, on_delete=models.CASCADE)

然后使用如下代码添加:

models.Book2Author.objects.create(author_id=chong.pk, book_id=book.pk)
models.Book2Author.objects.create(author_id=ying.pk, book_id=book.pk)

hello_django/urls.py中添加

from BookModel import views as book_views

urlpatterns = [
    ...
    path("add_book2author/", book_views.add_b2a, name="add_b2a"),
]

数据库添加结果:

关联管理器(对象调用)

前提

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

语法格式

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

注意:一对多只能反向

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

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

*[ ] 的使用:

# 方式一:传对象
book_obj = models.Book.objects.get(id=10)
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【出现bug:‘Author’ object has no attribute ‘book_set’】

def association_set(request):
    ying = models.Author.objects.filter(name="任盈盈").first()
    book = models.Book.objects.filter(title="冲灵剑法").first()
    ying.book_set.add(book)  # 影响 book_authors 表

    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.pk)
# 会影响book表和book2author表
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()

ORM - 查询

基于对象的跨表查询。

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

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

BoolModel/views.py

def one2many_find(request):
    book = models.Book.objects.filter(pk=1).first()
    res = book.publish.city
    print(res, type(res))
    return HttpResponse(res)

hello_django/urls.py添加path("find1/", book_views.one2many_find, name="one2many_find"),

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

反向:对象.小写类名_set 可以跳转到关联的表。

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

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

一对一

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

正向:对象.属性 可以跳转到关联的表

BoolModel/views.py

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

hello_django/urls.py添加path("find2/", book_views.one2one_find, name="one2one_find"),

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

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

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

"吸星大法"所有作者的名字以及手机号(正向)——book.authors

正向:对象.属性可以跳转到关联的表。

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

def many2many_find(request):
    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")

还需要对urls.py进行修改

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

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

基于双下划线的跨表查询

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

一对多

查询“华山出版社”出版过的所有书籍的名字与价格。(正向)

BookModel/views.py

def cross_table_find(request):
    res = models.Book.objects.filter(publish__name="华山出版社").values_list("title", "price")
    return HttpResponse(res)

hello_django/urls.py添加:path("find4/", book_views.cross_table_find),

反向:(book__title,book__price)跨表获取数据。

res = models.Publish.objects.filter(name="菜鸟出版社").values_list("book__title","book__price")
return HttpResponse(res)
多对多

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

正向:(authors__name) 跨表获取数据:

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

反向:(book__title) 跨表获取数据:

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

查询任我行的手机号。

正向:(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")

Django ORM – 聚合与分组查询

聚合查询(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


def use_aggregate(request):
    res = models.Book.objects.aggregate(Avg("price"))
    # print(res)  # {'price__avg': Decimal('250.000000')}
    return HttpResponse(res["price__avg"])  # 250.000000

urls.py添加path("use_aggregate/", book_views.use_aggregate),

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

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

分组查询(annotate)

返回值

  • 分组后,用 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 里写其别名。
准备数据和创建模型

BookModel/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)

在 MySQL 命令行中执行

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

INSERT INTO `bookmodel_dep` (`id`, `title`) VALUES ('1', '销售部'); 
INSERT INTO `bookmodel_dep` (`id`, `title`) VALUES ('2', '关公部'); 

INSERT INTO `bookmodel_emps` (`id`, `name`, `age`, `salary`, `province`, `dep_id`) VALUES ('2', '令狐冲', '24', '8000.00', '河南', '1'); 
INSERT INTO `bookmodel_emps` (`id`, `name`, `age`, `salary`, `province`, `dep_id`) VALUES ('3', '任盈盈', '18', '9000.00', '广东', '2'); 
INSERT INTO `bookmodel_emps` (`id`, `name`, `age`, `salary`, `province`, `dep_id`) VALUES ('4', '任我行', '57', '10000.00', '广东', '1');
INSERT INTO `bookmodel_emps` (`id`, `name`, `age`, `salary`, `province`, `dep_id`) VALUES ('5', '岳灵珊', '19', '6000.00', '河南', '2');
INSERT INTO `bookmodel_emps` (`id`, `name`, `age`, `salary`, `province`, `dep_id`) VALUES ('6', '小龙女', '20', '8000.00', '河北', '2');
几个比较典型的实例

上面插入的数据暂时没用上

统计每一个出版社的最便宜的书的价格(BookModel/views.py

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

urls.py添加path("use_annotate/", book_views.use_annotate),

统计每一本书的作者个数

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

统计每一本以**“冲”**开头的书籍的作者个数:

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)

F() 查询

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

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

使用前要先导包:

from django.db.models import F

用法F("字段名称")

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

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

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


下面使用上一小节创建的数据

查询工资大于年龄的人

from django.db.models import F
def use_f(request):
    person = models.Emp.objects.filter(salary__gt=F("age")).values("name", "age")
    return HttpResponse(person)

urls.py添加path("use_f/", book_views.use_f),

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

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

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
def use_q(request):
    res = models.Book.objects.filter(Q(price__gt=350) | Q(title__startswith="冲")).values("title", "price")
    return HttpResponse(res)

urls.py添加path("use_q/", book_views.use_q),

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

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

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

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

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

Django Form 组件

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

报错信息显示顺序:

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

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

简单例子

BookModel 目录下创建一个 my_forms.py:

from django import forms


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。

BookModel/views.py

from django.shortcuts import render, HttpResponse
from BookModel.My_Forms import EmpForm
from BookModel import models
from django.core.exceptions import ValidationError


# Create your views here.
def add_emp(request):
    form = EmpForm()
    return render(request, "add_emp.html", {"form": form})

BookModel/urls.py 文件添加规则:path('add_emp/', views.add_emp)

添加文件templates/add_emp.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Add EMP</title>
</head>
<body>

<h3>添加员工</h3>

<!-- 法1 自己写
<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>

局部钩子和全局钩子

修改BookModel 目录下的 my_forms.py

from django import forms
from django.core.exceptions import ValidationError
from BookModel 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("请确认工资是否一致。")

修改BookModel/views.py 文件代码:

from django.shortcuts import render, HttpResponse, redirect
from BookModel.My_Forms import EmpForm
from BookModel import models
from django.core.exceptions import ValidationError


# Create your views here.
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})

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>

Django 用户认证(Auth)组件

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

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

使用以下代码,按照先编写views.py,再加入urls.py路由的方式,向auth_user表添加数据

# 认证模块
from django.contrib import auth
# 对应数据库
from django.contrib.auth.models import User

# User.objects.create(username='runbooo',password='123')
# User.objects.create_user(username='runbooo',password='123')
User.objects.create_superuser(username='runboooo',password='123',email='runboo@163.com')

返回值是用户对象。(来自auth_user表)

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

  • create():创建一个普通用户,密码是明文的。

  • create_user():创建一个普通用户,密码是密文的。

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

    等价于,通过命令 python manage.py createsuperuser 来创建超级用户

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

使用前要导入:from django.contrib import auth

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

views.py添加:

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() 方法。

返回值:None。

修改views.py如下:

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 赋值为匿名用户。

views.py添加:

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 参数的值。

但是,若用户一开始就输入登录页面 login,request.GET.get("next") 就取不到值,所以在后面加 or,可以设置自定义返回的页面。

# 如果直接输入 login、get() 就取不到值,path 可以自定义设置返回的页面 
path = request.GET.get("next") or "/index/"
return redirect(path)

Django cookie 与 session

Cookie 是存储在客户端计算机上的文本文件,并保留了各种跟踪信息。

识别返回用户包括三个步骤:

  • 服务器脚本向浏览器发送一组 Cookie。例如:姓名、年龄或识别号码等。
  • 浏览器将这些信息存储在本地计算机上,以备将来使用。
  • 当下一次浏览器向 Web 服务器发送任何请求时,浏览器会把这些 Cookie 信息发送到服务器,服务器将使用这些信息来识别用户。

HTTP 是一种"无状态"协议,这意味着每次客户端检索网页时,客户端打开一个单独的连接到 Web 服务器,服务器会自动不保留之前客户端请求的任何记录。

但是仍然有以下三种方式来维持 Web 客户端和 Web 服务器之间的 session 会话

Cookies

一个 Web 服务器可以分配一个唯一的 session 会话 ID 作为每个 Web 客户端的 cookie,对于客户端的后续请求可以使用接收到的 cookie 来识别。

在Web开发中,使用 session 来完成会话跟踪,session 底层依赖 Cookie 技术

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)

创建应用和模型

(base) PS D:\Python\py_files\LearnDjango\hello_django> django-admin startapp CookieModel 

修改CookieModel/models.py

class UserInfo(models.Model):
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=64)

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

INSTALLED_APPS = [
    ...
    "CookieModel",  # new added
]

插入数据库(执行以下命令):

$ python manage.py makemigrations CookieModel  # 让 Django 知道我们在我们的模型有一些变更
Migrations for 'CookieModel':
  CookieModel\migrations\0001_initial.py
    - Create model UserInfo
$ python manage.py migrate CookieModel   # 创建表结构
Operations to perform:
  Apply all migrations: CookieModel
Running migrations:
  Applying CookieModel.0001_initial... OK

测试数据:insert into cookiemodel_userinfo(username, password) values ("moonjay_cookie", "123456")

CookieModel/views.py

from django.shortcuts import render, redirect

from CookieModel import models


# Create your views here.
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_cookie.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")

hello_django/urls.py添加:

from CookieModel import views as cookie_views

urlpatterns = [
    path("admin/", admin.site.urls),  # 前面添加过
    ...
    path("index/", cookie_views.index),  # 前面添加过
    ...

    path('login/', cookie_views.login),
    path('logout/', cookie_views.logout),
    path('order/', cookie_views.order),
]

以下创建三个模板文件:login.htmlindex_cookie.htmlorder.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Login (COOKIES) Page</title>
</head>
<body>
<h3>用户登录</h3>
<form action="" method="post">
    {% csrf_token %}
    <label>用户名:<input type="text" name="username">
    </label>
    <label>密码:<input type="password" name="pwd">
    </label>
    <input type="submit">
</form>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Index Cookie Page</title>
</head>
<body>
<h2>Cookie index 页面...</h2>
<a href="/logout/">注销</a>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Order Page</title>
</head>
<body>
<h2>order 页面。。。</h2>
<a href="/logout/">注销</a>
</body>
</html>

Session

保存在服务端的键值对

服务器在运行时可以为每一个用户的浏览器创建一个其独享的 session 对象,由于 session 为用户浏览器独享,所以用户在访问服务器的 web 资源时,可以把各自的数据放在各自的 session 中,当用户再去访问该服务器中的其它 web 资源时,其它 web 资源再从用户各自的 session 中取出数据为用户服务。

工作原理

  • a. 浏览器第一次请求获取登录页面 login。

  • b. 浏览器输入账号密码第二次请求,若输入正确,服务器响应浏览器一个 index 页面和一个键为 sessionid,值为随机字符串的 cookie,即 set_cookie ("sessionid",随机字符串)

  • c. 服务器内部在 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

执行步骤:

  • a. 生成随机字符串
  • b. 把随机字符串和设置的键值对保存到 django_session 表的 session_keysession_data
  • c. 设置 cookie: set_cookie(“sessionid”,随机字符串) 响应给浏览器

session 获取request.session.get('key')

执行步骤:

  • a. 从 cookie 中获取 sessionid 键的值,即随机字符串。
  • b. 根据随机字符串从 django_session 表过滤出记录。
  • c. 取出 session_data 字段的数据。

session 删除,删除整条记录(包括 session_key、session_data、expire_date 三个字段):request.session.flush()

删除 session_data 里的其中一组键值对:del request.session["key"]

执行步骤:

  • a. 从 cookie 中获取 sessionid 键的值,即随机字符串
  • b. 根据随机字符串从 django_session 表过滤出记录
  • c. 删除过滤出来的记录

实例

CookieModel/views.py添加代码:

def s_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/')

创建路由(hello_django/urls.py):

from CookieModel import views as cookie_views

urlpatterns = [
    ...
    path('session_login/', cookie_views.s_login),
    path('s_index/', cookie_views.s_index),
    path('s_logout/', cookie_views.s_logout),
]

s_index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Session Page</title>
</head>
<body>
<h2>session_index 页面。。。{{ request.session.user1 }}</h2>
<a href="/s_logout/">注销</a>
</body>
</html>

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",
]

自定义中间件步骤

以APP:BookModel为例,在 BookModel 目录下新建一个 py 文件,名字自定义(middle_ware.py),并在该 py 文件中导入 MiddlewareMixin

from django.utils.deprecation import MiddlewareMixin

自定义的中间件类,要继承父类 MiddlewareMixin

class MW1(MiddlewareMixin): 
    pass

settings.py 中的 MIDDLEWARE 里注册自定义的中间件类:

MIDDLEWARE = [
    ....
    'BookModel.middle_ware.MW1',
]

自定义中间件类的方法

4个方法:

process_request(self,request)
process_view(self, request, view_func, view_args, view_kwargs)
process_exception(self, request, exception)
process_response(self, request, response)
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 MW1(MiddlewareMixin):
    def process_request(self, request):
        print("mw1  process_request 方法。", id(request))  # 在视图之前执行

访问任意页面,控制台输出:

mw1  process_request 方法。 2498897104224
[16/Jul/2024 15:54:40] "GET / HTTP/1.1" 200 1454
...
process_response 方法

process_response 方法有两个参数,一个是 request,一个是 response,request 是请求对象,response 是视图函数返回的 HttpResponse 对象,该方法必须要有返回值,且必须是response

process_response 方法是在视图函数之后执行的。

配置多个中间件时,会按照 MIDDLEWARE 中的注册顺序,也就是列表的索引值,倒序执行

MW1类中添加方法定义:

    def process_response(self, request, response):  # 基于请求响应
        print("mw1  process_response 方法!", id(request))  # 在视图之后
        return response

访问任意页面,控制台输出:

mw1  process_request 方法。 2790043344560
mw1  process_response 方法! 2790043344560
[16/Jul/2024 16:06:03] "GET / HTTP/1.1" 200 1454

从下图看,正常的情况下按照绿色的路线进行执行,假设中间件1有返回值,则按照红色的路线走,直接执行该类下的 process_response 方法返回,后面的其他中间件就不会执行。

process_view 方法

process_view 方法格式如下:process_view(request, view_func, view_args, view_kwargs)

  • 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 将不执行后续视图函数之前执行的方法以及视图函数,直接以该中间件为起点,倒序执行中间件,且执行的是视图函数之后执行的方法。
  • 返回值是 view_func(request),Django 将不执行后续视图函数之前执行的方法,提前执行视图函数,然后再倒序执行视图函数之后执行的方法。
  • 当最后一个中间件的 process_request 到达路由关系映射之后,返回到第一个中间件 process_view,然后依次往下,到达视图函数。

MW1类中添加方法定义:

    def process_view(self,request, view_func, view_args, view_kwargs):
        print("md1  process_view 方法!") #在视图之前执行 顺序执行
        #return view_func(request)
mw1  process_request 方法。 2388860657376
mw1  process_view 方法!
mw1  process_response 方法! 2388860657376
[16/Jul/2024 16:15:08] "GET / HTTP/1.1" 200 1454

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 方法开始倒序执行(在MW1类中添加方法定义):

    def process_exception(self, request, exception):  # 引发错误 才会触发这个方法
        print("mw1  process_exception 方法!")
        # return HttpResponse(exception)  # 返回错误信息

访问一个不存在的路由:

mw1  process_request 方法。 2707135433648
mw1  process_response 方法! 2707135433648
Not Found: /404
[16/Jul/2024 16:24:18] "GET /404 HTTP/1.1" 404 5527

Django 视图 - FBV 与 CBV

FBV(function base views) 基于函数的视图,就是在视图里使用函数处理请求

前面的小节都是使用这种方式

CBV(class base views) 基于类的视图,就是在视图里使用类处理请求

不同的请求我们可以在类中使用不同方法来处理,这样大大的提高了代码的可读性。

定义的类要继承父类 View,所以需要先引入库:from django.views import View

执行对应请求的方法前会优先执行 dispatch() 方法(在get/post/put...方法前执行),dispatch() 方法会根据请求的不同调用相应的方法来处理。

其实,在我们前面学到的知识都知道 Django 的 url 是将一个请求分配给可调用的函数的,而不是一个类,那是如何实现基于类的视图的呢?

主要还是通过父类 View 提供的一个静态方法 as_view()as_view() 方法是基于类的外部接口, 它返回一个视图函数,调用后请求会传递给 dispatch() 方法,dispatch() 方法再根据不同请求来处理不同的方法。

hello_django/views.py添加代码:

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 == "runoob" and pwd == "123456":
            return HttpResponse("POST 方法")
        else:
            return HttpResponse("POST 方法 1")

修改urls.py文件:

urlpatterns = [
    ...
    path("login/", views.Login.as_view()),
]

Django Nginx+uwsgi 安装配置

在前面的章节中我们使用 python manage.py runserver 来运行服务器。这只适用测试环境中使用。

正式发布的服务,需要一个可以稳定而持续的服务器,比如apache, Nginx, lighttpd等。


网站公告

今日签到

点亮在社区的每一天
去签到