Skip to content

达内教育Django全套教程

19021字约63分钟

PythonDjango

2024-01-24

Django

1.Django 介绍

1.1 组件

Django 包含的组件如下:

  • 基本配置文件/路由系统
  • 模型层(M)/模板层(T)/视图层(V)
  • CookiesSession
  • 分页及分发邮件
  • Admin 管理后台

1.2 用途

Django 能够实现所有 HTTP 请求处理。

  • 网站/微信公众号/小程序后端开发
  • 人工智能平台融合

1.3 版本

1.4 安装

1.4.1 在线安装

pip install django==2.2.12

1.4.2 离线安装

# 将安装包下载到本地
# 解压缩并进入到解压缩后的目录
# 执行如下命令进行安装
python setup.py install
# 执行如下命令检查是否安装成功
pip freeze | grep -i 'Django'

2.Django 的项目结构

2.1 创建项目

  • 成功安装 Django 后,虚拟机终端会有 django-admin 命令

  • 执行 django-admin startproject [项目名] 即可创建出对应项目的文件夹

    # 创建 mysite1 项目
    django-admin startproject mysite1

2.2 启动服务

  • 启动[测试开发阶段]
    1. 终端 cd 进入到项目文件夹

    2. 进入到项目文件夹后,执行 python manage.py runserver [端口] 启动 django 服务,端口不指定即为 8000

      image-20230807225358311

    3. 浏览器访问 http://127.0.0.1:8000 可看到 django 的启动页面。

      image-20230807225440250

2.3 关闭服务

  • 关闭服务[测试开发阶段]

    • 方式一[在runserver启动终端下]:

      执行 ctrl + c 可退出 Django 服务。

    • 方式二[在其他终端下]:

      执行 sudo lsof -i:8000 查询出 Django 的进程 id

      执行 kill -9 Django 进程的 id

      image-20230807225907267

2.4 项目结构解析

image-20230807230100663

mysite
├── db.sqlite3 [Django默认的数据库,sqlite]
├── manage.py
└── mysite
    ├── __init__.py
    ├── __pycache__
    │   ├── __init__.cpython-310.pyc
    │   ├── settings.cpython-310.pyc
    │   ├── urls.cpython-310.pyc
    │   └── wsgi.cpython-310.pyc
    ├── settings.py
    ├── urls.py
    └── wsgi.py

manage.py

包含项目管理的子命令,如:

# 启动服务
python manage.py runserver
# 创建应用
python manage.py startapp [appname]
# 生成迁移文件
python manage.py makemigrations
# 数据迁移
python manage.py migrate
...
# 直接执行 python manage.py 会列出所有Django的子命令

image-20230807230440939

项目同名文件夹 - mysite/mysite

  • __init__ : Python 包的初始化文件。
  • wsgi.py : web 服务网关配置文件,Django 正式启动时,需要用到。
  • urls.py : 项目的主路由配置,Http 请求进入 Django 时,优先调用该文件。
  • settings.py : 项目的配置文件,包含项目启动时需要的配置。

项目配置文件 - settings.py

  • settings.py 包含了 Django 项目启动时所有的配置项。

  • 配置项分为公有配置和自定义配置。

  • 配置项格式:BASE_DIR = 'xxxx'。

  • 公有配置,Django官方提供的基础配置,可到如下网站查询。

    https://docs.djangoproject.com/en/2.2/ref/settings/

    ALLOWED_HOSTS

    设置允许访问到本项目的 host 头值。

    • 示例:如果要在局域网其他主机也能访问此主机的Django服务,启动方式如下:

      (1) python3 manage.py runserver 0.0.0.0:5000

      (2) 指定网络设备如果内网环境下其他主机想正常访问该站点,需要添加 ALLOWED_HOSTS = ['内网IP']

      image-20230807232602075

      在 windows 主机访问虚拟机启动的 Django 项目,显示主机ip应该添加到 ALLOWED_HOSTS,在 settings.py 添加后,再次访问,页面能够被正常文件。

      image-20230807232951884

    LANGUAGE_CODE

    设置项目的页面展示语言,简体中文设置为:zh-Hans

    TIME_ZONE

    用于指定当前服务端时区,默认为格林威治时间(UTC),中国地区设置为 Asia/Shanghai

    BASE_DIR

    用于绑定当前项目的绝对路径(动态计算出来的),所有文件夹都可以依赖此路径。

    DEBUG

    用于配置 Django 项目的启动模式,取值:True,表示开发环境中使用;False,表示当前项目运行在生产环境中。

    INSTALLED_APPS

    指定当前项目中安装的应用列表。

    MIDDLEWARE

    用于注册中间件。

    TEMPLATES

    用于指定模板的配置信息。

    DATABASES

    用于指定数据库的配置信息。

    ROOT_URLCONF

    用于配置主 url 配置,ROOT_URLCONF = 'mysite.urls'

3.URL 和 视图函数

3.1 URL

定义:即统一资源定位符 Uniform Resource Locator

作用:用来表示互联网上某个资源的地址

URL的一般语法为:

# portocol:协议(http、https、file)
# hostname:主机名
# port:端口
# path:路由地址
# query:查询的参数,可有多个参数,用 & 隔开,每个参数的名和值用 = 隔开。
# fragment:信息片段,字符串,用于指定网络资源中的片段。例如一个网页中有多个名词解释,可使用fragment直接定位到某一名词解释。
protocol://hostname[:port]/path[?query][#fragment]

例如:

# 直接定位到页面指定信息片段的位置
https://docs.djangoproject.com/en/4.2/#how-the-documentation-is-organized

3.2 处理 URL 请求

