第十三周学习笔记总结

总结

测试环境搭建

django自带一个sqlite3小型数据库,该数据库功能非常有限并且针对日期类型的数据兼容性很差。

方式1:在任意空的py文件中准备环境

import os

def main():
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'moxingceng.settings')
    import django
    django.setup()

    from app01 import models
    models.User.objects.filter()

if __name__ == '__main__':
    main()

方式2:pycharm提供测试环境

python console 命令行测试环境
python console 自带测试环境

ORM常见查询关键字

创建数据

方式一:create():创建数据,返回值就是当前创建的数据对象。

方式二:可以利用类实例化对象然后调用save方法创建。

查询数据

需要注意事项:

①当需要查询数据主键字段值的时候可以使用pk忽略掉数据字段真正的名字

②在模型类中可以定义一个__ str__方法便于后续数据对象被打印展示的是查看方便

③Queryset中如果是列表套对象那么直接for循环和索引取值但是索引不支持负数

④虽然queryset支持索引但是当queryset没有数据的时候索引会报错、推荐使用first

# __str__方法是对象被执行打印(print、页面展示、数据查询)操作的时候自动触发
def __str__(self):
    return f'对象:{self.name}'  # __str__方法必须要返回一个字符串类型的数据
关键字 说明
filter() 筛选数据、返回值是一个QuerySet(可以看成是列表套数据对象) 括号内不写查询条件、默认就是查询所有 括号内可以填写条件并且支持多个、逗号隔开、默认是and关系
all() 查询所有数据、返回值是一个QuerySet(可以看成是列表套数据对象)
first() 获取Queryset中第一个数据对象、如果为空则返回None
last() 获取Queryset中最后一个数据对象、如果为空则返回None
get() 直接根据条件查询具体的数据对象但是条件不存在直接报错、不推荐使用
values() 指定查询字段、结果是Queryset(可以看成是列表套字典数据)
values_list() 指定查询字段、结果是Queryset(可以看成是列表套元组数据)
order_by() 指定字段排序默认是升序、在字段前加负号则为降序、并且支持多个字段排序
count() 统计orm查询之后结果集中的数据个数
distinct() 针对重复的数据集进行去重、一定要注意数据对象中的主键
exclude() 针对括号内的条件取反进行数据查询、QuerySet(可以看成是列表套数据对象)
reverse() 针对已经排了序的结果集做颠倒
exists() 判断查询结果集是否有数据返回布尔值、但是几乎不用因为所有数据自带布尔值
raw() 执行SQL语句、括号里可以编写SQL语句

更新数据

update()

删除数据

delete()

查看ORM底层SQL语句

方式1:

如果是Queryset对象,那么可以直接点query查看SQL语句

方式2:

配置文件配置、打印所有的ORM操作对应的SQL语句、直接拷贝使用即可

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console':{
            'level':'DEBUG',
            'class':'logging.StreamHandler',
        },
    },
    'loggers': {
        'django.db.backends': {
            'handlers': ['console'],
            'propagate': True,
            'level':'DEBUG',
        },
    }
}

双下划线查询

filter里是不能写逻辑运算符的,而是给定了一个特定的方法去提供逻辑查询,那就是双下划线。

方法 功能
字段__gt 大于
字段__lt 小于
字段__gte 大于等于
字段__lte 小于等于
字段__in 成员运算、在什么里
字段__range 范围查询
字段__contains 模糊查询,区分大小写
字段__icontains 模糊查询,忽略大小写
字段__startswith 匹配开头
字段__endswith 匹配结尾
字段__regex 正则表达式
字段__year 按照年份筛选数据
字段__month 按照月份筛选数据
字段__day 按照天筛选数据

ORM外键字段创建

ORM中的外键创建和mysql几乎一样,我们以创建图书表,出版社表,作者表和作者详情表来举例子。

两张表 关系 方法 外键位置
书与出版社 一对多关系 ForeignKey(to='出版社表') 一对多关系也是建在多的一方,建在书的表里
书与作者 多对多关系 ManyToManyField(to='作者表') 多对多关系,可以不用自己创建第三张表
作者与作者详情 一对一关系 OneToOneField=(to='作者详情表') 一对一关系,建在查询频率较高的表中,建在作者表里

ps:三个关键字里面的参数,to用于指定跟哪张表有关系,自动关联主键。to_field\to_fields,也可以自己指定关联字段。

ManyToManyField不会在表中创建实际的字段,而是告诉django orm自动创建第三张关系表。ForeignKey、OneToOneField会在字段的后面自动添加_id后缀,如果你在定义模型类的时候自己添加了该后缀那么迁移的时候还会添加,所以不要自己加下划线id后缀。

多对多三种创建方式

1.自动创建

authors = models.ManyToManyField(to='Author')
'''
优点:orm自动创建第三张表
缺点:第三张表扩展性差
'''

