开发 python开发-Django1

开发 python开发-Django1

https://www.bilibili.com/video/BV1aCxNzUEyi?spm_id_from=333.788.player.switch&vd_source=379b5659b7b00bb6caa4cadf9cc37ad6&p=8

https://docs.djangoproject.com/zh-hans/6.0/

项目流程(重要)

创建项目、创建子应用、项目settings.py注册子应用、项目settings.py配置数据库、项目init.py文件里导入数据库并执行相应函数、子应用models.py配置模型、数据库里先创建好对应数据库、迁移模型、定义子应用视图views.py、定义子应用路由urls.py、定义项目路由urls.py

创建项目

1
2
3
4
5
6
7
8
9
10
django-admin start-project project_name

project_name>
>manage.py # 管理工具/脚本
>子应用
>project_name>
settings.py 设置相关
urls.py 路由相关
asgi.py
wsgi.py 程序入口

settings.py

1
2
3
4
5
6
7
注册子应用
数据库设置
admin时区、语言
模版路径设置
静态文件路径设置
DEBUG
BASE_DIR

运行项目

1
2
python manage.py runserver 
python manage.py runserver 127.0.0.1:8000
ALLOWED_HOSTS

项目settings.py中配置,配置完成后需要用python manage.py runserver 0:8000来启动,命令中指定具体的ip,则只能用对应ip加端口访问。命令不加ip和端口号则还是以127来访问

1
2
3
4
5
# ALLOWED_HOSTS 允许以哪个主机的形式访问后端,安全机制,只能用罗列的来访问
# 默认是 127.0.0.1
# 如果你改变了允许方式,需要将运行的ip/城名 添加进来
# 这时默认的127需要自己添加才可以再次访问
ALLOWED_HOSTS = ['192.168.31.10','127.0.0.1']

创建子应用

子应用应用里涉及到了MVT的M、V,T在工程里会讲

要先进入前面创建的项目文件内,再执行创建子应用命令!

1
2
3
4
5
6
7
8
9
10
python manage.py startapp app_name # django-admin startapp xxx一样的

app_name>
>
migrations(迁移相关)数据库
apps.py(当前子应用相关)
tests.py(测试相关)
admin.py(后台相关)
models.py(模型相关) 与migrations有关联
views.py(视图相关) 增删改查、最重要的文件 与models.py有关联

image-20260602223258170

注册/注册子应用

重复注册会报错

项目settings.py里配置列表:

1
2
3
4
5
6
7
8
9
10
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'firstapp', # 方法一 子应用名
'secondapp.apps.SecondappConfig' # 方法二 子应用类名
]

配置子应用

通过配置对应子应用的配置类进行配置

AppConfig.name属性表示这个配置类是加载到哪个应用的,每个配置类必须包含此属性,默认自动生

AppConfig.verbose_name属性用于设置该应用的直观可读的名字,此名字在Django提供的Admin管理站点中会显示

1
2
3
4
5
6
from django.apps import AppConfig


class FirstappConfig(AppConfig):
name = 'firstapp'
verbose_name = "第一个应用后台管理"

MODEL模型

ORM面向对象操作数据库

MVT设计模式中的Model,专门负责和数据库交互.

对应(models.py)由于Model中内嵌了ORM框架,所以不需要直接面向数据库编程而是定义模型类,通过 模型关和对象 完成数据库表的 增刪改查

ORM框架就是把数据库表的行与相应的对象建立关联,互相转换.使得数据库的操作面向对象

image-20260602232024600

对数据库的操作转换成对ORM的操作,类一旦定义好,后续使用时只关心对类或者实例的操作

image-20260602232553845

1
2
3
4
5
6
create database mydb charset utf-8;

create table mytable(
id int primary key,
name varchar(10)
)charset utf-8;

Django数据库开发的步骤

1.定义模型类2.模型迁移3.操作数据库

定义模型类
字段类型

AutoField:自动增长的IntegerField,通常不用指定,不指定时Django会动创建属性名为id的
自动增长属住

BooleanField布尔字段,值为True或False

NullBooleanField:支持Null、True、False三种值

CharField:字符串,参数max_length表示最大字符个数

TextField:大文本字段,一般超过4000个字符时使用

IntegerField:整数

DecimalField:十进制浮点数,参数max_digits表示总位数,参数decimal_places表示小数位数

FloatField:浮点数

DateField:日期,参数auto_now表示每次保存对象时,自动设置该字段为当前时间,用于”最后一次修改”的时间戳,它总是使用当前日期,默认为False;参数auto_now_add表示当对象第一次被创建时自动设置当前时间,用于创建的时间戳,它总是使用当前日期,默认为False;参数auto_now_add和auto_now是相互排斥的,组合将会发生错误

TimeField:时间,參数同DateField

DateTimeField:日期时间,参数同DateField

FileField:上传文件字段

ImageField:继承于FileField,对上传的内容进行校验,确保是有效的图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
from django.db import models

"""
1.定义模型类
2.模型迁移
2.1先生成迁移文件(不会在数据库中生成表,只会创建一个 数据表和模型的对应关系)
python manage.py makemigratons
2.2再迁移(会在数据库中生成表)
python manage.py migrate
3.操作数据库


在哪里定义模型
模型继承自谁就可以
ORM对应的关系:
表-->类
字段-->属性
"""
# Create your models here.
class BookInfo(models.Model):
"""
1. ORM
表 --> 类
字段 --> 属性
2.模型类需要继承自models.Model
3.模型类会自动为我们添加(生成)一个主键
4.属性名=属性类型(选项)
属性名:不要使用 python,mysql关健字
不要使用 连续的下划线(__)
属性类型:和mysql的类型类似相似
charFiled 必须设置 max_length
null 是否为空
unique 唯一
default 设置默认值
verbose_name 主要是 admin后台显示
"""
name = models.CharField(max_length=10,unique=True)
pub_date = models.DateField(null=True)
readcount = models.IntegerField(default=0)
commentcount = models.IntegerField(default=0)
is_delete = models.BooleanField(default=False)

def __str__(self):
return self.name

class PeopleInfo(models.Model):
name = models.CharField(max_length=10)
gender = models.BooleanField()
book = models.ForeignKey(BookInfo, on_delete=models.CASCADE)

"""
ForeignKey字段缺少了必需的 on_delete参数。这是 Django 2.0 版本之后强制要求的。

# 1. 级联删除(当BookInfo记录被删除时,相关PeopleInfo也被删除)
book = models.ForeignKey(BookInfo, on_delete=models.CASCADE)

# 2. 保护(禁止删除有关联数据的BookInfo)
book = models.ForeignKey(BookInfo, on_delete=models.PROTECT)

# 3. 设置为NULL(需要允许字段为NULL)
book = models.ForeignKey(BookInfo, on_delete=models.SET_NULL, null=True)

# 4. 设置为默认值
book = models.ForeignKey(BookInfo, on_delete=models.SET_DEFAULT, default=1)

# 5. 什么都不做(数据库层面可能会出错,不推荐)
book = models.ForeignKey(BookInfo, on_delete=models.DO_NOTHING)
"""

def __str__(self):
# 显示姓名、性别、所属书籍
gender_str = "男" if self.gender else "女"
return f"{self.name}({gender_str}) - 《{self.book.name}》"
不使用默认表名

默认表名为:子应用名_类名(小写)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class BookInfo(models.Model):
"""
1. ORM
表 --> 类
字段 --> 属性
2.模型类需要继承自models.Model
3.模型类会自动为我们添加(生成)一个主键
4.属性名=属性类型(选项)
属性名:不要使用 python,mysql关健字
不要使用 连续的下划线(__)
属性类型:和mysql的类型类似相似
charFiled 必须设置 max_length
null 是否为空
unique 唯一
default 设置默认值
verbose_name 主要是 admin后台显示
"""
name = models.CharField(max_length=10,unique=True)
pub_date = models.DateField(null=True)
readcount = models.IntegerField(default=0)
commentcount = models.IntegerField(default=0)
is_delete = models.BooleanField(default=False)

def __str__(self):
return self.name

class Meta:
db_table = "bookinfo"
# admin显示信息配置
# verbose_name = 'admin'
外键

在设置外键时,需要通过on_delete选项指明主表删除数据时,对于外键引用表数据如何处理,在django.db.models中包含了可选常量:
CASCADE级联,刪除主表数据时连同一起删除外键表中数据
PROTECT保护,通过抛出ProtectedError异常,来阻止删除主表中被外键应用的数据

SET_NULL设置为NULL,仅在该字段null=True允许为null时可用