以URL(http://127.0.0.1:8000/page/2003)为例。

  1. Django 从配置文件中根据 ROOT_URLCONF 找到主路由文件;默认情况下,该文件为项目同名目录下的 urls.py
  2. Django 加载主路由文件中的 urlpatterns 变量[包含很多路由的数组]。
  3. 依次匹配 urlpatterns 中的 path ,匹配到第一个合适的中断后续匹配。
  4. 匹配成功-调用对应的视图函数处理请求,返回响应。
  5. 匹配失败-返回 404 响应。

主路由 - urls.py

from django.contrib import admin
from django.urls import path
from . import views
urlpatterns = [
    path('admin/', admin.site.urls),
    path('page/2023', views.page_2023),
    path('page/2024', views.page_2024)
]

3.3 视图函数

定义:视图函数是用于接收一个浏览器请求(HttpRequest对象)并通过HttpResponse对象返回响应的函数,此函数可以接收浏览器请求并根据业务逻辑返回响应的响应内容给浏览器。

语法:

def xxx_view(request[,其他参数]):
    return HttpResponse 对象

样例:

from django.http import HttpResponse

def page_2023(request):
    html = "<h1>这是第一个页面</h1>"
    return HttpResponse(html)

image-20230809231829556

4.路由配置

settings.py 中的 ROOT_URLCONF 指定了主路由配置列表 urlpatterns 的文件位置。

urlpatterns = [
    path('admin/', admin.site.urls),
    path('page/2023/', views.page_2023),
    path('page/2024/', views.page_2024),
    ... # 配置URL和视图函数
]

4.1 path 函数

path函数定义

# 导入
from django.urls import path
# 语法
path(route,views,[name=])
# 参数
route:字符串类型,匹配请求路径
views:指定路径所对应视图函数的名称
name:为路由地址起别名,在模板中地址反向解析时使用

4.2 path 转换器

# 语法
<转换器类型:自定义名>
# 作用
如果转换器类型匹配到对应类型的数据,则将数据按照关键字传参的方式传递给视图函数
# 例如
path('page/\<int:page>',views.xxx)

转换器列表

转换器类型作用样例
str匹配除了'/'之外的非空字符串v1/users/<str:username>匹配 /v1/users/guoxiaonao
int匹配0或者任意正整数,返回一个intpage/<int:page> 匹配 /page/100
slug匹配任意由 ASCII字母或数字以及连字符和下划线组成的短标签detail/<slug:sl> 匹配 /detail/this-is-django
path匹配非空字段,包括路径分隔符'/'v1/users/<path:ph> 匹配 /v1/goods/a/b/c

转换器测试

int 转换器测试

# URL
urlpatterns = [
    ...
    path('page/<int:number>', views.page_n)
]
# 视图函数
def page_n(request,number):
    html = "<h1>这是第{number}个页面</h1>".format(number=number)
    return HttpResponse(html)

image-20230809233613509

str 转换器测试

# URL
urlpatterns = [
    ...
    path('page/<str:name>', views.page_welcome),
]
# 视图函数
def page_welcome(request, name):
    html = "<h1>hello {name}!</h1>".format(name=name)
    return HttpResponse(html)

image-20230809233831396

slug 转换器测试

# URL
urlpatterns = [
    ...
    path('page/show/<slug:slug>', views.page_show)
]
# 视图函数
def page_show(request, slug):
    print('2')
    html = "<h1>title:{slug}</h1>".format(slug=slug)
    return HttpResponse(html)
# 注意
使用slug时,如果前方存在str的匹配,会进入str的视图函数,因为slug也是字符串

image-20230809234448118

path 转换器测试

# URL
urlpatterns = [
    ...
    path('page/path/<path:path>', views.path_mate)
]
# 视图函数
def path_mate(request, path):
    html = "<h1>path:{path}</h2>".format(path=path)
    return HttpResponse(html)

image-20230809234825556

转换器练习

# 定义一个路由器的格式为: http:127.0.0.1:8000/整数/操作字符串[add/sub/mul]/整数,从路由中提取数据,做相应的操作后返回给浏览器

# 代码如下:
## URL
urlpatterns = [
    ...
    path('calculator/<int:number1>/<str:method>/<int:number2>', views.calculator)
]

## 视图函数
def calculator(request, number1, method, number2):
    if method not in ['add', 'sub', 'mul']:
        return HttpResponse("<h1>method {method} is not support!</h1>".format(method=method))
    if method == 'add':
        html = "<h1>{number1} + {number2} = {result}</h1>".format(number1=number1, number2=number2, result=number1 + number2)
    elif method == 'sub':
        html = "<h1>{number1} - {number2} = {result}</h1>".format(number1=number1, number2=number2, result=number1 - number2)
    else:
        html = "<h1>{number1} * {number2} = {result}</h1>".format(number1=number1, number2=number2, result=number1 * number2)
    return HttpResponse(html)

image-20230809235732904

4.3 re_path 函数

re_path 函数定义

在 url 的匹配过程中可以使用正则表达式进行精确匹配。

# 导入
from django.urls import re_path
# 语法
re_path(reg,view,[name=])
# 说明
正则表达式为命名分组模式(?P<name>pattern);匹配提取参数后用关键字传参方式传递给视图函数
# 注意
使用正则表达式时,需要尽量精准,因此在正则表达式的末尾一定要添加 $ ,否则会导致出现不想要的匹配效果

re_path 函数示例

# 实现效果:使用 re_path 路由函数匹配数字和字符串,定义路由为 http:127.0.0.1:8000/整数/操作字符串[add/sub/mul]/整数,匹配整数均为两位数的操作

# URL
from django.urls import path,re_path
urlpatterns = [
    ...
    re_path(r'calculator/(?P<number1>\d{1,2})/(?P<method>\w+)/(?P<number2>\d{1,2})$', views.calculator_reg),
    path('calculator/<int:number1>/<str:method>/<int:number2>', views.calculator),
]
# 视图函数
def calculator(request, number1, method, number2):
    if method not in ['add', 'sub', 'mul']:
        return HttpResponse("<h1>method {method} is not support!</h1>".format(method=method))
    if method == 'add':
        html = "<h1>{number1} + {number2} = {result}</h1>".format(number1=number1, number2=number2, result=number1 + number2)
    elif method == 'sub':
        html = "<h1>{number1} - {number2} = {result}</h1>".format(number1=number1, number2=number2, result=number1 - number2)
    else:
        html = "<h1>{number1} * {number2} = {result}</h1>".format(number1=number1, number2=number2, result=number1 * number2)
    return HttpResponse(html)

def calculator_reg(request, number1, method, number2):
    html = "<h1>正则匹配</h1>"

    return HttpResponse(html)

image-20230810132355346

image-20230810132019638

re_path 练习

# 实现:
# 1.访问地址:http://127.0.0.1:8000/birthday/四位数字/一到两位数字/一到两位数字
# 2.访问地址:http://127.0.0.1:8000/birthday/一到两位数字/一到两位数字/四位数字
# 访问路由页面展示:生日是 xxxx年 xx月 xx日

# URL
urlpatterns = [
    ...
    # http://127.0.0.1:8000/birthday/四位数字/一到两位数字/一到两位数字
    re_path('birthday/(?P<year>\d{4})/(?P<month>\d{1,2})/(?P<day>\d{1,2})$', views.happy_birthday),
    # http://127.0.0.1:8000/birthday/一到两位数字/一到两位数字/四位数字
    re_path('birthday/(?P<day>\d{1,2})/(?P<month>\d{1,2})/(?P<year>\d{4})$', views.happy_birthday_2),
]
# 视图函数
def happy_birthday(request, year, month, day):
    html = "happy birthday {year}/{month}/{day}".format(year=year, month=month, day=day)
    return HttpResponse(html)

def happy_birthday_2(request, day, month, year):
    html = "happy birthday 2 you : {day}/{month}/{year}".format(day=day, month=month, year=year)
    return HttpResponse(html)

image-20230810135933577

image-20230810140105975

5.请求和响应

5.1 HTTP 协议的请求和响应

  • 请求是指浏览器端通过 HTTP 协议发送给服务器端的数据。
  • 响应是指服务器端接收到请求后做相应的处理后再回复给浏览器端的数据。

请求中的方法

序号方法描述
1GET请求指定的页面信息,并返回实体主体。
2HEAD类似于GET请求,只不过返回的响应中没有具体的内容,用于获取报头。
3POST向指定资源提交数据进行处理请求(例如提交表单或者上传文件),数据被包含在请求体中,POST请求可能会导致新的资源的建立和/或已有资源的修改。
4PUT从客户端向服务器传送的数据取代指定的文档内的内容。
5DELETE请求服务器删除指定的页面。
6CONNECTHTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。
7OPTIONS允许客户端查看服务器的性能。
8TRACE回显服务器收到的请求,主要用于测或判断。

5.2 Django中的请求

  • 请求在Django中实则就是视图函数的第一个参数,即HttpRequest对象。
  • Django接收到http协议的请求后,会根据请求数据报文创建HttpRequest对象。
  • HttpRequest对象通过属性描述了请求的所有相关信息。
  • path_info:url字符串。
  • methods:字符串,表示HTTP请求方法,常用值:GETPOST
  • COOKIES:Python字典,包含所有的cookie,键和值都为字符串。
  • session:类似于字典的对接,表示当前的会话。
  • body:字符串,请求体的内容(POST或PUT)。
  • scheme:请求协议('http/https')。
  • request.get_full_path():请求的完整路径。
  • request.META:请求中的元数据(消息头)。
    • request.META['REMOTE_ADDR']:客户端IP地址。

image-20240108094558431

5.3 响应

响应状态码的英文为HTTP Status Code,以下为常见的HTTP状态码:

  • 200,请求成功。
  • 301,永久重定向,资源(网页等)被永久转移到其他URL。
  • 302,临时重定向。
  • 404,请求的资源(网页等)不存在。
  • 500,内部服务器错误。

HTTP状态码由三个十进制数字组成,第一个十进制数字定义了状态码的类型,后两个数字没有分类的作用,HTTP状态码共分为五种五种类型:

分类分类描述
1**信息,服务器收到请求,需要请求者继续执行操作
2**成功,操作被成功接收并处理
3**重定向,需要进一步的操作以完成请求
4**客户端错误,请求包含语法错误或无法完成请求
5**服务器错误,服务器在处理请求的过程中发生了错误

5.4 Django中的响应对象

构造函数格式:

HttpResponse(content=响应体,content_type=响应体数据类型,status=状态码)

作用:

向客户端浏览器返回响应,同时携带响应体内容。

常用的 Content-Type 如下:

  • text/html(默认的,html文件)
  • text/plain(纯文本)
  • text/css(css文件)
  • text/javascript(javascript文件)
  • multipart/form-data(文件提交)
  • application/json(json传输)
  • application/xml(xml文件)

在编辑器中,使用不同的响应类即可。

image-20240108100314014

5.5 GET请求和POST请求

定义:无论是 GET 还是 POST,统一都由视图函数接收请求,通过判断request.method 区分具体的请求动作。

样例

if request.method == 'GET':
    处理GET请求时的业务逻辑
elif request.method == 'POST':
	处理POST请求时的业务逻辑
else:
    其他请求业务逻辑

5.5.1 GET处理

  • GET请求动作,一般用于向服务器获取数据。

  • 能够产生GET请求的场景:

    • 浏览器地址栏中输入URL,回车。
    • <a href="地址?参数=值&参数=值">
    • form 表单中的 methodget
  • GET请求方式中,如果有数据需要传递给服务器,通常会用查询字符串(Query String)传递:

    • URL格式:xxx?参数名1=值1&参数名2=值2...

      如:http://127.0.0.1:8000/page1?a=100&b=200

    • 服务器端接收参数,获取客户端请求GET请求提交的数据。

      方法示例:

      request.GET['参数名']
      request.GET.get('参数名','默认值')
      # 适用于参数有多个值的情况
      request.GET.getlist('参数名')

    image-20240108103057403

5.5.2 POST处理

  • POST请求动作,一般用于向服务器提交大量/隐私数据。

  • 客户端通过表单等POST请求将数据传递给服务器端,如:

    <form method='post', action='/login'>
        姓名:<input type='text' name='username'>
        <input type='submit' value='登陆'>
    </form>
  • 使用post方式接收客户端数据。

    request.POST['参数名']
    request.POST.get('参数名','')
    request.POST.getlist('参数名')

    POST请求需要取消CSRF验证,否则Django会拒绝客户端发来的POST请求,报403响应。image-20240108104306814

    settings.py文件中,注释掉django.middleware.csrf.CsrfViewMiddlewareimage-20240108104613012

    image-20240108104452771

    image-20240108104918403

6.静态文件

  • 静态文件配置 - settings.py 中

    # 静态文件:CSS、JavaScript、Images
    
    ## 静态文件路由配置
    STATIC_URL = '/static/'
    
    ## 静态文件存储路径配置
    STATICFILES_DIRS = (os.path.join(BASE_DIR,"static"),)
    
    # 需要在指定的路径下创建static目录,存放静态文件进行访问

    image-20240119115354613

7.Django应用及分布式路由

  • 应用在 Django 项目中是一个独立的业务模块,可以包含自己的路由、视图、模板、模型。

7.1 创建应用

  1. manage.py 中的子命令 startapp 创建应用文件夹。
  2. settings.py 中的 INSTALLED_APPS 列表中配置安装此应用。image-20240119133532831

7.2 分布式路由

Django 中,主路由配置文件(urls.py)可以不处理用户具体路由,主路由配置文件可以做请求的分发(分布式请求处理),具体的请求可以由各自的应用来处理。

image-20240119133731691

分布式路由配置步骤:

  1. 主路由中调用 include 函数。

    # 语法:inclued('app名称.url模块名')
    # 作用:用于将当前路由转到各个应用的路由配置文件的 urlpatterns 进行分布式处理
    # 示例如下:
    from django.urls import path, re_path, include
    urlpatterns = [
        path('admin/', admin.site.urls),
        ...
        path('demo/', include('demo.urls'))
    ]
  2. 应用下配置 urls.py

    # 应用下手动创建urls.py 文件,内容结构同主路由完全一致
    from django.urls import path
    from . import views
    urlpatterns = [
        path('index', views.index_view)
    ]
  3. 访问页面路由。

    image-20240119134544771

8.模型层及ORM介绍

DjangoMTV 结构。

image-20240119134709377

8.1 Django 配置 mysql

8.1.1 依赖安装

  • 安装 mysqlclient [版本 mysqlclient 1.3.13以上]

  • 安装前需要确认 ubuntu 是否已安装 python3-devdefault-libmysqlclient-dev

    python3-dev # 1.检查安装第三方包的情况
    sudo apt list --installed | grep -E 'libmysqlclient-dev|python3-dev'
    # 2.未安装则进行安装
    sudo apt install python3-dev default-libmysqlclient-dev
  • 安装 mysqlclient 第三方包。

    sudo pip3 install mysqlclient

8.1.2 Django配置

  • 修改settings.py文件中 DATABASES 配置。

    DATABASES = {
        'default':{
            'ENGINE':'django.db.backends.mysql',
            'HOST':'host_ip',
            'PORT':3306,
            'USER':'username',
            'PASSWORD':'password',
            'NAME':'datanabaseNmae'
        }
    }
  • 执行数据库的迁移操作。

    python manage.py migrate

    image-20240123155958495

8.1.3 迁移中出现的报错

进行数据库迁移时,报错:SystemError: PY_SSIZE_T_CLEAN macro must be defined for ‘#’ formats,这个是Python3.10不兼容引起的,需要升级用到的 mysqlclient 包到最新版本。

image-20240123154340874

解决方法:升级mysqlclient到最新版。

image-20240123154750086

再次进行数据库迁移,正常无报错。

image-20240123154957933

8.2 模型

8.2.1 模型

  • 模型是一个Python类,它是由django.db.models.Model派生出的子类。
  • 一个模型类代表数据库中的一张表。
  • 模型类中每一个类属性都代表数据库中的一个字段。
  • 模型是数据交互的接口,是表示和操作数据库的方法和方式。

8.2.2 ORM框架

  • 定义:ORM(Object Relational Mapping)即对象关系映射,它是一种程序技术,允许你使用类和对象对数据库进行操作,从而避免通过SQL语句操作数据库。
  • 作用:
    1. 简历模型类和表之间的对应关系,允许我们通过面向对象的方式来操作数据库。
    2. 根据设计的模型类生成数据库中的表格。
    3. 通过简单的配置就可以进行数据库的切换。
  • 优点:
    • 只需要面向对象编程,不需要面向数据库编写代码。
    • 实现了数据模型与数据库的解耦,屏蔽了不同数据库操作上的差异。

image-20240124113237393

8.2.3 ORM框架使用

  1. 创建APP应用,创建的应用需要在settings.py中进行注册。

    创建APP应用

    python manage.py startapp bookstore

    注册应用

    INSTALLED_APPS = [
        ...
        'bookstore'
    ]
  2. 编写数据库ORM模型。

    在 bookstore 应用的模型文件 models.py 中添加如下代码。

    class Books(models.Model):
        title = models.CharField("书名", max_length=50, default='')
        price = models.DecimalField("定价", max_digits=7, decimal_places=2, default=0.0)
  3. 数据库迁移。

    # 生成迁移文件
    python manage.py makemigrations
    # 执行迁移脚本,同步数据库
    python manage.py migrate

    image-20240124114021537

8.2.4 ORM概念及操作

8.2.4.1 模型类定义
from django.db import models

class 模型类名(models.Model):
    字段名 = models.字段类型(字段选项)
8.2.4.2 基础字段
  • BooleanField()
    • 数据库类型:tinyint(1)
    • 编程语言中:使用 True 或者False 来表示值
    • 在数据库中:使用1或0来表示具体的值
  • CharFild()
    • 数据库类型:varchar
    • 注意:必须要指定 max_length 参数值
  • DateField()
    • 数据库类型:date
    • 作用:表示日期
    • 参数:
      1. auto_now:每次保存对象时,自动设置该字段为当前时间(取值:True/False)
      2. auto_now_add:当对象第一次被创建时,自动设置当前时间(取值:True/False)
      3. default:设置当前时间值(取值:字符串格式时间,如”2019-6-1“)
    • 以上三个参数只能多选一。
  • DateTimeField()
    • 数据库类型:datetime(6)
    • 作用:表示日期和时间
    • 参数同 DateField
  • FloatField()
    • 数据库类型:double
    • 编程语言和数据库中都是用小数表示值
  • DecimalField()
    • 数据库类型:decimal(x,y)
    • 编程语言中:使用小数表示该列的值
    • 在数据库中:使用小数
    • 参数:
      • max_digits:位数总数,包括小数点后的位数,该数必须大于等于decimal_palces
      • decimal_places:小数点后的数字数量
  • EmailField()
    • 数据库类型:varchar
    • 编程语言和数据库中使用字符串
  • IntegerField()
    • 数据库类型:int
    • 编程语言和数据库中使用整数
  • ImageField()
    • 数据库类型:varchar(100)
    • 作用:在数据库中为了保存图片的路径
    • 编程语言和数据库中使用字符串
  • TextField()
    • 数据库类型:longtext
    • 作用:表示不定长的字符数据

代码示例:

class Author(models.Model):

    name = models.CharField("姓名", max_length=11, default='')
    age = models.IntegerField("年龄")
    emal = models.EmailField("邮箱")
8.2.4.3 字段选项
  • 字段选项,指定创建的列的额外的信息
  • 允许出现多个字段选项,多个选项之间使用 , 隔开
  • 注:字段选项的官方文档地址(https://docs.djangoproject.com/en/2.2/ref/models/fields/#field-options)
  • primary_key
    • 如果设置为 True,表示该列为主键,如果指定一个字段为主键,则此数据库表不会创建 id 字段。
  • blank
    • 设置为 True 时,字段可以为空,设置为 False 时,字段为必填项。
  • null
    • 如果设置为 True,表示该列值允许为空。
    • 默认为 False,如果此选项为False,建议加入 default 选项来设置默认值。
  • default
    • 设置所在列的默认值,如果字段选项 null = False,建议添加此项。
  • db_index
    • 如果设置为 True,表示为该列增加索引
  • unique
    • 如果设置为 True,表示该字段在数据库中的值必须是独一无二的。
  • db_column
    • 指定列的名称,如果不指定的话则采用属性名作为列名。
  • verbose_name
    • 设置此字段在 admin 界面上显示的名称。

代码示例:

# 创建一个属性,表示用户名称,长度40个字符,必须是唯一的,不能为空,添加索引
name = models.CharField("用户名称", max_length=40, unique=True, null=False, default='', db_index=True)
8.2.4.4 Meta类
  • 使用内部Meta类,来给模型赋予属性,Meta类下有很多内建的类属性,可对模型类做一些控制。
  • db_table,修改当前模型对应的表名。
  • unique_together,用于组合多个字段,这些字段组成的值,在数据表中是独一无二的。
  • 注:Meta类官方文档(https://docs.djangoproject.com/zh-hans/2.2/ref/models/options/)

代码示例:

class Books(models.Model):
    title = models.CharField("书名", max_length=50, default='')
    price = models.DecimalField("定价", max_digits=7, decimal_places=2, default=0.0)
    info = models.CharField("描述", max_length=100, default='')

    class Meta:
        db_table = 'book' # 可以改变当前模型类对应的表名

image-20240124132657474

8.2.4.5 创建数据

Django ORM 使用一种直观的方式把数据库表中的数据表示成 Python 对象。

创建数据中每一条记录就是创建一个数据对象。

# 方法一
MyModel.objects.create(属性1=值1,属性2=值2,...)
# 成功:返回创建好的实体对象
# 失败:抛出异常

# 方法二
# 创建MyModel 实例对象,并调用 save() 进行保存
obj = MyModel(属性=)
obj.属性 =
obj.save()

Django提供了一个交互式的操作项目叫 Django Shell,它能够在交互模式用项目工程的代码执行相应的操作。

注意:项目代码发生变化时,需要重新进入Django Shell。

启动方式:

python manage.py shell

代码示例:

from bookstore.models import Books
# 方式一
Books.objects.create(title='大江大河', price=53.00, info='大江大河', pub='东海工业出版社', market_price=55.00)
# 方式二
bookObj = Books(title='Django入门教程', price=53.00, info='大江大河', pub='清华大学出版社', market_price=55.00)
bookObj.info = 'Django入门教程'
bookObj.save()

image-20240124135252237

数据库中对应出现两条记录。

image-20240124135355074

8.2.4.6 查询方法
  • 数据库的查询需要使用管理器对象进行。

  • 通过 MyModel.objects 管理器方法调用查询方法。

    方法说明
    all()查询全部记录,返回QuerySet查询对象
    get()查询符合条件的单一记录
    filter()查询符合条件的多条记录
    exclude()查询符合条件之外的全部记录
  • all()方法

    用法:MyModel.objects.all()

    作用:查询 MyModel 实体中所有的数据,等同于 select * from table

    返回值:QuerySet 容器对象,内部存放 MyModel 实例

    代码示例:

    from bookstore.models import Books
    books = Books.objects.all()
    for book in books:
      print("书名",book.title, "出版社", book.pub)

    image-20240124214503814

    也可以在 ORM 模型中定义 __str__ 方法,直接返回表中的信息。

    class Books(models.Model):
        title = models.CharField("书名", max_length=50, default='', unique=True)
        price = models.DecimalField("定价", max_digits=7, decimal_places=2, default=0.0)
        info = models.CharField("描述", max_length=100, default='')
        pub = models.CharField("出版社", max_length=100, null=False, default='')
        market_price = models.DecimalField("图书零售价", max_digits=7, decimal_places=2, default=0.0)
    
        class Meta:
            # 可以改变当前模型类对应的表名
            db_table = 'book'
    
        def __str__(self):
            return '%s_%s_%s_%s' % (self.title, self.info, self.pub, self.market_price)

    image-20240124215031402

  • values('列1','列2')

    用法:MyModel.objects.values(...)

    作用:查询部分列的数据并返回,等同于 select 列1,列2 from table

    返回值:QuerySet,返回查询结果容器,容器内存字典,每个字典代表一条数据,格式为:{'列1':'值1','列2':'值2'}

    代码示例:

    from bookstore.models import Books
    books = Books.objects.values('title','pub')
    print(books)

    image-20240124215602575

  • values_list('列1','列2')

    用法:MyModel.objects.values_list(...)

    作用:返回元组形式的查询结果,等同于select 列1,列2 from table

    返回值:QuerySet 容器对象,内存存放元组。

    会将查询出来的数据封装到元组中,再封装到查询集合 QuerySet 中。

    代码示例:

    books = Books.objects.values_list('title','pub')
    print(books)

    image-20240124215939781

  • order_by()

    用法:MyModel.objects.order_by('-列','列')

    作用:与 all() 方法不同,它会用 SQL 语句的 ORDER BY 子句对查询结果进行根据某个字段选择性的进行排序。

    说明:默认是按照升序排序,降序排序则需要在列前增加 - 表示。

    代码示例:

    from bookstore.models import Books
    books = Books.objects.order_by('title')
    print(books)
    
    books = Books.objects.order_by('-title')
    print(books)

    image-20240124220636265

  • filter(条件)

    语法:MyModel.objects.filter(属性1=值1,属性2=值2)

    作用:返回包含此条件的全部数据集

    返回值:QuerySet 容器对象,内部存放 MyModel 示例

    说明:当多个属性在一起时为 "与" 关系

    代码示例:

    # 查询书中出版社为"清华大学出版社"的图书
    from bookstore.models import Books
    books = Books.objects.filter(pub='清华大学出版社')
    print(books)
    
    # 查询Author实体中name为王老师并且年龄为28岁的
    from bookstore.models import Author
    authores = Author.objects.all()
    print(authores)
    
    authores = Author.objects.filter(name='王老师',age=28)
    print(authores)

    image-20240124222243402

    image-20240124222115904

  • exclude(条件)

    语法:MyModel.objects.exclude(条件)

    作用:返回不包含此条件的全部数据集

    代码示例:

    from bookstore.models import Author,Books
    books = Books.objects.exclude(pub='清华大学出版社')
    print(books)

    image-20240124223219617

  • get(条件)

    语法:MyModel.objects.get(条件)

    作用:返回满足条件的唯一数据

    说明:该方法只能返回一条数据,查询结果多于一条数据则抛出 Model.MultipleObjectsReturned 异常,查询结果如果没有数据则抛出 Model.DoesNotExist 异常

    代码示例:

    image-20240124223717187

8.2.4.7 查询谓词

定义:做更灵活的条件查询时需要使用查询谓词。

说明:每一个查询谓词是一个独立的查询功能。

  • __exact,等值匹配

    代码示例

    Books.objects.filter(id__exact=1)
    # 等同于 select * from author where id = 1
  • __contains,包含指定值

    Books.objects.filter(name__contains='Python')
    # 等同于 select * from books where name link '%Python%'
  • __startswith,以 xxx 开始

  • __endswith,以 xxx 结束

  • __gt,大于指定值

    Author.objects.filter(age__gt=50)
    # 等同于 select * from author where age > 50
  • __gte,大于等于

  • __lt,小于

  • __lte,小于等于

  • __in,查找数据是否在指定范围内

    Author.objects.filter(country__in=['中国','日本','韩国'])
    # 等同于 select * from author where country in ('中国','日本','韩国')
  • __range,查找数据是否在指定的区间范围内

    # 查找年龄在某一区间内的所有作者
    Author.objects.filter(age__range=(35,50))
    # 等同于 select * from author where age between 35 and 50
8.2.4.8 更新操作
  • 修改单个实体的某些字段值的步骤:

    • 查,通过 get() 得到要修改的实体对象。
    • 改,通过 对象.属性 的方式修改数据。
    • 保存,通过 对象.save() 保存数据。

    代码示例:

    image-20240124231329221

  • 批量更新数据

    • 直接调用 QuerySet 的 update(属性=值) 实现批量修改

    代码示例:

    image-20240124231621418

8.2.4.9 删除操作
  • 单个数据删除的步骤:

    • 查找查询结果对应的一个数据对象
    • 调用这个数据对象的 delete() 方法实现删除。

    代码示例:

    image-20240124231847751

  • 批量删除的步骤:

    • 查找查询结果集中满足条件的全部 QuerySet 查询集合对象
    • 调用查询集合对象的 delete() 方法实现删除

    代码示例:

    image-20240124232229707

  • 伪查询

    • 通常不会轻易在业务里把数据真正删掉,取而代之的是做伪删除,即在表中添加一个布尔型字段(is_active),默认是True,执行删除时,将欲删除数据的 is_active 字段设置为 False。
    • 注意:用伪删除时,确保显示数据的地方,均添加了 is_active = True 的过滤查询。
8.2.4.10 F对象和Q对象

F对象

  • 一个F对象代表数据库中某条记录的字段的信息

  • 作用:

    • 通常是对数据库中的字段值在不获取的情况下进行操作
    • 用于类属性(字段)之间的比较
  • 语法

    from django.db.models import F
    F('列名')
  • 代码示例:

    # 示例1:更新Book示例中所有的零售价涨10元
    from django.db.models import F
    from bookstore.models import Book
    Book.objects.all().update(market_price=F('market_price')+10)

    image-20240124233659683

    # 示例2:对数据库中两个字段的值进行比较,列出那些书的零售价高于定价
    from django.db.models import F
    from bookstore.models import Book
    Book.objects.filter(market_price__gt=F('price'))

    image-20240124234128957

Q对象

  • 当在获取查询结果集使用复杂的逻辑或|、逻辑非 ~ 等操作时可以借助Q对象进行操作

  • 作用:在条件中用来实现除 and(&) 以外的 or(|) 或 not(~) 操作

  • 语法:

    from django.db.models import Q
    Q(条件1)|(Q条件2) # 条件1成立或条件2成立
    Q(条件1)&Q(条件2) # 条件1和条件2同时成立
    Q(条件1)&~Q(条件2) # 条件1成立且条件2不成立
  • 代码示例:

    # 示例1:查找清华大学出版社的书或价格低于50的书
    Book.objects.filter(Q(pub='清华大学出版社')|Q(price__lt=50))
    # 示例2:查找不···	是机械出版社且价格低于50的书
    Book.objects.filter(Q(price__lt=50)|~Q(pub='清华大学出版社'))
8.2.4.11 聚合查询和原生数据库操作

聚合查询

聚合查询是指对一个数据表中的一个字段的数据进行部分或全部进行统计查询,查 bookstore 数据表中的全部书的平均价格,查询所有书的总个数等,都要使用聚合查询。

聚合查询分为:整表聚合、分组聚合

聚合查询 - 整表聚合

不带分组的聚合查询是指将全部数据进行集中统计查询。

聚合函数[需要导入]:

# 导入方法
from django.db.models import *
# 聚合函数:Sum、Avg、Count、Max、Min

语法:MyModel.objects.aggregate(结果变量名=聚合函数('列'))

返回结果:结果变量名和值组成的字典,格式为:

代码示例:

image-20240125074907149

聚合查询 - 分组聚合

分组聚合是指通过计算查询结果中每一个对象所关联的对象集合,从而得出总计值(也可以是平均值或总和),即为查询集的每一项生成聚合。

语法:QuerySet.annotate(结果变量名=聚合函数('列'))

返回值:QuerySet

代码示例:

image-20240125080525797

原生数据库操作

Django 页可以支持直接使用SQL语句的方式通信数据库。

查询:使用 MyModel.objects.raw() 进行数据库的查询操作。

语法:MyModel.objects.raw(sql语句,拼接参数)

返回值:RawQuery 集合对象,只支持基础的操作,比如循环

代码示例:

image-20240125081249994

原生数据库操作 - SQL注入

使用原生语句时小心SQL注入。

定义:用户通过数据上传,将恶意的SQL语句提交给服务器,从而达到攻击效果。

案例1:用户在搜索图表的SQL传入 1 or 1=1,可以查询出所有的图书数据。

image-20240125081701040

image-20240125081723909

SQL注入防范

错误 -> s1 = Book.objects.raw('select * from book where id = %s'%('1 or 1=1'))

正确 -> s1 = Book.objects.raw('select * from book where id = %s',['1 or 1=1'])

image-20240125081958288

原生数据库操作 - cursor

完全跨过模型类操作数据库 - 查询/更新/删除

  1. 导入 cursor 所在的包。

    from django.db import connection
  2. 用创建 cursor 类的构造函数创建 cursor 对象,再使用 cursor 对象,为保证在出现异常时能释放 cursor 资源,通常使用 with 语句进行创建操作。

    from django.db import connection
    with connection.cursor() as cur:
        cur.excute('执行SQL语句','拼接参数')

代码示例:

image-20240125082736075

image-20240125082824840

image-20240125083222994

原生SQL查询防止SQL注入

image-20240125083401086

9.admin后台管理

  • django 提供了比较完善的后台管理数据库的接口,可供开发过程中的调用和测试使用。
  • django 会搜集所有已注册的模型类,为这些模型类提供数据管理界面,供开发者使用。

image-20240125103018596

9.1 Admin 配置步骤

  1. 创建后台管理账号,该账号为后台管理最高权限的账号

    python3 manage.py createsuperuser

    image-20240125103323005

  2. 使用创建的用户登录管理后台。

    image-20240125103413255

9.2 注册自定义模型类

如果需要将自己定义的模型类也能在 /admin 后台管理中显示和管理,需要将自己的类注册到后台管理界面。

注册步骤:

  1. 在应用中的 admin.py 中导入注册要管理的模型 models 类,如:

    from .models import Book
  2. 调用 admin.site.register 方法进行注册,如:

    admin.site.register(自定义模型类)

代码示例:

image-20240125104514186

image-20240125104557201

image-20240125104613120

9.3 模型管理器类

作用:为后台管理界面添加便于操作的新功能。

说明:后台管理器类必须继承自 django.contrib.admin 里的 ModelAdmin类。

使用方法:

  1. 在 [应用]/admin.py 里定义模型管理类

    class xxxManager(admin.ModelAdmin):
        ...
  2. 绑定注册模型管理器和模型类

    from django.contrib import admin
    from .models import *
    admin.site.register(YYYY,xxxManager)
    # 绑定 YYY模型类与xxxManager管理器类

代码示例1:信息展示

from django.contrib import admin
from .models import Book

# Register your models here.

class BookManager(admin.ModelAdmin):
    # 列表页显示哪些字段的列
    list_display = ['id', 'title', 'pub', 'price']

admin.site.register(Book, BookManager)

image-20240125105402325

代码示例2:信息修改

from django.contrib import admin
from .models import Book

# Register your models here.
# admin.site.register(Book)

class BookManager(admin.ModelAdmin):
    # 列表页显示哪些字段的列
    list_display = ['id', 'title', 'pub', 'price']
    # 点击那个字段能够进入信息的修改页面
    list_display_links = ['title']

admin.site.register(Book, BookManager)

image-20240125105623582

image-20240125105706271

代码示例3:过滤器

from django.contrib import admin
from .models import Book

# Register your models here.
# admin.site.register(Book)

class BookManager(admin.ModelAdmin):
    # 列表页显示哪些字段的列
    list_display = ['id', 'title', 'pub', 'price']
    # 点击那个字段能够进入信息的修改页面
    list_display_links = ['title']
    # 添加过滤器
    list_filter = ['pub']

admin.site.register(Book, BookManager)

image-20240125110916475

代码示例4:搜索框

from django.contrib import admin
from .models import Book

# Register your models here.
# admin.site.register(Book)

class BookManager(admin.ModelAdmin):
    # 列表页显示哪些字段的列
    list_display = ['id', 'title', 'pub', 'price']
    # 点击那个字段能够进入信息的修改页面
    list_display_links = ['title']
    # 添加过滤器
    list_filter = ['pub']
    # 添加搜索框[模糊查询]
    search_fields = ['title']
    

admin.site.register(Book, BookManager)

image-20240125111051469

代码示例5:页面可编辑的字段

from django.contrib import admin
from .models import Book

# Register your models here.
# admin.site.register(Book)

class BookManager(admin.ModelAdmin):
    # 列表页显示哪些字段的列
    list_display = ['id', 'title', 'pub', 'price']
    # 点击那个字段能够进入信息的修改页面
    list_display_links = ['title']
    # 添加过滤器
    list_filter = ['pub']
    # 添加搜索框[模糊查询]
    search_fields = ['title']
    # 添加页面可编辑的字段
    list_editable = ['price']


admin.site.register(Book, BookManager)

image-20240125111226382

10.关系映射

在关系型数据库中,通常不会把所有数据都放在同一张表中,不易于扩展,常见的关系映射有:

  1. 一对一映射,如一个身份证对应一个人。
  2. 一对多映射,如一个班级可以有多个学生。
  3. 多对多映射,如一个学生可以报多个课程,一个课程可以有多个学生学习。

10.1 一对一映射

10.1.1 一对一映射定义

  • 一对一是表示现实事物间存在的一对一的对应关系。如:一个家庭只有一个户主,一个男人有一个妻子,一个人有一个唯一的身份证号等。

10.1.2 一对一映射创建模型

  • 语法:OneToOneField(类名,on_delete=xxx),on_delete:级联删除。

    class A(model.Model):
        ...
    class B(model.Model):
        属性 = models.OneToOneField(A, on_delete=xxx)
  • 特殊字段选项【必须】,on_delete,级联删除。

Django中的一对一映射

  1. models.CASCADE 级联删除,Django模拟SQL约束ON DELETE CASCADE的行为,并删除包含ForegnKey的对象。
  2. models.PROTECT 抛出ProtectedError 以组织被引用对象的删除,等同于 mysql 默认的RESTRICT。
  3. SET_NULL 设置 ForeignKey null,需要指定 null=True;
  4. SET_DEFAULT 将 ForeignKey 设置为其默认值,必须设置ForeignKey默认值。

代码示例:

from django.db import models

# 关系说明:一个人只有一个身份证号码,一个身份证号码对应一个人
# Create your models here.
class Person(models.Model):
    """
    个人类
    """
    name = models.CharField("姓名", max_length=11, default='', null=False)
    age = models.IntegerField("年龄", default=1)
    home = models.CharField("住址", max_length=256, default='')

    def __str__(self):
        return "%s_%s_%s" % (self.name, self.age, self.email)

class IdCard(models.Model):
    """
    身份证件
    """
    idCardNUmber = models.CharField("身份证号码", max_length=32, null=False)
    person = models.OneToOneField(Person, on_delete=models.CASCADE) # 一对一属性

image-20240203140651540

10.1.3 一对一映射创建数据

# 无外键的模型类[Person]:
person = Person.objects.create(name='南歌', age=20, home='陕西省延安市')
# 有外键的模型类[IdCard]
idcard = IdCard.objects.create(idCardNUmber='xxxx', person=person)
# 也即关联个人的主键值
idcard = IdCard.objects.create(idCardNUmber='xxxx', person_id=person_id)

直接关联外键对应的对象。

image-20240203142124625

数据库查看。

image-20240203141621367

关联外键对应对象的主键值。

image-20240203142058673

数据库查看。

image-20240203142039521

10.1.4 一对一映射查询数据

  1. 正向查询:直接通过关联的外键属性查询,则称为正向查询。

    # 通过IdCard查找Person
    from oto.models import *
    idCard = IdCard.objects.get(idCardNUmber='xxxx')
    
    print("身份证号:{idCardNUmber}的人姓名为:{name}".format(idCardNUmber=idCard.idCardNUmber, name=idCard.person.name))

    image-20240203142417516

  2. 反向查询:没有外键属性的一方,可以调用反向属性查询到关联的另一方。

    注:

    • 反向关联属性为 实例对象.引用类名(小写),如个人的反向引用为 个人对象.idcard
    • 当反向引用不存在时,则会触发异常。
    # 通过IdCard查找Person
    from oto.models import *
    
    person = Person.objects.get(name='南歌')
    person.idcard.idCardNUmber

    image-20240203142911022

    反向属性不存在时,触发异常。

    image-20240203143032384

10.2 一对多映射

10.2.1 一对多映射定义

  • 一对多是表现现实事物间存在的一对多的对应关系。如:一个学校有多个班级,一个班级有多个学生,一本图书只能属于一个出版社,一个出版社允许出版多本图书。
  • 一对多需要明确出具体角色,在多表上设置外键。

10.2.2 一对多映射创建模型

  • 语法

    # 当一个A类对象可以关联多个B类对象时
    class A(models.Model):
        ...
        
    class B(models.Model):
        属性  = models.Foreignkey(""的模型类, on_delete=xx)
    # Fortignkey 必须指定 on_delete模式

代码示例:

from django.db import models

# Create your models here.
# 关系说明:一个出版社可以有多本图书,一个图书只能对应一个出版社
class Press(models.Model):
    """
    出版社
    """
    name = models.CharField("名称", max_length=128, unique=True)


class Book(models.Model):
    """
    图书
    """
    name = models.CharField("书名", max_length=128)
    press = models.ForeignKey(Press, on_delete=models.CASCADE)

image-20240203144707614

image-20240203144835619

10.2.3 一对多映射创建数据

# 先创建一,再创建多
from otm.models import *
press = Press.objects.create(name="东北大学出版社")

Book.objects.create(name="C++教程", press=press)
Book.objects.create(name="Python教程", press_id=press_id)

image-20240203145557344

10.2.4 一对多映射查询数据

  1. 正向查询:直接通过关联的外键属性查询,则称为正向查询。

    # 通过Book查找Press
    from otm.models import *
    book = Book.objects.get(name='Python教程')
    
    print("{book}的出版社是{name}".format(book=book.name, name=book.press.name))

    image-20240203150909390

  2. 反向查询:没有外键属性的一方,可以调用反向属性查询到关联的另一方。

    # 通过 Press 查找 Book
    from otm.models import *
    press = Press.objects.filter(name='东北大学出版社')
    books = press.book_set.all()

    image-20240203151912867

10.3 多对多映射

10.3.1 多对多定义

  • 多对多表达对象之间多对多复杂关系,如:每个人都有不同的学校(小学,初中,高中,...),每个学校都有不同的学生...
  • Mysql中创建多对多需要依赖第三张表来实现。
  • Django中无需手动创建第三张表,Django自动完成。

10.3.2 多对多映射创建模型

  • 语法:在关联的两个类中的任意一个类中增加:

    属性 = models.ManyToManyField(MyModel)
  • 代码示例:

    # 关系说明:一个作者可以出版多本图书,一本图书可以被多名坐着同时编写
    
    from django.db import models
    
    # Create your models here.
    class Author(models.Model):
        name = models.CharField("作者", max_length=128)
    
    
    class Book(models.Model):
        name = models.CharField("书名", max_length=128)
        authors = models.ManyToManyField(Author)

    image-20240203152823410

10.3.3 多对多映射创建数据

# 方案一:先创建author再关联book
author1 = Author.objects.create(name="南歌")
author2 = Author.objects.create(name="EuanSu")
## 南歌和EuanSu同时写了一本《Django教程》
book = author1.book_set.create(name="Django教程")
author2.book_set.add(book)

# 方案二:先创建book再关联author
book = Book.objects.create(name="Python教程")
## 南歌和EuanSu都参与了《Python教程》的编写
author = book.authors.create(name="南歌")
book.authors.add(author)

image-20240203153600783

image-20240203154943581

10.3.4 多对多映射查询数据

  1. 正向查询:直接通过关联的外键属性查询,则称为正向查询。

    # 通过Book查询对应所有的 Author,此时多对多属性等价于objects
    ## 获取 book 对应的所有author信息
    book.authors.all() 
    ## 获取book对应作者中名字为南歌的author
    book.authors.filter(name="南歌")

    image-20240203160248197

  2. 反向查询:没有外键属性的一方,可以调用反向属性查询到关联的另一方。

    # 通过Author查询对应所有的book,利用反向属性book_set
    author.book_set.all()
    author.book_set.filter(name="Django教程")

    image-20240203160831514

11.Cookies和Session

11.1 会话

  • 从打开浏览器访问一个网站,到关闭浏览器结束此次访问,称之为一次会话。
  • HTTP协议是无状态的,导致会话状态难以保持。
  • Cookies和Session就是为了保持会话状态而诞生的两个存储技术。

11.2 Cookies

11.2.1 Cookies定义

  • Cookies是保存再客户端浏览器上的存储空间。

    • Chrome 浏览器可能通过开发者工具的 Application >> Storage >> Cookies 查看和操作浏览器端所有的Cookies值。

      image-20240203170139881

    • 火狐浏览器 通过开发者工具的 存储 >> Cookie 查看。

      image-20240203170443169

11.2.2 Cookies特点

  • Cookies 在浏览器上是以键值对的形式进行存储到,键和值都是以ASCII字符串的形式存储(不能是中文字符串)。
  • 存储的数据带有生命周期。
  • Cookies 中的数据是按照域存储隔离的,不同的域之间无法访问。
  • Cookie 的内部数据会在每次访问此网址时都会携带到服务器端,如果Cookies过大会降低响应速度。

image-20240203170944803

11.2.3 Cookies的使用

# 设置/修改Cookie
HttpResponse.set_cookie(key,value='',max_age=None,expires=None)
## key:Cookie的名字
## value:Cookie的值
## max_age:Cookie存储时间,秒为单位
## expires:具体的过期时间
## 当不指定 max_age 和 expires 时,关闭浏览器此时数据失效

# 删除/获取Cookie
HttpResponse.delete_cookie(key)
## 删除指定的key的Cookie,如果key不存在则什么也不发生

# 获取Cookies
request.COOKIES.get('Cookie名','默认值')
## 通过request.COOKIES 绑定的字典(dict)获取客户端的COOKIES数据

代码示例

# 浏览器设置一个名为uname的cookie,值是euansu,过期时间为10分钟的cookie
from django.shortcuts import render
from django.http import HttpResponse
# Create your views here.

def set_cookies(request):

    response = HttpResponse("set cookies")
    response.set_cookie("uname", "euansu", 600)
    return response

image-20240203173519032

代码示例

# 浏览器设置一个键为 my_cookie,值为123,过期时间为1个小时的cookie
def set_cookies_new(request):
    response = HttpResponse("set cookies")
    response.set_cookie("my_cookie", "123", 3600)
    return response
# 浏览器修改 my_cookie的值为123,过期时间为2个小时
def update_cookies(request):
    response = HttpResponse("update cookies")
    response.set_cookie("my_cookie", "456", 7200)
    return response

浏览器设置 cookie

image-20240203174306716

浏览器修改 cookie。

image-20240203174357891

代码示例

# 浏览器删除Cookies
def delete_cookies(request):
    response = HttpResponse("delete cookies")
    response.delete_cookie("my_cookie")
    return response

image-20240203175455270

代码示例

# 获取Cookies
def get_cookies(request):
    values = request.COOKIES.get("my_cookie", "my_cookie")
    return HttpResponse("my_cookie value is {my_cookie}".format(my_cookie=values))

image-20240203180238529

11.3 session

11.3.1 session定义

session 是在服务器上开辟一段空间用于保留浏览器和服务器交互时的重要数据。

实现方式:

  • 使用 session 需要在浏览器客户端启动 cookie,且在cookie中存储sessionid。
  • 每个客户端都可以在服务器端有一个独立的session。
  • 注意:不同的请求者之间不会共享这个数据,与请求者一一对应。

11.3.2 session初始配置

settings.py 中配置 session

  1. 向 INSTALLED_APPS 列表中添加:

    INSTALLED_APPS = [
        ...
        # 启用 sessions 应用
        'django.contrib.sessions',
    ]
  2. 向 MIDDLEWARE 列表中添加:

    MIDDLEWARE = [
        ...
        # 启用 session 中间件
        'django.contrib.sessions.middleware.SessionMiddleware',
    ]

11.3.3 session的使用

session 对象是一个类似于字典的sessionstore类型的对象,可以用类拟于字典的方式进行操作。

session 能够存储如字符串、整型、字典、列表等。

  1. 保存 session 的值到服务器。

    request.session['key'] = value
  2. 获取 session 的值。

    value = request.session['key']
    vakue = request.session.get('key', 默认值)
  3. 删除 session。

    del request.session['key']

代码示例

# 设置session
def set_session(request):

    request.session['uname'] = 'euansu'
    return HttpResponse("set session is ok!")

image-20240203183851558

代码示例

# 获取session
def get_session(request):

    value = request.session.get("uname", "")
    return HttpResponse("session unmae values is {value}".format(value=value))

image-20240203183911955

代码示例

# 修改session
def update_session(request):

    request.session['uname'] = '南歌'
    return HttpResponse("update session is ok!")

image-20240203184050356

image-20240203184211232

代码示例

# 删除session
def delete_session(request):

    del request.session["uname"]
    return HttpResponse("delete session is ok!")

image-20240203184303818

image-20240203184334420

11.3.4 session相关配置项

  1. SESSION_COOKIE_AGE

    作用:指定 sessionid 在 cookies 中的保存时长(默认是两周),如下:

    SESSION_COOKIE_AGE = 60*60*24*7*2
  2. SESSION_EXPIRE_AT_BROWSER_CLOSE = True

    设置只要浏览器关闭时,session就失效(默认为False)

11.3.5 Django session的问题

  1. django_session 表是单表设计,且该表数据量持续增持(浏览器故意删掉sessionid&过期数据未删除)。
  2. 可以每晚执行 python3 manage.py clearsessions,该命令可以删除已过期的session数据。

11.3.6 Cookies 和 session 对比

image-20240203190443016

12.缓存

12.1 缓存的定义

  • 定义:缓存是一类可以更快的读取数据的介质统称,也指其他可以加快数据读取的存储方式,一般用来存储临时数据,常用介质的是读取速度很快的内存。

  • 意义:视图渲染有一定成本,数据库的频繁查询过高,所以对于低频变动的页面可以考虑使用缓存技术,减少实际渲染次数,用户拿到响应的时间成本会更低。

  • Django 缓存的实现方法

    # Django 官网
    # https://docs.djangoproject.com/en/5.0/topics/cache/
    # given a URL, try finding that page in the cache
    if the page is in the cache:
        return the cached page
    else:
        generate the page
        save the generated page in the cache (for next time)
        return the generated page
  • 缓存场景:

    1. 博客列表页
    2. 电商商品详情页

    场景特点:缓存的地方,数据变动频率较少。

12.2 缓存的配置

12.2.1 Mysql缓存

将缓存的数据存储在数据库中。

说明:尽管存储介质没有更换,但是当把一次负责查询的结果直接存储到表里,比如多个条件的过滤查询结果,可避免重复进行复杂的查询,提升效率。

settings.py 中添加 CACHES 配置块。

CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.db.DatabaseCache",
        "LOCATION": "my_cache_table", # 缓存表的名称
        "TIMEOUT": 300, #缓存的保存时间,单位秒,默认值是300
        "POTIONS":{
            "MAX_ENTRIES": 300, # 缓存最大的数据条数
            "CULL_FREQUENCY": 2 # 缓存条数达到最大值时,删除 1/CULL_FREQUENCY 的缓存数据
        }
    }
}

创建缓存表。

python3 manage.py createcachetable

image-20240204142505782

查看数据库。

image-20240204142408207

使用 mysql 进行缓存,会将数据存储在指定的 mysql 表中。

image-20240205235812467

12.2.2 Redis缓存

Redis是一个内存数据库,可用于缓存。

settings.py 中添加如下 CACHES 块。

CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.redis.RedisCache",
        "LOCATION": "redis://username:password@127.0.0.1:6379", # username、password需要看redis是否启用身份认证,如未启用,这里则不需要配置。
        # "LOCATION": [
        #    "redis://127.0.0.1:6379",  # leader
        #    "redis://127.0.0.1:6378",  # read-replica 1
        #    "redis://127.0.0.1:6377",  # read-replica 2
        # ],
        # 如果有多个redis服务器时,这样配置即可
    }
}