2.手动创建

class Book(models.Model):
    pass
class Author(models.Model):
    pass
class Book2Author(models.Model):
    book_id = models.ForeignKey(to="Book")
    author_id = models.ForeignKey(to="Author")

'''
优点:第三张表扩展性强
缺点:无法使用正反向查询以及多对多四个方法
'''

3.半自动创建

class Book(models.Model):
    authors = models.ManyToManyField(to='Author',
                                     through='Book2Author',
                                     through_fields=('book_id','author_id')
                                    )
class Author(models.Model):
    pass
class Book2Author(models.Model):
    book_id = models.ForeignKey(to="Book")
    author_id = models.ForeignKey(to="Author")

'''
优点:第三张表扩展性强并且支持正反向查询
缺点:无法使用多对多四个方法
'''

外键字段数据操作

一对多和一对一

models.ForeignKey(to='Publish', on_delete=models.CASCADE)

models.OneToOneField(to='AuthorDetail', on_delete=models.CASCADE)

方式1:直接给实际字段添加关联数据值

models.Book.objects.create(title='python从入门到放弃',price=29800.88,publish_id=1)

方式2:间接使用外键虚拟字段添加数据对象

publish_obj = models.Publish.objects.filter(pk=2).first()
models.Book.objects.create(title='django从入门到入土',price=19888.88,publish=publish_obj)

多对多

models.ManyToManyField(to='Author')

第三张关系表添加数据

book_obj = models.Book.objects.filter(pk=1).first()
book_obj.authors.add()  # 括号内即可以填写数字值也可以填写数据对象 支持多个

第三张关系表修改数据

book_obj.authors.set()  # 括号内必须是一个可迭代对象,元素同样支持主键值或者数据对象

第三张关系表删除数据

book_obj.authors.remove()  # 括号内即可以填写数字值也可以填写数据对象 支持多个

第三张关系表清空指定数据

book_obj.authors.clear()  # 括号内无需传值 直接清空当前表在第三张关系表中的绑定记录

正反向概念

正反向的概念核心就在于外键字段在谁手上

正向查询:通过书查询出版社,外键字段在书表中

反向查询:通过出版社查询书,外键字段不在出版社表中

ORM跨表查询口诀:正向查询按外键字段名、反向查询按表名小写

基于对象的跨表查询(子查询)

基于对象的正向跨表查询

1.查询主键为1的书籍对应的出版社

1.先根据条件查询数据对象(先查书籍对象)
book_obj = models.Book.objects.filter(pk=1).first()
2.以对象为基准,思考正反向概念(书查出版社 外键字段在书表中 所以是正向查询)
print(book_obj.publish)

2.查询主键为3的书籍对应的作者

1.先根据条件查询数据对象(先查书籍对象)
book_obj = models.Book.objects.filter(pk=3).first()
2.以对象为基准 思考正反向概念(书查作者 外键字段在书表中 所以是正向查询)
print(book_obj.authors)  # app01.Author.None  因为书和作者是多对多的关系,所以需要在加个all()
print(book_obj.authors.all())

3.查询jason的作者详情

1.先根据条件查询数据对象
author_obj = models.Author.objects.filter(name='jason').first()
2.以对象为基准 思考正反向概念
print(author_obj.author_detail)

基于对象的反向跨表查询

1.查询南方出版社出版的书籍

1.先拿出版社对象
publish_obj = models.Publish.objects.filter(name='南方出版社').first()
2.思考正反向
print(publish_obj.book)  # 报错
print(publish_obj.book_set)  # app01.Book.None
print(publish_obj.book_set.all())

2.查询jason写过的书

1.先拿作者对象
author_obj = models.Author.objects.filter(name='jason').first()
2.思考正反向
print(author_obj.book)  # 报错
print(author_obj.book_set)  # app01.Book.None
print(author_obj.book_set.all())

3.查询电话是110的作者

1.先拿作者详情对象
author_detail_obj = models.AuthorDetail.objects.filter(phone=110).first()
2.思考正反向
print(author_detail_obj.author)

基于双下划线的跨表查询(连表操作)

基于双下划线的正向跨表查询

1.查询主键为1的书籍对应的出版社名称及书名

res = models.Book.objects.filter(pk=1).values('publish__name','title')
print(res)

2.查询主键为3的书籍对应的作者姓名及书名

res = models.Book.objects.filter(pk=3).values('authors__name', 'title')
print(res)

3.查询jason的作者的电话号码和地址

res = models.Author.objects.filter(name='jason').values('author_detail__phone','author_detail__addr')
print(res)

基于双下划线的反向跨表查询

1.查询南方出版社出版的书籍名称和价格

res = models.Publish.objects.filter(name='南方出版社').values('book__title','book__price')
print(res)

2.查询jason写过的书的名称和日期