SET_DEFAULT设置为默认值,仅在该字段设置了默认值时可用
SET()设置为特定值或者调用特定方法
DO_NOTHING不做任何操作,如果数据库前置指明级联性,此选项会抛出IntegrityError异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class PeopleInfo(models.Model):
# 有序字典
GENDER_CHOICES = (
(0,"male"),
(1,"female")
)
name = models.CharField(max_length=10,verbose_name='名字')
# 设置为枚举类型效果
gender = models.SmallIntegerField(choices=GENDER_CHOICES,default=0,verbose_name="性别")
description = models.CharField(max_length=200,null=True,verbose_name="描述")
book = models.ForeignKey(BookInfo, on_delete=models.CASCADE,verbose_name="书名")
is_delete = models.BooleanField(default=False,verbose_name="逻辑删除")
"""
ForeignKey字段缺少了必需的 on_delete参数。这是 Django 2.0 版本之后强制要求的。

# 1. 级联删除(当BookInfo记录被删除时,相关PeopleInfo也被删除)
book = models.ForeignKey(BookInfo, on_delete=models.CASCADE)

# 2. 保护(禁止删除有关联数据的BookInfo)
book = models.ForeignKey(BookInfo, on_delete=models.PROTECT)

# 3. 设置为NULL(需要允许字段为NULL)
book = models.ForeignKey(BookInfo, on_delete=models.SET_NULL, null=True)

# 4. 设置为默认值
book = models.ForeignKey(BookInfo, on_delete=models.SET_DEFAULT, default=1)

# 5. 什么都不做(数据库层面可能会出错,不推荐)
book = models.ForeignKey(BookInfo, on_delete=models.DO_NOTHING)
"""

def __str__(self):
# 显示姓名、性别、所属书籍
gender_str = "男" if self.gender else "女"
return f"{self.name}({gender_str}) - 《{self.book.name}》"
修改数据库

sqlite 主要是一个嵌入式的关系型数据库
主要是在 移动端使用

sqlite 属于小型的关系型数据库

中型数据库:mysql(免费)、postgreql、sqlserver(收费)

大型数据库:oracle(收费)、DB2(收费)

1
2
3
4
5
6
7
8
pip install PyMySQL # 安装驱动
1.在工程的settings.py里设置对应引擎及其配置
2.在工程的init.py文件里导入pymysql,并调用指定方法
3.数据库里手动创建指定数据库(否则报错)
create database book charset 'utf8';
4.做模型迁移

mysql -uroot -pxxx -h 127.0.0.1 --port 3306
1
2
3
4
5
6
7
8
9
10
11
12
DATABASES = {
'default': {
# 'ENGINE': 'django.db.backends.sqlite3',
# 指定引擎
'ENGINE': 'django.db.backends.mysql',
'HOST':'127.0.0.1',
'PORT':'3306',
'USER':'root',
'PASSWORD':'tyy123456',
'NAME': "book", # 指定数据库
}
}
1
2
import pymysql
pymysql.install_as_MySQLdb()
shell

python manage.py shell 类似于ipython,可在命令行进行数据库增删改查测试

1
2
3
4
python manage.py shell 
>>from .models import BookInfo
>>books = BookInfo.objects.all()
>>books
模型迁移

数据库配置在工程目录下的setttings.py

python manage.py makemigrations

迁移之后,会在迁移文件夹中生成文件(多次修改模型类,多次执行生成迁移文件命令会生成多个迁移文件)

python manage.py migrate

(会在数据库中生成表,数据库中django_migrations表会记录每一个子应用相应表的迁移文件名)

也可以先有数据库中的表,再迁移

代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
from django.db import models

"""
1.定义模型类
2.模型迁移
2.1先生成迁移文件(不会在数据库中生成表,只会创建一个 数据表和模型的对应关系)
python manage.py makemigratons
2.2再迁移(会在数据库中生成表)
python manage.py migrate
3.操作数据库


在哪里定义模型
模型继承自谁就可以
ORM对应的关系:
表-->类
字段-->属性
"""
# Create your models here.
class BookInfo(models.Model):
"""
1.主键当前会自动生成
"""
name = models.CharField(max_length=10)


class PeopleInfo(models.Model):
name = models.CharField(max_length=10)
gender = models.BooleanField()
book = models.ForeignKey(BookInfo, on_delete=models.CASCADE)

"""
ForeignKey字段缺少了必需的 on_delete参数。这是 Django 2.0 版本之后强制要求的。

# 1. 级联删除(当BookInfo记录被删除时,相关PeopleInfo也被删除)
book = models.ForeignKey(BookInfo, on_delete=models.CASCADE)

# 2. 保护(禁止删除有关联数据的BookInfo)
book = models.ForeignKey(BookInfo, on_delete=models.PROTECT)

# 3. 设置为NULL(需要允许字段为NULL)
book = models.ForeignKey(BookInfo, on_delete=models.SET_NULL, null=True)

# 4. 设置为默认值
book = models.ForeignKey(BookInfo, on_delete=models.SET_DEFAULT, default=1)

# 5. 什么都不做(数据库层面可能会出错,不推荐)
book = models.ForeignKey(BookInfo, on_delete=models.DO_NOTHING)
"""
操作数据库
新增数据

两种方式新增数据,一种需要手动入库,一种自动入库。代码写在views.py里,或者django shell里也行。下面代码第二次启动项目时会报错数据重复!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
from django.shortcuts import render
from django.http import HttpResponse
from .models import BookInfo
"""
视图
1.是python函数
2.函数的第一个参数就是 请求 和请求相关的 它是 HttpRequest的实例对象
3.我们必须要返回一个相应相应是HttpResponse的实例对象/于类实例对象
"""
# Create your views here.
def firstapp01(request):
"""
request:请求参数
template_name:哪一个模版
context=None:传给模版的参数,为字典类型
"""
context = {"name":"如花"}
return render(request,"index.html",context=context)
# return HttpResponse("firstapp01")

def firstapp02(request):
# 1.到数据库查询数据
books = BookInfo.objects.all()
# 2.组织数据
context = {"books":books}
# 3.传递给模版
return render(request,"get_all_books.html",context=context)
# return HttpResponse("firstapp02")

# 新增数据
# 方式一 会把新生成的对象返回
book = BookInfo(
name = "python",
pub_date = "2020-01-01"
)
# 需要手动调用save方法
book.save()

# 方式二 直接入库
# objects模型管理类
# 对模型的增删改查都找它
# 也会返回生成的对象 但不需要手动入库
BookInfo.objects.create(
name = 'java',
pub_date = '2026-01-01'
)
查询数据

get、all、count

1
2
3
4
5
6
7
8
9
10
############ 查询数据 ##############
try:
book = BookInfo.objects.get(id=10000) # 存在返回对应实例,不存在报错

books = BookInfo.objects.all() # 返回列表

count1 = BookInfo.objects.count()
count2 = BookInfo.objects.all().count() # count1和count2的值是一样的
except BookInfo.DoesNotExist: # Exception
pass

filter、exclude

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
"""
select from bookinfo where 条件语句
相当于where查询
filter:筛选/过滤 返回n个结果(n = 0/1/n) 返回列表
get:返回1个结果
exclude:排除掉符合条件剩下的结果 相当于not

语法形式(pycharm教学视频IDE是有提示的):
以filter(字段名__运算符=值)为例
"""
# 查询编号为1的图书
BookInfo.objects.get(id=1)
BookInfo.objects.get(id__exact=1)

BookInfo.objects.filter(id=1)
BookInfo.objects.filter(id__exact=1)
# 查询书名包念“湖'的图书
BookInfo.objects.filter(name__contains='湖')
# 查询书名以'部'结尾的图书
BookInfo.objects.filter(name__endswith='部')
# 查询书名为空的图书
BookInfo.objects.filter(name__isnull=True)
# 叠询端号为1或3或5的图书
BookInfo.objects.filter(name__in=[1,3,5])
# 查询编号大于3的图书
BookInfo.objects.filter(id__gt=3) # gt大于 gte大于等于 lt小于 lte小于等于

# 查询编号不为3的图书
BookInfo.objects.exclude(id__exact=3)
BookInfo.objects.exclude(id=3)

# 查询1980年发表的图书
BookInfo.objects.filter(pub_date__year='1980')

# 查询1990年1月1日后发表的图书
BookInfo.objects.filter(pub_date__gt='1980-1-1') # 日期格式要为这种YYYY-MM-DD

F对象

1
2
3
4
5
6
7
8
9
10
"""
F对象语法格式
filter(字段名_运算符=F('字段名'))
"""
# 查询阅读量大于等于评论量的图书
from django.db.models import F
BookInfo.objects.filter(readcount__gte=F('commentcount'))

# 查询阅读量大于等于评论量2倍的图书
BookInfo.objects.filter(readcount__gte=F('commentcount')*2)

Q对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
"""
Q对象语法格式
OR:Q(x) | Q(x)
AND:Q(x) & Q(x) # filter(x).filter(x)和filter(x,x)一样的效果
NOT: ~Q(x) # exclude一样的效果
"""
# 需要查询id大于2并且阅读量大于20的书籍(and)
#方式1 filter().filter()
BookInfo.objects.filter(id__gt=2).filter(readcount__gt=20)
# 方式2 filter(条件1,条件2)
BookInfo.objects.filter(id__gt=2,readcount__gt=20)