12.2.3 本地内存缓存

数据缓存到服务器内存中,如果配置文件中没有指定其他缓存,那么这是默认的缓存。如果你想获得内存缓存的速度优势,但又不具备运行 Memcached 的能力,可以考虑使用本地内存缓存后端。这个缓存是每进程所有(见下文)和线程安全的。

settings.py 中添加如下 CACHES 块。

# 说明:如下仅供测试,建议将数据存储到内存数据库中,如redis
# 每个进程都会有自己的私有缓存实例,这意味着不可能进行跨进程缓存,也即本地内存缓存的内存效率不是特别高,所以对于生产环境来说,它可能不是一个好的选择。对于开发来说是不错的选择。
CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.locmem.LocMemCache",
        "LOCATION": "unique_snowflake", 
    }
}

12.2.4 文件系统缓存

将缓存的数据存储到本地文件中。

settings.py 中添加如下 CACHES 块。

CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.filebased.FileBasedCache",
        "LOCATION": "/home/euansu/tmp", # 这个是文件夹的路径
        # "LOCATION": "c:\test\cache", # windows下示例
    }
}

使用文件系统缓存,会在配置的目录下生成对应的缓存文件。

image-20240205235509437

12.3 整体缓存策略

整体缓存这里指的是直接缓存一个视图函数全部的结果,与下部分的局部缓存形成对照。