res = models.Author.objects.filter(name='jason').values('book__title','book__publish_time')
print(res)

3.查询电话是110的作者姓名和年龄

res = models.AuthorDetail.objects.filter(phone=110).values('author__name','author__age')
print(res)

研究ORM跨表本质

查询主键为1的书籍对应的作者电话号码

res = models.Book.objects.filter(pk=1).values('authors__author_detail__phone')
print(res)

正反向查询进阶操作

基于双下划线的跨表查询的结果也可以是完整的数据对象,手上有条件所在的表可以不被models点,直接点最终的目标数据对应的表。

1.查询主键为1的书籍对应的出版社名称及书名

res = models.Publish.objects.filter(book__pk=1).values('name','book__title')
print(res)

2.查询主键为3的书籍对应的作者姓名及书名

res = models.Author.objects.filter(book__pk=1).values('name', 'book__title')
print(res)

3.查询jason的作者的电话号码和地址

res = models.AuthorDetail.objects.filter(author__name='jason').values('phone','addr')
print(res)

4.查询南方出版社出版的书籍名称和价格

res = models.Book.objects.filter(publish__name='南方出版社').values('title','price')
print(res)

5.查询jason写过的书的名称和日期

res = models.Book.objects.filter(authors__name='jason').values('title','publish_time')
print(res)

6.查询电话是110的作者姓名和年龄

res = models.Author.objects.filter(author_detail__phone=110).values('name','age')
print(res)

7.查询主键为1的书籍对应的作者电话号码

res = models.AuthorDetail.objects.filter(author__book__pk=1).values('phone')
print(res)
res = models.Author.objects.filter(book__pk=1).values('author_detail__phone')
print(res)

聚合查询

聚合函数有Max, Min, Sum, Avg, Count。

聚合函数 说明
Max 取最大值
Min 取最小值
Sum 求和
Avg 求平均
Count 计数

聚合查询:没有分组之前如果单纯的使用聚合函数,需要关键字aggregate、默认整体就是一组。

分组查询

分组有一个特性,默认只能够直接获取分组的字段,想要获取其他字段需要使用方法,我们也可以忽略掉该特性,将sql_mode中only_full_group_by配置移除即可。

分组两种方式:

1.按照整条数据分组

models.Book.objects.annotate()  # 按照一条条书籍记录分组

2.按照表中某个字段分组

models.Book.objects.values('title').annotate()  # 按照annotate之前values括号中指定的字段分组

F与Q查询

F查询:查询条件不是自定义的而是来自于表中其他字段

当表中已经有数据的情况下,添加额外的字段,需要指定默认值或者可以为null。

方式1:设置字段值允许为空

方式2:设置字段默认值

方式3:在终端中直接给出默认值

Q查询:可以改变filter括号内多个条件之间的逻辑运算符

from django.db.models import Q
res = models.Book.objects.filter(pk=1, publish_id=3)  # 默认是and关系
res = models.Book.objects.filter(Q(pk=1), Q(publish_id=3))  # 逗号分割还是and关系
res = models.Book.objects.filter(Q(pk=1) | Q(publish_id=3))  # 管道符是or关系
res = models.Book.objects.filter(~Q(pk=1) | Q(publish_id=3))  # 波浪号是not关系 

Q查询还可以将查询条件的字段改为字符串形式

from django.db.models import Q
q_obj = Q()
q_obj.connector = 'or'  # q对象默认的多个条件也是and关系 可以修改为or
q_obj.children.append(('pk', 1))
q_obj.children.append(('publish_id', 3))
res = models.Book.objects.filter(q_obj)

ORM查询优化

django orm查询默认都是惰性查询,只有orm的语句在后续的代码中真正需要使用的时候才会执行。还自带limit分页功能,是为了减轻数据库端以及服务端的压力。

ORM查询优化之only与defer

only:会将括号内填写的字段封装成一个个数据对象,对象在点击括号内的字段的时候不会再走数据库查询,但是对象也可以点击括号内没有的字段,只不过每次都会走数据库。

defer:与only刚好相反,数据对象点击括号内没有的字段,每次都会走数据库;数据对象点括号内没有的字段,不会走数据库查询。

select_related:括号内只能接受外键字段自动连表,得出的数据对象在点表中数据的时候都不会再走数据库查询。

prefetch_related:括号内填写外键字段,底层是子查询,得出的数据对象在点表中数据的时候都不会再走数据库查询。

ORM常见字段类型

ORM字段和mysql里字段的对应关系。

ORM MYSQL
AutoField() int auto_increment
CharField() 必须提供max_length参数,对应的数据库中是varchar类型
IntergerField() int
DecimalField() decimal
DateField() date
DateTimeField() datetime
BigIntergerField() bigint
BooleanField() 传布尔值 存0和1
TextField() 存储大段文本
FileField() 存储文件数据 自动找指定位置存储 字段存具体路径
EmailField() 本质还是varchar类型