# 需要查询id大于2或者阅读量大于20的书籍(OR)
from django.db.models import Q
BookInfo.objects.filter(Q(id__gt=2) | Q(readcount__gt=20))

# 需要查询id不为3的书籍(NOT)
BookInfo.objects.filter(~Q(id__exact=3))

聚合函数

1
2
3
4
5
6
7
8
9
10
"""
聚合函数
Sum,Max,Min,Avg,Count
聚合函数需要使用 aggregate
语法形式是:aggragte(Xxx('字段'))
"""
# 当前数据的阅读总量
from django.db.models import Sum,Max,Min,Avg,Count
BookInfo.objects.aggregate(Sum('readcount'))
BookInfo.objects.filter().aggregate()

排序

1
2
3
4
5
6
7
8
"""
排序
order_by()
"""
# 默认升序
BookInfo.objects.all().order_by('readcount')
# 降序 -
BookInfo.objects.all().order_by('-readcount')

关联查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
"""
关联查询

书籍和人物的关系是1:n
书籍 中没有任何关于人物的字段
人物 中有关于书籍的字段 book 外键

语法形式:
1.
通过书籍查询人物信息(已知 主表数据,关联查询从表数据)
主表模型(实例对象).关联模型类名小写_set.all()
2.
通过人物查询书籍信息(已知 从表数据,关联查询主表数据)
从表模型(实例对象).外健
"""
# 查询书籍为1的所有人物信息

#通过书籍 查询人物
#1.查询书籍
book=BookInfo.objects.get(id=1)
#2.根据书籍关联人物信息
book.peopleinfo_set.all() # 系统自动生成该方法

# 查询人物为1的书籍信息
# 1.查询人物
from .models import PeopleInfo
person = PeopleInfo.objects.get(id=1)
# 2.根据人物关联查询书籍
book = person.book # 获得book实例对象
book.name

关联查询筛选

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
"""
关联查询的筛选

语法形式
我们需要的是 书籍信息,已知条件是 人物信息
我们需要的是 主表数据,已知条件是 从表信息
filter(关联模型类名小写__字段__运算符=值

我们需要的是 人物信息,已知条件是 书籍信息
我们需要是是 从表数据,已知条件是 主表信息
filter(外健字段运算符=值)
"""
# 查询图书,要求图书人物为“郭靖"
BookInfo.objects.filter(peopleinfo__name__exact='郭靖')
BookInfo.objects.filter(peopleinfo__name='郭靖')

# 查询图书,要求围书中人物的描述包含“八”
BookInfo.objects.filter(peopleinfo__description__contains='八')
# 法二
peoples = PeopleInfo.objects.filter(description__contains='八')
for people in peoples:
people.book.name

# 查询书名为“天龙八部”的所有人物
PeopleInfo.objects.filter(bookname='天龙八部')
PeopleInfo.objects.filter(bookname__exact='天龙八部')

# 查询围书阅读量大于30的所有人物
PeopleInfo.objects.filter(book__readcount__gt=30)

# 法二:
books = BookInfo.objects.filter(readcount__gt=30)
for book in books:
book.peopleinfo_set.all()
查询结果集

all、order_by、filter、exclude都是得到的QuerySet查询结果集,不是简单的列表

查询结果集可以再调用过滤器

查询结果集等价于SQL语句,只有当真正使用时才会在数据库中转换成sql语句执行。也就是view中有些结果集代码没有真正使用时,不用注释也不会影响性能

exists():判断查询集中是否有数据,如果有则返回True,没有则返回False。

查询结果集特点:

  • 惰性执行:只有真正使用时才会去数据库执行sql语句,如:books = BookInfo.objects.all()、shell中执行时当执行>>books回车时,或者循环打印时才会转换成sql语句执行
  • 缓存:使用同一个查询集,第一次使用时会发生数据库的查询,然后Djang0会把结果绥存下来,再次使用这个查询集时会使用缓存的数据,减少了数据库的查询次数。

通过数据库日志验证惰性执行:

查看mysq数据库日志可以查看对数据库的操作记录。mysql日志文件默认没有产生,需要做如下配置:

sudo vi /etc/mysql/mysql.conf.d/mysqld.cnf的68、69行

1
2
general_log_file    = /var/log/mysql/mysql.log
general_log =1

sudo service mysql restart

sudo tail -f /var/log/mysql/mysql.log

验证缓存:

下面代码前三次执行只有1需要到数据库查询,2和3都是使用缓存。4和5和6都执行了数据库查询

1
2
3
4
5
6
7
8
books = BookInfo.objects.all()
[book.id for book in books] 1
[book.id for book in books] 2
[book.id for book in books] 3

[book.id for book in BookInfo.objects.all()] 4
[book.id for book in BookInfo.objects.all()] 5
[book.id for book in BookInfo.objects.all()] 6
限制查询结果集

可以使用下标、切片方式访问查询结果集(下标不支持负数),等同于sql中的limit和offset子句

对查询集进行切片后返回一个新的查询集,不会立即执行查询。

1
2
books[0]
books[:3]
分页
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from django.core.paginator import Paginator
objects = ['john','paul','george','ringo']
p = Paginator(objects,2)

p.count # 总数据数
>>4
p.num_pages # 页数
>>2

p.page_range # 页取值范围
>>range(1,3)

page1 = p.page(1) # 第一页
>>page1.object_list
['john','paul']
page2 = p.page(2)
>>page2.object_list
['george','ringo']
>>page2.has_next()
false
1
2
3
4
5
6
7
8
9
10
from django.core.paginator import Paginator
books = BookInfo.objects.all()
# object_list 结果集/列表
# per_page 每页多少条记录
# object_list,per_page
p = Paginator(books,2)
p1 = p.page(1)
p1.object_list
p1[0]
p1[1]
更新数据
1
2
3
4
5
6
7
8
9
10
11
12
############ 更新数据 ##############
# 方式一 修改实例属性、再保存
book = BookInfo.objects.get(id=1)
book.readcount = 20
book.save()

# 方式二
# filter过滤 再调用update方法修改 返回值为整数表示修改的行数
BookInfo.objects.filter(id=1).update(
readcount = 200,
commentcount = 300
)
删除数据
1
2
3
4
5
6
7
8
############ 删除数据 ##############
# 返回值为元组(删除条数,{"当前表":条数,"关联表":条数...})
# 方式一
book = BookInfo.objects.get(id=100)
book.delete()

# 方式二
BookInfo.objects.filter(id=1000).delete()

admin后台管理

时区、语言

工程目录下的settings.py文件中可以设置admin的语言、时区

管理员

创建管理员:python manage.py createsuperuser

数据库中某一张表会存储账号密码

让后台能够管理模型

子应用的admin.py中注册相应的模型类

为了使后台能够正常显示每一行数据,模型类需要实现–str–()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
from django.db import models

"""
1.定义模型类
2.模型迁移
2.1先生成迁移文件(不会在数据库中生成表,只会创建一个 数据表和模型的对应关系)
python manage.py makemigratons
2.2再迁移(会在数据库中生成表)
python manage.py migrate
3.操作数据库


在哪里定义模型
模型继承自谁就可以
ORM对应的关系:
表-->类
字段-->属性
"""
# Create your models here.
class BookInfo(models.Model):
"""
1.主键当前会自动生成
"""
name = models.CharField(max_length=10)

def __str__(self):
return self.name

class PeopleInfo(models.Model):
name = models.CharField(max_length=10)
gender = models.BooleanField()
book = models.ForeignKey(BookInfo, on_delete=models.CASCADE)

"""
ForeignKey字段缺少了必需的 on_delete参数。这是 Django 2.0 版本之后强制要求的。

# 1. 级联删除(当BookInfo记录被删除时,相关PeopleInfo也被删除)
book = models.ForeignKey(BookInfo, on_delete=models.CASCADE)

# 2. 保护(禁止删除有关联数据的BookInfo)
book = models.ForeignKey(BookInfo, on_delete=models.PROTECT)

# 3. 设置为NULL(需要允许字段为NULL)
book = models.ForeignKey(BookInfo, on_delete=models.SET_NULL, null=True)

# 4. 设置为默认值
book = models.ForeignKey(BookInfo, on_delete=models.SET_DEFAULT, default=1)

# 5. 什么都不做(数据库层面可能会出错,不推荐)
book = models.ForeignKey(BookInfo, on_delete=models.DO_NOTHING)
"""

def __str__(self):
# 显示姓名、性别、所属书籍
gender_str = "男" if self.gender else "女"
return f"{self.name}({gender_str}) - 《{self.book.name}》"

视图view和url路由分发

ROOT_URLCONF

工程目录的settings.py里:

1
2
3
4
5
# ROOT_URLCONF工程的url的配置入口,可以修改但是默认不改