12.3.1 视图函数

from django.views.decorators.cache import cache_page

@cache_page(30)
def my_view(request):
    ...

代码示例

from django.shortcuts import render
from django.views.decorators.cache import cache_page
from django.http import HttpResponse
# Create your views here.
from datetime import datetime
import pytz

@cache_page(30)
def mysql_cache(request):
    """
    MysqlCache
    :param request: 
    :return: 
    """
    target_timezone = pytz.timezone('Asia/Shanghai')
    current_time = datetime.now()
    current_time_shanghai = current_time.replace(tzinfo=pytz.utc).astimezone(target_timezone)

    time_string = current_time_shanghai.strftime("%Y-%m-%d %H:%M:%S")

    return HttpResponse(time_string)

image-20240205205732896

12.3.2 路由函数

from django.views.decorators.cache import cache_page

urlpatterns = [
    path('foo/', cache_page(60)(my_view)),
]

代码示例

from django.urls import path
from django.views.decorators.cache import cache_page
from . import views

urlpatterns = [
    path("mysql_cache/", cache_page(60)(views.mysql_cache)),
]

image-20240205234033811

12.4 局部缓存策略

视图函数局部耗时较多,但视图函数其他区域并不耗时。

12.4.1 使用场景

  • 使用场景

    # 局部耗时较多,但视图函数其他区域并不耗时
    from django.shortcuts import render
    
    def index(request):
        # 时间复杂度极高的渲染
        book_list = Book.objects.all() # 假设此处耗时2s
        return render(request,'idnex.html',locals())