关系字段

ForeignKey()   OneToOneField()   ManyToManyField()

ORM还支持自定义字段

ORM常见字段参数

参数 说明
primary_key 主键
max_length 最大字符长度
verbose_name 对应的名字
null 是否可以为空
default 默认值
max_digits 数字允许的最大位数
decimal_places 小数的最大位数
unique 字段的值唯一
db_index 是否为字段设置索引
auto_now 更新修改数据时间
auto_now_add 数据添加的时间
to 设置关联的表
to_field 设置关联的字段
db_constraint 是否创建外键约束,默认True
related_name 修改正向查询的字段名,之后就可以使用修改后的字段名,类似于起别名

choices:当字段数据的可能性是可以完全列举出来的时候应该考虑使用该参数

on_delete:当删除关联表中的数据时,当前表与其关联的行的行为

models.CASCADE:级联操作,当主表中被连接的一条数据删除时,从表中所有与之关联的数据同时被删除

ORM事务操作

from django.db import transaction
try:
    with transaction.atomic():
        pass  # 多条ORM语句
except Exception as e:
        print(e)

# with一结束事务就结束了,自动提交和回滚

Ajax异步提交

Ajax:页面不刷新的情况下可以与后端进行数据交互。特点:异步提交,局部刷新。

Ajax不是一门全新知识,本质就是一些js代码,我们学习Ajax直接使用jQuery封装之后的版本(语法更加简单),使用Ajax的前提必须要引入jQuery文件。

学习Ajax一定要能够发现与form表单提交数据的区别。

Ajax:提交数据页面不用刷新,原始数据还在,处理数据的过程中不影响页面其他操作

form:表单提交数据页面刷新,原始数据不在,并且处理数据的过程中无法做其他操作

基础语法

$.ajax({
    url:'',    # 控制数据的提交地址
    type:'',  # 控制请求方式(默认get请求)
    data:{},   # 组织提交的数据
    success:function(形参){
        # 异步回调函数
    }
})

数据编码格式

请求体中携带编码格式:Content-Type: ...

django针对不同编码方式对应的数据格式会采用不同的处理策略

urlencoded
    不支持携带文件数据 form表单和ajax默认的编码格式
    数据格式: username=jason&pwd=123&hobby=read
    django针对上述数据类型会全部处理到request.POST中
formdata
    支持携带文件数据 form表单可以指定的编码格式
    数据格式: 无法查阅、隐藏不让看
    django针对携带文件数据的对象会处理成request.POST request.FILES
application/json
    发送json格式数据 ajax可以指定的编码格式
    django针对json格式数据不做任何处理原封不动的放在request.body中
    data:JSON.stringify(),  # 序列化方法将数据转为json格式
    contentType:'application/json'  # 不写默认是urlencoded编码
FormData对象
    ajax携带文件数据的方式
    data:formDataObj,
    contentType:false,  # 不使用任何编码
    processData:false,  # 不处理数据对象
    django针对携带文件数据的对象会处理成request.POST request.FILES

回调函数

后端跟ajax交互不应该再返回页面,通常情况下都是返回json格式数据

使用ajax交互 所有的操作都不再直接影响整个页面(局部操作)

// 如果需要实现重定向:
success:function (args) { // 异步回调函数
    window.location.href=args // 处理异步回调返回的结果(js的BOM操作)
    console.log(JSON.parse(args)) //将前端的数据反序列化为js里面的自定义对象
            }
'''返回的数据都在args这个形参中'''

前端针对HttpResponse和JsonResponse返回的json格式数据处理策略不同

前者不会自动反序列化、而后者会自动反序列化
    如果想让前者也自动反序列化可以添加一个固定的参数
        dataType:'JSON'

序列化组件

serializers序列化组件可以把我们用ORM操作产生的QuerySet对象直接转成json格式数据,比我们之前JsonResponse方便。

from app01 import models
from django.http import JsonResponse

# 原理
def data(request):
    data_list = []  # [{}, {}, {}]
    user_queryset = models.User.objects.all()
    for user_obj in user_queryset:
        data_list.append({
            'pk': user_obj.pk,
            'name': user_obj.name,
            'age': user_obj.age,
            'gender': user_obj.gender,
            'gender_real': user_obj.get_gender_display(),
            'addr': user_obj.addr
        })
    return JsonResponse(data_list, safe=False)

# 模块
    #拿到用户表里面的所有的用户对象
    user_list = models.User.objects.all()
    #导入内置序列化模块
    from django.core import serializers
    #调用该模块下的方法,第一个参数是你想以什么样的方式序列化你的数据
    ret = serializers.serialize('json', user_list)
    return HttpResponse(ret)

批量操作数据

批量数据创建:bulk_create()

批量数据修改:bulk_update()

自定义分页器