# 默认是 工程名.urls

ROOT_URLCONF = 'learn_django.urls'

路由规则

1.正则部分推荐使用r,表示字符串不转义

2.路由匹配时不能在开始加反斜杠,推荐在结束位置加反斜杠/

正确:path/、path

错误:/path、/path/

(虽然路田结尾带/能带来上述好处,但是却违背了HTTP中URL表示资源位置路径的设计理念,是否结尾带/以所属公司定义风格为准。)

3.请求url在路由匹配时,只匹配路径部分(协议、域名、请求参数不匹配)

http://127.0.0.1:8000/18/?a=10只匹配18/

工程目录的urls.py

工程目录的urls.py里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
"""
URL configuration for learn_django project.

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

print(sys.path)

"""
1.urlpatterns 是固定写法,它的值是 列表
2.我们在浏览器中输入的路径和会urlpatterns中的每一项顺序进行匹配
如果匹配成功,则直接引导到相应的模块,
如果匹配不成功(把urlpatterns中的每一个都匹配过了),则直接返回404

3.Django 1.x 时代:url()函数 + 正则表达式(r 转义 ^ 严格的开始 $ 严格的结束)
urlpatterns = [
url(r'^admin/', admin.site.urls), # 使用正则表达式
url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive), # 正则捕获组
url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive),
]

Django 2.0+ 时代:path()和 re_path()
from django.urls import path, re_path
from django.contrib import admin
from . import views
urlpatterns = [
path('admin/', admin.site.urls), # 使用简单字符串
path('articles/<int:year>/', views.year_archive), # 路径转换器
re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive), # 正则表达式
]

4.我们在浏览器中输入的路由 中 哪些部分参与正则匹配?
http://ip:port/path/?key=value
我们的http://ip:port/和 get post参数不参与正则匹配

5.如果和当前的某一项匹配成功,则引导到子应用中继续匹配
如果匹配成功,则停止匹配返回相应的视围
如果匹配不成功,则继承和后边的工程中的url的每一项继续匹配,直到匹配每一项,

"""

# 也可以在这里时直接引用到具体视图,但是建议先引用到子应用中
urlpatterns = [
path('admin/', admin.site.urls),
path('firstapp/',include('firstapp.urls')),
path('secondapp/',include('secondapp.urls')),
path('testapp/',views.test)
]

子应用的urls.py

firstapp子应用的urls.py

1
2
3
4
5
6
7
8
from django.urls import path,include
import views


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

secondapp子应用的urls.py

1
2
3
4
5
6
7
8
from django.urls import path,include
from . import views


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

子应用的views.py

firstapp子应用的views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from django.shortcuts import render
from django.http import HttpResponse
"""
视图
1.是python函数
2.函数的第一个参数就是 请求 和请求相关的 它是 HttpRequest的实例对象
3.我们必须要返回一个相应相应是HttpResponse的实例对象/于类实例对象
"""
# Create your views here.
def firstapp01(request):
return HttpResponse("firstapp01")

def firstapp02(request):
return HttpResponse("firstapp02")

secondapp子应用的views.py

1
2
3
4
5
6
7
8
from django.shortcuts import render
from django.http import HttpResponse
# Create your views here.
def secondapp01(request):
return HttpResponse("secondapp01")

def secondapp02(request):
return HttpResponse("secondapp02")

image-20260603222710952

image-20260603223154326

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from django.shortcuts import render
from django.http import HttpResponse
"""
视图
1.是python函数
2.函数的第一个参数就是 请求 和请求相关的 它是 HttpRequest的实例对象
3.我们必须要返回一个相应相应是HttpResponse的实例对象/于类实例对象
"""
# Create your views here.
def firstapp01(request):
return HttpResponse("firstapp01")

def firstapp02(request):
return HttpResponse("firstapp02")
reverse

以登录、注册成功后访问同一个页面index为例

给路由取一个别名,方便在views.py中动态获取路径。避免路由名字更改时,urls.py,views.py中多处需要进行修改。

1
2
3
4
5
6
7
8
9
# 子应用urls.py
from django.urls import path,include,re_path
from . import views

# name 就是给url起一个名字
# 我们可以通过name找到这个路由
urlpatterns = [
re_path('^index/$', views.index, name='index'),
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# views.py
from django.shortcuts import render
from django.http import HttpResponse
from django.urls import reverse
# Create your views here.
def index(request):
# viewname 通过视图名字
# 路由是动态获取的
path = reverse('index')

# 跳转页面
# 登陆成功之后需要跳转到首页
# return redirect('/home/')
# return redirect(path)
return HttpResponse('index')
namespace

避免多个子应用中相同路由别名name问题

reverse就是通过 name 来动态获取路径(路由)
如没有设置namespace 则可以通过name来获取 reverse(name)
如有设置namespace 可通过namespace:name获取 reverse(namespace:name)

django 1.x和新版本这里使用有些区别,设置主路由设置namespace后直接在views.py里使用就可以了namespace:name

1
2
3
4
5
6
7
8
9
# 主路由urls.py
urlpatterns = [
path('admin/', admin.site.urls),
# 在include 的第二个参数中添加一个namespace
# 这样的话 我们的name 就变为了 namespace:name
# namespace 习惯上使用 子应用的名字
re_path(r'^',include('book.urls',namespace='book')),

]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 子路由urls.py
from django.urls import path,include,re_path
from . import views

# name 就是给url起一个名字
# 我们可以通过name找到这个路由
# 主路由中设置namespace 这里需要指明namespace,两种方式
# 方式一
app_name = 'book'
urlpatterns = [
re_path('^index/$', views.index, name='index'),
]

# 方式二 直接将主路由中替换为下面这种方式
# re_path(r'^', include(('book.urls', 'book'), namespace='book'))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# views.py
from django.shortcuts import render
from django.http import HttpResponse
from django.urls import reverse
# Create your views here.
def index(request):
"""
登陆成功之后需要跳转到首页
注册成功之后需要跳转到首页

"""
# viewname 通过视图名字
# 路由是动态获取的
# path = reverse('index')
# print(path)

#如果我们设置了namespance 这个时候就需通过 namespace:name来获取路由
path=reverse('book:index')
print(path)
# 跳转页面
# 登陆成功之后需要跳转到首页
# return redirect('/home/')
# return redirect(path)
return HttpResponse('index')

redirect

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
from django.shortcuts import redirect, render
from django.http import HttpResponse
from django.urls import reverse
# Create your views here.
def index(request):
"""
登陆成功之后需要跳转到首页
注册成功之后需要跳转到首页

reverse就是通过 name 来动态获取路径(路由)
如没有设置namespace 则可以通过name来获取 reverse(name)
如有设置namespace 可通过namespace:name获取 reverse(namespace:name)
"""
# viewname 通过视图名字
# 路由是动态获取的
# path = reverse('index')
# print(path)

#如果我们设置了namespance 这个时候就需通过 namespace:name来获取路由
path=reverse('book:index')
# print(request.GET)
# print(path)
# 跳转页面
# 登陆成功之后需要跳转到首页
# return redirect('/home/')
# return redirect(path)
return HttpResponse('index')

def home(request):
print(request.POST) #
# body = request.body
# print(body.decode(encoding='utf-8'))
return HttpResponse('home')

def test_redirect(request):
# return redirect('/index/') #这种方式重定向时,需要重定向的路径切换时,需要改这里的值还有子应用settings里也需要改
path = reverse('book:index') # 只需要更改子应用settings
return redirect(path)
1
2
3
4
5
6
7
8
9
10
11
12
13
from django.urls import path,include,re_path
from . import views

# name 就是给url起一个名字
# 我们可以通过name找到这个路由
# 主路由中设置namespace 这里需要指明namespace,两种方式
# 方式一
app_name = 'book'
urlpatterns = [
re_path('^index/$', views.index, name='index'),# 假如注册成功后需要访问该路径
re_path('^home/$', views.home, name='home'),
re_path('^test_redirect/$', views.test_redirect)
]

view查询模型数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from django.shortcuts import render
from django.http import HttpResponse
from .models import BookInfo
"""
视图
1.是python函数
2.函数的第一个参数就是 请求 和请求相关的 它是 HttpRequest的实例对象
3.我们必须要返回一个相应相应是HttpResponse的实例对象/于类实例对象
"""
# Create your views here.
def firstapp01(request):
"""
request:请求参数
template_name:哪一个模版
context=None:传给模版的参数,为字典类型
"""
context = {"name":"如花"}
return render(request,"index.html",context=context)
# return HttpResponse("firstapp01")

def firstapp02(request):
books = BookInfo.objects.all()
context = {"books":books}
return render(request,"get_all_books.html",context=context)
# return HttpResponse("firstapp02")

状态保持

浏览器请求服务器是无状态的。
无状态:指一次用户请求时,浏览器、服务器无法知道之前这个用户做过什么,每次请求都是一次新的请求。
无状态原因:浏览器与服务器是使用Socket通信的,服务器将请求结果返回给浏览器之后,会关闭当前的Socket连接,而且服务器也会在处理页面完毕之后销毁页面对象

有时需要保持下来用户浏览的状态,比如用户是否登录过,浏览过哪些商品等实现状态保持主要有两种方式:
。在客户端存储信息使用cookile
。在服务器端存储信息使用Session

cookie的流程(重要)

image-20260619115258955

1.因为是第一次请求,所以没有任何cookie信息(请求头没有cookie信息)

2.服务器接收到请求后,没有接收到cookie信息,它就设置cookie信息
3.cookie信息设置在响应中

4.浏览器接收到这个响应之后(响应头中包含Set-Cookie字段,值为key-value),会发现响应中有cookie信息name=teast 浏览器会将cookie信息保存在浏览器中
5.浏览器第二次及其之后的请求都会携带cookie信息

6.服务器会接收到携带的cookie信息name:itcast

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
def test_cookie(request):
"""
面试题:
你如何理解cookie的?/你谈一谈cookie?
1.概念
2.流程
3.在开发过程中哪里使用了
4.你在开发过程中遇到什么印象深刻的地方