12.4.2 局部缓存的使用

# 先引入cache对象

## 方式一:使用caches['CACHE配置key']导入具体对象
from django.core.cache import cache
cache1 = caches['default']
cache2 = caches['custom']

## 方式二:
from django.core.cache import cache
# 相当于直接引入 CACHES 配置项中的'default'项

12.4.3 局部缓存的方法

  1. cache.set(key, value, timeout),存储缓存

    key:缓存的key,字符串类型
    value:Python对象
    timeout:缓存存储时间(s),默认为CACHES中的TIMEOUT值
    返回值:None
  2. cache.get(key),获取缓存

    key:缓存的key
    返回值:为key的具体值,如果没有数据,则返回None
  3. cache.add(key, value),存储缓存,只在key不存在时生效

    返回值:True[存储成功] or False[存储失败]
  4. cache.get_or_set(key, value, timeout),如果未获取到数据,则执行set操作

    返回值:value
  5. cache.set_many(dict, timeout),批量存储缓存

    dict:key和value的字典
    timout:存储的时间(s)
    返回值:插入不成功的key的数组
  6. cache.get_many(key_list),批量获取缓存数据。

    key_list:包含key的数组
    返回值:渠道的key和value的字典
  7. cache.delete(key),删除key的缓存数据。

    返回值:None
  8. cache.delete_many(key_list),批量删除。

    返回值:None