from app01.plugins import mypage
book_query = models.Book.objects.all()
# 产生分页器对象
page_obj =
mypage.Pagination(current_page=request.GET.get('page'),all_count=book_query.count())
# 产生分页数据对象
page_query = book_query[page_obj.start:page_obj.end]
return render(request, 'bookList.html', locals())

{% for book_obj in page_query %}
    <p class="text-center">{{ book_obj.title }}</p>
{% endfor %}
{{ page_obj.page_html|safe }}

form组件

form组件

1.数据校验:支持提前设置各种校验规则之后自动校验

2.渲染页面:支持直接渲染获取用户数据的各种标签

3.展示信息:支持针对不同的校验失败展示不同的提示

基本使用

from django import forms

class MyForm(forms.Form):
    name = forms.CharField(max_length=8, min_length=3)  # 用户名最长八个字符 最短三个字符
    age = forms.IntegerField(max_value=150, min_value=0)  # 年龄最小0岁 最大150岁
    email = forms.EmailField()  # 邮箱必须符合邮箱格式(至少有个@符号)

数据校验功能

from app01 import views
# 1.将数据传入实例化对象
form_obj = views.MyForm({'name':'jason','age':18,'email':123})
# 2.判断所有的数据是否符合校验(全部符合结果才是True)
form_obj.is_valid()
# 3.获取符合校验规则的数据
form_obj.claned_data
    {'name': 'jason', 'age': 18}
# 4.查阅不符合校验规则的数据及错误原因
form_obj.errors
    {'email': ['Enter a valid email address.']}

"""
1.forms类中所有的字段数据默认都是必填的 少传则肯定通不过校验 
     如果想忽略某些字段可以添加    required=False
2.forms类中额外传入的字段数据不会做任何的校验 直接忽略
"""

渲染标签功能

# 渲染方式1:封装程度高、扩展性较差 主要用于快速生成页面测试功能
  {{ form_obj.as_p }}
  {{ form_obj.as_table }}
  {{ form_obj.as_ul }}

# 渲染方式2:封装程度低、扩展性较好 但是字段比较多的情况下不方便,编写困难
  {{ form_obj.name.label }}  # 文本提示
  {{ form_obj.name }}          # 获取用户数据的标签

# 渲染方式3:推荐使用!!!
{% for form in form_obj %}
    <p>{{ form.label }}{{ form }}</p>
{% endfor %}

"""
1.forms组件只负责渲染获取用户数据的标签
    form表单标签和提交按钮需要自己写
2.渲染标签中文提示 可以使用参数 label指定 不指定默认使用字段名首字母大写
"""

展示提示信息

form表单如何取消浏览器自动添加的数据校验功能(novalidate)
<form action="" method="post" novalidate>
    {% for form in form_obj %}
        <p>
            {{ form.label }}{{ form }}
            <span style="color: red;">{{ form.errors.0 }}</span> # 点零只拿错误信息固定文本
        </p>
    {% endfor %}
<input type="submit" value="提交">

def func(request):
    form_obj = MyForm()
    if request.method == 'POST':
        form_obj = MyForm(request.POST)
        if form_obj.is_valid():
            form_obj.cleaned_data
    return render(request,'func.html',locals())

提示信息可以自定义
username = forms.CharField(min_length=3,max_length=8,label='用户名',
    error_messages={
        'min_length':'用户名最短3位',
        'max_length':'用户名最长8位',
        'required':'用户名必填'
    })

重要字段参数

参数 说明
min_length 最小长度
max_length 最大长度
min_value 最小值
max_value 最大值
label 字段注释
error_messages 错误提示
initial 默认值
validators 正则校验
widget 控制渲染出来的标签各项属性
choices 选择类型的标签内部对应关系
required 是否为空

钩子函数

钩子函数的作用就是提供自定义的校验方式

局部钩子(校验单个字段)

我们在Form类中定义clean_字段名()方法,就能够实现对特定字段进行校验。

全局钩子(校验多个字段)

我们在Form类中定义 clean() 方法,就能够实现对字段进行全局校验。

modelform组件

modelform是form的优化版本使用更简单功能更强大,由于模型类里面的一张张表数据要经常校验,这时候modelform就诞生了,是专门针对模型类使用的。

基本使用

class MyModelForm(forms.ModelForm):
    class Meta:
        model = models.User  # 指定对哪张表做数据校验
        fields = '__all__'    # 指定对所有字段做数据校验
        labels = {}  # 给每个字段加名字
        widgets = {}  # 给每个字段加属性

class Meta下常用参数

model = models.Book  # 对应的Model中的类
fields = "__all__"  # 字段,如果是__all__,就是表示列出所有的字段
exclude = None  # 排除的字段
labels = None  # 提示信息
help_texts = None  # 帮助提示信息
widgets = None  # 自定义插件
error_messages = None  # 自定义错误信息

save()方法

