这里写目录标题
- 第 2 种渲染方式,label 对应的字段
- 后端添加 label 属性
- 第 3 种渲染方式,for+label+span(推荐使用)
- Day 68 - 08 forms组件展示错误信息
- Day 68 - 09 forms组件钩子函数
- Day 68 - 10 重要参数介绍
- Day 68 - 11 其他字段类型
- Day 69
- Day 69 - 01 内容回顾
- Day 69 - 02 今日内容概要
- Day 69 - 03 forms组件源码
- Day 69 - 04 cookie与session简介
- Day 69 - 05 django操作cookie
- Day 69 - 06 session操作
- Day 69 - 07 CBV添加装饰器的三种方式
Day 60
Day 60 - 03 静态文件配置
Day 60 - 04 request对象方法
Day 60 - 05 pycharm链接MySQL
Day 60 - 06 django链接MySQL
Day 60 - 07 django orm前戏
Day 60 - 08 字段的增删改查
Day 60 - 09 数据的查询
Day 60 - 10 数据的增加
Day 61
Day 61 - 01 昨日内容回顾
Day 61 - 02 今日内容概要
Day 61 - 03 数据展示
Day 61 - 04 数据编辑
Day 61 - 05 数据删除
Day 61 - 06 orm创建表关系
Day 61 - 07 django请求生命周期流程图
Day 61 - 08 路由匹配
Day 61 - 09 无名有名分组
Day 61 - 10 反向解析
Day 62
Day 62 - 01 昨日内容回顾
02 今日内容概要
03 无名有名反向解析
04 路由分发
05 名称空间
06 伪静态概念
07 虚拟环境
08 django版本区别
09 三板斧介绍
10 JsonResponse对象
11 文件上传
12 request对象方法补充
13 FBV与CBV
Day 63
Day 63 - 01 昨日内容回顾
02 今日内容概要
03 CBV源码剖析
04 模版语法传值
- 模板语法传值(locals方法,返回所有的名字)
def index(request): # 模板语法可以传递的后端python数据类型 n = 123 f = 11.11 ... def func(): print('我被执行了') return '你的另一半在等你' class MyClass(object): def get_self(self): return 'self' @staticmethod def get_func(): return 'func' @classmethod def get_class(cls): return 'cls' def __str__(self): return '你到底会不会?' obj=MyClass() return render(request, 'index.html', locals())
-
总结
-
《花括号》与《百分号》含义
- {{ }} 变量相关
- {% %} 逻辑相关
- 传递变量
- {{ n }} 或 {{ f }} 或 {{ s }}
- 等等各种类型全部都可以传
- 传递函数
- {{ func }}
- 函数
自动帮你加括号调用
,传的是返回值。 切记,不支持给函数传参数
,会给你报错。
- 传递类
- 类 {{ MyClass }}
- 对象 {{ obj }}
- 传类名的时候,也会自动加括号调用(实例化)
- {{ obj.get_self }}
- {{ obj.get_func }}
- {{ obj.get_class }}
- 总结:
- 内部能够自动判断出当前的变量名是否可以加括号调用,
- 如果可以就自动执行(针对函数名和类名)
- 列表和字典取值
- 只能用句点符 . ,哪怕字典列表嵌套
- d.info.3.hobby
05 模版语法之过滤器
- 什么是模版语法过滤器?
类似于一些简单的内置方法,后端传来一个变量,前端展示关于变量的信息
Django内置 60+ 过滤器,我们了解10个左右,差不多了。后面碰到了再去记忆-
通用格式
{{ 变量名|过滤器:'参数' }}
-
下面都可以包含在 <p></p> 标签中
{{ s|length }}
统计长度{{ b|default: '啥也不是' }}
默认值- 后端
file_size = 123421
,前端{{ file_size|filesizeformat }}
文件大小(自动帮你和1024做计算。) {{ l|slice:'0:4:2' }}
切片操作info = 'aeioajflvnaowenvzbonod'
{{ info|truncatechars:9 }}
切取字符(例如文章摘要,加上三个点):egl = 'My name is jason, and my ...'
{{ egl|truncatedwords:9 }}
msg = 'I Love You and you?'
切取单词(不包含三个点了,只认空格):{{ l|cut:' ' }}
移除字符{{ l|join:'$' }}
拼接操作:{{ n|add:10 }}
拼接操作(加法{{ s|add:msg }}
-
日期格式化
后端
import datetime
current_time = datetime.datetime.now()
前端
{{ current_time|date:'Y-m-d H:i:s' }}
-
safe:取消转义
hhh = '<h1>敏敏</h1>'
不转义:{{ hhh }}
转义:{{ hhh|safe }}
06 模版语法之标签
07 自定义过滤器、标签、inclusion_tag
08 模版的继承
09 模版的导入
Day 64
Day 64 - 01 昨日内容回顾
如果需要后台进行表格的修改(例如书本打折)
# 修改 user_list = models.User.objects.filter(name__contains='egon') for user_obj in user_list: user_obj.age = user_obj.id + 1 user_obj.save()
02 今日内容概要
03 单表及测试环境准备
- myTest.py(与manage.py同级建立)
from django.test import TestCase import os os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings") if __name__ == "__main__": import django django.setup() from app01 import models from app01 import views
filter()
values()
values_list()
返回结果的区别
user_query = models.User.objects.filter(name__contains='egon') print(user_query) # <QuerySet [<User: 对象:egonDSB>, <User: 对象:egonAPP>,。。。 user_query = models.User.objects.filter(name__contains='egon').values() print(user_query) #<QuerySet [{'id': 5, 'name': 'egonDSB', 'age': 6, 'register_time': datetime.date(2020, 8, 25)}, user_query = models.User.objects.filter(name__contains='egon').values_list() print(user_query) # <QuerySet [(5, 'egonDSB', 6, datetime.date(2020, 8, 25)),
04 必知必会13条
- 13条
all()
和filter()
- values() 和 values_list() # (重点)
get(pk=5)
- first() 和 last() # queryset 的第一个或最后一个元素
- distinct()
- order_by() 和 reverse()
- count() 统计数字
- exclude()
- exists()
- 注意
filter 是筛选条件
values 和 values_list 是字段要点objects,再点方法。
查询的返回的结果都是 queryset# values,values_list res = models.User.objects.values('name', 'age') # 返回结果:queryset,列表套字典 print(res) res = models.User.objects.values_list('name', 'age') # 返回结果:queryset,列表套元组 print(res) # 查看内部sql语句 print(res.query) # SELECT `day64_user`. `name`, `day64_user`. `age` FROM `day64_user` # distinct # 默认主键别忽略了 res = models.User.objects.values('name', 'age').distinct() print(res) # 排序 res = models.User.objects.values_list().order_by('age') # 升序 res = models.User.objects.values_list().order_by('-age') # 降序 print(res) # reverse 反转(前提是跟在order_by后面) # count 统计个数 res = models.User.objects.all().count() # 统计个数 print(res) # exclude 排除在外 # exists 基本用不到,返回bool
05 神奇的双下划线查询
- (其实就是内置方法)查询
- 大于小于
- 某个范围内
- 包含不包含(区分大小写)开头结尾
- 时间字段中定位年月日
双下划线例题
# 年龄大于35的数据 res = models.User.objects.filter(age__gt=35).values('name') print(res) # 年龄小于35的数据 res = models.User.objects.filter(age__lt=35).values('name') print(res) # 大于等于,小于等于 res = models.User.objects.filter(age__gte=35).values('name') print(res) # 年龄是18,或者32,或者40 res = models.User.objects.filter(age__in=[18, 32, 40]).values('name') print(res) # 年龄是18 到 40 res = models.User.objects.filter(age__range=[18, 40]).values('name') print(res) # 模糊查询:名字里含有s(默认区分大小写的) res = models.User.objects.filter(name__contains='s').values('name') print(res) # 模糊查询:忽略大小写的) res = models.User.objects.filter(name__icontains='s').values('name') print(res) # 开头是j,结尾是j res = models.User.objects.filter(name__startswith='j').values('name') print(res) # 查询出注册时间是2020年,1月 res = models.User.objects.filter(register_time__month='1').values('name') print(res)
06 多表操作前期准备
- 本质上只有《增加》可以考虑为针对表格的操作
- 其他都要找到某个对象,才可以操作。
07 外键的增删改查
# 一对多外键增删改查 # 增加 # 方法1:直接写实际字段 models.Book.objects.create(title='论语', price=998.23, publish_id=1) models.Book.objects.create(title='聊斋', price=444.23, publish_id=2) models.Book.objects.create(title='老子', price=333.23, publish_id=1) # 方法2:虚拟字段 对象 # 给外键创建一个对象,加到一的外键字段中去 publish_obj = models.Publish.objects.filter(pk=2).first() models.Book.objects.create(title='红楼梦', price=666.23, publish=publish_obj) # 删除 models.Publish.objects.filter(pk=1).delete()
修改
# update 方法 # 编号为1的书,把出版社修改为2 # 方法1, models.Book.objects.filter(pk=1).update(publish_id=2) # 方法2 publish_obj = models.Publish.objects.filter(pk=1).first() models.Book.objects.filter(pk=1).update(publish=publish_obj) # ------------------------------ # 区别在于: # 1. **实际字段** 是数据库表格中的字段名称。 # 2. **虚拟字段** 是类定义中的外键名称
–
# 如何给书籍添加作者? # 这里很尴尬,因为表格不是我们创的,句点符点不到。 book_obj = models.Book.objects.filter(pk=1).first() # 类似于已经到了第三张关系表了 print(book_obj.authors) # 书籍id=1的书,绑定一个id为1的作者,两个三个都可以,因为一本书可以有多个作者。 book_obj.authors.add(1) book_obj.authors.add(2, 3) author_obj = models.Author.objects.filter(pk=1).first() author_obj1 = models.Author.objects.filter(pk=2).first() author_obj2 = models.Author.objects.filter(pk=3).first() book_obj.authors.add(author_obj1, author_obj2) ''' add 方法给第三张关系表添加数据 括号内可以传数字,也可以传对象,并且都支持多个。 ''' # 删除 # book为1的书,作者2没了。 book_obj.authors.remove(2) # 同样支持多个和对象 # 多对多关系的修改 -- set方法 # 找到一个 book 对象,点虚拟字段,点set方法 # 括号内必须是一个可迭代对象,元祖列表都可以。 book_obj.authors.set((1, 2)) ''' 可以是数字,也可以是对象。 ''' # 清空 # 在第三张关系表格中,清空书籍与作者的绑定关系。 # 括号内不要加任何参数 book_obj.authors.clear()
08 正反向的概念
09 基于对象的跨表查询
10 基于双下划线的跨表查询
Day 65
Day 65 - 01 内容回顾
02 今日内容概要
03 聚合查询
- 聚合查询(聚合函数aggregate的使用)
什么是聚合查询?
max、min、sun、count、avg
需要先导入5个模块from django.db.models import Max, Min, Sum, Count, Avg
和数据库有关,要不在django.db.models
,要不在django.db
from day64 import models from django.db.models import Max, Min, Sum, Count, Avg # 1. 统计书的平均价格 res = models.Book.objects.aggregate(Avg('price')) print(res) # 2. 上述方法一次性使用 res = models.Book.objects.aggregate(Max('price'), Min('price'), Sum('price'), Count('price'), Avg('price')) print(res)
04 分组查询(group by)annotate
- MySQL分组查询特点
分组之后,只能获取到分组的依据,组内其他字段都无法直接获取了。
严格模式:ONLY_FULL_GROUP_BY
- 分组查询技巧
“每个”,按什么分组
models
后面点什么,按照什么分组。
author_num
是我们自己定义的字段,用来存储统计每本书对应的作者个数。# 1. 统计每一本书的作者个数 res = models.Book.objects.annotate(author_num=Count('authors')).values('title', 'author_num') print(res) # 2. 统计每个出版社卖的最便宜的书的价格 res = models.Publish.objects.annotate(min_price=Min('book__price')).values('name', 'min_price') print(res) # 3. 统计不止一个作者的图书 # 步骤:统计每本书几个作者,过滤出作者大于1的。 res = models.Book.objects.annotate(author_num=Count('authors')).filter(author_num__gt=1).values('title', 'author_num') print(res) ''' 只要结果还是一个queryset对象,就可以无限次点queryset方法。 比如点filter(),点到一滴都不剩。 ''' # 4. 查询每个作者出的书的总价格。 res = models.Author.objects.annotate(sum_price=Sum('book__price')).values('name', 'sum_price') print(res) # 5. 想按照指定的字段分组,如何处理? # models.Book.objects.values('price').annotate() # 只要出现分组报错,需要修改数据库严格模式。
05 F查询
- F 查询(针对某个字段的值操作)
# 1. 查询卖出数量大于库存数量的书籍(条件不是具体的数字) from django.db.models import F res = models.Book.objects.filter(maichu__gt=F('kucun')) print(res) # 2. 所有书的加个,提升500块. # 看一眼数据库,都加了500 # res = models.Book.objects.update(price=F('price') + 500) # print(res) # 3. 所有书的名称后面加上爆款两个字 # F 不能直接做字符串的拼接,需要导入模块。 from django.db.models.functions import Concat from django.db.models import Value res = models.Book.objects.update(title=Concat(F('title'), Value('爆款'))) print(res)
06 Q查询
- Q 查询(条件)
# 1. 查询卖出数量大于 100 或者价格小于 600 的书籍 # Q 包裹,可以用连接符了。 from django.db.models import Q # 与或非 res = models.Book.objects.filter(Q(maichu__gt=100) , Q(maichu__lt=600)) res = models.Book.objects.filter(Q(maichu__gt=100) | Q(maichu__lt=600)) res = models.Book.objects.filter(~Q(maichu__gt=100) | Q(maichu__lt=600)) print(res) # Q的高阶用法,实现搜索功能,条件左边也是字符串,不需要是变量名 # Q 对象 默认还是 AND 关系。 q = Q() # 修改拼接关系 q.connector = 'or' q.children.append(('maichu__gt', 100)) q.children.append(('maichu__gt', 100)) # filter 括号内支持 Q 对象 res = models.Book.objects.filter(q) print(res)
07 django中开启事务
- 复习
- 事务的四个特性 ACID
- 回滚 rollback
- 确认 commit
- Django 中如何简单地开启事务
from django.db import transaction try: with transaction.atomic(): # sql1 # sql3 # sql3 # 在 with 代码块中所有的 orm 操作都是同一个事务 pass except Exception as e: print(e) print('执行其他操作')
08 orm 常用字段及参数
常用字段 说明 参数 AutoField 主键字段 primary_key=True CharField varchar,verbose_name 字段注释,max_length IntegerField int BigIntegerField big int DecimalField max_digits=8,decimal_places=2 EmailField varcahr(254) DateFiled date DateTimeField datetime,auto_now,auto_now_add: BooleanFiled(Field) 传值False/True,数据库里存0或1 TextField(Field) 存大段内容(文章,博客……)没有字数限制,bbs作业的文章字段会用。 FileField 文件字段,upload_to="/data" 给该字段传一个文件对象,会自动将文件保存到/data目录下,然后将文件路径保存到数据库中。/data/a.txt,后面bbs作业也会讲。 upload_to="/data" - 更多字段,直接参考博客
09 数据库查询优化
- 查询优化内容
- orm 惰性查询
- only 与 defer (只查某字段与就不查某字段)
- select_related 与 prefetch_related (外键查询,双下划线联表操作)
- orm 惰性查询 特点
如果你仅仅书写了orm语句,在后面根本没有用到该语句所查询出来的参数,
那么 orm 会自动识别,直接不执行例如
res = models.Book.objects.all() print(res) # 没这句不执行上面的。 # 默认 all 会查询全部。
- only 与 defer
- 都是返回
列表套对象
- only
只查括号内字段
,点别的字段,就要重新走数据库 - defer 别的都查,
就不查括号内字段
。
想要获取书籍表中所有书的名字
res = models.Book.objects.values('title') for d in res: print(d.get('title'))
用 only 获取书籍表中所有书的名字以及价格。
# 如果要查看别的属性,那就还要走数据库查询语句。 res = models.Book.objects.only('title') # 取出《only 列表套对象》 for i in res: # 点击only括号内的字段,不会走数据库 print(i.title) # 对于 only 括号内没有的字段,会重新走数据库查询。 print(i.price)
用 defer 获取书籍表中所有书的名字以及价格
# 除了括号里的没有,其他都有 res = models.Book.objects.defer('title') for i in res: print(i.price)
- select_related prefetch_related
- select_related 和 prefetch_related 都和
跨表操作
有关 - 内部
直接联表操作
,一次性将大表所有数据封装给查询出来的对象。 - 无论点击book表的数据,还是publish的数据,无需再走数据库查询。
- select_related 括号里只能放
外键字段的一对一,一对多
。 - prefetch_related 内部就是
内部子查询
。
res = models.Book.objects.all() for i in res: # 《书名》和《出版社名》在两张表上, # 每查询一次,都要走一次数据库。 print(i.publish.name)
用 select_related
res = models.Book.objects.select_related('publish') for i in res: print(i.publish.name) # prefetch_related 内部就是内部子查询。 # 使用者感觉不粗差距 res = models.Book.objects.prefetch_related('publish') for i in res: print(i.publish.name)
- 补充一个方法
res = models.Book.objects.select_related('外键字段1__外键字段2__外键字段3') for i in res: print(i.publish.name)
10 图书管理系统首页展示
- 模板制作
- 首页
- 书的增删改查
Day 66
Day 66 - 01 内容回顾
02 今日内容概要
03 图书列表展示页
04 书籍的添加
05 书籍的编辑
06 书籍的删除
07 choices参数
- 设计一个表,比如用户表。
性别,学历,工作经验,客户来源……
能够列举完全的字段,那么一般情况下采用choices 参数
取的时候用get_字段_display()
方法models.py
class User(models.Model): username = models.CharField(max_length=32) age = models.IntegerField() # 性别 gender_choice = ( (1, '男'), (2, '女'), (3, '其他'), ) gender = models.IntegerField(choices=gender_choice) ''' gender 字段存的还是数字, 保证 choices 字段和字段内类型一致即可。 '''
# 存,没有被列举出来的数字也可以存 models.User.objects.create(username='jason', age=18, gender=1) models.User.objects.create(username='egon', age=84, gender=2) models.User.objects.create(username='tank', age=40, gender=3) models.User.objects.create(username='tony', age=30, gender=4) # 取,用 get_字段_display() 方法 user_obj = models.User.objects.filter(pk=1).first() print(user_obj.gender, user_obj.get_gender_display()) # 如果没有对应字段,那存的是什么,取出还是什么 user_obj = models.User.objects.filter(pk=4).first() print(user_obj.gender, user_obj.get_gender_display())
08 MTV与MVC模型
- 这些简称没有任何卵用
- MTV:django 给自己起的别名,本质还是 MVC
- models,templates,views。
- models,views,
controller(urls.py,)
09 多对多三种创建方式
- 全自动
- 纯手动
- 半自动
例如,图书与作者关系
- 第一种 全自动 – 利用orm帮我们创建
class Book(models.Model): ... authors = models.ManyToManyField(to='Author') ...
优点
- 不用写代码,方便,快速
缺点
- 第三张表的扩展性极差
- 第二种 纯手动
class Book2Author(models.Model): ... book_id = models.ForeignKey(to='Book') author_id = models.ForeignKey(to='Author') ...
优点
- 第三张表完全取决于你
缺点
- 代码太多,无法使用 orm 提供的正反向查询和简单方法(add,set,remove,clear···)
- 不推荐使用《纯手动》
- 第三种:半自动
class Book(models.Model): ... authors = models.ManyToManyField( to='Author', through='Book2Author', through_field=('book', 'author'), ) ... class Author(models.Model): ... # Book2Author 还是需要自己写,在 Book 中关联 class Book2Author(models.Model): ... book_id = models.ForeignKey(to='Book') author_id = models.ForeignKey(to='Author') ...
半自动可以使用正反向查询,但是依旧不能使用 add,set,remove,clear···
- 总结
- 需要掌握《全自动》和《半自动》,一般采用《半自动》
10 Ajax 简介(重点,全栈必备)
- 八个大字,精髓
局部筛选,异步提交
- ajax 有什么用?
例子:github 注册
- 动态获取用户名,实时发到后端检测
我们学过的发送请求的方式
- 地址栏输入url,只有get
- a标签的href属性,只有get
- from表单,get | post
- ajax,get | post
->我们现在大部分都是 ajax。
- 注意
- 不是新的编程语言,是一种使用现有标准的新方法(例如装饰器)
- 不需要重新加载整个网页,就与服务器交互,更新网页数据。
- 我们只学 jQuery 封装之后的版本。原生的不太用。(不知 jQuery,其他框架也可以,就是关键字不同,换汤不换药)
- 所以一定要确保导入了 jQuery。
11 Ajax基本语法
- 先来个总结,方便复制黏贴
- 后端
views.py
导入模块from django.http import JsonResponse
视图函数
返回return JsonResponse(back_dic)
前端页面
导入 jQuery<script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
- 前端基本语法
<input type="text" name="" id="d1">+ <input type="text" name="" id="d2">= <input type="text" name="" id="d3"> <p> <button id="btn">点我</button> </p> <script> // 用 jquery 给按钮绑定一个点击事件 $('#btn').click(function(){ // 朝后端发送 ajax 请求 $.ajax({ url: '', type: 'post', dataType: 'Json', data: {'i1': $('#d1').val(), 'i2': $('#d2').val()}, success:function(args){ // alert(args) $('#d3').val(args) } }) }) </script>
- 最后一个success是回调函数(形参写什么都可以)。无论输入啥,最后都是返回给回调函数。不是浏览器。
- 如果要返回字典的话,需要用 json 格式。
- 不管什么格式,前端(这个页面)收到的参数都是字符串格式。
- 后端返回数据(不是 JsonResponse,需要解析)
def ab_ajax(request): if request.method == "POST": print(request.POST) i1 = request.POST.get('i1') i2 = request.POST.get('i2') i3 = int(i1) + int(i2) return HttpResponse(i3) return render(request, 'ab_ajax.html')
- 如果后端返回的是
HttpResoponse
,只能在前端,Json.parse()
自己解决 - 如果后端返回的是
JsonResponse
,前端会自动帮你反序列化dataType: 'Json',
- 后端
from django.http import JsonResponse def ab_ajax(request): ... # 这里 d 是个字典。 return JsonResponse(d) ...
以后写 ajax 的时候,可以直接加上,以防万一。
Day 67
Day 67 - 01 内容回顾
Day 67 - 02 今日内容概要
Day 67 - 03 前后端传输数据编码格式
(主要研究 POST 请求的编码格式)
向后端发psot请求的两种方式
- form 表单
- ajax
三种格式
- urlencoded
- formdata
- json
- 第一种,form 表单
- 发送文本:默认 encoded
- 查看方法:检查 – Network – 左侧 name – 在请求头(Request Heads)里面找 content-type == encoded
- 最底下的 view source,可以看到
name=jason&password=123
django 后端会自动帮忙就解析到 request.POST 中。
- 发送文件,记得
enctype="multipart/form-data"
如果是 encoded
只要长得是name=jason&password=123
,都会帮你解析到 request.POST 。
但是如果把编码格式,改成formdata
,
字符还是会帮你解析到 request.POST .
文件
会解析到request.FILES
中- 第二种,ajax
默认也是 urlencoded
所以,无论哪种,都可以通过 request.POST 中获得。Day 67 - 04 ajax发送json格式数据
- 为什么要学这个?
防止前后端
不全用
Django框架写的。
前后端一定要保证编码格式和数据真正格式是一致的。前端
data: JSON.stringify({'username': 'jason', 'age': ''18})
转换成JSON格式contentType: 'application/json', // 指定编码格式,
- 数据是真正的 json 格式
后端
要判断 ajax 请求:if(request.is_ajax()):
- 在 request.POST 里面找不到,要在
request.body
中自己去找。 - 如果
前端生成内置对象
,并且有文件
,一定要拒绝编码处理,文件在后端的request.FILES
中寻找,普通键值对在request.POST
中寻找。
- 前端 html 文件(写死版本)
<button class="btn btn-success" id="d1">点我</button> <script> // 用 jquery 给按钮绑定一个点击事件 $('#d1').click(function(){ $.ajax({ url: '', type: 'post', // 转换成 json 格式 data: JSON.stringify({'username': 'jason', 'age': '18'}), // 指定编码格式 contentType: 'application/json', success:function(){ } }) }) </script> <!-- 朝后端发送的数据 --> <!-- {"username":"jason","age":"18"} -->
- 方法补充:判断 ajax 请求
if(request.is_ajax()): pass
django对于ajax 的 json 数据,不会做任何处理。
在 request.body 里面
针对 json 格式数据,需要手动处理。# 第一种写法(全面) if(request.is_ajax()): # bytes -- str -- dict json_bytes = request.body json_str = json_bytes.decode('utf-8') json_dict = json.loads(json_str) print(json_dict, type(json_dict)) # 第二种写法(简便) if(request.is_ajax()): # bytes -- dict (json.loads() 会先自动解码,再自动反序列化) json_bytes = request.body json_dict = json.loads(json_bytes) print(json_dict, type(json_dict))
Day 67 - 05 ajax发送文件数据
向后端发送普通键值对,也发送文件
p*3>inp
前端(通过内置对象发送,建议背诵)
<p>username:<input type="text" name="" id="d1"></p> <p>password<input type="text" name="" id="d2"></p> <p><input type="file" name="" id="d3"></p> <button class="btn btn-info" id="d4">点我,有你好看</button> <script> // 点击按钮,向后端发送普通键值对和文件 $('#d4').on('click', function() { // 1. 先生成一个内置对象 let formDataObj = new FormData(); // 2. 可以添加普通键值对,也可以添加文件 formDataObj.append('username', $('#d1').val()); formDataObj.append('password', $('#d2').val()); formDataObj.append('myfile', $('#d3')[0].files[0]); // 3. 将对象基于 ajax 发送给后端 $.ajax({ url: '', type: 'post', // 直接放对象即可 data: formDataObj, // 必须指定两个参数,告诉后端编码和浏览器,别对数据动手动脚 contentType: false, processData: false, success: function(args){ } }) }) </script>
后端
def ab_file(request): if request.is_ajax(): if request.method == 'POST': print(request.POST) print(request.FILES) # 结果: # <QueryDict: {'username': ['jason'], 'password': ['123']}> # <MultiValueDict: {'myfile': [<InMemoryUploadedFile: WechatIMG746.jpeg (image/jpeg)>]}> pass return render(request, 'ab_file.html', locals())
- 总结
- 需要使用对象 formData
- 指定两个关键的参数,
拒绝编码和数据处理
- 后端可以直接直接识别
formData
对象并且自动解析,将普通键值对
封装到request.POST
中,将文件
封装到request.FILES
中。
Day 67 - 06 django自带的序列化组件
Day 67 - 08 批量插入数据
Day 67 - 09 自定义分页器推导过程
Day 68
Day 68 - 01 内容回顾
Day 68 - 02 今日内容概要
Day 68 - 03 自定义分页器使用
Day 68 - 04 form组件前戏
Day 68 - 05 forms组件类书写
Day 68 - 06 forms校验数据
Day 68 - 07 forms组件渲染标签
Day 68 - 08 forms组件展示错误信息
Day 68 - 09 forms组件钩子函数
Day 68 - 10 重要参数介绍
Day 68 - 11 其他字段类型
Day 69
Day 69 - 01 内容回顾
Day 69 - 02 今日内容概要
Day 69 - 03 forms组件源码
Day 69 - 04 cookie与session简介
Day 69 - 05 django操作cookie
Day 69 - 06 session操作
Day 69 - 07 CBV添加装饰器的三种方式
Day66
Day 66 - 01 内容回顾
02 今日内容概要
03 图书列表展示页
04 书籍的添加
05 书籍的编辑
06 书籍的删除
07 choices参数
1. 啥是choices参数?
- 设计一个表,比如用户表。有一些字段:性别,学历,工作经验,客户来源……
- 这些字段的内容大量重复,并能够列举完全,那么一般情况下采用 choices 参数
2. 如何定义choices参数?
- 以性别为例,元组套元组。例如
gender_choice
- 参数
models.IntegerField(choices=gender_choice)e
- gender 字段存的还是数字,保证 choices 子元组第一个参数和 gender 字段内类型一致即可
models.py
class User(models.Model): username = models.CharField(max_length=32) age = models.IntegerField() # 性别 gender_choice = ( (1, '男'), (2, '女'), (3, '其他'), ) gender = models.IntegerField(choices=gender_choice)
取的时候用 get_字段_display() 方法
例如user_obj.get_gender_display()
# 存,没有被列举出来的数字也可以存 models.User.objects.create(username='jason', age=18, gender=1) models.User.objects.create(username='egon', age=84, gender=2) models.User.objects.create(username='tank', age=40, gender=3) models.User.objects.create(username='tony', age=30, gender=4) # 取,用 get_字段_display() 方法。--》男 user_obj = models.User.objects.filter(pk=1).first() print(user_obj.gender, user_obj.get_gender_display()) # 如果没有对应字段,那存的是什么,取出还是什么。--》4 user_obj = models.User.objects.filter(pk=4).first() print(user_obj.gender, user_obj.get_gender_display())
08 MTV 与 MVC 模型
- 这些简称没有任何卵用
- MTV:django 给自己起的别名,本质还是 MVC。
- 全称
- models,templates,views。
- models,views,controller(urls.py,)
09 多对多三种创建方式
例如图书表与作者表关系,是多对多。
一本书可以有多个作者,一位作者可以有多本书。- 半自动(推荐)
- 《Book》中
authors = models.ManyToManyField(...),
- 三个参数 to, through, through_field。
- 《Book2Author》还是要自己写。
- 《Book》中
- 全自动(方便写,但不好扩展)
-《Book》中:authors = models.ManyToManyField(to='Author')
- 纯手动(无必要)
- 手动创建《book2author》
- 《book2author》中:
models.ForeignKey(to='Book')
1. 半自动创建多对多关系的方法(推荐方法,要掌握)
class Book(models.Model): ... authors = models.ManyToManyField( to='Author', through='Book2Author', through_field=('book', 'author'), ) ... class Author(models.Model): ... # Book2Author 还是需要自己写,在 Book 中关联 class Book2Author(models.Model): ... book_id = models.ForeignKey(to='Book') author_id = models.ForeignKey(to='Author') ...
2. 全自动(利用orm帮我们创建)
class Book(models.Model): ... authors = models.ManyToManyField(to='Author') ...
-
优点
- 不用写代码,方便,快速
-
缺点
- 第三张表的扩展性极差
3. 纯手动
class Book2Author(models.Model): ... book_id = models.ForeignKey(to='Book') author_id = models.ForeignKey(to='Author') ...
- 优点
- 第三张表完全取决于你
- 缺点
- 代码太多,无法使用 orm 提供的正反向查询和简单方法(add,set,remove,clear···)
- 不推荐使用《纯手动》
10 Ajax简介
1. 什么是 Ajax ?
- 例子:
-github 注册用户名时,
页面会动态获取用户名的输入情况,实时发到后端检测是否已经被注册过 - 八个大字,精髓
- 局部筛选,异步提交
- 回顾一下我们学过的提交请求方式
- 地址栏输入url,只有get
- a标签的href属性,只有get
- from表单,get | post
- ajax,get | post
- 现在大部分都是 Ajax。
- 我们学的是 Jquery版本。所以要确保导入了JQuery。
11 Ajax基本语法
- 小例子:三个框,输入计算1 + 2 = 3
1. 前端
<input type="text" name="" id="d1">+ <input type="text" name="" id="d2">= <input type="text" name="" id="d3"> <p> <button id="btn">点我</button> </p> <script> // 用 jquery 给按钮绑定一个点击事件 $('#btn').click(function(){ // 朝后端发送 ajax 请求 $.ajax({ url: '', type: 'post', dataType: 'Json', data: {'i1': $('#d1').val(), 'i2': $('#d2').val()}, success:function(args){ // alert(args) $('#d3').val(args) } }) }) </script>
- 最后一个success是回调函数(形参写什么都可以)。无论输入啥,最后都是返回给回调函数。不是浏览器。
- 如果要返回字典的话,需要用 json 格式。
- 不管后端提交的什么格式,前端(这个页面)收到的参数都是字符串格式。
2. 后端 | 返回数据
def ab_ajax(request): if request.method == "POST": print(request.POST) i1 = request.POST.get('i1') i2 = request.POST.get('i2') i3 = int(i1) + int(i2) return HttpResponse(i3) return render(request, 'ab_ajax.html')
- 如果后端返回的是 HttpResoponse,只能在前端,Json.parse() 自己解决
- 如果后端返回的是 JsonResponse ,前端会自动帮你反序列化
dataType: 'Json',
import json def ab_ajax(request): ... # 这里 d 是个字典。 return JsonResponse(d) ...
- 以后写 ajax 的时候,可以直接加上,以防万一。
- Json 如何传输,看下一页。
Day67
Day 67 - 01 内容回顾
1. 如果出bug了,如何排查?
- Pycharm 窗口提示 | 前端 console 界面
- 单词写错没?
- 浏览器缓存没有清空或者端口号冲突(正在跑之前的项目)
- 重启计算机
Day 67 - 02 今日内容概要
Day 67 - 03 前后端传输数据编码格式
无论哪一种,最后都可以通过 request.POST 获取。
1. 前后端传输数据编码格式(POST)
-
前端向后端发psot请求的两种方式
- form 表单
- ajax
-
三种格式
- urlencoded
- formdata
- json
2. form 表单
- 发送文本
- 默认 encoded
- 查看方法
- 检查 – Network – 左侧 name – 在请求头(Request Heads)里面找
- content-type == encoded
- 最底下的 view source,可以看到
name=jason&password=123
- django 后端会自动帮忙就解析到 request.POST 中。
- 发送文件,
<form>
记得enctype="multipart/form-data"
- 如果是 encoded,
- 只要长得是
name=jason&password=123
,都会帮你解析到 request.POST 。
- 只要长得是
- 但是如果把编码格式改成 formdata,
- 字符还是会帮你解析到 request.POST 。
- 文件会解析到request.FILES 中
- 如果是 encoded,
3. ajax
- 默认也是 urlencoded
- 所以,无论哪种,只要是字符格式,都可以通过 request.POST 中获取。
Day 67 - 04 ajax 发送 json 格式数据
前后端一定要保证编码格式和数据真正格式是一致的。
比如说好的列表,最后发了个字符串。contentType: 'application/json', // 指定编码格式,
- 数据是真正的 json 格式
- 在 request.POST 里面找不到,要在 request.body 中自己去找。
1. 前端 html 文件 | 提交 json 格式数据
- 把数据转成json格式
- 指定编码格式
<button class="btn btn-success" id="d1">点我</button> <script> // 用 jquery 给按钮绑定一个点击事件 $('#d1').click(function(){ $.ajax({ url: '', type: 'post', // 转换成 json 格式 data: JSON.stringify({'username': 'jason', 'age': '18'}), // 指定编码格式 contentType: 'application/json', // 回调函数 success:function(){} }) }) </script> <!-- 朝后端发送的数据 --> <!-- {"username":"jason","age":"18"} -->
2. 方法补充:判断 ajax 请求
if(request.is_ajax()): pass
3. 后端 | 处理收到的 ajax json 格式数据
- 判断是 ajax 请求。
if(request.is_ajax()): ...
- 数据都在 request.body 里面。
- 用
json.loads(json_bytes)
,转换成字典,即可
# 第一种写法(全面) if(request.is_ajax()): # bytes -- str -- dict json_bytes = request.body json_str = json_bytes.decode('utf-8') json_dict = json.loads(json_str) print(json_dict, type(json_dict)) # 第二种写法(简便) if(request.is_ajax()): # bytes -- dict (json.loads() 会先自动解码,再自动反序列化) json_bytes = request.body json_dict = json.loads(json_bytes) print(json_dict, type(json_dict))
Day 67 - 05 ajax发送文件数据(非Json)
步骤
- 生成内置对象,存储数据
- 用 append(键值对) 的方式添加数据
- 注意,添加文件的时候
formDataObj.append('myfile', $('#d3')[0].files[0]);
- 必须添加两行代码,告诉浏览器别对数据动手动脚。参数:contentType,processData,都设置成false。
前端
<p>username:<input type="text" name="" id="d1"></p> <p>password<input type="text" name="" id="d2"></p> <p><input type="file" name="" id="d3"></p> <button class="btn btn-info" id="d4">点我,有你好看</button> <script> // 点击按钮,向后端发送普通键值对和文件 $('#d4').on('click', function() { // 1. 先生成一个内置对象 let formDataObj = new FormData(); // 2. 可以添加普通键值对,也可以添加文件 formDataObj.append('username', $('#d1').val()); formDataObj.append('password', $('#d2').val()); formDataObj.append('myfile', $('#d3')[0].files[0]); // 3. 将对象基于 ajax 发送给后端 $.ajax({ url: '', type: 'post', // 直接放对象即可 data: formDataObj, // 必须指定两个参数,告诉后端编码和浏览器,别对数据动手动脚 contentType: false, processData: false, // 回调函数 success: function(args){} }) }) </script>
后端
- 键值对都在 reuqest.POST 中
- 文件在 reuqest.FILES 中
def ab_file(request): if request.is_ajax(): if request.method == 'POST': print(request.POST) print(request.FILES) # 结果: # <QueryDict: {'username': ['jason'], 'password': ['123']}> # <MultiValueDict: {'myfile': [<InMemoryUploadedFile: WechatIMG746.jpeg (image/jpeg)>]}> return render(request, 'ab_file.html', locals())
总结
- 需要使用对象 formData
- 指定两个关键的参数,拒绝编码和数据处理
- 后端可以直接直接识别 formData 对象并且自动解析,将 普通键值对 封装到 request.POST 中,将文件封装到 request.FILES 中。
Day 67 - 06 django自带的序列化组件
1. 序列化组件有什么用?
- 因为如果前后端分离,前端不是 django 写的,那么就无法用模板语法了。
- 所以要用 json 格式
解决办法
- 再写一个接口文档(最多)
- 后端用户表里所有数据,发给前端。要是《列表套字典》的形式(本节课内容)
方法1 笨办法:(手动)列表套字典
def ab_ser(request): import json from django.http import JsonResponse user_queryset = models.User.objects.all() user_list = [] for user_obj in user_queryset: temp = { 'pk': user_obj.pk, 'username': user_obj.username, 'age': user_obj.age, 'gender': user_obj.get_gender_display(), } user_list.append(temp) return JsonResponse(user_list, safe=False)
结果(人工智能换行)
[ {"pk": 1, "username": "jason", "age": 18, "gender": "\u7537"}, {"pk": 2, "username": "egon", "age": 84, "gender": "\u5973"}, {"pk": 3, "username": "tank", "age": 40, "gender": "\u5176\u4ed6"}, {"pk": 4, "username": "tony", "age": 30, "gender": 4} ]
方法2 serializers 模块
def ab_ser(request): from django.core import serializers user_queryset = models.User.objects.all() # 自动帮你变成 json 格式字符串,而且非常全面 res = serializers.serialize('json', user_queryset) return HttpResponse(res)
结果
[ { "model": "day66.user", "pk": 1, "fields": {"username": "jason", "age": 18, "gender": 1} }, { "model": "day66.user", "pk": 2, "fields": {"username": "egon", "age": 84, "gender": 2} }, { "model": "day66.user", "pk": 3, "fields": {"username": "tank", "age": 40, "gender": 3} }, { "model": "day66.user", "pk": 4, "fields": {"username": "tony", "age": 30, "gender": 4} } ]
- 这种情况,要写个文档告诉前端对应关系。
- 前端只要点点点就可以了
Day 67 - 08 批量插入数据
1. 笨办法
- 一条一条往数据库里 create。
def ab_batch_insert(request): ''' 1. 先给Book,插入一万条数据 1. 所有数据查询并展示到页面。 :param request: :return: ''' for i in range(10000): models.Book.objects.create(title='第%i本书' %i) book_queryset = models.Book.objects.all() return render(request, 'ab_batch_insert.html', locals())
2. 批量插入(bulk_create)
- 先建列表 book_list ,往里面添加一大堆 book_obj
- 直接 bulk_create,插入列表
models.Book.objects.bulk_create(book_list)
def ab_batch_insert(request): book_list = [] for i in range(10000): book_obj = models.Book(title='第%i本书' %i) book_list.append(book_obj) models.Book.objects.bulk_create(book_list) book_queryset = models.Book.objects.all() return render(request, 'ab_batch_insert.html', locals())
Day 67 - 09 自定义分页器推导过程
1. 分页逻辑
# 1. 访问哪一页?通过get请求,如果没有,就展示第一页 current_page = request.GET.get('page', 1) # 数据类型转换 try: current_page = int(current_page) except Exception: current_page = 1 # 2. 每页展示多少条? per_page_num = 10 # 分页规律 # 3. 起始位置 start_page = (current_page - 1) * per_page_num # 4. 终止位置 end_page = current_page * per_page_num ''' 这要计算页面显示多少页的按钮 ''' # 展示所有数据 (切片操作) book_queryset = models.Book.objects.all()[start_page:end_page]
2. 第二部分
# 1. 访问哪一页?通过get请求,如果没有,就展示第一页 current_page = request.GET.get('page', 1) # 数据类型转换 try: current_page = int(current_page) except Exception: current_page = 1 # 1. 每页展示多少条? per_page_num = 10 # 规律 start_page = (current_page - 1) * per_page_num # 1. 起始位置 start_page = (current_page - 1) * per_page_num # 1. 终止位置 end_page = current_page * per_page_num # 分页,计算页面要显示多少页的按钮 book_list = models.Book.objects.all() all_count = book_list.count() page_count, more = divmod(all_count, per_page_num) if more: page_count += 1 page_html = '' xxx = current_page if current_page < 6: current_page = 6 for i in range(current_page-5, current_page+5): # 做一个高亮显示 if xxx == i: page_html += '<li class="active"><a href="?page=%s">%s</a></li>' %(i, i) else: page_html += '<li><a href="?page=%s">%s</a></li>' %(i, i) # 展示所有数据(切片操作) book_queryset = models.Book.objects.all()[start_page:end_page] return render(request, 'ab_batch_insert.html', locals())
Day 68
Day 68 - 01 内容回顾
Day 68 - 02 今日内容概要
Day 68 - 03 自定义分页器使用
- 知识补充
@property
,将方法伪装成属性,不需要加括号也可以调用- bootstrap 代码都不用拷贝了,直接调用下面就可以
- 拷贝大法步骤
- utils 文件夹,新建 myPage.py,拷贝下列代码。
- 可以在每个应用下,也可以在全局创建。
1. 自定义分页器源代码
myPage.py
class Pagination(object): def __init__(self, current_page, all_count, per_page_num=2, pager_count=11): """ 封装分页相关数据 :param current_page: 当前页 :param all_count: 数据库中的数据总条数 :param per_page_num: 每页显示的数据条数 :param pager_count: 最多显示的页码个数 """ try: current_page = int(current_page) except Exception as e: current_page = 1 if current_page < 1: current_page = 1 self.current_page = current_page self.all_count = all_count self.per_page_num = per_page_num # 总页码 all_pager, tmp = divmod(all_count, per_page_num) if tmp: all_pager += 1 self.all_pager = all_pager self.pager_count = pager_count self.pager_count_half = int((pager_count - 1) / 2) @property def start(self): return (self.current_page - 1) * self.per_page_num @property def end(self): return self.current_page * self.per_page_num def page_html(self): # 如果总页码 < 11个: if self.all_pager <= self.pager_count: pager_start = 1 pager_end = self.all_pager + 1 # 总页码 > 11 else: # 当前页如果<=页面上最多显示11/2个页码 if self.current_page <= self.pager_count_half: pager_start = 1 pager_end = self.pager_count + 1 # 当前页大于5 else: # 页码翻到最后 if (self.current_page + self.pager_count_half) > self.all_pager: pager_end = self.all_pager + 1 pager_start = self.all_pager - self.pager_count + 1 else: pager_start = self.current_page - self.pager_count_half pager_end = self.current_page + self.pager_count_half + 1 page_html_list = [] # 添加前面的nav和ul标签 page_html_list.append(''' <nav aria-label='Page navigation>' <ul class='pagination'> ''') first_page = '<li><a href="?page=%s">首页</a></li>' % (1) page_html_list.append(first_page) if self.current_page <= 1: prev_page = '<li class="disabled"><a href="#">上一页</a></li>' else: prev_page = '<li><a href="?page=%s">上一页</a></li>' % (self.current_page - 1,) page_html_list.append(prev_page) for i in range(pager_start, pager_end): if i == self.current_page: temp = '<li class="active"><a href="?page=%s">%s</a></li>' % (i, i,) else: temp = '<li><a href="?page=%s">%s</a></li>' % (i, i,) page_html_list.append(temp) if self.current_page >= self.all_pager: next_page = '<li class="disabled"><a href="#">下一页</a></li>' else: next_page = '<li><a href="?page=%s">下一页</a></li>' % (self.current_page + 1,) page_html_list.append(next_page) last_page = '<li><a href="?page=%s">尾页</a></li>' % (self.all_pager,) page_html_list.append(last_page) # 尾部添加标签 page_html_list.append(''' </nav> </ul> ''') return ''.join(page_html_list)
2. 使用自定义分页器代码
views.py
from utils.myPage import Pagination
前端
{% for book_obj in page_queryset %} <p>{{ book_obj.title }}</p> {% endfor %} {{ page_obj.page_html|safe }}
Day 68 - 04 forms组件前戏
1. forms 组件能干什么?
不使用 ajax 校验数据,直接返回给前端。
- 渲染 html 代码
- 校验数据
- 展示提示信息
需求
- 写一个 html 页面,获取用户名密码,form 表单提交数据
- 后端判断用户名和密码是否符合一定条件
1. 用户名中不能含有金瓶梅
1. 密码不能少于三位 - 将提示信息展现到 html 页面中。
解决思路
- 前端页面
- 用户名和密码的输入框后面,放一个看不见的span标签,用模板语法给它传值。
- 模板语法内容:
{{ back_dic.username }}
- 后端views
- 自己定义一个字典:
back_dic = {'username': '', 'password': ''}
。 - key 是每个变量的名字,value 先是空的。
- 如果有错误,修改 key 对应的 value。
- 写一个分支,如果校验正确,返回 back_dic,提示正确。如果校验错误,返回 back_dic,提示输入有误。
小例子
前端页面<form action="" method="post"> <p>username: <input type="text" class="form-control" name="username"> <span style="color: red">{{ back_dic.username }}</span> </p> <p>password: <input type="text" class="form-control" name="password"> <span style="color: red">{{ back_dic.password }}</span> </p> <input type="submit" class="btn- btn-info"> </form>
后端视图
def ab_form(request): back_dic = {'username': '', 'password': ''} if request.method == 'POST': username = request.POST.get('username') password = request.POST.get('password') if '金瓶梅' in username: back_dic['username'] = '不符合社会主义核心价值观' if len(password) < 3: back_dic['password'] = '太短了' return render(request, 'ab_form.html', locals())
注意
- 数据校验放在哪里?
- 前端可有可无,但是后端校验必须要有。
- 因为前端校验可以通过爬虫绕过。
- 购物网站原理
- 获取用户所选择的商品的主键值
- 后端查询出价格,再次计算一遍
- 如果和前端一致,那么完成支付。
Day 68 - 05 forms组件类书写
1. 主要记忆的代码
from django import forms
(导入模块)class MyForm(forms.Form):
(定义类)username = forms.CharField(..., ...)
(类似于orm语句)def clean_username(self): ...
(局部钩子)def clean(self): ...
(全局钩子)
2. 后面几节课的内容都在这里了
views.py
from django import forms # forms 组件 class MyForm(forms.Form): username = forms.CharField(min_length=3, max_length=8, label='用户名', error_messages={'min_length': '最少3位', 'max_length': '最多8位', 'required': '不能为空', }) password = forms.CharField(min_length=3, max_length=8, label='密码', error_messages={'min_length': '最少3位', 'max_length': '最多8位', 'required': '不能为空', }) confirm_password = forms.CharField(min_length=3, max_length=8, label='密码', error_messages={'min_length': '最少3位', 'max_length': '最多8位', 'required': '不能为空', }) email = forms.EmailField(error_messages={'invalid': '格式错误', 'required': '不能为空', }) # 局部钩子 def clean_username(self): username = self.clean_data.get('username') if '666' in username: self.add_error('username', '名称不能包含666') return username # 钩子放回去 # 全局钩子 def clean(self): password = self.clean_data.get('password') confirm_password = self.clean_data.get('confirm_password') if not password == confirm_password: self.add_error('confirm_password', '两次密码不一致') return self.cleaned_data # 钩子放回去,返回全部数据
Day 68 - 06 forms校验数据
- 额外学习
python django 的测试环境,测试forms组件
找到需要测试的文件
Pycharm 下方,点击一下:python console- 逐行输入
from app01 import models from app01 import views form_obj = views.MyForm({'username':'jason', 'password':'123', 'email':'112233'}) form_obj.is_vaild() # 返回True或False,数据是否全部合法 form_obj.cleaned_data() # 返回一个字典,包含正确的kv对 form_obj.errors() # 返回一个字典,包含错误的kv对,错误字段+错误原因(list)
- 补充少传、穿错的情况
''' 多传一个不需要的值 ''' form_obj = views.MyForm({'username':'jason', 'password':'123', 'email':'112233', 'hobby':'study'}) form_obj.is_vaild() # True form_obj.cleaned_data() # 返回一个字典,除了多出来的hobby form_obj.errors() # 空字典:{} ''' 少传一个kv值对 ''' form_obj = views.MyForm({'username':'jason', 'password':'123'}) form_obj.is_vaild() # False form_obj.cleaned_data() # 返回一个字典,两个正确的 form_obj.errors() # {'email':['This field is required.']}
- 验证 form校验组件
- python console 中写测试(我的没法用)
- 通过 myTests 验证报错信息
- 结论:
有几个参数,只找几个参数,多了的不管,少了有错
mytests.py
# 默认情况下,所有的字段必须传值 form_obj = views.MyForm({'username': 'jason', 'password': '123',}) print(form_obj.is_valid()) print(form_obj.cleaned_data) print(form_obj.errors) # 结果 # {'username': 'jason', 'password': '123'} # < ul class ="errorlist" > < li > email < ul class ="errorlist" > < li > This field is required.< / li > < / ul > < / li > < / ul >
Day 68 - 07 forms组件渲染标签
需求
写一个页面,在页面中获取用户名、密码、邮箱。
有了form组件,所有需要获取用户输入的标签,都不用自己写了。- views.py 定义函数(一行搞定)
def ab_form(request): # 1. 产生一个空对象 form_obj = MyForm() # 2. 直接将空对象传给 html 页面 return render(request, 'index.html', locals())
- index.html 前端页面
第 1 种渲染方式,以下三者选一。
缺点:封装程度太高,只适合本地测试
<form action="" method="post"> {{ form_obj.as_p }} {{ form_obj.as_ul }} {{ form_obj.as_table }} <input type="submit" class="btn- btn-info"> </form>
第 2 种渲染方式,label 对应的字段
优点:封装程度适中,可以自己决定 inp 在哪里,label 在哪里
缺点;要写的代码太多,一般情况下也不用
<form action="" method="post"> <p>第 2 种渲染方式</p> <p>{{ form_obj.username.label }} {{ form_obj.username }}</p> <p>{{ form_obj.password.label }} {{ form_obj.password }}</p> <p>{{ form_obj.email.label }} {{ form_obj.email }}</p> <input type="submit" class="btn- btn-info"> </form>
后端添加 label 属性
username = forms.CharField(min_length=3, max_length=8, label='用户名',)
第 3 种渲染方式,for+label+span(推荐使用)
- for循环
- .label 和 form
- 加一个看不见的sapn
<form action="" method="post"> {% for form in form_obj %} <p> {{ form.label }} : {{ form }} <span style="color: red">{{ form.errors }}</span> </p> {% endfor %} <input type="submit" class="btn- btn-info"> </form>
class MyForm(forms.Form): username = forms.CharField(min_length=3, max_length=8, label='用户名',) password = forms.CharField(min_length=3, max_length=8, label='密码',) confirm_password = forms.CharField(min_length=3, max_length=8, label='密码',) email = forms.EmailField()
推荐写法
- for循环
- 用点lable放置提示信息。
- 添加一个 span,打印错误信息(点errors,可自定义)
Day 68 - 08 forms组件展示错误信息
- 后端代码
- 为什么每次刷新页面点时候,输入框内的信息才不会消失?
一开始一定要产生一个
form_obj
空对象
并且 if 判断:if reuqest.method == 'POST':
内和外的form_obj
都要名字一致。
这样,你输入了什么,最后还会原原本本返回给前端页面,看上去和没消失一样。def login(request): form_obj = MyForm() if reuqest.method == 'POST': form_obj = MyForm(request.POST) if form_obj.is_valid(): return HttpResponse('OK') else: pass return render(request, 'login.html', locals())
- 前端
- 前端校验弱不禁风,如果要打破前端校验,直接修改检查即可。,默认浏览器不做校验,标签内添加属性 novalidate :
<form action="" method="post" novalidate>
- 在后方展示错误提示信息
{{ form.errors.0 }}
要点0。因为默认form.errors是一个字典,会帮你自动生成<ul>标签
<form action="" method="post"> {% for form in form_obj %} <p> {{ form.label }} : {{ form }} <span style="color: red">{{ form.errors.0 }}</span> </p> {% endfor %} <input type="submit" class="btn- btn-info"> </form>
Day 68 - 09 forms组件钩子函数
1. 什么是钩子函数?
在特定节点触发
- form 组件钩子有哪些?什么情况下用?
- 局部钩子
- 全局钩子
局部钩子,例如:用户名,需要给
某个字段
增加校验规则的时候使用。
全局钩子,例如:确认密码,多个字段
增加校验规则的时候使用
2. 怎么写form 组件的钩子函数?
- 在 MyForm 类里面写两个函数。
def clean_username(self):
def clean(self):
- 钩子函数中,获取 MyForm 类里面的参数
username = self.clean_data.get('username')
获某个 key
self.add_error('username':'xxx')
给某个 key 添加错误信息- 钩子放回去
return username # 钩子放回去
局部钩子
return self.cleaned_data # 钩子放回去,返回全部数据
全局钩子- 修改 CSS样式,就提交以下参数
widget=forms.widget.TextInput(attrs={'class': 'form-control'})
attrs 属性,里面以字典形式放 css 属性
代码展示:
class MyForm(forms.Form): username = forms.CharField( min_length=3, max_length=8, label='用户名', error_messages={'min_length': '最少3位', 'max_length': '最多8位', 'required': '不能为空', }) password = forms.CharField( min_length=3, max_length=8, label='密码', error_messages={'min_length': '最少3位', 'max_length': '最多8位', 'required': '不能为空', }) confirm_password = forms.CharField( min_length=3, max_length=8, label='密码', error_messages={'min_length': '最少3位', 'max_length': '最多8位', 'required': '不能为空', }) email = forms.EmailField( error_messages={'invalid': '格式错误', 'required': '不能为空', }) # 局部钩子 def clean_username(self): username = self.clean_data.get('username') if '666' in username: self.add_error('username', '名称不能包含666') return username # 钩子放回去 # 全局钩子 def clean(self): password = self.clean_data.get('password') confirm_password = self.clean_data.get('confirm_password') if not password == confirm_password: self.add_error('confirm_password', '两次密码不一致') return self.cleaned_data # 钩子放回去,返回全部数据
Day 68 - 10 重要参数介绍
1. form 组件其他参数及知识点(其他看看博客)
字段 含义 填充 lable 字段名 error_message 自定义报错信息 initial 默认值 required 字段是否必须填写 2. 其他验证
参考:
- RegexValidator验证器(要导入模块,是一个列表,可以写多个正则)
- 正则校验:别自己写,网上找
phone = forms.CharField( validators=[ RegexValidator(r'[0-9]+&', '请输入数字'), RegexValidator(r'159[0-9]+&', '必须以159开头'), ] )
Day 68 - 11 其他字段类型
-
其他类型渲染
Json博客园(django-form组件)
select,checkbox,radio自己看博客,拷贝过来改改即可使用。 -
代码示例
hobby = forms.MultipleChoiceField( choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ), label="爱好", initial=[1, 3], widget=forms.widgets.SelectMultiple() )
- 规律
字段只要是单选框,都是
字段 = forms.ChoiceField(...)
,
字段只要是多选框,都是字段 = forms.MultipleChoiceField(...)
,
widget = forms.widgets.什么()
,就是什么选择标签Day 69
Day 69 - 01 内容回顾
- 封装程序的思路
- 先写面条版,完成功能再去考虑优化
- 尝试封装函数
- 尝试面向对象
Day 69 - 02 今日内容概要
- forms 组件
- cookie 与 session
- django 中间件(目前最好的)
- csrf 跨站请求伪造
- 视图函数(CBV)如何添加装饰器
Day 69 - 03 forms组件源码
- forms 组件源码的切入点
form_obj.is_valid()
- forms.py 源码
def is_valid(self): """ Returns True if the form has no errors. Otherwise, False. If errors are being ignored, returns False. """ return self.is_bound and not self.errors
- self.is_bound 和 not self.errors 必须同时为 True
- self.is_bound 只要有值,就是 True。
- errors 通过 @property,伪装成属性。
# 关于 bound def __init__(self, data=None, ...): self.is_bound = data is not None or files is not None # 关于 errors @property def errors(self): "Returns an ErrorDict for the data provided for the form" if self._errors is None: self.full_clean() return self._errors # 精髓所在,校验功能,钩子功能,都出自于 full_clean 方法。 def full_clean(self): """ Cleans all of self.data and populates self._errors and self.cleaned_data. """ self._errors = ErrorDict() if not self.is_bound: # Stop further processing. return self.cleaned_data = {} # If the form is permitted to be empty, and none of the form data has # changed from the initial data, short circuit any validation. if self.empty_permitted and not self.has_changed(): return self._clean_fields() # 校验字段源码 self._clean_form() self._post_clean()
- 回顾一下反射的知识
- 有了反射,可以自己通过字符串的形式,给对象添加/修改/删除属性方法等等操作。
- 主要有 setattr / getattr / hasattr / delattr方法
- 如果要修改函数的话,使用lambda表达式。
''' 定义一个类 ''' >>> class Foo(object): ... def __init__(self): ... pass ... >>> obj = Foo() ''' 继承关系 ''' >>> isinstance(obj, Foo) True >>> issubclass(Foo, object) True >>> setattr(obj, 'sb', True) >>> obj.sb True >>> obj.sb() Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'bool' object is not callable ''' 设置成函数,加括号调用,注意参数要把对象自己传进去。 ''' >>> setattr(obj, 'sb', lambda self, name: print(name + 'sb')) >>> obj.sb('egon') Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: <lambda>() missing 1 required positional argument: 'name' >>> obj.sb(obj, 'egon') egonsb >>> setattr(obj, 'sb', lambda self, name: print(name + '_dsb')) >>> getattr(obj, 'sb')(obj, 'egon') egon_dsb >>>
- 加括号调用
>>> setattr(obj, 'p', lambda : print('xxx')) >>> obj.p() xxx >>> name = 'p' >>> getattr(obj, name)() xxx >>>
Day 69 - 04 cookie与session简介
- 保存在浏览器的信息都可以叫 cookie
- 用户一次登录成功以后,信息自动保存在
浏览器本地
。 - 之后访问的时候,自动将
保存在本地浏览器的用户名和密码
发送给服务端
- 服务端获取后自动验证。
1. cookie
- 保存在浏览器的 kv键值对,都可以叫 cookie
- 都是键值对的形式
- 安全优化 1
1. 当登录成功以后,服务端产生一个随机字符串,交给浏览器客户端保存
2. 服务端用 kv键值对的形式保存这个随机字符串
3. 形式:随机字符串1:用户1信息
4. 之后访问,都带着这个随机字符串
5. 隐患:如果你拿到了随机字符串,也可以冒充当前用户访问。 - 安全优化 2
1. session
2. session
保存在服务端
的 kv键值对(可以有多个)基于 cookie 工作
,其实大部分保存用户状态的操作,都是通过 cookie 的。- 缺点:经不住量大
3. token(下次再讲)
- 服务端不再保存数据,
- 登录成功之后,用自己独有的方式,将用户信息加密处理
- 加密以后,密文拼接在用户信息后面,整体返回给浏览器保存。
- 浏览器下次访问的时候,依旧是用户信息+密文两段,服务端切取用户信息段,再次加密。跟尾部密文段进行比对。
4. jwt 认证(后期会讲)
5. 小总结
- cookie 在浏览器
- session 在服务端
- session 基于 cookie 工作。
- 大部分保存用户状态操作,都需要 cookie
Day 69 - 05 django操作cookie
1. 为什么要 cookie 操作
因为http协议是无状态的,如果一个人1000次访问,1000次都是初见。
查看方法
浏览器 / applications / cookies,里面有很多键值对
- 所有要用户登录的网站,都要保存cookie
- 当然浏览器可以选择拒绝保存(设置里面)
1. 后果:然后登录页面都没了
2. cookies 最简单的操作原理
- 设置 cookie 和获取 cooke
''' 设置 ''' obj = HttpResponse() obj.set_cookie(key, value) obj.set_signed_cookie(key, value, salt='盐') ''' 获取 ''' request.COOKIES.get(key) request.get_signed_cookie(key, salt='盐')
- 返回 cookie
''' 原本的 views 函数返回值 ''' return HttpResponse(...) ''' 操作 cookies 就改成这样 ''' obj1 = HttpResponse() ... return obj1
4. 操作 cookie,写一个真正的登录功能
- 目标函数外面套上装饰器,把 request 拿出来
1. 获取目标url。
1. 如果从 cookie 获取到了用户名,那就执行目标函数(跳转过去)
1. 如果没有,就重定向到登录页面,redirect(’/day66/login/?next=%s’ %target_url) - 登录页面中,分离出 next 参数。
1. 如果登录上了,就走到下一个。
1. 万一下一个是空,就到home
# 装饰器 def login_auth(func): # request 要单独拿出来 def inner(request, *args, **kwargs): target_url = request.get_full_path() if request.COOKIES.get('username'): res = func(request, *args, **kwargs) return res else: return redirect('/day66/login/?next=%s' %target_url) return inner def login(request): if request.method == 'POST': username = request.POST.get('username') password = request.POST.get('password') if username == 'jason' and password == '123': target_url = request.GET.get('next') # 获取用户上一次想要访问的 ur(这个结果可能是 None) if target_url: obj = redirect(target_url) else: obj = redirect('/day66/home/') # 保存用户状态(可以设置超时时间,3秒,ie浏览器就是expires=3) obj.set_cookie('username', 'jason666', max_age=3) # 跳转到一个用户登录才能看到的页面 return obj return render(request, 'login.html', locals()) @login_auth def home(request): return HttpResponse('我是 home 登录才能看到我哦') @login_auth def index(request): return HttpResponse('我是 index 登录才能看到我哦') @login_auth def func(request): return HttpResponse('我是 func 登录才能看到我哦')
5. 设置 cookie 超时时间
- obj.set_cookie() 中设置参数,都是以秒为单位。
max_age=3
3 秒超时expires
(针对 ie)
- 主动注销,删除 cookie 。
@login_auth def logout(request): obj = redirect('/day66/login/') # 写上要删除的键值对 obj.delete_cookie('username') return obj
Day 69 - 06 session操作
1. 什么是 session?
session 保存在服务端,返回给客户端一个随机字符串
sessionid : 随机字符串
应用场景
- 用户登录
- 当需要保存一段数据的时候,也可以操作session表,例如验证码
''' 设置 session ''' request.session['key'] = value ''' 获取 session ''' request.session.get('key')
注意
- 有一张默认的django_session表来存储数据.
- session_key,
- session_data,
- expire_date, 过期时间
- 在新项目中,一定要要执行过数据库迁移命令,才能使用
- 查看方法
1. chrome / Application / storage / cookie
2. Safari / 存储空间 / cookie
过期时间
默认的过期时间是14天,但是可以自己手动设置。
2. 如何操作和获取 session
def set_session(request): # 设置 session request.session['hobby'] = 'girl' request.session['hobby1'] = 'girl1' # 设置超时时间 request.session.set_expiry(0) return HttpResponse('嘿嘿嘿')
设置 session 的时候,内部
- 自动帮你生成一个随机字符串
- 将随机字符串和对应的数据存储到 django_session 表中(不是直接生效的,分为两小步)
- 在内存中产生操作数据的缓存
- 在经过中间件 SessionMiddlewre 的时候,才真正操作数据库
- 随机字符串返回给浏览器保存
def get_session(request): print(request.session.get('hobby')) return HttpResponse('哈哈哈')
获取session 的时候,内部
- 自动从浏览器请求头中获取 sessionid 对应的随机字符串
- 拿着随机字符串去 django_session 表中查找对应数据
- 比对
- 比对上了,就取出对应数据,并封装到 request.session 字典中
- 比对不上,request.session.get()返回none
其他
- session
可以设置多个键值对
- django_session 表中
数据条数取决于浏览器
。- 同一个计算机同一个浏览器
只有一条数据
生效。 - 同一个ip,同一个计算机,不同浏览器,就不同数据。
- 当过期的时候,短暂可能出现多条,但是内部会自动清理。
- 主要为了节省服务端资源
- 同一个计算机同一个浏览器
- session 也
可以设置过期时间
(四种类型的参数)。
request.session.set_expiry(0)
设置过期时间
request.session.set_expiry(0)
的参数- 整数 -> 秒数
- 日期对象 -> 到指定日期失效
- 0 -> 关闭浏览器就自动失效
- 不写 -> 取决于 django 内部全局 session 失效时间(默认14天)
- 手动清除session
# 浏览器和服务端都清空(推荐使用) request.session.flush() # 只删服务端(不太用) request.session.delete()
- session 是保存在服务端的,但是位置可以有多种选择
- MySQL
- 文件
- redis
- memache
- ···
Day 69 - 07 CBV添加装饰器的三种方式
导入模块
CBV中,django不建议直接给方法加装饰器,要导入模块
from django.utils.decorators import method_decorator
方式 1 ,直接在方法上面装
# 导入 views 和 方法装饰器 from django import views from django.utils.decorators import method_decorator class MyLogin(views): # 方式 1 @method_decorator(login_auth) def get(self): return HttpResponse('get 请求') def post(self): return HttpResponse('post 请求')
方式 2,在类上面装
可以装多个,可以针对不同的方法加不同的装饰器
from django import views from django.utils.decorators import method_decorator @method_decorator(login_auth, name='get') @method_decorator(login_auth, name='post') class MyLogin(views): def get(self): return HttpResponse('get 请求') def post(self): return HttpResponse('post 请求')
方式 3,重写,加在dispatch方法上
直接作用于所有方法
from django import views from django.utils.decorators import method_decorator class MyLogin(views): @method_decorator(login_auth) def dispatch(self, request, *args, **kwargs): pass def get(self): return HttpResponse('get 请求') def post(self): return HttpResponse('post 请求')