12.4.4 局部缓存测试

代码示例:

# 设置cache
from django.core.cache import cache

cache.set("uname", "euansu", 300)

cache.get("uname")

image-20240206003131256

数据库中出现对应cache记录。

image-20240206003015431

# 修改cache
cache.set("uname", "nange", 300)

cache.get("uname")

image-20240206003235729

# 删除cache
cache.delete("uname")

cache.get("uname")

image-20240206003341884

查询数据库,对应的缓存消失

image-20240206003400681

12.5 浏览器缓存策略

浏览器也具备缓存技术,对于浏览器来说,每次向服务器发出请求都是耗时的操作,如果本身浏览器内部就具备当前 url 的内容,则一定时间内可以不必给服务器发消息,从而提升网页体验,降低服务器的请求压力。

162db6359673e7d0~tplv-t2oaga2asx-jj-mark_3024_0_0_0_q75

12.5.1 浏览器缓存-强缓存

不会向服务器发送请求,直接从缓存中读取资源。

服务器和浏览器之间的请求,通过如下两个响应头进行沟通。

  1. 响应头 - Expires

    定义:缓存过期时间,用来指定资源到期的时间,是服务器端的具体的时间点
    样例:Expires:Thu, 02 Apr 2030 05:15:08 GMT
  2. 响应头 - Cache-Control

    在HTTP/1.1中,Cache-Control主要用于控制网页缓存,比如当 Cache-Control:max-age=120 代表请求创建后的120秒,缓存失败
    说明:目前服务器都会带着这两个头同时响应给浏览器,浏览器优先使用 Cache-Control

例如如下请求,浏览器中显示已缓存。

image-20240206004848246

刷新页面,但浏览器中仅有一次请求。

image-20240206004910849

查看请求响应,发现返回的响应中有 Cache-ControlExpires 两个响应头。

image-20240206005142750

这里的 max-age=60 是通过 cache-page 实现的。

image-20240206005554767

修改 cache_page 的参数,响应头中的 max-age 也发生变化。

image-20240206005755552

12.5.2 浏览器缓存-协商缓存