每个ModelForm还具有一个save()方法。 这个方法根据表单绑定的数据创建并保存数据库对象。 ModelForm的子类可以接受现有的模型实例作为关键字参数instance;如果提供此功能,则save()将更新该实例。 如果没有提供,save() 将创建模型的一个新实例。

class MyModelForm(forms.ModelForm):
    class Meta:
        model = models.User
        fields = '__all__'
    def clean_name(self):
        name = self.cleaned_data.get('name')
        res = models.User.objects.filter(name=name).first()
        if res:
            self.add_error('name','用户名已存在')
        return name

def md(request):
    modelform_obj = MyModelForm()
    if request.method == 'POST':
        # edit_obj = models.User.objects.filter(name='jason').first()
        # modelform_obj = MyModelForm(request.POST,instance=edit_obj)  获取修改对象之后再次使用save()方法是修改
        modelform_obj = MyModelForm(request.POST)
        if modelform_obj.is_valid():
            modelform_obj.save()  # 保存数据
    return render(request,'ab.html',locals())

cookie与session

Cookie

互联网刚兴起的时候,所有人访问网址都是一样的数据,服务端无法识别客户端问题不大,经过互联网的发展服务端不得不想办法记住客户端的状态,cookie与session就应运而生了。

cookie就是保存在客户端上跟用户信息(状态)相关的数据。它是服务器发送出来存储在浏览器上的一组组键值对,下次访问服务器时浏览器会自动携带这些键值对,以便服务器提取有用信息。

工作原理:
让服务端知道你是谁的方式很单一>>>:携带用户名和密码(身份标识)
每次操作之前都需要输入用户名和密码
当你成功登录之后浏览器会在本地帮你保存用户名和密码
后面每次操作浏览器会自动发送用户名和密码

'''简单的记忆:cookie就是存在客户端上跟用户信息(状态)相关的数据'''

Session

Cookie本身保存在客户端,可能被拦截或窃取,不安全。因此就需要有一种新的东西,它能支持更多的字节,并且他保存在服务器,有较高的安全性。这就是Session。本质就是保存在服务端上跟用户信息(状态)相关的数据。

工作原理:
用户登录成功之后服务端生成一个随机字符串返回给客户端保存
之后客户端每次发请求携带该随机字符串服务端获取之后比对后台数据
eg:
    服务端
        随机字符串1    用户数据1
        随机字符串2    用户数据2
        随机字符串3    用户数据3
    客户端
        随机字符串1、随机字符串2、随机字符串3

'''简单的记忆:session就是存在服务端上跟用户信息(状态)相关的数据'''

注意:

1.session的工作需要依赖于cookie、就算是目前所有能够识别用户身份的网址也都需要使用cookie

2.客户端浏览器也有权拒绝保存cookie

django操作cookie

视图函数返回值都是需要一个HttpResonse,我们想要操作cookie,就不能直接返回对象,而是先用变量名指代,后操作对象方法。

obj = HttpResponse()
obj.操作cookie的方法
return obj

obj = render()
obj.操作cookie的方法
return obj

obj = redirect()
obj.操作cookie的方法
return obj

obj = JsonRepsonse()
obj.操作cookie的方法
return obj

基本使用

obj.set_cookie()  # 设置
obj.COOKIE.get()  # 获取

cookie其他操作

set_cookie(key,value,salt='加密盐')  # 加密盐
# 会在value后面产生一个随机字符串eg:1ntTFz:bFkwr34ztzsHQLU1qYp

set_cookie(key,value,max_age=超时时间:默认是秒数)  # 后台控制过期时间
# expires:专门针对IE浏览器设置超时时间

# 删除Cookie
# HttpResponse对象.delete_cookie(key) 
def logout(request):
    rep = redirect("/login/")
    rep.delete_cookie("user")  # 删除用户浏览器上之前设置的usercookie值
    return rep

django操作session

请求来之后服务端产生随机字符串并发送给客户端保存、服务端存储随机字符串与用户信息的对应关系、之后客户端携带随机字符串 、服务端自动校验。

针对保存 django需要一张表来处理:django_session表,即django数据库迁移命令会产生一堆默认的表,其中就有一张django_session表、里面只有三个字段(session_keysession_dataexpire_date)。

设置

request.session['key'] = value  # 可以设置多组

1.django自动产生一个随机字符串返回给客户端(对name加密)
2.往django_session表中创建数据(对jason加密)
3.sessionid:随机字符串

获取

request.session.get('key')  # 可以获取多组

1.自动从请求中获取sessionid对应的随机字符串
2.拿着随机字符串去django__session表中匹配数据
3.如果匹配上还会自动解密数据并展示

注意:

1.django默认的session失效时间是14天

2.客户端会接受到键值对,键默认是sessionid值是加密的随机字符串

session其他操作