保存在客户端的数据叫做 cookie
cookie是保存在客户端(换浏览器,浏览器和服务端都没有cookie信息了)
cookie是基于域名(IP)的

0.概念
1.流程(原理)
第一次请求过程
1我们的浏览器第一次请求服务器的时候,不会携带任何cookie信息
服务器接收到请求之后,发现 请求中没有任何cookie信息
2服务器设置一个cookie,这个cookie设置在相应中
我们的浏览器接收到这个相应之后,发现相应中有cookie信息,浏览器会将cookie信息保存起来

第二次及其之后的过程
当我们的浏览器第二次及其之后的请求都会携带cookie信息
我们的服务器接收到请求之后,会发现请求中携带的cookie信息,这样的话就认识是谁的请求了

2.看效果
在浏览器里删除对应已有的cookie
第一次请求时,请求header里没有cookie信息
服务器view发现没有cookie,为响应设置cookie
浏览器响应header里能看到设置的cookie
第二次请求浏览器请求同域名header里cookie信息

保存在服务器的数据叫做 session
"""

# 1.先判断有没有cookie信息 先假设没有
cookies = request.COOKIES
# 2.获取用户名
username = request.GET.get('username','defalut') # http://127.0.0.1:8000/test_cookie/?username=ty
response = HttpResponse('test_cookie')
if not cookies:
# 3.假设没有cookie信息,我们服务器就要设置cookie信息
response.set_cookie(key='username',value='ty')
return response
cookie的过期时间和删除

新版chrome浏览器可以在f12开发者工具,application->storage中查看

过期时间为会话结束时(session)表示浏览器关闭时信息就没了,窗口关闭信息还在

image-20260619200618427

max_age

max_age默认是None,单位是秒

1
2
3
4
5
6
7
8
9
10
def test_setcookie_maxage(request):
cookies = request.COOKIES
# 2.获取用户名
username = request.GET.get('username','defalut') # http://127.0.0.1:8000/test_cookie/?username=ty
response = HttpResponse('test_setcookie_maxage')
if not cookies:
# 3.假设没有cookie信息,我们服务器就要设置cookie信息

response.set_cookie(key='username',value='ty',max_age=3600)
return response
删除cookie
1
2
3
4
5
6
7
8
9
10
def delete_cookie(request):
"""
删除cookie有两种方式
response.delete_cookie(key)
response.set_cookie(key,value,max_age=0) 上面种方法本质是它
"""
response = HttpResponse('test_setcookie_maxage')
response.delete_cookie(key='name')
# response.set_cookie(key='name',value='ty',max_age=0)
return response
session的流程

存储在数据库中,如下设置可以写,也可以不写,这是默认存储方式:

1
SESSION_ENGINE='django.contrib.sessions.backends.db'

cookie在客户端容易被删除,且敏感数据在客户端不安全

项目settings.py中已经自动注册session,包括中间件中也有session相关的配置

image-20260619225234730

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
def set_session(request):
"""
保存在服务器的数据叫做session(更安全)
session需要依赖于cookie
如果浏览器禁用了cookie(浏览器里可以禁用),则session不能实现

问题1:我换了浏览器,还能获取到 session信息吗?不可以(后期学习可以)
问题2:我不换浏览器,删除session id,则获取不到session数据
问题3:再去执行 set_sesison 的时候 会重新生成session_id

0.概念
1.流程

第一次请求:
我们第一次请求的时候可以携带一些信息(用户名/密码),cookie中没有任何信息
当我们的服务器接收到这个请求之后,进行用户名和密码的验证,验证没有问题可以设置session信息
在设置session信息的同时(session信息保存在服务器端).服务器会在响应头中设置一个sessionid的cookie信息(Set-Cookie:sessionid:xxx;Max-Age:xxxx;xxx)
客户端(浏览器)在接收到响应之后,会将cookie信息保存起来(保存 sessionid的信息)

第二次及其之后的请求:
第二次及其之后的请求都会携带 包含sessionid的cookie信息(Cookie:sessionid:xxx;xxx)
当服务器接收到这个请求之后,会获取到sessionid信息,然后进行验证,验证成功,则可以获取 session信息(session信息保存在服务器端)
"""
# 1. cookie中没有任何信息
cookies = request.COOKIES

# 2.对用户名和密码进行验证
# # 假设认为 用户名和密码正确
# username = request.GET.get('username','defalut')
# password = request.GET.get('password','defalut')
user_id = 666
# 3. 设置session信息(session信息会自动存在django数据库里,自动设置响应headers)
# request.session理解为字典
request.session['user_id'] = user_id # 如果浏览器重复请求携带相同的sessionid,这里则是更新user_id
response = HttpResponse('set_session')
return response

# Set-Cookie: sessionid=xxx;expires=xxx;Max-Age=xxx;Path=xxx

def get_session(request):
#1. 请求都会携带 session id信息
cookies = request.COOKIES
#2. 会获取到sessionid信息,然后进行验证
# 验证成功,可以获取 session信息

# request.session 字典
# user_id = request.session['user_id']
user_id = request.session.get('user_id') # 系统会自动拿浏览器请求携带的sessionid去数据库查询对应的session_key,有则能取出数据

return HttpResponse('get_session')
session存储位置

数据库、内存、混合模式

清空所有session(值部分)
清除session数据(整条)
删除session中指定键值
设置session的有效期

类视图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
"""
类视图
# 注册登录例子
注册和登录使用两个不同的视图
注册和登录使用同一个视图

类视图 是采用的面向对象的思路

1.定义类视图
继承自View(from django.views import View)
不同的请求方式有不同的业务逻辑
类视图的方法 就直接采用 http的请求方式的名字 作为我们的函数名.例如:get,post,put,dele
类视图的方法的第二个参数 必须是请求实例对象
类视图的方法 必须有返回值 返回值是HttpResopnse及其子类

2.类视图的url引导
"""

def register(request):
pass

def denglu(request):
pass

def login(request):
if request.method == 'GET':
pass
else:
pass

from django.views import View
class LoginView(View):
def get(self,request):
return HttpRequest('get')

def post(self,request):
return HttpRequest('post')
类视图原理(涉及源码分析)

子应用settings.py

views.LoginView.as_view()是返回一个函数,路由匹配时执行返回的函数,会先判断请求方法是否允许,然后类视图里有没有定义相应请求方法,后续(源码阅读)

1
2
3
4
5
6
7
8
9
10
11
12
13
urlpatterns = [
re_path('^index/$', views.index, name='index'),# 假如注册成功后需要访问该路径
re_path('^home/$', views.home, name='home'),
re_path('^test_redirect/$', views.test_redirect),
re_path('^set_cookie/$', views.set_cookie),
re_path('^test_setcookie_maxage/$', views.test_setcookie_maxage),
re_path('^delete_cookie/$', views.delete_cookie),
re_path('^set_session/$', views.set_session),
re_path('^get_session/$', views.get_session),
# url的第一个参数是 正则
# url的第二个参数是 视图函数名
re_path('^login/$', views.LoginView.as_view()),
]
登录验证(涉及源码分析)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
"""
个人中心页面 -- 必须登陆才能显示
GET 方式 展示 个人中心
P0ST 实现个人中心信息的修改

定义类视图
案例 进入个人中心前需要是否已登录验证,django已提供简便登录验证的方法
这里使用多继承 MRO
"""
from django.contrib.auth.mixins import LoginRequiredMixin
class CenterView(LoginRequiredMixin,View):
def get(self,request):
return HttpRequest('个人中心展示')

def post(self,request):
return HttpRequest('个人中心修改')

中间件(很少用)

Django中的中间件是一个轻量级、底层的插件系统,可以介入Django的请求和响应处理过程,修改Django的输入或输出。中间件的设计为开发者提供了一种无侵入式的开发方式,增强了Django框架的健壮性
我们可以使用中间件,在Django处理视图的不同阶段对输人或输出进行干预,

项目settings.py中的middleware设置

中间件的定义

middleware.py,创建这个文件的位置不需要固定,这里创建在子应用中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
"""
中间件的作用:每次请求和相应的时候都会调用