图片、静态文件等这类比较费带宽且不易变化的数据,浏览器会跟服务器协商,确认当前的缓存是否可用,如果可用,服务器不需要返回数据,浏览器继续使用原来缓存的数据,如果文件不可用,则返回最新的数据。

  1. Last-Modified 响应头和 If-Modified-Since 请求头。

    说明:

    • Last-modified 为文件的最近修改时间,浏览器第一次请求静态文件时,服务器第一次请求静态文件时,服务器如果返回Last-Modified响应头,则代表该资源为需要协商的缓存。
    • 当缓存到期后,浏览器将获取到的 Last-Modified 值作为请求头 If-Modified-Since 的值,与服务器发请求协商,服务器端返回304响应码[响应体为空],代表缓存继续使用,200响应码代表缓存不可用[响应体为最新资源]。

    代码示例

    from django.http import HttpResponse
    from django.utils import timezone
    
    def last_modified(request):
        """
        测试last-modified请求头
        :param request:
        :return:
        """
        last_modified_time = timezone.now()
    
        # 检查 If-Modified-Since 头
        if request.META.get('HTTP_IF_MODIFIED_SINCE'):
            # 如果资源没有发生变化,返回 304 Not Modified
            return HttpResponse(status=304)
    
        # 如果资源发生了变化,设置 Last-Modified 响应头
        response = HttpResponse("Your content here")
        response['Last-Modified'] = last_modified_time.strftime('%a, %d %b %Y %H:%M:%S GMT')
        return response

    image-20240206012530126

  2. ETag 响应头和 If-None-Match 请求头。

    说明:

    • ETag时服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成),只要资源有变化,ETag就会重新生成。
    • 缓存到期后,浏览器将ETag响应头的值作为If-None-Match请求头的值,给服务器发请求协商,服务器接到请求头后,比对文件标识,不一致则认为资源不可用,返回200响应码[响应体为最新资源],可用则返回304响应码。

    代码示例

    from django.http import HttpResponse
    import hashlib
    
    def etag_func(request):
        # 获取或计算资源的内容(假设这里是一个字符串)
        resource_content = "Your content here"
    
        # 计算 ETag(使用 MD5 散列作为示例,你可以根据需要选择其他方法)
        etag = hashlib.md5(resource_content.encode('utf-8')).hexdigest()
    
        # 检查 If-None-Match 头
        if request.META.get('HTTP_IF_NONE_MATCH') == etag:
            # 如果资源没有发生变化,返回 304 Not Modified
            return HttpResponse(status=304)
    
        # 如果资源发生了变化,设置 ETag 响应头
        response = HttpResponse(resource_content)
        response['ETag'] = etag
        return response

    首次访问返回200。

    image-20240206012804087

    再次请求,响应变为304。

    image-20240206012848251

13.中间件

13.1 中间件的定义

  • 中间件是Django请求/响应处理的钩子框架。它是一个轻量级的、低级的“插件”系统,用于全局改变Django的输入或输出。
  • 中间件以类的形式体现。
  • 每个中间件组件负责做一些特定的功能。例如,Django包含一个中间件组件AuthenticationMiddleware,它使用会话将用户与请求关联起来。

13.2 编写中间件

  • 中间件类必须继承自django.utils.deprecation.MiddlewareMixin类。
  • 中间件类必须实现下列五个方法中的一个或多个:
    • process_request(self, request),执行路由之前被调用,在每个请求上调用,返回None或HttpResponse对象。
    • process_view(self, request, callback, callback_args, callback_kwargs),调用视图之前被调用,在每个请求上调用,返回None或HttpResponse对象。
    • process_response(self, request, response),所有响应返回浏览器被调用,在每个请求上调用,返回HttpResponse对象。
    • process_exception(self, request, exception),当处理过程中抛出异常时调用,返回一个HttpResponse对象。
    • process_template_response(self, request, response),在视图函数执行完毕且视图返回的对象中包含render方法时被调用,该方法需要返回实现了render方法的响应对象。

注:中间件中的大多数方法在返回None时表示忽略当前操作进入下一项事件,当返回HttpResponse对象时表示此请求结束,直接返回给客户端。

13.3 注册中间件

  • settings.py中需要注册一下自定义的中间件

    MIDDLEWARE = [
        ...
    ]
  • 注意:配置为数组,中间件被调用时以“先上到下”再“由下到上”的顺序调用。

13.4 中间件的使用

  1. 首先是创建中间件的目录以及文件,如下图所示。

    image-20240209124029373

  2. 编写中间件的代码。

    from django.utils.deprecation import MiddlewareMixin
    
    class customMiddleware(MiddlewareMixin):
        """
        自定义中间件
        """
        def process_request(self, request):
            """
            请求达到路由前被调用
            :param request:
            :return:
            """
            print("customMiddleware process_request 被调用")
    
        def process_view(self, request, callback, callback_args, callback_kwargs):
            """
            调用视图函数前被调用
            :param request:
            :param callback:
            :param callback_args:
            :param callback_kwargs:
            :return:
            """
            print("customMiddleware process_view 被调用")
    
        def process_response(self, request, response):
            """
            响应返回前被调用
            :param request:
            :param response:
            :return:
            """
            print("customMiddleware process_response 被调用")
            return response
  3. 注册中间件。

    # settings.py
    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',
        # 注册中间件
        'middleware.custommiddleware.customMiddleware',
    ]
  4. 中间件调用测试。

    image-20240209124219952

  5. 中间件被调用时以“先上到下”再“由下到上”的顺序调用。

    • 增加一个新的中间件“customMiddleware2”。

      class customMiddleware2(MiddlewareMixin):
          """
          自定义中间件
          """
          def process_request(self, request):
              """
              请求达到路由前被调用
              :param request:
              :return:
              """
              print("customMiddleware2 process_request 被调用")
      
          def process_view(self, request, callback, callback_args, callback_kwargs):
              """
              调用视图函数前被调用
              :param request:
              :param callback:
              :param callback_args:
              :param callback_kwargs:
              :return:
              """
              print("customMiddleware2 process_view 被调用")
      
          def process_response(self, request, response):
              """
              响应返回前被调用
              :param request:
              :param response:
              :return:
              """
              print("customMiddleware2 process_response 被调用")
              return response
    • 注册中间件。

      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',
          'middleware.custommiddleware.customMiddleware',
          'middleware.custommiddleware.customMiddleware2',
      ]
    • 调用测试。

      image-20240209130826387

14.Django内建用户系统

14.1 Django中的用户认证

  • Django带有一个用户认证系统系统,它处理用户用户账号、组、权限以及基于cookie的用户会话。
  • 用户可以直接使用Django自带的用户表。
  • 官方文档:https://docs.djangoproject.com/zh-hans/2.2/topics/auth/

14.2 用户系统表的基本字段

模型类位置 from django.contrib.auth.models import User

字段名含义
username用户名
password密码
email邮箱
first_name
last_name
is_superuser是否管理员账号
is_staff是否可以访问admin管理界面
is_active是否是活跃用户,默认为True,一般不删除用户,而是将用户的is_active设为False
last_login上一次登录时间
date_joined用户创建的时间

14.3 用户系统的基本模型操作

  1. 创建普通用户 create_user:

    from django.contrib.auth.models import User
    user = User.objects.create_user(username='用户名', password='密码', email='邮箱', ...)
  2. 创建超级用户 create_superuser:

    from django.contrib.auth.models import User
    user = User.objects.create_superuser(username='用户名', password='密码', email='邮箱', ...)
  3. 删除用户:

    from django.contrib.auth.models import User
    try:
        user = User.objects.get(username='用户名')
        user.is_active = False
        user.save()
        print("删除普通用户成功")
    except:
        print("删除普通用户失败")
  4. 校验密码:

    # 说明:如果用户名密码校验成功则返回对应的User对象,否则返回None
    from django.contrib.auth import authenticate
    user = authenticate(username=username, password=password)
  5. 修改密码:

    from django.contrib.auth.models import User
    try:
        user = User.objects.get(username='用户名')
        user.set_password('123456')
        user.save()
        print("修改密码成功")
    except:
        print("修改密码失败")
  6. 登录状态保持:

    from django.contrib.auth import login,authenticate
    def login_view(request):
        user = authenticate(username='用户名', password='密码')
        login(request, user)
  7. 登录状态校验:

    from django.contrib.auth.decorators import login_required
    @login_required
    def index_view(request):
        # 该视图必须为用户登录状态下才可以访问
        # 当前登录用户可以通过request.user获取
        login_user = request.user
        ...
  8. 用户注销登录:

    from django.contrib.auth import logout
    def logout_view(request):
        logout(request)

代码示例:

  1. 创建 templates 目录,放置 html 页面。

    image-20240210104737887

  2. 编写对应的template页面。

    register.html,用户注册页。

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Register</title>
    </head>
    <body>
        <form method="post" action="{% url 'register_page' %}">
            {% csrf_token %}
            {{ form }}
            <input type="submit" value="Submit">
        </form>
    </body>
    </html>

    login.html,用户登录页。

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Login</title>
    </head>
    <body>
        <form method="post" action="{% url 'login' %}">
            {% csrf_token %}
            {{ form }}
            <input type="submit" value="Submit">
        </form>
    </body>
    </html>

    homepage.html,首页。

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>homepage</title>
    </head>
    <body>
        <form method="post" action="{% url 'logout' %}">
            {% csrf_token %}
            欢迎访问项目
            <input type="submit" value="注销用户">
        </form>
    </body>
    </html>
  3. 编写对应的视图函数。

    from django.shortcuts import render
    from django.contrib.auth.models import User
    from django.http import HttpResponseRedirect, HttpResponse
    from django.contrib.auth import authenticate, login, logout
    from .forms import loginForm, registerForm
    from django.contrib.auth.decorators import login_required
    
    # Create your views here.
    def login_page(request):
        """
        项目登录面
        :param request:
        :return:
        """
        # return render(request, 'hello.html')
        if request.method == 'POST':
            form = loginForm(request.POST)
            if form.is_valid():
                # 处理表单提交逻辑,可以访问 form.cleaned_data 获取表单字段的值
                username = form.cleaned_data['username']
                password = form.cleaned_data['password']
                if not username or not password:
                    return HttpResponse("请输入正确的参数")
                # 校验密码
                user = authenticate(username=username, password=password)
                print('校验用户')
                print(user)
                if not user:
                    return HttpResponse("用户名或密码错误")
                else:
                    # 记录会话状态
                    login(request, user)
                    return HttpResponseRedirect('/homepage')
        else:
            form = loginForm()
    
        return render(request, 'login.html', {'form': form})
    
    
    def register_page(request):
        """
        项目注册页面
        :param request:
        :return:
        """
        # return render(request, 'hello.html')
        if request.method == 'POST':
            form = registerForm(request.POST)
            if form.is_valid():
                # 处理表单提交逻辑,可以访问 form.cleaned_data 获取表单字段的值
                username = form.cleaned_data['username']
                password1 = form.cleaned_data['password1']
                password2 = form.cleaned_data['password2']
                if not username or not password1 or not password2:
                    return HttpResponse("请传入正确的参数")
                # 进行注册操作
                try:
                    if password1 != password2:
                        return HttpResponse("两次输入的密码不一致")
                    # 创建用户
                    user = User.objects.create_user(username=username, password=password1)
                    # 注册成功
                    return HttpResponseRedirect('/login')
                except Exception as e:
                    # 注册失败
                    print(e)
                    return HttpResponse("注册用户失败,请联系管理员进行处理")
        else:
            form = registerForm()
    
        return render(request, 'register.html', {'form': form})
    
    def logout_func(request):
        """
        注销登录
        :param request:
        :return:
        """
        logout(request)
        return HttpResponseRedirect('/login')
  4. 编写对应的路由函数。

    from django.urls import path, re_path
    from . import views
    urlpatterns = [
        path("", views.login_page, name='login'),
        path("register/", views.register_page, name='register_page'),
        path("logout/", views.logout_func, name='logout')
    ]
  5. 配置项目默认的登录页面。

    # settings.py
    # 配置了登录地址,当访问到需要登录的页面时,如果此时用户未登录,则跳转至登录页面
    LOGIN_URL = '/login'
  6. 页面调用测试。

    注册页面输入用户名,密码,确认密码后点击submit进行登录。

    image-20240210112043282

    成功注册后,跳转至login页面,输入刚才注册的用户名和密码,点击submit进行登录。

    image-20240210112331431

    登录成功,跳转至项目首页。

    image-20240210112412307

    再打开一个标签页,注销登录后,项目首页无法访问。

    image-20240210112605282