request.session.session_key  # 获取产生的随机字符串
request.session.delete()  # 只删客户端
request.session.flush()  # 服务端和客户端都删
request.session.set_expiry(value)  # 设置超时时间
    * 如果value是个整数,session会在些秒数后失效。
    * 如果value是个datatime或timedelta,session就会在这个时间后失效。
    * 如果value是0,用户关闭浏览器session就会失效。
    * 如果value是None,session会依赖全局session失效策略。

针对session数据的存储位置,有五种方案:

1.数据库
2.缓存数据库
3.文件
4.缓存+数据库
5.加密cookie

django中间件

django中间件类似于django的门户、所有的请求来和响应走都必须经过中间件。

django默认自带七个中间件每个中间件都有各自负责的功能。

django中间件的使用场景:只要是与全局相关的功能都可以在中间件中编写。

自定义中间件

django支持自定义中间件可以有5个方法。

process_request(self,request)
process_view(self, request, view_func, view_args, view_kwargs)
process_template_response(self,request,response)
process_exception(self, request, exception)
process_response(self, request, response)

基本使用

  1. 创建一个任意名称的文件夹
  2. 在该文件夹内创建一个任意名称的py文件
  3. 在该py文件内编写中间件类
  4. 配置文件中注册
# 步骤二创建的py文件
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse,redirect,render

class MyMdd1(MiddlewareMixin):
    def process_request(self, request):
        print('from MyMdd1 process_request')

    def process_response(self, request, response):
        print('from MyMdd1 process_response')
        return response  # response就是视图函数返回给客户端的数据

# settings.py
MIDDLEWARE = [
    # 注册自定义中间件
    'app01.mymiddleware.mymdd.MyMdd1',
    'app01.mymiddleware.mymdd.MyMdd2',
]

需要掌握的方法

1.process_request

def process_request(self, request):
    print('from MyMdd1 process_request')
    return HttpResponse('from MyMdd1 process_request')
1.请求来的时候会按照配置文件中注册了的中间件从上往下依次执行每一个中间件里面process_request方法、如果没有则直接跳过
2.如果该方法自己返回了HttpResponse对象,那么请求不再继续往后执行原路返回相应的数据

2.process_response

def process_response(self, request, response):
    print('from MyMdd1 process_response')
    return response  # response就是视图函数返回给客户端的数据
1.响应走的时候会按照配置文件中注册了的中间件从下往上依次执行每一个中间件里process_response方法、如果没有则直接跳过
2.该方法有两个形参request和response 并且默认情况下应该返回response
3.如果该方法自己返回了HttpResponse对象,那么响应会替换成该HttpResponse对象数据,而不再是视图函数想要返回给客户端的数据

注意:

如果请求的过程中process_request方法直接返回了HttpResponse对象那么会原地执行同级别process_response方法返回(flask则不同)。

需要了解的方法

1.process_view

当路由匹配成功之后执行视图函数之前自动从上往下执行配置文件中注册了的中间件里面的process_view方法

2.process_exception

当视图函数执行过程中报错并在返回响应的时候自动从下往上执行配置文件中注册了的中间件里面的process_exception

3.processtemplate_response

当视图函数执行完毕之后返回的对象中含有render属性对应render函数,则会从下往上执行配置文件中注册了的中间件里面的process_template_response方法

csrf跨站请求伪造

钓鱼网站:一个模仿正规网站的网址,诱骗用户在该网站上做本应该在正规网站上做的操作,从而获取到该用户在正规网站上的数据甚至是财产。

模拟钓鱼网站:

一台计算机上两个服务端不同端口启动,钓鱼网站提交地址改为正规网站地址。

如何预防钓鱼网站:

csrf策略:通过在返回的页面上添加独一无二的标识信息从而区分正规网站和钓鱼网站的请求

csrf操作

form表单:

会自动产生一个input标签,用于校验。submit提交的时候会带上这个生成的input里的value值,然后会和之前前后端返回的页面里的value进行比对,如果正确,这个提交请求就让他过,如果不正确就不让请求通过。

ajax:

方式一:先编写csrf模板语法,然后利用标签查找和值获取,手动添加

方式二:直接利用模板语法即可

方式三:通用方式(js脚本)扩展性最高

js脚本自动处理(只能适用于ajax提交),创建一个js文件,复制下面内容,然后在页面上引入js文件即可。

csrf相关装饰器

当整个网站默认都不校验csrf,但是局部视图函数需要校验,如何处理

csrf_protect 校验csrf

当整个网站默认都校验csrf,但是局部视图函数不需要校验,如何处理

csrf_exempt 不校验csrf

针对FBV

from django.views.decorators.csrf import csrf_protect,csrf_exempt

# @csrf_protect
@csrf_exempt
def home(request):
    return HttpResponse('哈哈哈')

针对CBV

针对CBV不能直接在方法上添加装饰器 需要借助于专门添加装饰器的方法

