跳到正文
南歌 EuanSu
返回

达内教育Django全套教程

Django

1.Django 介绍

1.1 组件

Django 包含的组件如下:

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 创建项目

2.2 启动服务

2.3 关闭服务

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

项目配置文件 - settings.py

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 协议的请求和响应

请求中的方法

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

5.2 Django中的请求

image-20240108094558431

5.3 响应

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

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

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

5.4 Django中的响应对象

构造函数格式:

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

作用:

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

常用的 Content-Type 如下:

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

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处理

5.5.2 POST处理

6.静态文件

7.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介绍

Django 的 MTV 结构。

image-20240119134709377

8.1 Django 配置 mysql

8.1.1 依赖安装

8.1.2 Django配置

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 模型

8.2.2 ORM框架

image-20240124113237393

ORM映射图

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 基础字段

代码示例:

class Author(models.Model):

    name = models.CharField("姓名", max_length=11, default='')
    age = models.IntegerField("年龄")
    emal = models.EmailField("邮箱")
8.2.4.3 字段选项

代码示例:

# 创建一个属性,表示用户名称,长度40个字符,必须是唯一的,不能为空,添加索引
name = models.CharField("用户名称", max_length=40, unique=True, null=False, default='', db_index=True)
8.2.4.4 Meta类

代码示例:

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 查询方法
8.2.4.7 查询谓词

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

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

8.2.4.8 更新操作
8.2.4.9 删除操作
8.2.4.10 F对象和Q对象

F对象

Q对象

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后台管理

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 一对一映射创建模型

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 一对多映射创建模型

代码示例:

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 多对多定义

10.3.2 多对多映射创建模型

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 会话

11.2 Cookies

11.2.1 Cookies定义

11.2.2 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 是在服务器上开辟一段空间用于保留浏览器和服务器交互时的重要数据。

实现方式:

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)

注意:Django 中的session数据存储在数据库中,所以使用session前需要确保已经执行migrate。

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 缓存的定义

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 使用场景

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-Control 和 Expires 两个响应头。

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 中间件的定义

13.2 编写中间件

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

13.3 注册中间件

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中的用户认证

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 用户系统的扩展字段

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 定义&场景

15.2 上传规范-前端[html]

15.3 上传规范-后端[Django]

说明:

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

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

文件写入方案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]开头,有如下配置项:

特殊说明,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的运行管理

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


评论


上一篇
SQL注入检测工具sqlmap
下一篇
Python中的单元测试