14.4 用户系统的扩展字段

  • 通过建立新表,跟内建表做一对一映射。
  • 继承内建的抽象User模型类。

14.4.1 继承内部抽象类

步骤:

  1. 添加新的应用。
  2. 定义模型类,集成AbstractUser。
  3. settings.py中指明AUTH_USER_MODEL='应用名.类名'。

注:该操作需要在第一次migrate之前进行,否则会报如下的错误。

image-20240210114945393

代码测试:

  1. 添加新的应用。

    python3 manage.py startapp useradmin
  2. 定义模型类,集成AbstractUser。

    from django.db import models
    from django.contrib.auth.models import AbstractUser
    # Create your models here.
    
    class UserInfo(AbstractUser):
    
        phone = models.CharField(max_length=11, default='')
  3. settings.py中指明AUTH_USER_MODEL='应用名.类名'。

    AUTH_USER_MODEL = 'useradmin.UserInfo'
  4. 执行代码迁移。

    python3 manage.py makemigrations
    python3 manage.py migrate

    image-20240210115303551

  5. 迁移成功后,查看数据库,auth_user表消失,出现了一个useradmin_userinfo的表。

    image-20240210115343556

  6. 创建新用户测试。

    from django.contrib.auth.models import User
    user = User.objects.create_user(username='euansu', password='euansu', email='euansu@euansu.cn', phone=13000000000)

    image-20240210115602783

    查看数据库

    image-20240210115647581

    使用新创建的用户登录刚才的项目,可以正常登录。

    image-20240210115823564

15.文件上传

15.1 定义&场景

  • 定义:用户可以通过浏览器将图片等文件上传至网站。
  • 场景:
    • 用户上传头像。
    • 上传流程性的文档[pdf,txt等]

15.2 上传规范-前端[html]

  • 文件上传必须为POST提交方式
  • 表单 <form> 中文件上传时必须带有 enctype="multipart/form-data" 时才会包含文件内容数据。
  • 表单中用 <input type="file" name="xxx"> 标签上传文件。

15.3 上传规范-后端[Django]

  • 视图函数中,用request.FILES取文件框的内容
  • file=request.FILES['xxx']

说明:

  1. FILE的key对应页面中file框的name值。
  2. file绑定文件流对象。
  3. file.name文件名。
  4. file.file文件的字节流数据。

配置文件的访问路径和存储路径:

  • 在settings.py中设置MEDIA相关配置,Django把用户上传的文件统称为media资源,需要与静态资源static进行区分。

    # file:settings.py
    MEDIA_URL = '/media/'
    MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

    MEDIA_URL 和 MEDIA_ROOT 需要手动绑定。

    方法:主路由中添加路由。

    # 说明:等价于做了MEDIA_URL开头的路由,Django接到该特征请求后去MEDIA_ROOT路径查找资源
    from django.conf impot settings
    from django.conf.urls.static import static
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

文件写入方案1:传统的open方式

@csrf_exempt
def file_upload(request):
    if request.method == 'GET':
        return render(request, 'file_upload.html')
    elif request.method == 'POST':
        upload_file = request.FILES['myfile']
        print("上传的文件名是:", upload_file.name)
        file_path = os.path.join(settings.MEDIA_ROOT, upload_file.name)
        with open(file_path, 'wb') as f:
            data = upload_file.file.read()
            f.write(data)
        return HttpResponse("接收文件:" + upload_file.name + "成功")

文件写入方案2:ORM

# 字段名:FileField(upload='子目录名')
@csrf_exempt
def file_upload(request):
    if request.method == 'GET':
        return render(request, 'file_upload.html')
    elif request.method == 'POST':
        upload_title = request.POST['title']
        upload_file = request.FILES['myfile']
        Content.objects.create(desc=upload_title, myfile=upload_file)
        return HttpResponse("接收文件:" + upload_file.name + "成功")

文件上传代码测试:

  1. 配置上传文件的访问路径和存储路径。

    # settings.py
    # 存储的路由
    MEDIA_URL = '/media/'
    # 存储的位置
    MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
  2. 编写html静态文件。

    # apps/templates
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>文件上传</title>
    </head>
    <body>
        <form action="/file/upload/" method="post" enctype="multipart/form-data">{% csrf_token %}
            <p>
                <input type="text" name="title">
            </p>
            <p>
                <input type="file" name="myfile">
            </p>
            <p>
                <input type="submit" value="上传">
            </p>
        </form>
    </body>
    </html>
  3. 编写model模型文件。

    from django.db import models
    
    # Create your models here.
    class Content(models.Model):
        """
        文件存储对象
        """
        title = models.CharField('文件名', max_length=11)
        #子目录的名称即为:upload_to所指定的字段
        picture = models.FileField('子目录名称', upload_to='picture')
  4. 编写view视图文件。

    port render
    from django.http import HttpResponse
    from .models import *
    # Create your views here.
    
    def file_upload(request):
    
        if request.method == 'GET':
            return render(request, 'file_upload.html')
        elif request.method == 'POST':
            title = request.POST['title']
            myfile = request.FILES['myfile']
            Content.objects.create(title=title, picture=myfile)
            return HttpResponse("文件上传成功")
        else:
            return HttpResponse("请求方法错误")
  5. 编写url路由文件。

    from django.urls import path, re_path
    from . import views
    urlpatterns = [
        path("upload/", views.file_upload, name="file_upload")
    ]
  6. 请求测试。

    image-20240211210425660

    文件上传成功。

    image-20240211210448232

    使用URL进行访问。

    image-20240211210613808

16.项目部署

16.1 基本概念

项目部署是指在软件开发完毕后,将开发机器上运行的软件实际安装到服务器上进行长期运行。

  1. 在安装机器上安装和配置同版本的环境[python,数据库等]

  2. django项目迁移

    scp /home/euansu/Code/Python/website euansu@xx.xx.xx.xx:/home/euansu/xxx
  3. 用uWSGI替代python3 manage.py runserver方法启动服务器。

  4. 配置nginx反向代理服务器。

  5. 用nginx配置静态文件路径,解决静态路径问题。

16.2 WSGI定义

WSGI(Web Server Gateway Interface)Web服务器网关接口,是Python应用程序或框架和web服务器之间的一种接口,被广泛使用。

使用Python manage.py runserver通常只在开发和测试环境中使用,当开发结束后,完善的项目代码需要在一个搞笑稳定的环境中运行,这是可以使用WSGI。

image-20240211212557096

开发环境中,runserver将http协议的内容规范成WSGI规范给Django处理,将WSGI的规范转换成http规范进行返回。

16.3 uWSGI

16.3.1 uWSGI定义

uWSGI是WSGI的一种,它实现了http协议WSGI协议以及uwsgi协议。uWSGI功能完善,支持协议众多,在Python web热度极高。

uWSGI主要以学习配置为主。

16.3.2 uWSGI安装

pip install uwsgi==2.0.18 -i https://pypi.tuna.tsinghua.edu.cn/simple/
# 检查是否安装成功,如果成功安装则会输出uWSGI==2.0.18
pip freeze | grep -i 'uwsgi'

image-20240211213954693

16.3.3 uWSGI配置

添加配置文件 项目同名文件夹/uwsgi.ini,如:website/website/uwsgi.ini

文件以[uwsgi]开头,有如下配置项:

  • 套接字方式的 IP地址:端口号 【此模式需要有nginx】

    socket=127.0.0.1:8000
  • http通信方式的 IP地址:端口号

    http=127.0.0.1:8000
  • 项目当前工作目录

    chdir=/home/euansu/website
  • 项目中wsgi.py文件的目录,相对于当前工作目录

    wsgi-file=website/wsgi.py
  • 进程个数

    process=4
  • 每个进程的线程个数

    threads=2
  • 服务的pid记录文件

    pidfile=uwsgi.pid
  • 服务的日志文件,配置该选项后,说明项目后台启动,且日志输出到该文件中

    daemonize=uwsgi.log
  • 开启主进程管理模式

    master=true

特殊说明,Django的setting.py需要做如下配置:

  1. 修改settings.py,将DEBUG=True改为DEBUG=False
  2. 修改settings.py,将ALLOWED_HOSTS=[] 改为 ALLOWED_HOSTS=['网站域名/服务器监听的ip地址']

实际项目的配置文件如下:

# 项目名/uwsgi.ini
[uwsgi]
# http通信方式的 IP地址:端口号
http=127.0.0.1:8000
# 项目当前工作目录
chdir=/home/euansu/Code/Python/website
# 项目中wsgi.py文件的位置
wsgi-file=website/wsgi.py
# 进程个数
process=4
# 线程个数
threads=2
# 服务的pid记录文件
pidfile=uwsgi.pid
# 服务的日志文件,是不是由后台启动以及日志输出到哪里
daemonize=uwsgi.log
# 开启主进程管理模式
master=true

16.3.4 uWSGI的运行管理

  • 启动uwsgi

    # cd到uWSGI配置文件所在目录
    uwsgi --ini uwsgi.ini

    启动以及检测进程是否成功。

    image-20240211221014350

    网页访问。

    image-20240211221046750

  • 停止uwsgi

    # cd到uWSGI配置文件所在目录
    uwsgi --stop uwsgi.pid

    image-20240211221608439

16.4 项目部署测试

  1. 将项目上传至远程linux主机。

    image-20240212013154145

  2. 启动项目,这里需要提前在远程linux主机安装python以及所需要的依赖项。

    image-20240212012824018

    页面能够正常访问到django项目的内容。

    image-20240212012903228

    注:这里启动的时候要注意,命令应该是 python manage.py runserver 0.0.0.0:8080,否则有可能出现无法访问的现象。

  3. 远程主机安装uWSGI。

    这里安装uWSGI失败,报错如下,暂时无法解决,这里改用gunicorn进行项目的部署。

    image-20240212021642842

    安装Gunicorn。

    pip install uvicorn
    pip install gunicorn

    运行项目

    # 后台启动
    gunicorn --bind 0.0.0.0:8080 website.wsgi:application --daemon

    image-20240212022910514

    页面能够正常访问项目。

    image-20240212022931270