csrf_protect:三种CBV添加装饰器的方式都可以
csrf_exempt:只有方式3可以生效(给重写的dispatch方法装)
# @method_decorator(csrf_protect,name='post')  # 可以    # 方式2:指名道姓的添加
# @method_decorator(csrf_exempt,name='post')  # 无效
class MyView(views.View):
    # @method_decorator(csrf_protect)  # 可以   # 方式3:影响类中所有的方法
    @method_decorator(csrf_exempt)  # 有效
    def dispatch(self, request, *args, **kwargs):
        return super(MyView, self).dispatch(request,*args,**kwargs)

    # @method_decorator(csrf_protect)  # 可以
    # @method_decorator(csrf_exempt)  # 无效
    def post(self,request):   # 方式1:指名道姓的添加
        return HttpResponse('Home Post view')

auth认证模块

django执行数据库迁移命令后会产生一个auth_user表,该表可以配合auth模块做用户相关的功能,eg:注册、登录、修改密码、注销...。该表还是django admin后台管理默认的表

django自带的admin后台管理用户登录参考的就是auth_user表。

创建admin后台管理员用户
python manage.py createsuperuser
自动对用户密码进行加密处理并保存

auth模块常见功能

1.创建用户

from django.contrib.auth.models import User
User.object.create_user(username,password)   # 普通用户
User.object.create_superuser(username,password,email)   # 管理员

2.校验用户名和密码是否正确

from django.contrib import auth
auth.authenticate(request,username,password)   # 校验正确返回的是用户对象 错误返回的是None

3.用户登录

auth.login(request,user_obj)   # 自动帮你操作cookie与session相关

4.判断用户是否登录

request.user.is_authecticated()   # 判断用户是否登录  返回布尔值

5.获取登录用户对象

request.user   # 获取当前登录的用户对象或者是匿名用户

6.校验用户登录装饰器

from django.contrib.auth.decorators import login_required
跳转局部配置
login_required(login_url='/login/')   # 每次都需要自己写 量大的情况下不方便
跳转全局配置
LOGIN_URL = '/login/'  # 需要在配置文件中添加配置

7.校验密码是否正确

request.user.check_password(old_password)   # 自动加密再比对

8.修改密码

request.user.set_password(new_passowrd)   # 临时修改密码
request.user.save()   # 将修改操作同步到数据库中

9.注销登录

auth.logout(request)   # 自动清除cookie和session

auth_user表切换

我们想使用auth模块的方法并且还想扩展auth_user表字段

方式1:编写一对一表关系(了解)

方式2:类继承(推荐)

# models.py
from django.contrib.auth.models import AbstractUser

class Userinfo(AbstractUser):
    '''扩展auth_user表中没有的字段  不能冲突'''
    phone = models.BigIntegerField()
    desc = models.TextField()

# settings.py
'''告诉auth模块 不再使用auth_user 而是使用自定义的表'''
AUTH_USER_MODEL = 'app01.Userinfo'

注意:

  1. 类继承之后,需要重新执行数据库迁移命令,并且库里面是第一次操作才可以
  2. auth模块所有的方法都可以直接在自定义模型类上面使用,自动切换参照表

基于django中间件设计项目功能

importlib模块:可以通过字符串的形式导入模块

常规导入方式

方式1import  句式

方式2from ... import ... 句式
from bbb import b
from bbb.b import name  # 可以直接导变量数据
print(b)  # <module 'bbb.b' from '/Users/jiboyuan/PycharmProjects/day64_1/bbb/b.py'>
print(b.name)

字符串导入方式

import importlib
module_path = 'bbb.b'
res = importlib.import_module(module_path)
print(res.name)
module_path1 = 'bbb.b.name'
importlib.import_module(module_path1)  # 不可以 最小导入单位是模块文件级别

以发送提示信息为需求编写功能的案例

方式1:简单的函数式封装

方式2:配置文件插拔式设计:

# settings
NOTIFY_FUNC_LIST = [
    'notify.qq.QQ',
    'notify.email.Email',
    'notify.msg.Msg',
    'notify.weixin.WeiXin',
]
# __init__
import settings
import importlib
def send_all(msg):
    # 1.循环获取配置文件中字符串信息
    for str_path in settings.NOTIFY_FUNC_LIST:  # 'notify.qq.QQ'
        # 2.切割路径信息
        module_path, class_str_name = str_path.rsplit('.', maxsplit=1)  # ['notify.qq','QQ']
        # 3.根据module_path导入模块文件
        module = importlib.import_module(module_path)  # from day64.notify import qq
        # 4.利用反射获取模块文件中对应的类名
        class_name = getattr(module, class_str_name)  # Email  Msg  QQ
        # 5.实例化
        obj = class_name()
        # 6.调用发送消息的功能
        obj.send(content)
posted @   空白o  阅读(26)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示