中间件的定义

中间件的使用:我们可以判断母次请求中是否携带了cookie中某些信息(举例)

请求前的执顺序是按照注册的顺序、请求后/响应后的执行顺序是 注册的倒序
"""

def simple_middleware(get_response):
# 这里是 中问件第一次调用执行的地方
# print('initllll')
def middleware(request):
#这里是 请求前
print('before request111')
response = get_response(request)

#这里就 响应后/请求后
print('after request/response111')
return response
return middleware


def simple_middleware2(get_response):
# 这里是 中问件第一次调用执行的地方
# print('init2222')
def middleware(request):
#这里是 请求前
print('before request222')
response = get_response(request)

#这里就 响应后/请求后
print('after request/response222')
return response
return middleware

项目settings.py

1
2
3
4
5
6
7
8
9
10
11
12
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
# csrf防护
# 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'book.middleware.simple_middleware',
'book.middleware.simple_middleware2'
]

模版

Template模版

项目settings.py里设置模版文件夹位置

render():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from django.shortcuts import render
from django.http import HttpResponse
"""
视图
1.是python函数
2.函数的第一个参数就是 请求 和请求相关的 它是 HttpRequest的实例对象
3.我们必须要返回一个相应相应是HttpResponse的实例对象/于类实例对象
"""
# Create your views here.
def firstapp01(request):
"""
request:请求参数
template_name:哪一个模版
context=None:传给模版的参数,为字典类型
"""
context = {"name":"如花"}
return render(request,"index.html",context=context)
# return HttpResponse("firstapp01")

def firstapp02(request):
return HttpResponse("firstapp02")

image-20260612190534063

模版语法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
"""
模版的使用
"""
from datetime import datetime
class P(View):
def get(self,request):
username = request.GET.get('username')

context = {
'username':username,
'age':60,
'birthday':datetime.now(),
'friends':['tom','jack','rose'],
'money':{
'2019':12000,
'2020':13000,
'2021':14000
},
'desc':'<script>alert("hot")</script>'
}
return render(request,'index.html',context=context)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<a href="xx">{{username}}</a>
<hr>
<!-- if条件判断 -->
我的年龄是 {{age}}
{% if age > 100 %}
年龄大于100
{% elif age > 80 %}
年龄(80,100]
{% else %}
年龄小于等于80
{% endif %}

<hr>
<!-- 列表访问 -->
朋友 {{friends}}
女朋友 {{friends.2}}

<hr>
<!-- for循环 -->
<!--ol有序列表,下面的forloop里面有很多值,用counter也可以实现有序列表 -->
<ul>
{% for item in friends %}
<li>{{ forloop.counter }} {{ item }}</li>
{% endfor %}
</ul>

<hr>
<!-- 字典访问 -->
第一年月薪 {{moneny.2019}}

<!-- 模版注释 -->
<hr>
{# 我是注释 #}
{% comment %}
我也是注释
{% endcomment %}

<hr>
<!-- 过滤器 -->
我的生日是{{birthday|date:"Y年m月j日 H时i分s秒"}}
<br>
我的简介是{{desc|safe}}
<br>
我没定义的变量:{{adcdefg|default:"None"}}
</body>
</html>
1
2
3
4
5
6
7
8
9
10
{{key}} # context里的key

{% for book in books %} # value为列表,遍历列表
<li>{{ book.name }}</li>
{% endfor %}

取列表里的值 key.0
value为字典,取字典里的值 key.username
条件判断
注释
根据key取值
value为列表
value为字典
if条件判断
for循环
注释
1
2
3
4
5
6
7
<!-- html注释,网页不会显示,但是浏览器查看源码有 -->


{# 模版语法,单行注释,网页不会显示,浏览器查看源码也没有 #}
{% comment %}
模版语法,多行注释,网页不会显示,浏览器查看源码也没有
{% endcomment %}
过滤器

使用管道符号|来应用过滤器,用于进行计算、转换操作,可以使用在变量、标签中。

如果过滤器需要参数,则使用冒号:传递参数
变量|过滤器:参款(冒号左右没有空格)

safe:禁用转义,告诉模版这个变量是安全的,可以解释执行

length:长度,返回字符串包含字符的个数,或列表、元组、字典的元素个数

default:默认值,如果变量不存在时则返回默认值

data|default:’默认值’

date:日期,用于对日期类型的值进行字符串格式化,常用的格式化字符如下:
Y表示年,格式为4位,y表示两位的年。
m表示月,格式为01,02,12等
d表示日,格式为01,02等
j表示日,格式为1,2等。
H表示时,24进制,h表示12进制的时。
i表示分,为0-59
s表示秒:为0-59
value|date:”Y年m月j日 H时i分s秒”

date
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<a href="xx">{{username}}</a>
<hr>
我的年龄是 {{age}}
{% if age > 100 %}
年龄大于100
{% elif age > 80 %}
年龄(80,100]
{% else %}
年龄小于等于80
{% endif %}
<hr>
朋友 {{friends}}
女朋友 {{friends.2}}
<hr>
<!--ol有序列表,下面的forloop里面有很多值,用counter也可以实现有序列表 -->
<ul>
{% for item in friends %}
<li>{{ forloop.counter }} {{ item }}</li>
{% endfor %}
</ul>

<hr>
第一年月薪 {{moneny.2019}}
<hr>
{# 我是注释 #}
{% comment %}
我也是注释
{% endcomment %}
<hr>
<!-- 过滤器 -->
我的生日是{{birthday|date:"Y年m月j日 H时i分s秒"}}
<br>
我的简介是{{desc|safe}}

</body>
</html>
safe

这里不用safe过滤器,script代码会被转义(确保数据没有问题时才使用safe)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
"""
模版的使用
"""
from datetime import datetime
class P(View):
def get(self,request):
username = request.GET.get('username')

context = {
'username':username,
'age':60,
'birthdaty':datetime.now(),
'friends':['tom','jack','rose'],
'money':{
'2019':12000,
'2020':13000,
'2021':14000
},
'desc':'<script>alert("hot")</script>'
}
return render(request,'index.html',context=context)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<a href="xx">{{username}}</a>
<hr>
我的年龄是 {{age}}
{% if age > 100 %}
年龄大于100
{% elif age > 80 %}
年龄(80,100]
{% else %}
年龄小于等于80
{% endif %}
<hr>
朋友 {{friends}}
女朋友 {{friends.2}}
<hr>
<!--ol有序列表,下面的forloop里面有很多值,用counter也可以实现有序列表 -->
<ul>
{% for item in friends %}
<li>{{ forloop.counter }} {{ item }}</li>
{% endfor %}
</ul>

<hr>
第一年月薪 {{moneny.2019}}
<hr>
{# 我是注释 #}
{% comment %}
我也是注释
{% endcomment %}
<hr>
<!-- 过滤器 -->
我的简介是{{desc|safe}}

</body>
</html>
default

模版里使用没有定义的变量不会报错,只是对应位置什么也不显式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<a href="xx">{{username}}</a>
<hr>
我的年龄是 {{age}}
{% if age > 100 %}
年龄大于100
{% elif age > 80 %}
年龄(80,100]
{% else %}
年龄小于等于80
{% endif %}
<hr>
朋友 {{friends}}
女朋友 {{friends.2}}
<hr>
<!--ol有序列表,下面的forloop里面有很多值,用counter也可以实现有序列表 -->
<ul>
{% for item in friends %}
<li>{{ forloop.counter }} {{ item }}</li>
{% endfor %}
</ul>

<hr>
第一年月薪 {{moneny.2019}}
<hr>
{# 我是注释 #}
{% comment %}
我也是注释
{% endcomment %}
<hr>
<!-- 过滤器 -->
我的生日是{{birthday|date:"Y年m月j日 H时i分s秒"}}
<br>
我的简介是{{desc|safe}}
<br>
我没定义的变量:{{adcdefg|default:"None"}}
</body>
</html>
模版继承

主要是为了提高代码重用,减轻开发人员的工作量

父模版:

如果发现在多个模板中某些内容相同,那就应该把这段内容定义到父模板中。

标签block:用于在父模板中预留区域,留给子模板填充差异性的内容,名字不能相同。为了更好的可读性,建议给endblock标签写上名字,这个名字与对应的block名字相同,父模板中也可以便用上下文中传递过来的数据,

子模板:
标签盛extends:继承,写在子模板文件的第一行。

1
{% extends "父模版路径" %}
预留区域,可以编写默认内容,也可以没有默认内容

子模版不用填充父模版中的所有预留区域,如果子模版没有填充,则使用父模版定义的默认值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}标题部分{% endblock title %}</title>
</head>
<body>
{% block header %}
<h1>顶部</h1>
{% endblock header %}

{% block main %}
<h1>主体部分</h1>
{% endblock main %}

{% block footer %}
<h1>底部</h1>
{% endblock footer %}
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
{% extends 'base.html' %}
{% block title %}
详情页面
{% endblock title %}

{% block main %}
<a href="#">点击</a>
{% endblock main %}

<!-- 底部部分什么也不写,就代表将base中的底部替换为空-->
{% block footer %}
{% endblock footer %}

Jinja2模版

Jinja2:是Python下一个被广泛应用的模板引擎,是由Python实现的模板语言,他的设计思想来源于Djang0的模板引擎,并扩展了其语法和一系列强大的功能,尤其是Flask框架内置的模板语言

由于django默认模板引擎功能不齐全,速度慢,所以我们也可以在Django中使用jinja2,jinja2宣称比django默认模板引擎快10-20倍

Django主流的第三方APP基本上也都同时支持Django默认模板及jinja2,所以要用jinja2也不会有多少障碍

安装jinja2
1
pip install Jinja2
配置

项目settings.py设置backend为jinja2(默认为DjangoTemplates)

使用

修改成jinja2引擎之后,使用之前template的语法可能会报错

jinja2没有多行注释

jinja2过滤器也有所不同

for循环看迭代几次是loop

loop.index:当前循环选代的次数(从1开始)

loop.index0:当前循环选代的次数(从0开始)

loop.revindex:到循环结束需要迭代的次数(从1开始)

loop.revindex0:到循环结束需要迭代的次数(从0开始)

loop.first:如果是第一次迭代·为True。

loop.last:如果是最后一次迭代·为True。

loop.length:序列中的项目数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
"""
jinja2模版的使用
"""
class usejinja2(View):
def get(self,request):
username = request.GET.get('username')

context = {
'username':username,
'age':60,
'birthday':datetime.now(),
'friends':['tom','jack','rose'],
'money':{
'2019':12000,
'2020':13000,
'2021':14000
},
'desc':'<script>alert("hot")</script>'
}
return render(request,'index_jinja2template.html',context=context)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<a href="xx">{{username}}</a>
<hr>
<!-- if条件判断 -->
我的年龄是 {{age}}
{% if age > 100 %}
年龄大于100
{% elif age > 80 %}
年龄(80,100]
{% else %}
年龄小于等于80
{% endif %}

<hr>
<!-- 列表访问 -->
<!-- 从零开始,不能使用friends[0]语法 -->
朋友 {{friends}}
女朋友 {{friends.2}}

<hr>
<!-- for循环 -->
<!--ol有序列表,下面的forloop里面有很多值,用counter也可以实现有序列表 -->
<ul>
{% for item in friends %}
<li>{{ loop }} {{ item }}</li>
{% endfor %}
</ul>

<hr>
<!-- 字典访问 -->
第一年月薪 {{money.2019}}

<!-- jinja2单行注释 -->
<hr>
{# 我是注释 #}
<!-- jinja2里没有多行注释 -->

<hr>
<!-- jinja2过滤器使用语法不一样 -->


{{ range(5) }}
</body>
</html>
过滤器

有目的地将默认配置保持在最低限度。如果一个模板写一个请求呈现(例如,使用时render())时,Jinja2后端添加全局request,csrf_input和csrf_token上下文,除此之外,这个后端不会创建一个Django风格的环境,它不知道Django过滤器和标签。要使用特定于Django的APl,必须将它们配置到环境中。

例,您可以myproject/jinja2_env.py(位置、名字都不是固定的)使用此内容创建:

1
2
3
4
5
6
7
8
9
10
11
12
from django.template.defaultfilters import date
from jinja2 import Environment


def environment(**option):
#1.创建 Environment实例
env=Environment(**option)
#2.指定(更新) jinja2的函数指向django的指定过滤器
env.globals.update({'date':date
})
#3.返回Environment实例
return env

项目settings.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
TEMPLATES = [
{
# Django 原生模板(必须保留,供 Admin 使用) 黑马django1.x时这里只需将原始的注释掉,然后添加jinja2引擎
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [], # [Path.joinpath(BASE_DIR,"templates")]
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
# Jinja2 模板(你自己用的)
{
'BACKEND': 'django.template.backends.jinja2.Jinja2',
'DIRS': [BASE_DIR / "templates"],
'APP_DIRS': False,
'OPTIONS': {
# 'environment':'jinja2.Environment', # 默认jinja2环境
'environment':'book.jinja2_env.environment', # 指定jinja2的环境
'context_processors': [
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]

jinja2模版使用过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<a href="xx">{{username}}</a>
<hr>
<!-- if条件判断 -->
我的年龄是 {{age}}
{% if age > 100 %}
年龄大于100
{% elif age > 80 %}
年龄(80,100]
{% else %}
年龄小于等于80
{% endif %}

<hr>
<!-- 列表访问 -->
<!-- 从零开始,不能使用friends[0]语法 -->
朋友 {{friends}}
女朋友 {{friends.2}}

<hr>
<!-- for循环 -->
<!--ol有序列表,下面的forloop里面有很多值,用counter也可以实现有序列表 -->
<ul>
{% for item in friends %}
<li>{{ loop }} {{ item }}</li>
{% endfor %}
</ul>

<hr>
<!-- 字典访问 -->
第一年月薪 {{ money.2019 }}

<!-- jinja2单行注释 -->
<hr>
{# 我是注释 #}
<!-- jinja2里没有多行注释 -->

<hr>
<!-- jinja2过滤器使用语法不一样 且默认的jinja2 vironment不认识django template过滤器-->
我的生日是{{date(birthday,"Y年m月j日 H时i分s秒")}}

</body>
</html>
pycharm\vscode设置

pycharm:默认的python html template是djangotemplate,编写jinja2模版时可能会显示一些不是错误的错误提示,可以改为jinja2

vscode:可以安装djLint插件,同时还需安装python -m pip install djlint,起到高亮和提示的作用

vscode右下角可以设置当前是什么类型的文档,可以选择为jinja html或者django html(两个都需要安装插件)

或者也可以设置.vscode/settings.json

CSRF

CSRF全拼为Cross Site Request Forgery译为跨站请求伪造

CSRF指攻击者盗用了你的身份,以你的名义发送恶意请求·。包括:以你名义发送邮件,发消息,盗取你的账号,甚至于购买商品,虚拟货币转账..

造成的问题:个人隐私泄露以及财产要全,

CSRF 流程

image-20260705144625146

使用短信验证码解决
使用cookie解决

同浏览器有一个同源策略,一个客户端上的网站只有协议、域名、端口都相同的网站才能获取对应网站的cookie信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# Create your views here.
"""
csrf解决方案:
一:短信验证码
二:隐藏随机码搭配cookie
"""


class TF(View):
def get(self,request):
cookies = request.COOKIES
if not cookies:
path = reverse('learn_csrf:index')
return redirect(path)
from django.middleware.csrf import get_token
'''
使用随机码
'''
csrf_token = get_token(request)
response = render(request,'learn_csrf/transfer.html',context={'csrf_token':csrf_token})
# csrf_token放在cookie中
response.set_cookie('csrf_token',csrf_token)
return response
def post(self,request):
data = request.POST
user_sms_code = request.POST.get('csrftoken')
server_sms_code = request.COOKIES.get('csrf_token')
if user_sms_code != server_sms_code:
return HttpResponse('哥们找到你了,敢转我账,你不知道验证码吧')
to_account = data.get('to_account')
money = data.get('money')
return HttpResponse(f"成功向账户:{to_account}转账:{money}")

class INDEX(View):
def get(self,request):
cookies = request.COOKIES
if cookies:
path = reverse('learn_csrf:transfer')
return redirect(path)
return render(request,'learn_csrf/index.html')
def post(self,request):
data = request.POST
print(data)
if data.get('account')=='ty' and data.get('password')=='123456':
path = reverse('learn_csrf:transfer')
response = redirect(path)
response.set_cookie(key='username',value='ty')
return response
response = HttpResponse('账号\密码错误!')
return response

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<form action="http://127.0.0.1:8000/csrf/transfer/" method="post">
<p>
<input type="hidden" name="csrftoken" value={{csrf_token}}>
<label>对方账号:</label>
<input type="text" name="to_account" value="">
</p>
<p>
<label>金额:</label>
<input type="text" name="money" value="">
</p>
<p>
<input type="submit" value="提交">
</p>

</form>

<!-- 黑客代码
<form action="http://127.0.0.1:8000/transfer" method="post">
<p>
<input type="hidden" name="to_account" value="黑客">
</p>
<p>
<input type="hidden" name="money" value="90000">
</p>
<p>
<input type="submit" value="点击领取优惠卷">
</p>

</form>

-->
</body>
</html>

静态文件

项目中的CSS、图片、js都是静态文件,一般会将静态文件放到一个单独的目录中,以方便管理。在html页面中调用时,也需要指定静态文件的路径,Django中提供了一种解析的方式配置静态文件路径,静态文件可以放在项目根目录下,也可以放在应用的目录下,由于有些静态文件在项目中是通用的,所以推荐放在项目的根目录下,方便管理

1
2
3
4
5
6
7
8
9
10
# django是如何区分 静态资源和动态资源的呢?
# 就是通过 STATIC_URL 可以设置任意名字,访问时都是去STATICFILES_DIRS里查找
# 我们在访问静态资源 http://ip:port+STATIC_URL +文件名
# django就会认为我们在访问静态资源,这个时候会去静态资源文件夹中进行匹配
STATIC_URL = 'static/'

# 告知系统静态文件在哪里
STATICFILES_DIRS = [
Path.joinpath(BASE_DIR,"static")
]

请求HttpRequest和响应HttpResponse

postman安装

软件或者浏览器插件

HttpRequest对象

回想一下,利用HTTP协议向服务器传参有几种途径?
1.提取URL的特定部分,如/weather/bejing/2018,可以在服务器端的路由中用正则表达式戳取;

2.查询字符串(query string),形如key1=value1&key2=value2;
3.请求体(body)中发送的数据,比如表单数据、json、xml;
4.在http报文的头(header)中

路由路径中包含的参数
url中的位置参数

1.我们进行正则分组的参数会传递给视图
2.定义视图的时,需要定义变量来接收参数

1
2
3
4
5
6
7
8
# 子应用urls.py
# django 1.x

#http://127.0.0.1:8000/分类id/书id/
#http://127.0.0.1:8000/category_id/book_id/
#分组来获取正则中的数据
# 根据位置来获取url中的参数
url(r'^(\d+)/(\d+)/$',detail)
1
2
3
4
5
6
# 子应用views.py
# django 1.x

def detail(request,category_id,book_id): # 按顺序传入路由正则分组的数据
print(category_id,book_id)
return HttpResponse('detail')
关键字参数(推荐)
1
2
3
4
5
6
7
8
# 子应用urls.py
# django 1.x

#http://127.0.0.1:8000/分类id/书id/
#http://127.0.0.1:8000/category_id/book_id/
#分组来获取正则中的数据
# 根据位置来获取url中的参数
url(r'^(?P<category_id>\d+)/(?P<book_id>\d+)/$',detail)
1
2
3
4
5
6
# 子应用views.py
# django 1.x

def detail(request,category_id,book_id): # 路由正则分组的名字和这里变量名字相同就行
print(category_id,book_id)
return HttpResponse('detail')
GET方式传递的查询字符串

https://search.bilibili.com/all?vt=63554966&keyword=%80%9A&from_source=websuggest_search&spm_id_from=333.1007&search_source=5&order=click

以?作为一个分隔

?前边 表示 路由

?后边 表示 get方式传递的参数 称之为 查询字符串

?key=value&key=value…

request.GET得到QueryDict

获取url中传递的参数,使用request.GET得到QueryDict(和普通字典特性相似)

1
2
3
4
5
6
7
8
9
# 子应用views.py
# django 1.x

def detail(request,category_id,book_id): # 路由正则分组的名字和这里变量名字相同就行
print(category_id,book_id)
params = request.GET
# xxx?name=ty&age=60&gender=male
print(params) # QueryDict {"name":"ty","age":60,"gender":"male"}
return HttpResponse('detail')

当同一个key有多个值时,获取到的对应value以列表组织(使用普通字典的方式获取对应key对应的多个value时,得到的不是列表,而是列表里的第一个值):xxx?name=ty&age=60&gender=male&name=yy

1
2
3
4
5
6
7
8
9
10
11
12
13
# 子应用views.py
# django 1.x

def detail(request,category_id,book_id): # 路由正则分组的名字和这里变量名字相同就行
print(category_id,book_id)
params = request.GET
# xxx?name=ty&age=60&gender=male
print(params) # QueryDict {"name":["ty","yy"],"age":60,"gender":"male"}
print(params["name"]) # 只能获取到列表里的第一个值 "ty"
print(params.get("name")) # 只能获取到列表里的第一个值 "ty"

print(parmas.getlist("name")) # ["ty","yy"]
return HttpResponse('detail')

QueryDict:get(key,默认值)、getlist(key,默认值)

Post请求体携带数据

请求体数据格式不固定,可以是表单类型字符串,可以是JSON字符事,可以是XML字符事,应区别对待。

CSRF防护

可以发送请求体数据的请求方式有POST、PUT、PATCH、DELETE,Django默认开启了CSRF防护,会对上请求方式进行CSRF防护验证,在测试时可以关闭CSRF防护机制,方法为在项目settings.py文件中注释掉CSRF中间件

1
2
3
4
5
报错
<p>You are seeing this message because this site requires a CSRF cookie when submitting forms. This cookie is
required for security reasons, to ensure that your browser is not being hijacked by third parties.</p>
<p>If you have configured your browser to disable cookies, please re-enable them, at least for this site, or for
“same-origin” requests.</p>
post发送Form表单数据

postman测试发送post请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# postman
post请求 http://127.0.0.1:8000/home/?a=1&b=2
表单:
name ty
age 60


服务器:
def home(request):
print(request.POST)
# body = request.body # 报错
# print(body.decode(encoding='utf-8'))
return HttpResponse('home')

<QueryDict: {'d': ['d'], 'a': ['v']}>
报错

header里会自动添加Content-Type:application/form-data

request.POST得到url中的参数部分,也是得到QueryDict

1
2
3
def home(request):
print(request.POST) # 也是得到QueryDict
return HttpResponse('home')
post发送json数据

postman测试post请求发送json数据

postman->body->raw->选择json

注意:json数据必须是双引号,且最后一个键值对后面不要引号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# postman
post请求 http://127.0.0.1:8000/home/?a=1&b=2
json数据:
{"name":"ty",
"age":60}


服务器:
def home(request):
print(request.POST) #
body = request.body
print(body.decode(encoding='utf-8'))
return HttpResponse('home')

<QueryDict: {}>
b'{\r\n "name":"ty",\r\n "age":60\r\n}'

header里会自动添加Content-Type:application/json

request.POST得到空QueryDict

request.body得到传递的json数据,是b’xxx’

请求头携带数据

请求头中携带的数据,使用request.META获取,得到字典数据类型

request.META[‘CONTENT_TYPE’]

CONTENT_TYPE:text/plain get请求

CONTENT_TYPE:application/json post请求携带json数据

使用postman测试,在headers中自定义添加name:ruhua

request.META[‘HTTP_NAME’] 获取到ruhua,系统自动大写key且加前缀HTTP_

其它常用HttpRquest对象属性

method:一个字符串,表示请求使用的HTTP方法,常用值括:GET、POST。
user:请求的用户对象。
path:一个字符串,表示请求的页面的完整路径,不包含域名和参数部分。
encoding:一个字符串,表示提交的数据的编码方式。
如果为None则表示使用浏览器的默认设置,一般为utf-8。
这个属性是可写的,可以通过修改它来修改访问表单数据使用的编码,接下来对属性的任何访问将使用新的encoding值。
FILES:一个类似于字典的对象,包含所有的上传文件,

HttpResponse对象

HttpResponse的content参数只能传递字符串,不要传递 对象,字典等数据

status:HTTP status code must be an integer from 100 to 599,只能使用系统规定的

content_type:是一个MIME类型(Multipurpose internet mail extension)

语法形式是:大类/小类

text/css、text/javascript、text/html

application/json

image/png、image/gif、image/jpeg等

1
return HttpResponse(content='xxx',status=200,content_type='text/plain')
JsonResponse

使用HttpResponse返回字典时需要将字典转换成字符串。使用HttpResponse的子类JsonResponse可以直接传字典

1
2
3
4
from django.http import JsonResponse

d = {'name':'tom','age':999}
return JsonResponse(d)
其它的HttpResponse子类

Django提供了一系列HttpResponse的子类,可以快速设置状态码
· HttpResponseRedirect 301
· HttpResponsePermanentRedirect 302

· HttpResponseNotModified 304

· HttpResponseBadRequest 400

· HttpResponseNotFound 404
· HttpResponseForbidden 403
· HttpResponseNotAllowed 405
· HttpResponseGone 410
· HttpResponseServerError 500

Django-ninja

报错

You called this URL via POST, but the URL doesn’t end in a slash and you have APPEND_SLASH set. Django can’t redirect to the slash URL while maintaining POST data. Change your form to point to 127.0.0.1:8000/learn_csrf/transfer/ (note the trailing slash), or set APPEND_SLASH=False in your Django settings.

1
模版里的表单url链接末尾要加/