十二,Django框架
可以以下链接获取Django框架学习笔记,md文档和pdf文档
Django框架超详细的学习笔记,点击我获取
12.1 命令行操作
# 创建django项目
django-admin startproject aini# 启动项目
cd /mysite
python3 manage.py runserver## 创建应用
"""
Next, start your first app by running python manage.py startapp [app_label].
"""python manage.py startapp app01## 应用名应该做到见名知意userorderweb...## 但是我们教学统一就用app01/02/03/04#有很多文件# pycharm操作# 1 new project 选择左侧第二个django即可# 2 启动1.还是用命令行启动2.点击绿色小箭头即可# 3 创建应用1.pycharm提供的终端直接输入完整命令2.pycharm tools run manage.py task提示(前期不要用 给我背完整命令)# 4 修改端口号以及创建server edit confi....
12.2 主要文件介绍
-mysite项目文件夹--mysite文件夹---settings.py ## 配置文件---urls.py ## 路由与视图函数对应关系(路由层)---wsgi.py ## wsgiref模块(不考虑)--manage.py ## django的入口文件--db.sqlite3 ## django自带的sqlite3数据库(小型数据库 功能不是很多还有bug)--app01文件夹---admin.py ## django后台管理---apps.py ## 注册使用---migrations文件夹 ## 数据库迁移记录---models.py ## 数据库相关的 模型类(orm)---tests.py ## 测试文件---views.py ## 视图函数(视图层)
12.3 应用
"""
django是一款专门用来开发app的web框架django框架就类似于是一所大学(空壳子)
app就类似于大学里面各个学院(具体功能的app)比如开发淘宝订单相关用户相关投诉相关创建不同的app对应不同的功能选课系统学生功能老师功能一个app就是一个独立的功能模块
"""
## ***********************创建的应用一定要去配置文件中注册**********************
INSTALLED_APPS = ['django.contrib.admin','django.contrib.auth','django.contrib.contenttypes','django.contrib.sessions','django.contrib.messages','django.contrib.staticfiles','app01.apps.App01Config', # 全写'app01', # 简写
]
# 创建出来的的应用第一步先去配置文件中注册 其他的先不要给我干
## ps:你在用pycharm创建项目的时候 pycharm可以帮你创建一个app并且自动注册
***********************************************************************
12.4 命令行与pycharm创建的区别
# 1 命令行创建不会自动有templatew文件夹 需要你自己手动创建而pycharm会自动帮你创建并且还会自动在配置文件中配置对应的路径
# pycharm创建
TEMPLATES = [{'BACKEND': 'django.template.backends.django.DjangoTemplates','DIRS': [os.path.join(BASE_DIR, 'templates')]
]
# 命令行创建
TEMPLATES = [{'BACKEND': 'django.template.backends.django.DjangoTemplates','DIRS': [],
]
"""
也就意味着你在用命令创建django项目的时候不单单需要创建templates文件夹还需要去配置文件中配置路径
'DIRS': [os.path.join(BASE_DIR, 'templates')]
12.5 django小白必会三板斧
"""
HttpResponse返回字符串类型的数据render返回html文件的redirect重定向return redirect('https://www.mzitu.com/')return redirect('/home/')
"""
12.6 静态文件配置
12.6.1 static文件作用
## 前段写好了的,能够直接调用使用的都称之为静态文件## 网站写好的js文件## 写好的css文件## 网站用到的图片文件## 第三方插件等等## 需要手动创建static文件
12.6.2 static文件配置
STATIC_URL = '/ooo/' # 类似于访问静态文件的令牌
"""如果你想要访问静态文件 你就必须以static开头"""
"""
/static/bootstrap-3.3.7-dist/js/bootstrap.min.js/static/令牌
取列表里面从上往下依次查找bootstrap-3.3.7-dist/js/bootstrap.min.js都没有才会报错
"""
# 静态文件配置(多个静态文件)
STATICFILES_DIRS = [os.path.join(BASE_DIR,'static'),os.path.join(BASE_DIR,'static1'),os.path.join(BASE_DIR,'static2'),
]
12.6.3 静态文件动态解析
# 静态文件动态解析{% load static %}<link rel="stylesheet" href="{% static 'bootstrap-3.3.7-dist/css/bootstrap.min.css' %}"><script src="{% static 'bootstrap-3.3.7-dist/js/bootstrap.min.js' %}"></script>
12.6.4 form表单提交数据,及action参数
# form表单默认是get请求数据http://127.0.0.1:8000/login/?username=jason&password=123
"""
form表单action参数1.不写 默认朝当前所在的url提交数据2.全写 指名道姓3.只写后缀 /login/
"""
12.6.5 需要配置文件组中注释掉一行代码
# 在前期我们使用django提交post请求的时候 需要取配置文件中注释掉一行代码
MIDDLEWARE = ['django.middleware.security.SecurityMiddleware','django.contrib.sessions.middleware.SessionMiddleware','django.middleware.common.CommonMiddleware',# 'django.middleware.csrf.CsrfViewMiddleware','django.contrib.auth.middleware.AuthenticationMiddleware','django.contrib.messages.middleware.MessageMiddleware','django.middleware.clickjacking.XFrameOptionsMiddleware',
]
12.7 request对象方法初识
12.7.1 方法
request.method # 返回请求方式 并且是全大写的字符串形式 <class 'str'>
request.POST # 获取用户post请求提交的普通数据不包含文件request.POST.get() # 只获取列表最后一个元素
request.POST.getlist() # 直接将列表取出request.GET # 获取用户提交的get请求数据
request.GET.get() # 只获取列表最后一个元素
request.GET.getlist() # 直接将列表取出
"""
get请求携带的数据是有大小限制的 大概好像只有4KB左右
而post请求则没有限制
"""
12.7.2 案例
def login(request):# 返回一个登陆界面"""get请求和post请求应该有不同的处理机制:param request: 请求相关的数据对象 里面有很多简易的方法:return:"""# print(type(request.method)) # 返回请求方式 并且是全大写的字符串形式 <class 'str'># if request.method == 'GET':# print('来了 老弟')# return render(request,'login.html')# elif request.method == 'POST':# return HttpResponse("收到了 宝贝")if request.method == 'POST':return HttpResponse("收到了 宝贝")return render(request, 'login.html')
12.7.3 request.POST结果
request.POST ## 结果是一个字典
## <QueryDict: {'username': ['aini'], 'password': ['123']}
## 值是列表
request.POST.get('password') # 'password'
## 取password这个键的值列表中的最后一个值## 如何取值列表呢
request.POST.getlist('password') ## ['password']
12.8 django链接数据库(MySQL)
# 默认用的是sqkite3
DATABASES = {'default': {'ENGINE': 'django.db.backends.sqlite3','NAME': os.path.join(BASE_DIR, 'db.sqlite3'),}
}# django链接MySQL1.第一步配置文件中配置DATABASES = {'default': {'ENGINE': 'django.db.backends.mysql','NAME': 'day60','USER':'root','PASSWORD':'admin123','HOST':'127.0.0.1','PORT':3306,'CHARSET':'utf8'}
}2.代码声明 ## django默认用的是mysqldb模块链接MySQL## 但是该模块的兼容性不好 需要手动改为用pymysql链接## 你需要告诉django不要用默认的mysqldb还是用pymysql# 在项目名下的__init__或者任意的应用名下的__init__文件中书写以下代码都可以import pymysqlpymysql.install_as_MySQLdb()
12.9 Django ORM
"""
ORM. 对象关系映射
作用:能够让一个不用sql语句的小白也能够通过python 面向对象的代码简单快捷的操作数据库
不足之处:封装程度太高 有时候sql语句的效率偏低 需要你自己写SQL语句类 表对象 记录对象属性 记录某个字段对应的值应用下面的models.py文件
"""# 1 先去models.py中书写一个类class User(models.Model):# id int primary_key auto_incrementid = models.AutoField(primary_key=True)# username varchar(32)username = models.CharField(max_length=32)# password intpassword = models.IntegerField()*************************# 2 数据库迁移命令*************************
python3 manage.py makemigrations
##将操作记录记录到小本本上(migrations文件夹)python3 manage.py migrate ## 将操作真正的同步到数据库中
# 只要你修改了models.py中跟数据库相关的代码 就必须重新执行上述的两条命令
******************************************************************class User(models.Model):# id int primary_key auto_incrementid = models.AutoField(primary_key=True,verbose_name='主键')# username varchar(32)username = models.CharField(max_length=32,verbose_name='用户名')"""CharField必须要指定max_length参数 不指定会直接报错verbose_name该参数是所有字段都有的 就是用来对字段的解释"""# password intpassword = models.IntegerField(verbose_name='密码')class Author(models.Model):# 由于一张表中必须要有一个主键字段 并且一般情况下都叫id字段# 所以orm当你不定义主键字段的时候 orm会自动帮你创建一个名为id主键字段# 也就意味着 后续我们在创建模型表的时候如果主键字段名没有额外的叫法 那么主键字段可以省略不写# username varchar(32)username = models.CharField(max_length=32)# password intpassword = models.IntegerField()
12.9.1 字段的增删改查
# 字段的增加1.可以在终端内直接给出默认值2.该字段可以为空info = models.CharField(max_length=32,verbose_name='个人简介',null=True)3.直接给字段设置默认值hobby = models.CharField(max_length=32,verbose_name='兴趣爱好',default='study')# 字段的修改## 直接修改代码然后执行数据库迁移的两条命令即可!# 字段的删(不建议)## 直接注释对应的字段然后执行数据库迁移的两条命令即可!## 执行完毕之后字段对应的数据也都没有了"""
在操作models.py的时候一定要细心千万不要注释一些字段执行迁移命令之前最好先检查一下自己写的代码
"""# 个人建议:当你离开你的计算机之后一定要锁屏
12.9.2 数据的增删改查
# 今天只会介绍一点点 后面会详细的介绍# 查
res = models.User.objects.filter(username=username)
user_obj = models.User.objects.filter(username=username).first()
"""
返回值你先看成是列表套数据对象的格式
它也支持索引取值 切片操作 但是不支持负数索引
它也不推荐你使用索引的方式取值"""
filter括号内可以携带多个参数 参数与参数之间默认是and关系
你可以把filter联想成where记忆# 增
from app01 import models
res = models.User.objects.create(username=username,password=password)
# 返回值就是当前被创建的对象本身# 第二种增加
user_obj = models.User(username=username,password=password)
user_obj.save() # 保存数据
12.9.3 查
# 查所有数据# 方式1data = models.User.objects.filter()print(data)# <QuerySet [<User: User object>, <User: User object>, <User: User object>, <User: User object>]># 方式2user_queryset = models.User.objects.all()# <QuerySet [<User: User object>, <User: User object>, <User: User object>, <User: User object>]># return render(request,'userlist.html',{'user_queryset':user_queryset})return render(request,'userlist.html',locals())
## locals() 函数可以把当前作用域中的变量传给前端
12.9.4 改
## 根据id 修改数据库中的数据
password = request.POST.get('password')
username = request.POST.get('username')## 方式1
models.User.objects.filter(id=edit_id).update(username=username,password=password)"""将filter查询出来的列表中所有的对象全部更新 批量更新操作只修改被修改的字段
"""## 方式2
edit_obj = models.User.objects.filter(id=user_id).first()
edit_obj.username = username
edit_obj.password = password
edit_obj.save()
"""上述方法当字段特别多的时候效率会非常的低从头到尾将数据的所有字段全部更新一边 无论该字段是否被修改
"""
12.9.5 删除
models.User.objects.filter(id=delete_id).delete() ## 可以批量删除
12.9.6 orm创建表关系
'''
表与表之间的关系一对多多对一一对一
'''
from django.db import models# Create your models here.
# 创建表关系 先将基表创建出来 然后再添加外键字段
class Book(models.Model):title = models.CharField(max_length=32)price = models.DecimalField(max_digits=8,decimal_places=2)## 小数总共八位,小数点后面占俩位'''外键关系:图书和出版社是一对多,并且书是多的一方,所以外键字段放在书表里面 '''publish = models.ForeignKey(to='Publish')## 还有个to_field参数,不指定的话默认就是与出版社表的主键字段做外键关联'''如果字段对应的是ForeignKey,那么会自动在字段的后面加id,如publish_id;如果自作聪明的加了_id,那么orm还是会给我们加_id''''''图书和出版社是一对多,并且书是多的一方,所以外键字段放在书表里面外键字段建在任意一方即可,但是推荐你建在查询频率较高的一方 '''# authors是虚拟字段,主要是告诉orm 书籍表和作者表是多对多的关系,让orm自动帮你创建第三张关系表authors = models.ManyToManyField(to='Author')class Publish(models.Model):name = models.CharField(max_length=32)addr = models.CharField(max_length=32)class Author(models.Model):name = models.CharField(max_length=32)age = models.IntegerField()'''作者与作者详情是一对一的关系,外键字段建在任意一方都可以,但是推荐你建立在查询频率较高的一方'''author_detail = models.OneToOneField(to='AuthorDetail')'''自动给字段加_id后缀,所以不要自作聪明加_id后缀'''class AuthorDetail(models.Model):phone = models.BigIntegerField()addr = models.CharField(max_length=32)
12-10 Django 请求声明周期流程图
# 扩展知识点"""缓存数据库提前已经将你想要的数据准备好了 你来直接拿就可以提高效率和响应时间当你在修改你的数据的时候 你会发现数据并不是立刻修改完成的而是需要经过一段时间才会修改博客园了解即可
"""
12-11 路由层
11-1 路由匹配
# 路由匹配
url(r'test',views.test),
url(r'testadd',views.testadd)
"""
url方法第一个参数是正则表达式只要第一个参数正则表达式能够匹配到内容 那么就会立刻停止往下匹配直接执行对应的视图函数你在输入url的时候会默认加斜杠django内部帮你做到重定向一次匹配不行url后面加斜杠再来一次
"""
# 取消自动加斜杠
APPEND_SLASH = False/True # 默认是自动加斜杠的urlpatterns = [url(r'^admin/', admin.site.urls),# 首页url(r'^$',views.home),# 路由匹配url(r'^test/$',views.test),url(r'^testadd/$',views.testadd),# 尾页(了解)url(r'',views.error),
]
11-2 无名分组
"""
分组:就是给某一段正则表达式用小括号扩起来
"""
url(r'^test/(\d+)/',views.test)def test(request,xx):print(xx)return HttpResponse('test')# 无名分组就是将括号内正则表达式匹配到的内容当作位置参数传递给后面的视图函数
11-3 有名分组
"""
可以给正则表达式起一个别名
"""
url(r'^testadd/(?P<year>\d+)',views.testadd)def testadd(request,year):print(year)return HttpResponse('testadd')# 有名分组就是将括号内正则表达式匹配到的内容当作关键字参数传递给后面的视图函数
11-4 无名有名是否可以混用
"""
嘻嘻 不能混用
但是同一个分组可以使用N多次
"""# 单个的分组可以使用多次
url(r'^index/(\d+)/(\d+)/(\d+)/',views.index),
url(r'^index/(?P<year>\d+)/(?P<age>\d+)/(?P<month>\d+)/',views.index),
11-5 反向解析
"""
本质:通过一些方法得到一个结果 该结果可以访问到对应的url从而触发视图函数的运行
"""
# 最简单的情况 url第一个参数里面没有正则符号url(r'^index/',view.index,name='xxx')
# 前端{% url 'xxx' %}
# 后端from django.shortcuts import reversereverse('xxx')
"""
别名不能出现冲突!!!
"""
11-6 无名分组反向解析
# 无名分组反向解析url(r'^index/(\d+)/',views.index,name='xxx')# 前端{% url 'xxx' 123 %}
# 后端reverse('xxx', args=(1,))"""
这个数字写代码的时候应该放什么数字一般情况下放的是数据的主键值 数据的编辑和删除url(r'^edit/(\d+)/',views.edit,name='xxx')def edit(request,edit_id):reverse('xxx',args=(edit_id,)){%for user_obj in user_queryset%}<a href="{% url 'xxx' user_obj.id %}">编辑</a>{%endfor%}今天每个人都必须完成的作业(*******)利用无名有名 反向解析 完成数据的增删改查
"""
11-7 有名分组的反向解析
# 有名分组反向解析url(r'^func/(?P<year>\d+)/',views.func,name='ooo')
# 前端<a href="{% url 'ooo' year=123 %}">111</a> 了解<a href="{% url 'ooo' 123 %}">222</a> 记忆# 后端 # 有名分组反向解析 写法1 了解print(reverse('ooo',kwargs={'year':123}))# 简便的写法 减少你的脑容量消耗 记跟无名一样的操作即可print(reverse('ooo',args=(111,)))
11-8 路由分发
"""
django的每一个应用都可以有自己的templates文件夹 urls.py static文件夹
正是基于上述的特点 django能够非常好的做到分组开发(每个人只写自己的app)
作为组长 只需要将手下书写的app全部拷贝到一个新的django项目中 然后在配置文件里面注册所有的app再利用路由分发的特点将所有的app整合起来当一个django项目中的url特别多的时候 总路由urls.py代码非常冗余不好维护
这个时候也可以利用路由分发来减轻总路由的压力利用路由分发之后 总路由不再干路由与视图函数的直接对应关系
而是做一个分发处理识别当前url是属于哪个应用下的 直接分发给对应的应用去处理"""# 总路由
from app01 import urls as app01_urls
from app02 import urls as app02_urls
urlpatterns = [url(r'^admin/', admin.site.urls),# 1.路由分发url(r'^app01/',include(app01_urls)), # 只要url前缀是app01开头 全部交给app01处理url(r'^app02/',include(app02_urls)) # 只要url前缀是app02开头 全部交给app02处理# 2.终极写法 推荐使用url(r'^app01/',include('app01.urls')),url(r'^app02/',include('app02.urls'))# 注意事项:总路由里面的url千万不能加$结尾
]# 子路由# app01 urls.pyfrom django.conf.urls import urlfrom app01 import viewsurlpatterns = [url(r'^reg/',views.reg)]# app02 urls.pyfrom django.conf.urls import urlfrom app02 import viewsurlpatterns = [url(r'^reg/',views.reg)]
11-9 命名空间
# 当多个应用出现了相同的别名 我们研究反向解析会不会自动识别应用前缀
"""
正常情况下的反向解析是没有办法自动识别前缀的
"""# 名称空间# 总路由url(r'^app01/',include('app01.urls',namespace='app01')),url(r'^app02/',include('app02.urls',namespace='app02'))# 解析的时候# app01urlpatterns = [url(r'^reg/',views.reg,name='reg')]# app02urlpatterns = [url(r'^reg/',views.reg,name='reg')]## 后端反向解析reverse('app01:reg')reverse('app02:reg')## 前端反向解析{% url 'app01:reg' %}{% url 'app02:reg' %}
# 其实只要保证名字不冲突 就没有必要使用名称空间
"""
一般情况下 有多个app的时候我们在起别名的时候会加上app的前缀
这样的话就能够确保多个app之间名字不冲突的问题
"""
urlpatterns = [url(r'^reg/',views.reg,name='app01_reg')
]
urlpatterns = [url(r'^reg/',views.reg,name='app02_reg')
]
11-10 伪静态
"""
静态网页数据是写死的 万年不变伪静态将一个动态网页伪装成静态网页为什么要伪装呢?https://www.cnblogs.com/Dominic-Ji/p/9234099.html伪装的目的在于增大本网站的seo查询力度并且增加搜索引擎收藏本网上的概率搜索引擎本质上就是一个巨大的爬虫程序总结:无论你怎么优化 怎么处理始终还是干不过RMB玩家
"""
urlpatterns = [url(r'^reg.html',views.reg,name='app02_reg')
]
12-12 django版本区别
1.django1.X路由层使用的是url方法而在django2.Xhe3.X版本中路由层使用的是path方法url()第一个参数支持正则path()第一个参数是不支持正则的 写什么就匹配什么如果你习惯使用path那么也给你提供了另外一个方法from django.urls import path, re_pathfrom django.conf.urls import urlre_path(r'^index/',index),url(r'^login/',login)2.X和3.X里面的re_path就等价于1.X里面的url2.虽然path不支持正则 但是它的内部支持五种转换器path('index/<int:id>/',index)# 将第二个路由里面的内容先转成整型然后以关键字的形式传递给后面的视图函数def index(request,id):print(id,type(id))return HttpResponse('index')str,匹配除了路径分隔符(/)之外的非空字符串,这是默认的形式
int,匹配正整数,包含0。
slug,匹配字母、数字以及横杠、下划线组成的字符串。
uuid,匹配格式化的uuid,如 075194d3-6885-417e-a8a8-6c931e272f00。
path,匹配任何非空字符串,包含了路径分隔符(/)(不能用?)3.除了有默认的五个转换器之外 还支持自定义转换器(了解)
class MonthConverter:regex='\d{2}' # 属性名必须为regexdef to_python(self, value):return int(value)def to_url(self, value):return value # 匹配的regex是两个数字,返回的结果也必须是两个数字from django.urls import path,register_converter
from app01.path_converts import MonthConverter# 先注册转换器
register_converter(MonthConverter,'mon')from app01 import viewsurlpatterns = [path('articles/<int:year>/<mon:month>/<slug:other>/', views.article_detail, name='aaa'),]4.模型层里面1.X外键默认都是级联更新删除的
但是到了2.X和3.X中需要你自己手动配置参数models.ForeignKey(to='Publish')els.ForeignKey(to='Publish',on_delete=models.CASCADE...)
"""
12-13 视图层
13-1 三板斧
"""
HttpResponse返回字符串类型
render返回html页面 并且在返回给浏览器之前还可以给html文件传值
redirect重定向
"""
# 视图函数必须要返回一个HttpResponse对象 正确 研究三者的源码即可得处结论
The view app01.views.index didn't return an HttpResponse object. It returned None instead.# render简单内部原理from django.template import Template,Contextres = Template('<h1>{{ user }}</h1>')con = Context({'user':{'username':'jason','password':123}})ret = res.render(con)print(ret)return HttpResponse(ret)
13-2 JsonResponse对象
"""
json格式的数据有什么用?前后端数据交互需要使用到json作为过渡 实现跨语言传输数据前端序列化JSON.stringify() json.dumps()JSON.parse() json.loads()
"""
import json
from django.http import JsonResponse
def ab_json(request):user_dict = {'username':'jason好帅哦,我好喜欢!','password':'123','hobby':'girl'}l = [111,222,333,444,555]# 先转成json格式字符串# json_str = json.dumps(user_dict,ensure_ascii=False)# 将该字符串返回# return HttpResponse(json_str)# 读源码掌握用法# return JsonResponse(user_dict,json_dumps_params={'ensure_ascii':False})# In order to allow non-dict objects to be serialized set the safe parameter to False.# return JsonResponse(l,safe=False) # 默认只能序列化字典 序列化其他需要加safe参数
13-3 form表单上传文件及后端如何操作
"""
form表单上传文件类型的数据1.method必须指定成post2.enctype必须换成formdata"""
def ab_file(request):if request.method == 'POST':# print(request.POST) # 只能获取普通的键值对数据 文件不行print(request.FILES) # 获取文件数据# <MultiValueDict: {'file': [<InMemoryUploadedFile: u=1288812541,1979816195&fm=26&gp=0.jpg (image/jpeg)>]}>file_obj = request.FILES.get('file') # 文件对象print(file_obj.name)with open(file_obj.name,'wb') as f:for line in file_obj.chunks(): # 推荐加上chunks方法 其实跟不加是一样的都是一行行的读取f.write(line)return render(request,'form.html')
13-4 request对象方法
"""
request.method
request.POST
request.GET
request.FILES
request.body # 原生的浏览器发过来的二进制数据 后面详细的讲
request.path
request.path_info
request.get_full_path() 能过获取完整的url及问号后面的参数
"""print(request.path) # /app01/ab_file/print(request.path_info) # /app01/ab_file/print(request.get_full_path()) # /app01/ab_file/?username=jason
13-5 FBV与CBV
# 视图函数既可以是函数也可以是类
def index(request):return HttpResponse('index')# CBV# CBV路由url(r'^login/',views.MyLogin.as_view())from django.views import View
class MyLogin(View):def get(self,request):return render(request,'form.html')def post(self,request):return HttpResponse('post方法')"""
FBV和CBV各有千秋
CBV特点能够直接根据请求方式的不同直接匹配到对应的方法执行内部到底是怎么实现的?CBV内部源码(******)
"""
13-6 CBV源码分析
# 你自己不要修改源码 出bug很难找# 突破口在urls.py
url(r'^login/',views.MyLogin.as_view())
# url(r'^login/',views.view) FBV一模一样
# CBV与FBV在路由匹配上本质是一样的 都是路由 对应 函数内存地址
"""
函数名/方法名 加括号执行优先级最高
猜测as_view()要么是被@staicmethod修饰的静态方法要么是被@classmethod修饰的类方法 正确@classonlymethoddef as_view(cls, **initkwargs):pass
"""@classonlymethoddef as_view(cls, **initkwargs):"""cls就是我们自己写的类 MyCBVMain entry point for a request-response process."""def view(request, *args, **kwargs):self = cls(**initkwargs) # cls是我们自己写的类# self = MyLogin(**initkwargs) 产生一个我们自己写的类的对象return self.dispatch(request, *args, **kwargs)"""以后你们会经常需要看源码 但是在看python源码的时候 一定要时刻提醒自己面向对象属性方法查找顺序先从对象自己找再去产生对象的类里面找之后再去父类找...总结:看源码只要看到了self点一个东西 一定要问你自己当前这个self到底是谁"""return view# CBV的精髓def dispatch(self, request, *args, **kwargs):# 获取当前请求的小写格式 然后比对当前请求方式是否合法# get请求为例# post请求if request.method.lower() in self.http_method_names:handler = getattr(self, request.method.lower(), self.http_method_not_allowed)"""反射:通过字符串来操作对象的属性或者方法handler = getattr(自己写的类产生的对象,'get',当找不到get属性或者方法的时候就会用第三个参数)handler = 我们自己写的类里面的get方法"""else:handler = self.http_method_not_allowedreturn handler(request, *args, **kwargs)"""自动调用get方法"""# 要求掌握到不看源码也能够描述出CBV的内部执行流程(******)
12-14 模板层
14-1 模版语法传值
{{}}:变量相关
{%%}:逻辑相关
def index(request):# 模版语法可以传递的后端python数据类型n = 123f = 11.11s = '我也想奔现'b = Truel = ['小红','姗姗','花花','茹茹']t = (111,222,333,444)d = {'username':'jason','age':18,'info':'这个人有点意思'}se = {'晶晶','洋洋','嘤嘤'}def func():print('我被执行了')return '你的另一半在等你'class MyClass(object):def get_self(self):return 'self'@staticmethoddef get_func():return 'func'@classmethoddef get_class(cls):return 'cls'# 对象被展示到html页面上 就类似于执行了打印操作也会触发__str__方法def __str__(self):return '到底会不会?' obj = MyClass()# return render(request,'index.html',{}) # 一个个传return render(request,'index.html',locals())<p>{{ n }}</p>
<p>{{ f }}</p>
<p>{{ s }}</p>
<p>{{ b }}</p>
<p>{{ l }}</p>
<p>{{ d }}</p>
<p>{{ t }}</p>
<p>{{ se }}</p>
<p>传递函数名会自动加括号调用 但是模版语法不支持给函数传额外的参数:{{ func }}</p>
<p>传类名的时候也会自动加括号调用(实例化){{ MyClass }}</p>
<p>内部能够自动判断出当前的变量名是否可以加括号调用 如果可以就会自动执行 针对的是函数名和类名</p>
<p>{{ obj }}</p>
<p>{{ obj.get_self }}</p>
<p>{{ obj.get_func }}</p>
<p>{{ obj.get_class }}</p># django模版语法的取值 是固定的格式 只能采用“句点符” .
<p>{{ d.username }}</p>
<p>{{ l.0 }}</p>
<p>{{ d.hobby.3.info }}</p>
# 即可以点键也可以点索引 还可以两者混
14-2 过滤器
# 过滤器就类似于是模版语法内置的 内置方法
# django内置有60多个过滤器 我们不需要学这么多 了解10个左右就差不多了 后面碰到了再去记忆# 基本语法
{{数据|过滤器:参数}}# 转义
# 前端|safe
# 后端
from django.utils.safestring import mark_saferes = mark_safe('<h1>新新</h1>')
"""
以后你在全栈项目的时候 前端代码不一定非要在前端页面书写
也可以现在先在后端写好 然后传递给前端页面
"""<h1>过滤器</h1>
<p>统计长度:{{ s|length }}</p>
<p>默认值(第一个参数布尔值是True就展示第一个参数的值否在展示冒号后面的值):{{ b|default:'啥也不是' }}</p>
<p>文件大小:{{ file_size|filesizeformat }}</p>
<p>日期格式化:{{ current_time|date:'Y-m-d H:i:s' }}</p>
<p>切片操作(支持步长):{{ l|slice:'0:4:2' }}</p>
<p>切取字符(包含三个点):{{ info|truncatechars:9 }}</p>
<p>切取单词(不包含三个点 按照空格切):{{ egl|truncatewords:9 }}</p>
<p>切取单词(不包含三个点 按照空格切):{{ info|truncatewords:9 }}</p>
<p>移除特定的字符:{{ msg|cut:' ' }}</p>
<p>拼接操作:{{ l|join:'$' }}</p>
<p>拼接操作(加法):{{ n|add:10 }}</p>
<p>拼接操作(加法):{{ s|add:msg }}</p>
<p>取消转义:{{ hhh|safe }}</p> ## 取消对html标签的转义
<p>转义:{{ sss|safe }}</p>
<p>转义:{{ res }}</p>
14-3 标签
# for循环{% for foo in l %}<p>{{ forloop }}</p><p>{{ foo }}</p> 一个个元素{% endfor %}{'parentloop': {}, 'counter0': 0, 'counter': 1, 'revcounter': 6, 'revcounter0': 5, 'first': True, 'last': False}# if判断
{% if b %}<p>baby</p>
{% elif s%}<p>都来把</p>
{% else %}<p>老baby</p>
{% endif %}# for与if混合使用
{% for foo in lll %}{% if forloop.first %}<p>这是我的第一次</p>{% elif forloop.last %}<p>这是最后一次啊</p>{% else %}<p>{{ foo }}</p>{% endif %}{% empty %}<p>for循环的可迭代对象内部没有元素 根本没法循环</p>
{% endfor %}
s
# 处理字典其他方法
{% for foo in d.keys %}<p>{{ foo }}</p>
{% endfor %}
{% for foo in d.values %}<p>{{ foo }}</p>
{% endfor %}
{% for foo in d.items %}<p>{{ foo }}</p>
{% endfor %}# with起别名
{% with d.hobby.3.info as nb %}<p>{{ nb }}</p>在with语法内就可以通过as后面的别名快速的使用到前面非常复杂获取数据的方式<p>{{ d.hobby.3.info }}</p>
{% endwith %}
14-4 自定义过滤器、标签、inclusion_tag
"""
先三步走1.在应用下创建一个名字”必须“叫templatetags文件夹2.在该文件夹内创建“任意”名称的py文件 eg:mytag.py3.在该py文件内"必须"先书写下面两句话(单词一个都不能错)from django import templateregister = template.Library()
"""# 自定义过滤器
@register.filter(name='baby')
def my_sum(v1, v2):return v1 + v2
# 使用
{% load mytag %}
<p>{{ n|baby:666 }}</p># 自定义标签(参数可以有多个) 类似于自定义函数
@register.simple_tag(name='plus')
def index(a,b,c,d):return '%s-%s-%s-%s'%(a,b,c,d)
# 使用
标签多个参数彼此之间空格隔开
<p>{% plus 'jason' 123 123 123 %}</p># 自定义inclusion_tag
"""
内部原理先定义一个方法 在页面上调用该方法 并且可以传值该方法会生成一些数据然后传递给一个html页面之后将渲染好的结果放到调用的位置
"""
@register.inclusion_tag('left_menu.html')
def left(n):data = ['第{}项'.format(i) for i in range(n)]# 第一种# return {'data':data} # 将data传递给left_menu.html# 第二种return locals() # 将data传递给left_menu.html{% left 5 %}
# 总结:当html页面某一个地方的页面需要传参数才能够动态的渲染出来,并且在多个页面上都需要使用到该局部 那么就考虑将该局部页面做成inclusion_tag形式
(在讲bbs的时候会使用到)
14-5 模板的继承
"""
你们有没有见过一些网站这些网站页面整体都大差不差 只是某一些局部在做变化
"""
# 模版的继承 你自己先选好一个你要想继承的模版页面
{% extends 'home.html' %}# 继承了之后子页面跟模版页面长的是一模一样的 你需要在模版页面上提前划定可以被修改的区域
{% block content %}模版内容
{% endblock %}# 子页面就可以声明想要修改哪块划定了的区域
{% block content %}子页面内容
{% endblock %}# 一般情况下模版页面上应该至少有三块可以被修改的区域1.css区域2.html区域3.js区域{% block css %}{% endblock %}{% block content %}{% endblock %}{% block js %}{% endblock %}# 每一个子页面就都可以有自己独有的css代码 html代码 js代码"""
一般情况下 模版的页面上划定的区域越多 那么该模版的扩展性就越高
但是如果太多 那还不如自己直接写
"""
模版的导入
{% include '模版文件名' %}
12-15 模型层
15-1 单表操作
15-1-1 增
# django自带的sqlite3数据库对日期格式不是很敏感 处理的时候容易出错
# 增res = models.User.objects.create(name='jason',age=18,register_time='2002-1-21')print(res)import datetimectime = datetime.datetime.now()user_obj = models.User(name='egon',age=84,register_time=ctime)user_obj.save()
15-1-2 删
# 删res = models.User.objects.filter(pk=2).delete()print(res)"""pk会自动查找到当前表的主键字段 指代的就是当前表的主键字段用了pk之后 你就不需要指代当前表的主键字段到底叫什么了uidpidsid..."""## 先拿到当前用户对象user_obj = models.User.objects.filter(pk=1).first()## 再对这个用户对象进行删除user_obj.delete()
15-1-3 修改
# 修改models.User.objects.filter(pk=4).update(name='egonDSB')## 直接拿到当前数据对象,但是该方法不推荐使用,如果数据不存在则直接报错user_obj = models.User.objects.get(pk=4)user_obj = models.User.objects.filter(pk=6)"""get方法返回的直接就是当前数据对象但是该方法不推荐使用一旦数据不存在该方法会直接报错而filter则不会所以我们还是用filter"""user_obj.name = 'egonPPP'user_obj.save()
15-2 必知必会13条
# 必知必会13条1.all() ## 查询所有数据2.filter() ## 带有过滤条件的查询3.get() ## 直接拿数据对象 但是条件不存在直接报错4.first() ## 拿queryset里面第一个元素res = models.User.objects.all().first()print(res)5.last()res = models.User.objects.all().last()print(res)6.values() ## 可以指定获取的数据字段 select name,age from ... 列表套字典res = models.User.objects.values('name','age') # <QuerySet [{'name': 'jason', 'age': 18}, {'name': 'egonPPP', 'age': 84}]>print(res)7.values_list() ## 列表套元祖res = models.User.objects.values_list('name','age') # <QuerySet [('jason', 18), ('egonPPP', 84)]>print(res)"""查看内部封装的sql语句上述查看sql语句的方式 只能用于queryset对象只有queryset对象才能够点击query查看内部的sql语句"""8.distinct() ## 去重res = models.User.objects.values('name','age').distinct()print(res)"""去重一定要是一模一样的数据如果带有主键那么肯定不一样 你在往后的查询中一定不要忽略主键"""9.order_by()res = models.User.objects.order_by('age') # 默认升序res = models.User.objects.order_by('-age') # 降序print(res)10.reverse() ## 反转的前提是 数据已经排过序了 order_by()res = models.User.objects.all()res1 = models.User.objects.order_by('age').reverse()print(res,res1)11.count() ## 统计当前数据的个数res = models.User.objects.count()print(res)12.exclude() ## 排除在外res = models.User.objects.exclude(name='jason')print(res)13.exists() ## 基本用不到因为数据本身就自带布尔值 返回的是布尔值res = models.User.objects.filter(pk=10).exists()print(res)
15-3 查看内部sql语句的方式
# 方式1
res = models.User.objects.values_list('name','age') # <QuerySet [('jason', 18), ('egonPPP', 84)]>
print(res.query)
queryset对象才能够点击query查看内部的sql语句# 方式2:所有的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',},}
}
15-4 测试脚本
"""
当你只是想测试django中的某一个py文件内容 那么你可以不用书写前后端交互的形式
而是直接写一个测试脚本即可脚本代码无论是写在应用下的tests.py还是自己单独开设py文件都可以
"""
# 测试环境的准备 去manage.py中拷贝前四行代码 然后自己写两行
import os
import sysif __name__ == "__main__":os.environ.setdefault("DJANGO_SETTINGS_MODULE", "day64.settings")import djangodjango.setup()# 在这个代码块的下面就可以测试django里面的单个py文件了
15-5 神奇的双下划线查询
## 神奇的双下划线查询# 年龄大于35岁的数据res = models.User.objects.filter(age__gt=35)print(res)# 年龄小于35岁的数据res = models.User.objects.filter(age__lt=35)print(res)## 大于等于 小于等于res = models.User.objects.filter(age__gte=32)print(res)res = models.User.objects.filter(age__lte=32)print(res)## 年龄是18 或者 32 或者40res = models.User.objects.filter(age__in=[18,32,40])print(res)## 年龄在18到40岁之间的 首尾都要res = models.User.objects.filter(age__range=[18,40])print(res)## 查询出名字里面含有s的数据 模糊查询res = models.User.objects.filter(name__contains='s')print(res)## 是否区分大小写 查询出名字里面含有p的数据 区分大小写res = models.User.objects.filter(name__contains='p')print(res)## 忽略大小写res = models.User.objects.filter(name__icontains='p')print(res)## 以..开头 以...结尾res = models.User.objects.filter(name__startswith='j')res1 = models.User.objects.filter(name__endswith='j')print(res,res1)## 查询出注册时间是 2020 1月res = models.User.objects.filter(register_time__month='1')res = models.User.objects.filter(register_time__year='2020')
15-6 案例表创建
class Book(models.Model):title = models.CharField(max_length=32)price = models.DecimalField(max_digits=8,decimal_places=2)publish_date = models.DateField(auto_now_add=True)## 一对多publish = models.ForeignKey(to='Publish')## 多对多authors = models.ManyToManyField(to='Author')class Publish(models.Model):name = models.CharField(max_length=32)addr = models.CharField(max_length=64)email = models.EmailField() ## 本质是varcharclass Author(models.Model):name = models.CharField(max_length=32)age = models.IntegerField()## 一对一author_detail = models.OneToOneField(to='AuthorDetail')class AuthorDetail(models.Model):phone = models.BigIntegerField()addr = models.CharField(max_length=64)
15-7 一对多外键增删改查
## 一对多外键增删改查## 增## 1 直接写实际字段 idmodels.Book.objects.create(title='论语',price=899.23,publish_id=1)models.Book.objects.create(title='聊斋',price=444.23,publish_id=2)models.Book.objects.create(title='老子',price=333.66,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() # 级联删除## 修改models.Book.objects.filter(pk=1).update(publish_id=2)publish_obj = models.Publish.objects.filter(pk=1).first()models.Book.objects.filter(pk=1).update(publish=publish_obj)
15-8 多对多外键增删改查
# 如何给书籍添加作者?book_obj = models.Book.objects.filter(pk=1).first()print(book_obj.authors) # 就类似于你已经到了第三张关系表了book_obj.authors.add(1) # 书籍id为1的书籍绑定一个主键为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_obj)book_obj.authors.add(author_obj1,author_obj2)
"""
add给第三张关系表添加数据括号内既可以传数字也可以传对象 并且都支持多个
"""# 删book_obj.authors.remove(2)book_obj.authors.remove(1,3)author_obj = models.Author.objects.filter(pk=2).first()author_obj1 = models.Author.objects.filter(pk=3).first()book_obj.authors.remove(author_obj,author_obj1)"""remove括号内既可以传数字也可以传对象 并且都支持多个"""## 修改book_obj.authors.set([1,2]) # 括号内必须给一个可迭代对象book_obj.authors.set([3]) # 括号内必须给一个可迭代对象author_obj = models.Author.objects.filter(pk=2).first()author_obj1 = models.Author.objects.filter(pk=3).first()book_obj.authors.set([author_obj,author_obj1]) # 括号内必须给一个可迭代对象"""set括号内必须传一个可迭代对象,该对象内既可以数字也可以对象 并且都支持多个"""## 清空
## 在第三张关系表中清空某个书籍与作者的绑定关系book_obj.authors.clear()"""clear括号内不要加任何参数
15-9 正反向的概念
# 正向
# 反向外键字段在我手上那么,我查你就是正向外键字段如果不在手上,我查你就是反向book >>>外键字段在书那儿(正向)>>> publishpublish >>>外键字段在书那儿(反向)>>>book一对一和多对多正反向的判断也是如此"""
正向查询按字段
反向查询按表名小写_set...
"""
15-10 多表查询
10-1 子查询(基于对象的跨表查询)
1.查询书籍主键为1的出版社
book_obj = models.Book.objects.filter(pk=1).first()
## 书查出版社 正向
res = book_obj.publish
print(res)
print(res.name)
print(res.addr)2.查询书籍主键为2的作者
book_obj = models.Book.objects.filter(pk=2).first()
# # 书查作者 正向
res = book_obj.authors # app01.Author.None
res = book_obj.authors.all()
# <QuerySet [<Author: Author object>, <Author: Author object>]>
print(res)3.查询作者jason的电话号码
author_obj = models.Author.objects.filter(name='jason').first()
res = author_obj.author_detail
print(res)
print(res.phone)
print(res.addr)"""
在书写orm语句的时候跟写sql语句一样的
不要企图一次性将orm语句写完 如果比较复杂 就写一点看一点正向什么时候需要加.all()当你的结果可能有多个的时候就需要加.all()如果是一个则直接拿到数据对象book_obj.publishbook_obj.authors.all()author_obj.author_detail
"""
4.查询出版社是东方出版社出版的书
publish_obj = models.Publish.objects.filter(name='东方出版社').first()
出版社查书 反向
res = publish_obj.book_set # app01.Book.None
res = publish_obj.book_set.all()
print(res)5.查询作者是jason写过的书
author_obj = models.Author.objects.filter(name='jason').first()
# 作者查书 反向
res = author_obj.book_set # app01.Book.None
res = author_obj.book_set.all()
print(res)6.查询手机号是110的作者姓名
author_detail_obj = models.AuthorDetail.objects.filter(phone=110).first()
res = author_detail_obj.author
print(res.name)
"""
基于对象 反向查询的时候当你的查询结果可以有多个的时候 就必须加_set.all()当你的结果只有一个的时候 不需要加_set.all()自己总结出 自己方便记忆的即可 每个人都可以不一样
"""
10-2 联表查询(基于双下划线的跨表查询)
# 基于双下划线的跨表查询1.查询jason的手机号和作者姓名res = models.Author.objects.filter(name='jason').values('author_detail__phone','name')print(res)# 反向res = models.AuthorDetail.objects.filter(author__name='jason') # 拿作者姓名是jason的作者详情res = models.AuthorDetail.objects.filter(author__name='jason').values('phone','author__name')print(res)2.查询书籍主键为1的出版社名称和书的名称res = models.Book.objects.filter(pk=1).values('title','publish__name')print(res)反向res = models.Publish.objects.filter(book__id=1).values('name','book__title')print(res)3.查询书籍主键为1的作者姓名res = models.Book.objects.filter(pk=1).values('authors__name')print(res)## 反向res = models.Author.objects.filter(book__id=1).values('name')print(res)查询书籍主键是1的作者的手机号book author authordetailres = models.Book.objects.filter(pk=1).values('authors__author_detail__phone')print(res)"""你只要掌握了正反向的概念以及双下划线那么你就可以无限制的跨表"""
15-11 聚合查询
'''只要跟数据库相关的模块基本上都在django.db.models里面如果没有name应该在django.db里面
'''
from app01 import models
from django.db.models import Max,Min,Sum,Count,Avg
# 统计所有数的平均价格
res = models.Book.objects.aggregate(Avg('price')) ## 如果没有跟分组一起搭配使用,则加aggregate
print(res)## 上述方法一次性使用
res = models.Book.objects.aggregate(Max('price'),Min('price'),Avg('price'),Sum('price'),Count('price'))
print(res)
15-12 分组查询
## 分组查询 关键字 annotate
## 1,统计每一本书的作者个数
# res = models.Book.objects.annotate() ## models后面点什么就按照什么分组
res = models.Book.objects.annotate(author_num=Count('authors__id')).values('title','author_num')
## models后面点什么就按照什么分组
print(res)
'''
aythor_num 是我们自己定义的字段,用来存储统计出来的每本书对应的作者个数
'''## 2,统计每个出版社卖的最便宜的书的价格
res = models.Publish.objects.annotate(min_price=Min('book__price')).values('name','min_price')
print(res)## 3,统计不止一个作者的图书
res = models.Book.objects.annotate(author_num=Count('authors__id')).filter(author_num__gt=1).values('title','author_num')
'''
只要是ORM查询的结果是一个queryset对象,则可以继续点这个对象封装的方法
'''
print(res)## 4,查询每个作者出版的书的总价格
res = models.Author.objects.annotate(sum_price=Sum('book__price')).values('name','sum_price')
print(res)
'''
如果按照指定字段分组,应该如何做?
models.Book.objects.values('price').annotate() ## 这时候就按照price分组了
'''
15-13 F查询
## F查询
'''
功能是帮助你直接获取到表中某个字段对应的数据
'''
# 1 查询卖出书大于库存数的书籍
from django.db.models import F
res = models.Book.objects.filter(maichu__gt=F('kucun')) ## F 可以拿出每一本书的库存
print(res)# 2,将所有书籍的提升50元
res = models.Book.objects.update(price=F('price') + 50)
print(res)## 3,将所有书的名称后面加上爆款两个字
from django.db.models.functions import Concat
from django.db.models import Value
'''
在操作字符类型的数据的时候,F不能直接做到字符串的拼接,没有效果的,需要用以下方法
'''
res = models.Book.objects.update(title=Concat(F('title'),Value('——爆款')))
print(res)
15-14 Q 查询
## Q 查询
from django.db.models import Q
## 1,查询卖出书大于100或者价格小于600的书籍
res = models.Book.objects.filter(maichu__gt=100,price__lt=600)
## filter括号内多个参数是and 关系,逗号分割
print(res)res = models.Book.objects.filter(Q(maichu__gt=100) | Q(price__lt= 600))
'''
或 |
非 ~
res = models.Book.objects.filter(~Q(maichu__gt=100) | ~Q(price__lt= 600))
'''
print(res)## Q 的高阶用法 可以把查询条件的左边也可以改成字符串的形式
q = Q()
q.connector = 'or' ## 可以改默认的and连接关系
q.children.append(('maichu__gt',100))
q.children.append(('price__lt',600))
res = models.Book.objects.filter(q) ## filter括号内支持直接放Q对象,默认还是and关系
print(res)
15-15 Django中开启事务
"""
事务ACID原子性不可分割的最小单位一致性跟原子性是相辅相成隔离性事务之间互相不干扰持久性事务一旦确认永久生效事务的回滚 rollback事务的确认commit
"""
# 目前你只需要掌握Django中如何简单的开启事务
# 事务from django.db import transactiontry:with transaction.atomic():# sql1# sql2...# 在with代码快内书写的所有orm操作都是属于同一个事务except Exception as e:print(e)print('执行其他操作')
15 -16 ORM中常用字段及参数
AutoField主键字段 primary_key=TrueCharField varcharverbose_name 字段的注释max_length 长度IntegerField int
BigIntegerField bigintDecimalFieldmax_digits=8decimal_places=2EmailFiled varchar(254)DateField date
DateTimeField datetimeauto_now: ## 每次修改数据的时候都会自动更新当前时间auto_now_add: ## 只在创建数据的时候记录创建时间后续不会自动修改了BooleanField(Field) - 布尔值类型该字段传布尔值(False/True) 数据库里面存0/1TextField(Field) - 文本类型该字段可以用来存大段内容(文章、博客...) 没有字数限制后面的bbs作业 文章字段用的就是TextFieldFileField(Field) - 字符类型upload_to = "/data"给该字段传一个文件对象,会自动将文件保存到/data目录下然后将文件路径保存到数据库中/data/a.txt后面bbs作业也会涉及# 更多字段
直接参考博客:https://www.cnblogs.com/Dominic-Ji/p/9203990.html# django除了给你提供了很多字段类型之外 还支持你自定义字段
class MyCharField(models.Field):def __init__(self,max_length,*args,**kwargs):self.max_length = max_length# 调用父类的init方法super().__init__(max_length=max_length,*args,**kwargs) # 一定要是关键字的形式传入def db_type(self, connection):"""返回真正的数据类型及各种约束条件:param connection::return:"""return 'char(%s)'%self.max_length# 自定义字段使用
myfield = MyCharField(max_length=16,null=True)# 外键字段及参数
unique=TrueForeignKey(unique=True) === OneToOneField()# 你在用前面字段创建一对一 orm会有一个提示信息 orm推荐你使用后者但是前者也能用db_index## 如果db_index=True 则代表着为此字段设置索引to_field## 设置要关联的表的字段 默认不写关联的就是另外一张的主键字段on_delete## 当删除关联表中的数据时,当前表与其关联的行的行为。"""django2.X及以上版本 需要你自己指定外键字段的级联更新级联删除"""
15-16 数据库查询优化
only与defer
select_related与prefetch_related"""
orm语句的特点:惰性查询如果你仅仅只是书写了orm语句 在后面根本没有用到该语句所查询出来的参数那么orm会自动识别 直接不执行
"""
# only与defer
# res = models.Book.objects.all()# print(res) # 要用数据了才会走数据库# 想要获取书籍表中所有数的名字# res = models.Book.objects.values('title')# for d in res:# print(d.get('title'))# 你给我实现获取到的是一个数据对象 然后点title就能够拿到书名 并且没有其他字段# res = models.Book.objects.only('title')# res = models.Book.objects.all()# print(res) # <QuerySet [<Book: 三国演义爆款>, <Book: 红楼梦爆款>, <Book: 论语爆款>, <Book: 聊斋爆款>, <Book: 老子爆款>]># for i in res:# print(i.title) # 点击only括号内的字段 不会走数据库# print(i.price) # 点击only括号内没有的字段 会重新走数据库查询而all不需要走了res = models.Book.objects.defer('title') # 对象除了没有title属性之外其他的都有for i in res:print(i.price)"""defer与only刚好相反defer括号内放的字段不在查询出来的对象里面 查询该字段需要重新走数据而如果查询的是非括号内的字段 则不需要走数据库了"""# select_related与prefetch_related
# select_related与prefetch_related 跟跨表操作有关# res = models.Book.objects.all()# for i in res:# print(i.publish.name) # 每循环一次就要走一次数据库查询# res = models.Book.objects.select_related('authors') # INNER JOIN"""select_related内部直接先将book与publish连起来 然后一次性将大表里面的所有数据全部封装给查询出来的对象这个时候对象无论是点击book表的数据还是publish的数据都无需再走数据库查询了select_related括号内只能放外键字段 一对多 一对一多对多也不行"""# for i in res:# print(i.publish.name) # 每循环一次就要走一次数据库查询res = models.Book.objects.prefetch_related('publish') # 子查询"""prefetch_related该方法内部其实就是子查询将子查询查询出来的所有结果也给你封装到对象中给你的感觉好像也是一次性搞定的"""for i in res:print(i.publish.name)
15-17 图书馆里系统的增删改查
17-1 models.py
from django.db import models# Create your models here.
from django.db import models# Create your models here.class Book(models.Model):title = models.CharField(max_length=32)price = models.DecimalField(max_digits=8,decimal_places=2)publish_date = models.DateField(auto_now_add=True)## 一对多publish = models.ForeignKey(to='Publish')## 多对多authors = models.ManyToManyField(to='Author')def __str__(self):return self.titleclass Publish(models.Model):name = models.CharField(max_length=32)addr = models.CharField(max_length=64)email = models.EmailField() ## 本质是varchardef __str__(self):return '对象:%s'%self.nameclass Author(models.Model):name = models.CharField(max_length=32)age = models.IntegerField()## 一对一author_detail = models.OneToOneField(to='AuthorDetail')class AuthorDetail(models.Model):phone = models.BigIntegerField()addr = models.CharField(max_length=64)
17-2 views.py
from django.shortcuts import render,redirect,HttpResponse
from app01 import models
# Create your views here.def home(request):return render(request,'home.html')def book_list(request):# 先查询出所有的书籍信息 传递给html页面book_queryset = models.Book.objects.all()return render(request,'book_list.html',locals())def book_add(request):if request.method == 'POST':# 获取前端提交过来的所有数据title = request.POST.get("title")price = request.POST.get("price")publish_date = request.POST.get("publish_date")publish_id = request.POST.get("publish")authors_list = request.POST.getlist("authors") # [1,2,3,4,]# 操作数据库存储数据# 书籍表book_obj = models.Book.objects.create(title=title,price=price,publish_date=publish_date,publish_id=publish_id)# 书籍与作者的关系表book_obj.authors.add(*authors_list)# 跳转到书籍的展示页面"""redirect括号内可以直接写url其实也可以直接写别名但是如果你的别名需要额外给参数的话,那么就必须使用reverse解析了"""return redirect('book_list')# 先获取当前系统中所有的出版社信息和作者信息publish_queryset = models.Publish.objects.all()author_queryset = models.Author.objects.all()return render(request,'book_add.html',locals())def book_edit(request,edit_id):# 获取当前用户想要编辑的书籍对象 展示给用户看edit_obj = models.Book.objects.filter(pk=edit_id).first()if request.method == 'POST':title = request.POST.get("title")price = request.POST.get("price")publish_date = request.POST.get("publish_date")publish_id = request.POST.get("publish")authors_list = request.POST.getlist("authors") # [1,2,3,4,]models.Book.objects.filter(pk=edit_id).update(title=title,price=price,publish_date=publish_date,publish_id=publish_id)# 该第三张关系表edit_obj.authors.set(authors_list)return redirect('book_list')publish_queryset = models.Publish.objects.all()author_queryset = models.Author.objects.all()return render(request,'book_edit.html',locals())def book_delete(request,delete_id):# 简单粗暴 直接删除models.Book.objects.filter(pk=delete_id).delete()# 直接跳转到展示页return redirect('book_list')
17-3 urls.py
from django.conf.urls import url
from django.contrib import admin
from app01 import viewsurlpatterns = [url(r'^admin/', admin.site.urls),## 首页url(r'^$',views.home,name='home'),## 图书的展示页url(r'^book/list',views.book_list,name='book_list'),## 书籍的添加url(r'^book/add',views.book_add,name='book_add'),## 编辑页面的URLurl(r'^book/edit/(?P<edit_id>\d+)',views.book_edit,name='book_edit'),## 书籍的删除url(r'^book/del/(?P<del_id>\d+)',views.book_del,name='book_del')
]
17-4 home.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css"integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap-theme.min.css"integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous"><script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js"integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"crossorigin="anonymous"></script>{% block css %}{% endblock %}
</head>
<body>
<nav class="navbar navbar-inverse"><div class="container-fluid"><!-- Brand and toggle get grouped for better mobile display --><div class="navbar-header"><button type="button" class="navbar-toggle collapsed" data-toggle="collapse"data-target="#bs-example-navbar-collapse-1" aria-expanded="false"><span class="sr-only">Toggle navigation</span><span class="icon-bar"></span><span class="icon-bar"></span><span class="icon-bar"></span></button><a class="navbar-brand" href="#">图书馆里系统</a></div><!-- Collect the nav links, forms, and other content for toggling --><div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"><ul class="nav navbar-nav"><li class="active"><a href="#">图书<span class="sr-only">(current)</span></a></li><li><a href="#">作者</a></li><li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"aria-expanded="false">更多<span class="caret"></span></a><ul class="dropdown-menu"><li><a href="#">Action</a></li><li><a href="#">Another action</a></li><li><a href="#">Something else here</a></li><li role="separator" class="divider"></li><li><a href="#">Separated link</a></li><li role="separator" class="divider"></li><li><a href="#">One more separated link</a></li></ul></li></ul><form class="navbar-form navbar-left"><div class="form-group"><input type="text" class="form-control" placeholder="Search"></div><button type="submit" class="btn btn-default">Submit</button></form><ul class="nav navbar-nav navbar-right"><li><a href="#">Aini</a></li><li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true"aria-expanded="false">更多操作<span class="caret"></span></a><ul class="dropdown-menu"><li><a href="#">Action</a></li><li><a href="#">Another action</a></li><li><a href="#">Something else here</a></li><li role="separator" class="divider"></li><li><a href="#">Separated link</a></li></ul></li></ul></div><!-- /.navbar-collapse --></div><!-- /.container-fluid -->
</nav>
<div class="container-fluid"><div class="row"><div class="col-md-3"><div class="list-group"><a href="#" class="list-group-item active">首页</a><a href="{% url 'book_list' %}" class="list-group-item">图书列表</a><a href="#" class="list-group-item">出版社列表</a><a href="#" class="list-group-item">作者列表</a><a href="#" class="list-group-item">更多</a></div></div><div class="col-md-9"><div class="panel panel-primary"><div class="panel-heading"><h3 class="panel-title">BMS</h3></div><div class="panel-body">{% block content %}<div class="jumbotron"><h1>欢迎来到亚洲最大线上读书平台</h1><p>想看啥书都有</p><p><a class="btn btn-primary btn-lg" href="#" role="button">点击有你好看</a></p></div><div class="row"><div class="col-sm-6 col-md-4"><div class="thumbnail"><img src="https://img0.baidu.com/it/u=1226012578,3631571194&fm=253&app=120&size=w931&n=0&f=JPEG&fmt=auto?sec=1696438800&t=a213a0fb1c8620edcd1af5f2bebd341b"alt="..."><div class="caption"><h3>Thumbnail label</h3><p>...</p><p><a href="#" class="btn btn-primary" role="button">Button</a><a href="#" class="btn btn-default" role="button">Button</a></p></div></div></div><div class="col-sm-6 col-md-4"><div class="thumbnail"><img src="https://img0.baidu.com/it/u=1226012578,3631571194&fm=253&app=120&size=w931&n=0&f=JPEG&fmt=auto?sec=1696438800&t=a213a0fb1c8620edcd1af5f2bebd341b"alt="..."><div class="caption"><h3>Thumbnail label</h3><p>...</p><p><a href="#" class="btn btn-primary" role="button">Button</a><a href="#" class="btn btn-default" role="button">Button</a></p></div></div></div><div class="col-sm-6 col-md-4"><div class="thumbnail"><img src="https://img0.baidu.com/it/u=1226012578,3631571194&fm=253&app=120&size=w931&n=0&f=JPEG&fmt=auto?sec=1696438800&t=a213a0fb1c8620edcd1af5f2bebd341b"alt="..."><div class="caption"><h3>Thumbnail label</h3><p>...</p><p><a href="#" class="btn btn-primary" role="button">Button</a><a href="#" class="btn btn-default" role="button">Button</a></p></div></div></div></div>{% endblock %}</div></div></div></div>
</div>
{% block js %}{% endblock %}
</body>
</html>
17-5 book_list.html
{% extends 'home.html' %}
{% block content %}<a href="{% url 'book_add' %}" class="btn btn-success">添加</a></br></br><table class="border table-hover table-striped table-bordered" style="width:100%;text-align:center;"><thead><tr><th style="text-align:center;">ID</th><th style="text-align:center;">书名</th><th style="text-align:center;">价格</th><th style="text-align:center;">出版日期</th><th style="text-align:center;">出版社</th><th style="text-align:center;">作者</th><th style="text-align:center;">操作</th></tr></thead><tbody>{% for book_obj in book_queryset %}<tr><td style="padding:10px 20px;">{{ book_obj.pk }}</td><td style="padding:10px 20px;">{{ book_obj.title }}</td><td style="padding:10px 20px;">{{ book_obj.price }}</td><td style="padding:10px 20px;">{{ book_obj.publish_date | date:'Y-m-d' }}</td><td style="padding:10px 20px;">{{ book_obj.publish.name }}</td><td style="padding:10px 20px;">{% for item in book_obj.authors.all %}{% if forloop.last %}{{ item.name }}{% else %}{{ item.name }},{% endif %}{% endfor %}</td><td style="padding:10px 20px;"><a href="{% url 'book_edit' book_obj.pk %}" class="btn btn-primary btn-sm">编辑</a><a href="{% url 'book_del' book_obj.pk %}" class="btn btn-danger btn-sm">删除</a></td></tr>{% endfor %}</tbody></table>
{% endblock %}
17-6 book_add.html
{% extends 'home.html' %}
{% block content %}<h1 class="text-center">书籍的添加</h1><form action="" method="post"><p>书名<input type="text" name="title" class="form-control"></p><p>价格<input type="text" name="price" class="form-control"></p><p>出版日期<input type="date" name="publish_date" class="form-control"></p><p>出版社<select name="publish" id="" class="form-control">{% for item in publish_queryset %}<option value="{{ item.pk }}">{{ item.name }}</option>{% endfor %}</select></p><p>作者<select name="authors" id="" multiple class="form-control">{% for item in author_queryset %}<option value="{{ item.pk }}">{{ item.name }}</option>{% endfor %}</select></p><input type="submit" value="新增" class="btn btn-primary btn-block"></form>
{% endblock %}
17-7 book_del.html
{% extends 'home.html' %}
{% block content %}<h1 class="text-center">书籍的编辑</h1><form action="" method="post"><p>书名<input type="text" name="title" class="form-control" value="{{ edit_obj.title }}"></p><p>价格<input type="text" name="price" class="form-control" value="{{ edit_obj.price }}"></p><p>出版日期<input type="date" name="publish_date" class="form-control" value="{{ edit_obj.publish_date | date:'Y-m-d' }}"></p><p>出版社<select name="publish" id="" class="form-control">{% for item in publish_queryset %}
{# 当前书籍出版社应该默认选中昂#}{% if edit_obj.publish == item %}<option value="{{ item.pk }}" selected>{{ item.name }}</option>{% else %}<option value="{{ item.pk }}">{{ item.name }}</option>{% endif %}{% endfor %}</select></p><p>作者<select name="authors" id="" multiple class="form-control">{% for item in author_queryset %}{% if item in edit_obj.authors.all %}<option value="{{ item.pk }}" selected>{{ item.name }}</option>{% else %}<option value="{{ item.pk }}">{{ item.name }}</option>{% endif %}{% endfor %}</select></p><input type="submit" value="确定" class="btn btn-warning btn-block"></form>
{% endblock %}
15-18 choices参数
"""
用户表 性别学历工作经验是否结婚是否生子客户来源...
针对某个可以列举完全的可能性字段,我们应该如何存储只要某个字段的可能性是可以列举完全的,那么一般情况下都会采用choices参数
"""
class User(models.Model):username = models.CharField(max_length=32)age = models.IntegerField()# 性别gender_choices = ((1,'男'),(2,'女'),(3,'其他'),)gender = models.IntegerField(choices=gender_choices)score_choices = (('A','优秀'),('B','良好'),('C','及格'),('D','不合格'),)# 保证字段类型跟列举出来的元祖第一个数据类型一致即可score = models.CharField(choices=score_choices,null=True)"""该gender字段存的还是数字 但是如果存的数字在上面元祖列举的范围之内那么可以非常轻松的获取到数字对应的真正的内容1.gender字段存的数字不在上述元祖列举的范围内容2.如果在 如何获取对应的中文信息"""from app01 import models# models.User.objects.create(username='jason',age=18,gender=1)# models.User.objects.create(username='egon',age=85,gender=2)# models.User.objects.create(username='tank',age=40,gender=3)# 存的时候 没有列举出来的数字也能存(范围还是按照字段类型决定)# models.User.objects.create(username='tony',age=45,gender=4)# 取# user_obj = models.User.objects.filter(pk=1).first()# print(user_obj.gender)# 只要是choices参数的字段 如果你想要获取对应信息 固定写法 get_字段名_display()# print(user_obj.get_gender_display())user_obj = models.User.objects.filter(pk=4).first()# 如果没有对应关系 那么字段是什么还是展示什么print(user_obj.get_gender_display()) # 4# 实际项目案例
# CRM相关内部表
class School(models.Model):"""校区表如:北京沙河校区上海校区"""title = models.CharField(verbose_name='校区名称', max_length=32)def __str__(self):return self.titleclass Course(models.Model):"""课程表如:Linux基础Linux架构师Python自动化开发精英班Python自动化开发架构师班Python基础班go基础班"""name = models.CharField(verbose_name='课程名称', max_length=32)def __str__(self):return self.nameclass Department(models.Model):"""部门表市场部 1000销售 1001"""title = models.CharField(verbose_name='部门名称', max_length=16)code = models.IntegerField(verbose_name='部门编号', unique=True, null=False)def __str__(self):return self.titleclass UserInfo(models.Model):"""员工表"""name = models.CharField(verbose_name='员工姓名', max_length=16)email = models.EmailField(verbose_name='邮箱', max_length=64)depart = models.ForeignKey(verbose_name='部门', to="Department",to_field="code")user=models.OneToOneField("User",default=1)def __str__(self):return self.nameclass ClassList(models.Model):"""班级表如:Python全栈 面授班 5期 10000 2017-11-11 2018-5-11"""school = models.ForeignKey(verbose_name='校区', to='School')course = models.ForeignKey(verbose_name='课程名称', to='Course')semester = models.IntegerField(verbose_name="班级(期)")price = models.IntegerField(verbose_name="学费")start_date = models.DateField(verbose_name="开班日期")graduate_date = models.DateField(verbose_name="结业日期", null=True, blank=True)memo = models.CharField(verbose_name='说明', max_length=256, blank=True, null=True, )teachers = models.ManyToManyField(verbose_name='任课老师', to='UserInfo',limit_choices_to={'depart':1002})tutor = models.ForeignKey(verbose_name='班主任', to='UserInfo',related_name="class_list",limit_choices_to={'depart':1006})def __str__(self):return "{0}({1}期)".format(self.course.name, self.semester)class Customer(models.Model):"""客户表"""qq = models.CharField(verbose_name='qq', max_length=64, unique=True, help_text='QQ号必须唯一')name = models.CharField(verbose_name='学生姓名', max_length=16)gender_choices = ((1, '男'), (2, '女'))gender = models.SmallIntegerField(verbose_name='性别', choices=gender_choices)education_choices = ((1, '重点大学'),(2, '普通本科'),(3, '独立院校'),(4, '民办本科'),(5, '大专'),(6, '民办专科'),(7, '高中'),(8, '其他'))education = models.IntegerField(verbose_name='学历', choices=education_choices, blank=True, null=True, )graduation_school = models.CharField(verbose_name='毕业学校', max_length=64, blank=True, null=True)major = models.CharField(verbose_name='所学专业', max_length=64, blank=True, null=True)experience_choices = [(1, '在校生'),(2, '应届毕业'),(3, '半年以内'),(4, '半年至一年'),(5, '一年至三年'),(6, '三年至五年'),(7, '五年以上'),]experience = models.IntegerField(verbose_name='工作经验', blank=True, null=True, choices=experience_choices)work_status_choices = [(1, '在职'),(2, '无业')]work_status = models.IntegerField(verbose_name="职业状态", choices=work_status_choices, default=1, blank=True,null=True)company = models.CharField(verbose_name="目前就职公司", max_length=64, blank=True, null=True)salary = models.CharField(verbose_name="当前薪资", max_length=64, blank=True, null=True)source_choices = [(1, "qq群"),(2, "内部转介绍"),(3, "官方网站"),(4, "百度推广"),(5, "360推广"),(6, "搜狗推广"),(7, "腾讯课堂"),(8, "广点通"),(9, "高校宣讲"),(10, "渠道代理"),(11, "51cto"),(12, "智汇推"),(13, "网盟"),(14, "DSP"),(15, "SEO"),(16, "其它"),]source = models.SmallIntegerField('客户来源', choices=source_choices, default=1)referral_from = models.ForeignKey('self',blank=True,null=True,verbose_name="转介绍自学员",help_text="若此客户是转介绍自内部学员,请在此处选择内部学员姓名",related_name="internal_referral")course = models.ManyToManyField(verbose_name="咨询课程", to="Course")status_choices = [(1, "已报名"),(2, "未报名")]status = models.IntegerField(verbose_name="状态",choices=status_choices,default=2,help_text=u"选择客户此时的状态")consultant = models.ForeignKey(verbose_name="课程顾问", to='UserInfo', related_name='consultanter',limit_choices_to={'depart':1001})date = models.DateField(verbose_name="咨询日期", auto_now_add=True)recv_date = models.DateField(verbose_name="当前课程顾问的接单日期", null=True)last_consult_date = models.DateField(verbose_name="最后跟进日期", )def __str__(self):return self.nameclass ConsultRecord(models.Model):"""客户跟进记录"""customer = models.ForeignKey(verbose_name="所咨询客户", to='Customer')consultant = models.ForeignKey(verbose_name="跟踪人", to='UserInfo',limit_choices_to={'depart':1001})date = models.DateField(verbose_name="跟进日期", auto_now_add=True)note = models.TextField(verbose_name="跟进内容...")def __str__(self):return self.customer.name + ":" + self.consultant.nameclass Student(models.Model):"""学生表(已报名)"""customer = models.OneToOneField(verbose_name='客户信息', to='Customer')class_list = models.ManyToManyField(verbose_name="已报班级", to='ClassList', blank=True)emergency_contract = models.CharField(max_length=32, blank=True, null=True, verbose_name='紧急联系人')company = models.CharField(verbose_name='公司', max_length=128, blank=True, null=True)location = models.CharField(max_length=64, verbose_name='所在区域', blank=True, null=True)position = models.CharField(verbose_name='岗位', max_length=64, blank=True, null=True)salary = models.IntegerField(verbose_name='薪资', blank=True, null=True)welfare = models.CharField(verbose_name='福利', max_length=256, blank=True, null=True)date = models.DateField(verbose_name='入职时间', help_text='格式yyyy-mm-dd', blank=True, null=True)memo = models.CharField(verbose_name='备注', max_length=256, blank=True, null=True)def __str__(self):return self.customer.nameclass ClassStudyRecord(models.Model):"""上课记录表 (班级记录)"""class_obj = models.ForeignKey(verbose_name="班级", to="ClassList")day_num = models.IntegerField(verbose_name="节次", help_text=u"此处填写第几节课或第几天课程...,必须为数字")teacher = models.ForeignKey(verbose_name="讲师", to='UserInfo',limit_choices_to={'depart':1002})date = models.DateField(verbose_name="上课日期", auto_now_add=True)course_title = models.CharField(verbose_name='本节课程标题', max_length=64, blank=True, null=True)course_memo = models.TextField(verbose_name='本节课程内容概要', blank=True, null=True)has_homework = models.BooleanField(default=True, verbose_name="本节有作业")homework_title = models.CharField(verbose_name='本节作业标题', max_length=64, blank=True, null=True)homework_memo = models.TextField(verbose_name='作业描述', max_length=500, blank=True, null=True)exam = models.TextField(verbose_name='踩分点', max_length=300, blank=True, null=True)def __str__(self):return "{0} day{1}".format(self.class_obj, self.day_num)class StudentStudyRecord(models.Model):'''学生学习记录'''classstudyrecord = models.ForeignKey(verbose_name="第几天课程", to="ClassStudyRecord")student = models.ForeignKey(verbose_name="学员", to='Student')record_choices = (('checked', "已签到"),('vacate', "请假"),('late', "迟到"),('noshow', "缺勤"),('leave_early', "早退"),)record = models.CharField("上课纪录", choices=record_choices, default="checked", max_length=64)score_choices = ((100, 'A+'),(90, 'A'),(85, 'B+'),(80, 'B'),(70, 'B-'),(60, 'C+'),(50, 'C'),(40, 'C-'),(0, ' D'),(-1, 'N/A'),(-100, 'COPY'),(-1000, 'FAIL'),)score = models.IntegerField("本节成绩", choices=score_choices, default=-1)homework_note = models.CharField(verbose_name='作业评语', max_length=255, blank=True, null=True)note = models.CharField(verbose_name="备注", max_length=255, blank=True, null=True)homework = models.FileField(verbose_name='作业文件', blank=True, null=True, default=None)stu_memo = models.TextField(verbose_name='学员备注', blank=True, null=True)date = models.DateTimeField(verbose_name='提交作业日期', auto_now_add=True)def __str__(self):return "{0}-{1}".format(self.classstudyrecord, self.student)"""
chocies参数使用场景是非常广泛的
"""
15-19 MTV与MVC模型
# MTV:Django号称是MTV模型
M:models
T:templates
V:views
# MVC:其实django本质也是MVC
M:models
V:views
C:controller# vue框架:MVVM模型
15-20 多对多三种创建方式
# 全自动:利用orm自动帮我们创建第三张关系表class Book(models.Model):name = models.CharField(max_length=32)authors = models.ManyToManyField(to='Author')class Author(models.Model):name = models.CharField(max_length=32)"""优点:代码不需要你写 非常的方便 还支持orm提供操作第三张关系表的方法...不足之处:第三张关系表的扩展性极差(没有办法额外添加字段...)"""
# 纯手动class Book(models.Model):name = models.CharField(max_length=32)class Author(models.Model):name = models.CharField(max_length=32)class Book2Author(models.Model):book_id = models.ForeignKey(to='Book')author_id = models.ForeignKey(to='Author')'''优点:第三张表完全取决于你自己进行额外的扩展不足之处:需要写的代码较多,不能够再使用orm提供的简单的方法不建议你用该方式'''# 半自动
class Book(models.Model):name = models.CharField(max_length=32)authors = models.ManyToManyField(to='Author',through='Book2Author',through_fields=('book','author'))
class Author(models.Model):name = models.CharField(max_length=32)# books = models.ManyToManyField(to='Book',# through='Book2Author',# through_fields=('author','book')# )
class Book2Author(models.Model):book = models.ForeignKey(to='Book')author = models.ForeignKey(to='Author')"""
through_fields字段先后顺序判断的本质:通过第三张表查询对应的表 需要用到哪个字段就把哪个字段放前面你也可以简化判断当前表是谁 就把对应的关联字段放前面半自动:可以使用orm的正反向查询 但是没法使用add,set,remove,clear这四个方法
"""# 总结:你需要掌握的是全自动和半自动 为了扩展性更高 一般我们都会采用半自动(写代码要给自己留一条后路)
12-16 Ajax
16-1 小例子
/*
页面上有三个input框在前两个框中输入数字 点击按钮 朝后端发送ajax请求后端计算出结果 再返回给前端动态展示的到第三个input框中(整个过程页面不准有刷新,也不能在前端计算)
*/
$('#btn').click(function () {// 朝后端发送ajax请求$.ajax({// 1.指定朝哪个后端发送ajax请求url:'', // 不写就是朝当前地址提交// 2.请求方式type:'post', // 不指定默认就是get 都是小写// 3.数据{#data:{'username':'jason','password':123},#}data:{'i1':$('#d1').val(),'i2':$('#d2').val()},// 4.回调函数:当后端给你返回结果的时候会自动触发 args接受后端的返回结果success:function (args) {{#alert(args) // 通过DOM操作动态渲染到第三个input里面#}{#$('#d3').val(args)#}console.log(typeof args)}})})/*
针对后端如果是用HttpResponse返回的数据 回调函数不会自动帮你反序列化,需要在前端进行反序化
如果后端直接用的是JsonResponse返回的数据 回调函数会自动帮你反序列化HttpResponse解决方式1.自己在前端利用JSON.parse()2.在ajax里面配置一个参数dataType:true, // 数据会自动帮我们反序列化
*/
16-2 前后端传输数据的编码格式
# 我们主要研究post请求数据的编码格式
"""
get请求数据就是直接放在url后面的
url?username=jason&password=123
"""# 可以朝后端发送post请求的方式"""1.form表单2.ajax请求"""
"""
前后端传输数据的编码格式urlencodedformdatajson
"""
# 研究form表单
'''
## 第一种默认的数据编码格式是:aplication/x-www-form-urlencoded数据格式:username=jason&password=123django后端针对符合urlencoded编码格式的数据都会自动帮你解析封装到request.POST中username=jason&password=123 >>> request.POST## 第二种如果你把编码格式改成formdata,那么针对普通的键值对还是解析到request.POST中而将文件解析到request.FILES中form表单是没有办法发送json格式数据的
'''
# 研究ajax
'''默认的编码格式也是urlencoded数据格式:username=jason&age=20django后端针对符合urlencoded编码格式的数据都会自动帮你解析封装到request.POST中username=jason&age=20 >>> request.POST
'''
16-3 Ajax发送JSON格式
/*
前后端传输数据的时候一定要确保编码格式跟数据真正的格式是一致的
不要骗人家!!!{"username":"jason","age":25} 在request.POST里面肯定找不到django针对json格式的数据 不会做任何的处理 request对象方法补充request.is_ajax()判断当前请求是否是ajax请求 返回布尔值*/
<script>$('#d1').click(function () {$.ajax({url:'',type:'post',data:JSON.stringify({'username':'jason','age':25}),contentType:'application/json', // 指定编码格式success:function () {}})})
</script>
/*
ajax发送json格式数据需要注意点1.contentType参数指定成:application/json2.数据是真正的json格式数据3.django后端不会帮你处理json格式数据需要你自己去request.body获取并处理
*/
python后端解析JSON数据
json_bytes = request.body
json_str = json_bytes.decode('utf-8')
json_dict = json.loads(json_str)# json.loads括号内如果传入了一个二进制格式的数据那么内部自动解码再反序列化
json_dict = json.loads(json_bytes)
16-4 Ajax发送文件
4-1 笔记上的方法
"""
ajax发送文件需要借助于js内置对象FormData"""
<script>// 点击按钮朝后端发送普通键值对和文件数据$('#d4').on('click',function () {// 1 需要先利用FormData内置对象let formDateObj = new FormData();// 2 添加普通的键值对formDateObj.append('username',$('#d1').val());formDateObj.append('password',$('#d2').val());// 3 添加文件对象formDateObj.append('myfile',$('#d3')[0].files[0])// 4 将对象基于ajax发送给后端$.ajax({url:'',type:'post',data:formDateObj, // 直接将对象放在data后面即可// ajax发送文件必须要指定的两个参数contentType:false, // 不需使用任何编码 django后端能够自动识别formdata对象processData:false, // 告诉你的浏览器不要对你的数据进行任何处理success:function (args) {}})})
</script>def ab_file(request):if request.is_ajax():if request.method == 'POST':print(request.POST)print(request.FILES)return render(request,'ab_file.html')"""
总结:1.需要利用内置对象FormData// 2 添加普通的键值对formDateObj.append('username',$('#d1').val());formDateObj.append('password',$('#d2').val());// 3 添加文件对象formDateObj.append('myfile',$('#d3')[0].files[0])2.需要指定两个关键性的参数contentType:false, // 不需使用任何编码 django后端能够自动识别formdata对象processData:false, // 告诉你的浏览器不要对你的数据进行任何处理3.django后端能够直接识别到formdata对象并且能够将内部的普通键值自动解析并封装到request.POST中 文件数据自动解析并封装到request.FILES中
"""
4-2 原生JS实现文件上传
// 新版 XMLHttpRequest 对象,不仅可以发送文本信息,还可以上传文件。
实现步骤:
① 定义 UI 结构
② 验证是否选择了文件
③ 向 FormData 中追加文件
④ 使用 xhr 发起上传文件的请求
⑤ 监听 onreadystatechange 事件1. 定义UI结构<!-- 1. 文件选择框 --><input type="file" id="file1" /><!-- 2. 上传按钮 --><button id="btnUpload">上传文件</button><br /><!-- 3. 显示上传到服务器上的图片 --><img src="" alt="" id="img" width="800" />2. 验证是否选择了文件// 1. 获取上传文件的按钮var btnUpload = document.querySelector('#btnUpload')// 2. 为按钮添加 click 事件监听btnUpload.addEventListener('click', function() {// 3. 获取到选择的文件列表var files = document.querySelector('#file1').filesif (files.length <= 0) {return alert('请选择要上传的文件!')}// ...后续业务逻辑})3. 向FormData中追加文件// 1. 创建 FormData 对象var fd = new FormData()// 2. 向 FormData 中追加文件fd.append('avatar', files[0])4. 使用 xhr 发起上传文件的请求// 1. 创建 xhr 对象var xhr = new XMLHttpRequest()// 2. 调用 open 函数,指定请求类型与URL地址。其中,请求类型必须为 POSTxhr.open('POST', 'http://www.liulongbin.top:3006/api/upload/avatar')// 3. 发起请求xhr.send(fd)5. 监听onreadystatechange事件xhr.onreadystatechange = function() {if (xhr.readyState === 4 && xhr.status === 200) {var data = JSON.parse(xhr.responseText)if (data.status === 200) { // 上传文件成功// 将服务器返回的图片地址,设置为 <img> 标签的 src 属性document.querySelector('#img').src = 'http://www.liulongbin.top:3006' + data.url} else { // 上传文件失败console.log(data.message)}}}
4-3 jQuery实现文件上传
1. 定义UI结构<!-- 导入 jQuery --><script src="./lib/jquery.js"></script><!-- 文件选择框 --><input type="file" id="file1" /><!-- 上传文件按钮 --><button id="btnUpload">上传</button>2. 验证是否选择了文件$('#btnUpload').on('click', function() {// 1. 将 jQuery 对象转化为 DOM 对象,并获取选中的文件列表var files = $('#file1')[0].files// 2. 判断是否选择了文件if (files.length <= 0) {return alert('请选择图片后再上传!‘)}})3. 向FormData中追加文件// 向 FormData 中追加文件var fd = new FormData()fd.append('avatar', files[0])4. 使用jQuery发起上传文件的请求$.ajax({method: 'POST',url: 'http://www.liulongbin.top:3006/api/upload/avatar',data: fd,// 不修改 Content-Type 属性,使用 FormData 默认的 Content-Type 值contentType: false,// 不对 FormData 中的数据进行 url 编码,而是将 FormData 数据原样发送到服务器processData: false,success: function(res) {console.log(res)}})
16-5 显示文件上传的进度
// 新版本的 XMLHttpRequest 对象中,可以通过监听 xhr.upload.onprogress 事件,来获取到文件的上传进度。
语法格式如下:
// 创建 XHR 对象
var xhr = new XMLHttpRequest()
// 监听 xhr.upload 的 onprogress 事件
xhr.upload.onprogress = function(e) {
// e.lengthComputable 是一个布尔值,表示当前上传的资源是否具有可计算的长度
if (e.lengthComputable) {// e.loaded 已传输的字节// e.total 需传输的总字节var percentComplete = Math.ceil((e.loaded / e.total) * 100)}
}1. 导入需要的库<link rel="stylesheet" href="./lib/bootstrap.css" /><script src="./lib/jquery.js"></script>2. 基于Bootstrap渲染进度条<!-- 进度条 --><div class="progress" style="width: 500px; margin: 10px 0;"><div class="progress-bar progress-bar-info progress-barstriped active" id="percent" style="width: 0%">0%</div></div>3. 监听上传进度的事件xhr.upload.onprogress = function(e) {if (e.lengthComputable) {// 1. 计算出当前上传进度的百分比var percentComplete = Math.ceil((e.loaded / e.total) * 100)$('#percent')// 2. 设置进度条的宽度.attr('style', 'width:' + percentComplete + '%')// 3. 显示当前的上传进度百分比.html(percentComplete + '%')}}4. 监听上传完成的事件xhr.upload.onload = function() {$('#percent')// 移除上传中的类样式.removeClass()// 添加上传完成的类样式.addClass('progress-bar progress-bar-success')}
16-6 jQuery实现loading效果
1. ajaxStart(callback)//Ajax 请求开始时,执行 ajaxStart 函数。可以在 ajaxStart 的 callback 中显示 loading 效果,示例代码如下:// 自 jQuery 版本 1.8 起,该方法只能被附加到文档$(document).ajaxStart(function() {$('#loading').show()})注意: $(document).ajaxStart() 函数会监听当前文档内所有的 Ajax 请求。2. ajaxStop(callback)// Ajax 请求结束时,执行 ajaxStop 函数。可以在 ajaxStop 的 callback 中隐藏 loading 效果,示例代码如下:// 自 jQuery 版本 1.8 起,该方法只能被附加到文档$(document).ajaxStop(function() {$('#loading').hide()})
16-7 Axios
7.1 axios发起GET请求
// axios 发起 get 请求的语法:
axios.get('url', { params: { /*参数*/ } }).then(callback)//具体的请求示例如下:// 请求的 URL 地址var url = 'http://www.liulongbin.top:3006/api/get'// 请求的参数对象var paramsObj = { name: 'zs', age: 20 }// 调用 axios.get() 发起 GET 请求axios.get(url, { params: paramsObj }).then(function(res) {// res.data 是服务器返回的数据var result = res.dataconsole.log(res)})
7.2 直接使用axios发起POST请求
// axios 发起 post 请求的语法:axios.post('url', { /*参数*/ }).then(callback)具体的请求示例如下:// 请求的 URL 地址var url = 'http://www.liulongbin.top:3006/api/post'// 要提交到服务器的数据var dataObj = { location: '北京', address: '顺义' }// 调用 axios.post() 发起 POST 请求axios.post(url, dataObj).then(function(res) {// res.data 是服务器返回的数据var result = res.dataconsole.log(result)})
7,3 直接使用axios发起请求
// axios 也提供了类似于 jQuery 中 $.ajax() 的函数,语法如下:axios({method: '请求类型',url: '请求的URL地址',data: { /* POST数据 */ },params: { /* GET参数 */ }}) .then(callback)1. 直接使用axios发起GET请求axios({method: 'GET',url: 'http://www.liulongbin.top:3006/api/get',params: { // GET 参数要通过 params 属性提供name: 'zs',age: 20}}).then(function(res) {console.log(res.data)})2. 直接使用axios发起POST请求axios({method: 'POST',url: 'http://www.liulongbin.top:3006/api/post',data: { // POST 数据要通过 data 属性提供bookname: '程序员的自我修养',price: 666}}).then(function(res) {console.log(res.data)})
16-8 django自带的序列化组件(drf做铺垫)
"""
如果发现你可以直接使用MySQL但是无法使用sqlite3
不要慌张不要恐惧 你只需要按照之前MySQL的操作将sqlite3的驱动装一下即可
"""
# 需求:在前端给我获取到后端用户表里面所有的数据 并且要是列表套字典
import json
from django.http import JsonResponse
from django.core import serializers
def ab_ser(request):user_queryset = models.User.objects.all()# [{},{},{},{},{}]# user_list = []# for user_obj in user_queryset:# tmp = {# 'pk':user_obj.pk,# 'username':user_obj.username,# 'age':user_obj.age,# 'gender':user_obj.get_gender_display()# }# user_list.append(tmp)# return JsonResponse(user_list,safe=False)# return render(request,'ab_ser.html',locals())# 序列化res = serializers.serialize('json',user_queryset)"""会自动帮你将数据变成json格式的字符串 并且内部非常的全面"""return HttpResponse(res)
"""
[{"pk": 1, "username": "jason", "age": 25, "gender": "male"}, {"pk": 2, "username": "egon", "age": 31, "gender": "female"},{"pk": 3, "username": "kevin", "age": 32, "gender": "others"}, {"pk": 4, "username": "tank", "age": 40, "gender": 4}]
前后端分离的项目作为后端开发的你只需要写代码将数据处理好能够序列化返回给前端即可 再写一个接口文档 告诉前端每个字段代表的意思即可[
{ "model": "app01.user", "pk": 1, "fields": {"username": "jason", "age": 25, "gender": 1}}, { "model": "app01.user", "pk": 2, "fields": {"username": "egon", "age": 31, "gender": 2}}, { "model": "app01.user", "pk": 3, "fields": {"username": "kevin", "age": 32, "gender": 3}},{ "model": "app01.user", "pk": 4, "fields": {"username": "tank", "age": 40, "gender": 4}}
]
写接口就是利用序列化组件渲染数据然后写一个接口文档 该交代交代一下就完事
"""
16-9 ajax结合sweetalert
/*
自己要学会如何拷贝
学会基于别人的基础之上做修改
研究各个参数表示的意思 然后找葫芦画瓢
*/
<script>$('.del').on('click',function () {// 先将当前标签对象存储起来let currentBtn = $(this);// 二次确认弹框swal({title: "你确定要删吗?",text: "你可要考虑清除哦,可能需要拎包跑路哦!",type: "warning",showCancelButton: true,confirmButtonClass: "btn-danger",confirmButtonText: "是的,老子就要删!",cancelButtonText: "算了,算了!",closeOnConfirm: false,closeOnCancel: false,showLoaderOnConfirm: true},function(isConfirm) {if (isConfirm) {// 朝后端发送ajax请求删除数据之后 再弹下面的提示框$.ajax({{#url:'/delete/user/' + currentBtn.attr('delete_id'), // 1 传递主键值方式1#}url:'/delete/user/', // 2 放在请求体里面type:'post',data:{'delete_id':currentBtn.attr('delete_id')},success:function (args) { // args = {'code':'','msg':''}// 判断响应状态码 然后做不同的处理if(args.code === 1000){swal("删了!", args.msg, "success");// 1.lowb版本 直接刷新当前页面{#window.location.reload()#}// 2.利用DOM操作 动态刷新currentBtn.parent().parent().remove()}else{swal('完了','出现了位置的错误','info')}}})} else {swal("怂逼", "不要说我认识你", "error");}});})</script>
16-10 批量插入
def ab_pl(request):# 先给Book插入一万条数据# for i in range(10000):# models.Book.objects.create(title='第%s本书'%i)# # 再将所有的数据查询并展示到前端页面book_queryset = models.Book.objects.all()# 批量插入book_list = []for i in range(100000):book_obj = models.Book(title='第%s本书'%i)book_list.append(book_obj)models.Book.objects.bulk_create(book_list)"""当你想要批量插入数据的时候 使用orm给你提供的bulk_create能够大大的减少操作时间:param request: :return: """return render(request,'ab_pl.html',locals())
16-11 分页器
11-1 基础班
"""
总数据100 每页展示10 需要10
总数据101 每页展示10 需要11
总数据99 每页展示10 需要10如何通过代码动态的计算出到底需要多少页?在制作页码个数的时候 一般情况下都是奇数个 符合中国人对称美的标准
"""
# 分页book_list = models.Book.objects.all()# 想访问哪一页current_page = request.GET.get('page',1) # 如果获取不到当前页码 就展示第一页# 数据类型转换try:current_page = int(current_page)except Exception:current_page = 1# 每页展示多少条per_page_num = 10# 起始位置start_page = (current_page - 1) * per_page_num# 终止位置end_page = current_page * per_page_num# 计算出到底需要多少页all_count = book_list.count()page_count, more = divmod(all_count, per_page_num)if more:page_count += 1page_html = ''xxx = current_pageif current_page < 6:current_page = 6for i in range(current_page-5,current_page+6):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 = book_list[start_page:end_page]"""
django中有自带的分页器模块 但是书写起来很麻烦并且功能太简单
所以我们自己想法和设法的写自定义分页器上述推导代码你无需掌握 只需要知道内部逻辑即可我们基于上述的思路 已经封装好了我们自己的自定义分页器
之后需要使用直接拷贝即可
"""
11-2 现成的代码(直接用)
utils.mypage.py
class Pagination(object):def __init__(self, current_page, all_count, per_page_num=10, 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 = 1if current_page < 1:current_page = 1self.current_page = current_pageself.all_count = all_countself.per_page_num = per_page_num# 总页码all_pager, tmp = divmod(all_count, per_page_num)if tmp:all_pager += 1self.all_pager = all_pagerself.pager_count = pager_countself.pager_count_half = int((pager_count - 1) / 2)@propertydef start(self):return (self.current_page - 1) * self.per_page_num@propertydef end(self):return self.current_page * self.per_page_numdef page_html(self):# 如果总页码 < 11个:if self.all_pager <= self.pager_count:pager_start = 1pager_end = self.all_pager + 1# 总页码 > 11else:# 当前页如果<=页面上最多显示11/2个页码if self.current_page <= self.pager_count_half:pager_start = 1pager_end = self.pager_count + 1# 当前页大于5else:# 页码翻到最后if (self.current_page + self.pager_count_half) > self.all_pager:pager_end = self.all_pager + 1pager_start = self.all_pager - self.pager_count + 1else:pager_start = self.current_page - self.pager_count_halfpager_end = self.current_page + self.pager_count_half + 1page_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)
views.py
from django.shortcuts import render,HttpResponse
from app01 import modelsfrom utils.mypage import Paginationdef ab_pl(request):book_queryset = models.Book.objects.all()current_page = request.GET.get('page',1)all_count = book_queryset.count()# 1 传值生成对象page_obj = Pagination(current_page=current_page,all_count=all_count)# 2 直接对总数据进行切片操作page_queryset = book_queryset[page_obj.start:page_obj.end]# 3 将page_queryset传递到页面 替换之前的book_querysetreturn render(request,'ab_pl.html',locals())
"""
per_page_num = 10
current_page start_page end_page1 0 102 10 203 20 304 30 40per_page_num = 5
current_page start_page end_page1 0 52 5 103 10 154 15 20start_page = (current_page - 1) * per_page_num
end_page = current_page * per_page_num
"""
ab_pl.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><meta name="viewport" content="width=device-width, initial-scale=1"><link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet"><script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script><script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
</head>
<body>
{% for book_obj in page_queryset %}<p>{{ book_obj.title }}</p><nav aria-label="Page navigation">
</nav>
{% endfor %}
{#利用自定义分页器直接显示分页器样式#}
{{ page_obj.page_html|safe }}</body>
</html>
16-12 自定义分页器的拷贝及使用
"""
当我们需要使用到非django内置的第三方功能或者组件代码的时候
我们一般情况下会创建一个名为utils文件夹 在该文件夹内对模块进行功能性划分utils可以在每个应用下创建 具体结合实际情况我们到了后期封装代码的时候 不再局限于函数
还是尽量朝面向对象去封装我们自定义的分页器是基于bootstrap样式来的 所以你需要提前导入bootstrapbootstrap 版本 v3jQuery 版本 v3
"""
# 后端
book_queryset = models.Book.objects.all()
current_page = request.GET.get('page',1)
all_count = book_queryset.count()
# 1 传值生成对象
page_obj = Pagination(current_page=current_page,all_count=all_count)
# 2 直接对总数据进行切片操作
page_queryset = book_queryset[page_obj.start:page_obj.end]
# 3 将page_queryset传递到页面 替换之前的book_queryset# 前端
{% for book_obj in page_queryset %}<p>{{ book_obj.title }}</p><nav aria-label="Page navigation">
</nav>
{% endfor %}
{#利用自定义分页器直接显示分页器样式#}
{{ page_obj.page_html|safe }}"""
你们只需要掌握如何拷贝使用 以及大致的推导思路即可
"""
12-17 表单的基本使用
17.1 标签的属性
1-1 target
1-2 enctype
// 在涉及到文件上传的操作时,必须将 enctype 的值设置为 multipart/form-data
// 如果表单的提交不涉及到文件上传操作,则直接将 enctype 的值设置为 application/x-www-form-urlencoded 即可!
17-2 表单的同步提交及缺点
1. 什么是表单的同步提交// 通过点击 submit 按钮,触发表单提交的操作,从而使页面跳转到 action URL 的行为,叫做表单的同步提交。
2,表单同步提交的缺点// 页面会发生跳转// 页面之前的状态和数据会丢失
3,解决方案:表单只负责采集数据,Ajax 负责将数据提交到服务器。
17-3 通过Ajax提交表单数据
3.1 监听表单提交事件
// 在 jQuery 中,可以使用如下两种方式,监听到表单的提交事件:$('#form1').submit(function(e) {alert('监听到了表单的提交事件')
})$('#form1').on('submit', function(e) {alert('监听到了表单的提交事件')
})
3-2 阻止表单默认提交行为
// 当监听到表单的提交事件以后,可以调用事件对象的 event.preventDefault() 函数,来阻止表单的提交和页面的跳转,示例代码如下:$('#form1').submit(function(e) {// 阻止表单的提交和页面的跳转e.preventDefault()
})$('#form1').on('submit', function(e) {// 阻止表单的提交和页面的跳转e.preventDefault()
})
3-3 快速获取表单中的数据
serialize()函数
// 为了简化表单中数据的获取操作,jQuery 提供了 serialize() 函数,其语法格式如下]$(selector).serialize()// serialize() 函数的好处:可以一次性获取到表单中的所有的数据。
// 注意:在使用 serialize() 函数快速获取表单数据时,必须为每个表单元素添加 name 属性!<form id="form1">
<input type="text" name="username" />
<input type="password" name="password" />
<button type="submit">提交</button>
</form>$('#form1').serialize()
// 调用的结果:
// username=用户名的值&password=密码的值
12-18 forms组件
18-1 前戏
"""
写一个注册功能获取用户名和密码 利用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'] = '不能太短 不好!'"""无论是post请求还是get请求页面都能够获取到字典 只不过get请求来的时候 字典值都是空的而post请求来之后 字典可能有值"""return render(request,'ab_form.html',locals())<form action="" method="post"><p>username:<input type="text" name="username"><span style="color: red">{{ back_dic.username }}</span></p><p>password:<input type="text" name="password"><span style="color: red">{{ back_dic.password }}</span></p><input type="submit" class="btn btn-info">
</form>"""
1.手动书写前端获取用户数据的html代码 渲染html代码
2.后端对用户数据进行校验 校验数据
3.对不符合要求的数据进行前端提示 展示提示信息forms组件能够完成的事情1.渲染html代码2.校验数据3.展示提示信息为什么数据校验非要去后端 不能在前端利用js直接完成呢?数据校验前端可有可无但是后端必须要有!!!因为前端的校验是弱不禁风的 你可以直接修改或者利用爬虫程序绕过前端页面直接朝后端提交数据购物网站 选取了货物之后 会计算一个价格发送给后端 如果后端不做价格的校验实际是获取到用户选择的所有商品的主键值然后在后端查询出所有商品的价格 再次计算一遍如果跟前端一致 那么完成支付如果不一致直接拒绝
"""
18-2 基本使用
from django import forms
class MyForm(forms.Form):# username字符串类型最小3位最大8位username = forms.CharField(min_length=3,max_length=8)# password字符串类型最小3位最大8位password = forms.CharField(min_length=3,max_length=8)# email字段必须符合邮箱格式 xxx@xx.comemail = forms.EmailField()
18-3 校验数据
"""
1.测试环境的准备 可以自己拷贝代码准备
2.其实在pycharm里面已经帮你准备一个测试环境python console
"""
from app01 import views
# 1 将带校验的数据组织成字典的形式传入即可form_obj = views.MyForm({'username':'jason','password':'123','email':'123'})# 2 判断数据是否合法,注意该方法只有在所有的数据全部合法的情况下才会返回Trueform_obj.is_valid() # False# 3 查看所有校验通过的数据form_obj.cleaned_data ## {'username': 'jason', 'password': '123'}# 4 查看所有不符合校验规则以及不符合的原因form_obj.errors{'email': ['Enter a valid email address.']}# 5 校验数据只校验类中出现的字段 多传不影响 多传的字段直接忽略
form_obj = views.MyForm({'username':'jason','password':'123','email':'123@qq.com','hobby':'study'})
form_obj.is_valid() ## True# 6 校验数据 默认情况下 类里面所有的字段都必须传值
form_obj = views.MyForm({'username':'jason','password':'123'})
form_obj.is_valid() # False"""
也就意味着校验数据的时候 默认情况下数据可以多传但是绝不可能少传
"""
18-4 渲染标签
"""
forms组件只会自动帮你渲染获取用户输入的标签(input select radio checkbox)
不能帮你渲染提交按钮
"""
def index(request):# 1 先产生一个空对象form_obj = MyForm()# 2 直接将该空对象传递给html页面return render(request,'index.html',locals())# 前端利用空对象做操作<p> 第一种渲染方式:代码书写极少,封装程度太高 不便于后续的扩展 一般情况下只在本地测试使用</p>{{ form_obj.as_p }}{{ form_obj.as_ul }}{{ form_obj.as_table }}## 上面三种写法三选一即可,就是不同的渲染方式<p>第二种渲染方式:可扩展性很强 但是需要书写的代码太多 一般情况下不用</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><p>第三种渲染方式(推荐使用):代码书写简单 并且扩展性也高</p>{% for form in form_obj %}<p>{{ form.label }}:{{ form }}</p>{% endfor %}"""
label属性默认展示的是类中定义的字段首字母大写的形式
也可以自己修改 直接给字段对象加label属性即可username = forms.CharField(min_length=3,max_length=8,label='用户名')
"""
18-5 展示提示信息
"""
浏览器会自动帮你校验数据 但是前端的校验弱不禁风
如何让浏览器不做校验<form action="" method="post" novalidate>
"""def index(request):# 1 先产生一个空对象form_obj = MyForm()if request.method == 'POST':# 获取用户数据并且校验"""1.数据获取繁琐2.校验数据需要构造成字典的格式传入才行ps:但是request.POST可以看成就是一个字典"""# 3.校验数据form_obj = MyForm(request.POST)# 4.判断数据是否合法if form_obj.is_valid():# 5.如果合法 操作数据库存储数据return HttpResponse('OK')# 5.不合法 有错误# 2 直接将该空对象传递给html页面return render(request,'index.html',locals())## 前端页面渲染
{% for form in form_obj %}<p>{{ form.label }}:{{ form }}<span style="color: red">{{ form.errors.0 }}</span></p>
{% endfor %}## 如果不想让前端也进行校验,那么在form标签加一个novalidate"""
1.必备的条件 get请求和post传给html页面对象变量名必须一样
2.forms组件当你的数据不合法的情况下 会保存你上次的数据 让你基于之前的结果进行修改
更加的人性化
"""
# 针对错误的提示信息还可以自己自定制
class MyForm(forms.Form):# username字符串类型最小3位最大8位username = forms.CharField(min_length=3,max_length=8,label='用户名',error_messages={'min_length':'用户名最少3位','max_length':'用户名最大8位','required':"用户名不能为空"})# password字符串类型最小3位最大8位password = forms.CharField(min_length=3,max_length=8,label='密码',error_messages={'min_length': '密码最少3位','max_length': '密码最大8位','required': "密码不能为空"})# email字段必须符合邮箱格式 xxx@xx.comemail = forms.EmailField(label='邮箱',error_messages={'invalid':'邮箱格式不正确','required': "邮箱不能为空"})
18-6 钩子函数(HOOK)
"""
在特定的节点自动触发完成响应操作钩子函数在forms组件中就类似于第二道关卡,能够让我们自定义校验规则在forms组件中有两类钩子1.局部钩子当你需要给单个字段增加校验规则的时候可以使用2.全局钩子当你需要给多个字段增加校验规则的时候可以使用
"""
# 实际案例# 1.校验用户名中不能含有666 只是校验username字段 局部钩子# 2.校验密码和确认密码是否一致 password confirm两个字段 全局钩子# 钩子函数 在类里面书写方法即可# 局部钩子from django import formsclass MyForm(forms.Form):# username字符串类型最小3位最大8位username = forms.CharField(min_length=3,max_length=8)# password字符串类型最小3位最大8位password = forms.CharField(min_length=3,max_length=8)# email字段必须符合邮箱格式 xxx@xx.comemail = forms.EmailField()def clean_username(self):# 获取到用户名username = self.cleaned_data.get('username')if '666' in username:# 提示前端展示错误信息self.add_error('username','光喊666是不行滴~')# 将钩子函数钩去出来数据再放回去return usernameclass MyForm(forms.Form):# username字符串类型最小3位最大8位username = forms.CharField(min_length=3,max_length=8)# password字符串类型最小3位最大8位password = forms.CharField(min_length=3,max_length=8)# email字段必须符合邮箱格式 xxx@xx.comemail = forms.EmailField() # 全局钩子def clean(self):password = self.cleaned_data.get('password')confirm_password = self.cleaned_data.get('confirm_password')if not confirm_password == password:self.add_error('confirm_password','两次密码不一致')# 将钩子函数钩出来数据再放回去return self.cleaned_data
18-7 forms组件其他参数及补充知识点
label 字段名
error_messages 自定义报错信息
initial 默认值
required 控制字段是否必填
"""
1.字段没有样式
2.针对不同类型的input如何修改textpassworddateradiocheckbox...
"""
widget=forms.widgets.PasswordInput(attrs={'class':'form-control c1 c2'})
# 多个属性值的话 直接空格隔开即可from django.core.validators import RegexValidator
# 第一道关卡里面还支持正则校验
validators=[RegexValidator(r'^[0-9]+$', '请输入数字'),RegexValidator(r'^159[0-9]+$', '数字必须以159开头')]
18-8 其他类型渲染
# radiogender = forms.ChoiceField(choices=((1, "男"), (2, "女"), (3, "保密")),label="性别",initial=3,widget=forms.widgets.RadioSelect())# selecthobby = forms.ChoiceField(choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),label="爱好",initial=3,widget=forms.widgets.Select())# 多选hobby1 = forms.MultipleChoiceField(choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),label="爱好",initial=[1, 3],widget=forms.widgets.SelectMultiple())# 单选checkboxkeep = forms.ChoiceField(label="是否记住密码",initial="checked",widget=forms.widgets.CheckboxInput())# 多选checkboxhobby2 = forms.MultipleChoiceField(choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),label="爱好",initial=[1, 3],widget=forms.widgets.CheckboxSelectMultiple())
18-9 forms组件源码
"""
切入点:form_obj.is_valid()
"""
def is_valid(self):"""Returns True if the form has no errors. Otherwise, False. If errors arebeing ignored, returns False."""return self.is_bound and not self.errors# 如果is_valid要返回True的话 那么self.is_bound要为True self.errors要为Flaseself.is_bound = data is not None or files is not None # 只要你传值了肯定为True@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# forms组件所有的功能基本都出自于该方法
def full_clean(self):self._clean_fields() # 校验字段 + 局部钩子self._clean_form() # 全局钩子self._post_clean()
12-19 cookie与session
19-1 cookie与session
"""
发展史1.网站都没有保存用户功能的需求 所有用户访问返回的结果都是一样的eg:新闻、博客、文章...2.出现了一些需要保存用户信息的网站eg:淘宝、支付宝、京东...以登陆功能为例:如果不保存用户登陆状态 也就意味着用户每次访问网站都需要重复的输入用户名和密码(你觉得这样的网站你还想用吗?)当用户第一次登陆成功之后 将用户的用户名密码返回给用户浏览器 让用户浏览器保存在本地,之后访问网站的时候浏览器自动将保存在浏览器上的用户名和密码发送给服务端,服务端获取之后自动验证早起这种方式具有非常大的安全隐患优化:当用户登陆成功之后,服务端产生一个随机字符串(在服务端保存数据,用kv键值对的形式),交由客户端浏览器保存随机字符串1:用户1相关信息随机字符串2:用户2相关信息随机字符串3:用户3相关信息之后访问服务端的时候,都带着该随机字符串,服务端去数据库中比对是否有对应的随机字符串从而获取到对应的用户信息但是如果你拿到了截获到了该随机字符串,那么你就可以冒充当前用户 其实还是有安全隐患的你要知道在web领域没有绝对的安全也没有绝对的不安全
"""
cookie服务端保存在客户端浏览器上的信息都可以称之为cookie它的表现形式一般都是k:v键值对(可以有多个)
session数据是保存在服务端的并且它的表现形式一般也是k:v键值对(可以有多个)下述内容暂时了解即可 先给我搞明白最简单的cookie与session使用再说话!
tokensession虽然数据是保存在服务端的 但是禁不住数据量大服务端不再保存数据登陆成功之后 将一段用户信息进行加密处理(加密算法之后你公司开发知道)将加密之后的结果拼接在信息后面 整体返回给浏览器保存 浏览器下次访问的时候带着该信息 服务端自动切去前面一段信息再次使用自己的加密算法跟浏览器尾部的密文进行比对
jwt认证三段信息(后期会讲 结合django一起使用) 总结:1.cookie就是保存在客户端浏览器上的信息2.session就是保存在服务端上的信息3.session是基于cookie工作的(其实大部分的保存用户状态的操作都需要使用到cookie)
19-2 Django操作cookie
# 虽然cookie是服务端告诉客户端浏览器需要保存内容
# 但是客户端浏览器可以选择拒绝保存 如果禁止了 那么 只要是需要记录用户状态的网站登陆功能都无法使用了# 视图函数的返回值
return HttpResponse()
return render()
return redirect()obj1 = HttpResponse()
# 操作cookie
return obj1obj2 = render()
# 操作cookie
return obj2obj3 = redirect()
# 操作cookie
return obj3
# 如果你想要操作cookie,你就不得不利用obj对象## 设置cookieobj.set_cookie(key,value)
## 获取cookierequest.COOKIES.get(key)
## 在设置cookie的时候可以添加一个超时时间obj.set_cookie('username', 'jason666',max_age=3,expires=3)max_age ## 5秒超时expires## 两者都是设置超时时间的 并且都是以秒为单位## 需要注意的是 针对IE浏览器需要使用expires
## 主动删除cookie(注销功能)
19-3 登录装饰器
# 我们完成一个真正的登陆功能
# 校验用户是否登陆的装饰器
"""
用户如果在没有登陆的情况下想访问一个需要登陆的页面
那么先跳转到登陆页面 当用户输入正确的用户名和密码之后
应该跳转到用户之前想要访问的页面去 而不是直接写死
"""
def login_auth(func):def inner(request,*args,**kwargs):# print(request.path_info)# print(request.get_full_path()) # 能够获取到用户上一次想要访问的urltarget_url = request.get_full_path()if request.COOKIES.get('username'):return func(request,*args,**kwargs)else:## 以next查询字符串的方式,把用户想要访问的页面传到login页面return redirect('/login/?next=%s'%target_url)return innerdef login(request):if request.method == 'POST':username = request.POST.get('username')password = request.POST.get('password')if username == 'jason' and password == '123':# 获取用户上一次想要访问的urltarget_url = request.GET.get('next') # 这个结果可能是Noneif target_url:obj = redirect(target_url)else:# 保存用户登陆状态obj = redirect('/home/')# 让浏览器记录cookie数据obj.set_cookie('username', 'jason666')"""浏览器不单单会帮你存而且后面每次访问你的时候还会带着它过来"""# 跳转到一个需要用户登陆之后才能看的页面return objreturn render(request,'login.html')@login_auth
def home(request):# 获取cookie信息 判断你有没有# if request.COOKIES.get('username') == 'jason666':# return HttpResponse("我是home页面,只有登陆的用户才能进来哟~")# # 没有登陆应该跳转到登陆页面# return redirect('/login/')return HttpResponse("我是home页面,只有登陆的用户才能进来哟~")@login_auth
def logout(requests):obj = redirect('/login/')obj.delete_cookie('username')return obj
19-4 django操作session
## session数据是保存在服务端的(存?),给客户端返回的是一个随机字符串sessionid:随机字符串## 在默认情况下操作session的时候需要django默认的一张django_session表## 数据库迁移命令## django会自己创建很多表 django_session就是其中的一张## django默认session的过期时间是14天## 但是你也可以人为的修改它## 设置session request.session['key'] = value## 获取sessionrequest.session.get('key')## 设置过期时间request.session.set_expiry()## 括号内可以放四种类型的参数1.整数 多少秒2.日期对象 到指定日期就失效3.0 一旦当前浏览器窗口关闭立刻失效4.不写 失效时间就取决于django内部全局session默认的失效时间## 清除session request.session.delete() # 只删服务端的 客户端的不删request.session.flush() # 浏览器和服务端都清空(推荐使用)## session是保存在服务端的 但是session的保存位置可以有多种选择1.MySQL2.文件3.redis4.memcache...django_session表中的数据条数是取决于浏览器的## 同一个计算机上(IP地址)同一个浏览器只会有一条数据生效## (当session过期的时候可能会出现多条数据对应一个浏览器,但是该现象不会持续很久,内部会自动识别过期的数据清除 你也可以通过代码清除)## 主要是为了节省服务端数据库资源def set_session(request);request.session['hobby'] = 'girl'"""内部发送了那些事1.django内部会自动帮你生成一个随机字符串2.django内部自动将随机字符串和对应的数据存储到django_session表中2.1先在内存中产生操作数据的缓存2.2在响应结果django中间件的时候才真正的操作数据库3.将产生的随机字符串返回给客户端浏览器保存"""
def get_session(request):request.session.get('hobby')"""内部发送了那些事1.自动从浏览器请求中获取sessionid对应的随机字符串2.拿着该随机字符串去django_session表中查找对应的数据3.如果比对上了 则将对应的数据取出并以字典的形式封装到request.session中如果比对不上 则request.session.get()返回的是None"""# 利用session实现登陆验证
19-5 CBV如何添加装饰器
from django.views import View
from django.utils.decorators import method_decorator
"""
CBV中django不建议你直接给类的方法加装饰器
无论该装饰器能都正常给你 都不建议直接加
"""# @method_decorator(login_auth,name='get') # 方式2(可以添加多个针对不同的方法加不同的装饰器)
# @method_decorator(login_auth,name='post')
class MyLogin(View):@method_decorator(login_auth) # 方式3:它会直接作用于当前类里面的所有的方法def dispatch(self, request, *args, **kwargs):return super().dispatch(request,*args,**kwargs)# @method_decorator(login_auth) # 方式1:指名道姓def get(self,request):return HttpResponse("get请求")def post(self,request):return HttpResponse('post请求')
12-20 Django中间件
20-1 django中间件
"""
django中间件是django的门户
1.请求来的时候需要先经过中间件才能到达真正的django后端
2.响应走的时候最后也需要经过中间件才能发送出去django自带七个中间件
"""
## django请求生命周期流程图## 研究django中间件代码规律
MIDDLEWARE = ['django.middleware.security.SecurityMiddleware','django.contrib.sessions.middleware.SessionMiddleware','django.middleware.common.CommonMiddleware','django.middleware.csrf.CsrfViewMiddleware','django.contrib.auth.middleware.AuthenticationMiddleware','django.contrib.messages.middleware.MessageMiddleware','django.middleware.clickjacking.XFrameOptionsMiddleware',
]class SessionMiddleware(MiddlewareMixin):def process_request(self, request):session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME)request.session = self.SessionStore(session_key)def process_response(self, request, response):return responseclass CsrfViewMiddleware(MiddlewareMixin):def process_request(self, request):csrf_token = self._get_token(request)if csrf_token is not None:# Use same token next time.request.META['CSRF_COOKIE'] = csrf_tokendef process_view(self, request, callback, callback_args, callback_kwargs):return self._accept(request)def process_response(self, request, response):return responseclass AuthenticationMiddleware(MiddlewareMixin):def process_request(self, request):request.user = SimpleLazyObject(lambda: get_user(request))
"""
django支持程序员自定义中间件并且暴露给程序员五个可以自定义的方法1.必须掌握process_requestprocess_response2.了解即可process_viewprocess_template_responseprocess_exception
"""
20-2 如何自定义中间件
from django.utils.deprecation import MiddlewareMixinclass MyMiddleware(MiddlewareMixin):def process_request(self,request):print('我是第一个自定义中间件里的process_request方法')def process_response(self,request,response):## 必须返回response 要不然就等于拦截了return responsedef process_view(self,request,view_name,*args,**kwargs):## 在路由匹配成功之后,在视图函数执行之前触发这个方法print(view_name,args,kwargs)print('我是process_view方法')def process_template_response(self,request,response):## 执行条件很苛刻,了解一下即可,反正用的极少极少极少return responsedef process_exception(self,request,exception):## 当视图函数中出现异常的情况下触发print(exception)"""
1.在项目名或者应用名下创建一个任意名称的文件夹
2.在该文件夹内创建一个任意名称的py文件
3.在该py文件内需要书写类(这个类必须继承MiddlewareMixin)然后在这个类里面就可以自定义五个方法了(这五个方法并不是全部都需要书写,用几个写几个)
4.需要将类的路径以字符串的形式注册到配置文件中才能生效
MIDDLEWARE = ['django.middleware.security.SecurityMiddleware','django.contrib.sessions.middleware.SessionMiddleware','django.middleware.common.CommonMiddleware','django.middleware.csrf.CsrfViewMiddleware','django.contrib.auth.middleware.AuthenticationMiddleware','django.contrib.messages.middleware.MessageMiddleware','django.middleware.clickjacking.XFrameOptionsMiddleware','你自己写的中间件的路径1','你自己写的中间件的路径2','你自己写的中间件的路径3',
]"""
"""
1.必须掌握process_request 1.请求来的时候需要经过每一个中间件里面的process_request方法结果的顺序是按照配置文件中注册的中间件从上往下的顺序依次执行2.如果中间件里面没有定义该方法,那么直接跳过执行下一个中间件3.如果该方法返回了HttpResponse对象,那么请求将不再继续往后执行而是直接原路返回(校验失败不允许访问...)process_request方法就是用来做全局相关的所有限制功能process_response1.响应走的时候需要结果每一个中间件里面的process_response方法该方法有两个额外的参数request,response2.该方法必须返回一个HttpResponse对象1.默认返回的就是形参response2.你也可以自己返回自己的3.顺序是按照配置文件中注册了的中间件从下往上依次经过如果你没有定义的话 直接跳过执行下一个研究如果在第一个process_request方法就已经返回了HttpResponse对象,那么响应走的时候是经过所有的中间件里面的process_response还是有其他情况是其他情况就是会直接走同级别的process_reponse返回flask框架也有一个中间件但是它的规律只要返回数据了就必须经过所有中间件里面的类似于process_reponse方法2.了解即可process_view路由匹配成功之后执行视图函数之前,会自动执行中间件里面的该放法顺序是按照配置文件中注册的中间件从上往下的顺序依次执行process_template_response返回的HttpResponse对象有render属性的时候才会触发顺序是按照配置文件中注册了的中间件从下往上依次经过process_exception当视图函数中出现异常的情况下触发顺序是按照配置文件中注册了的中间件从下往上依次经过
"""
20-3 csrf跨站请求伪造
"""
钓鱼网站我搭建一个跟正规网站一模一样的界面(中国银行)用户不小心进入到了我们的网站,用户给某个人打钱打钱的操作确确实实是提交给了中国银行的系统,用户的钱也确确实实减少了但是唯一不同的时候打钱的账户不适用户想要打的账户变成了一个莫名其妙的账户大学英语四六级考之前需要学生自己网站登陆缴费内部本质我们在钓鱼网站的页面 针对对方账户 只给用户提供一个没有name属性的普通input框然后我们在内部隐藏一个已经写好name和value的input框如何规避上述问题csrf跨站请求伪造校验网站在给用户返回一个具有提交数据功能页面的时候会给这个页面加一个唯一标识当这个页面朝后端发送post请求的时候 我的后端会先校验唯一标识,如果唯一标识不对直接拒绝(403 forbbiden)如果成功则正常执行
"""
20-4 如何符合校验
# form表单如何符合校验
<form action="" method="post">{% csrf_token %}<p>username:<input type="text" name="username"></p><p>target_user:<input type="text" name="target_user"></p><p>money:<input type="text" name="money"></p><input type="submit">
</form># ajax如何符合校验
// 第一种 利用标签查找获取页面上的随机字符串
{#data:{"username":'jason','csrfmiddlewaretoken':$('[name=csrfmiddlewaretoken]').val()},#}
// 第二种 利用模版语法提供的快捷书写
{#data:{"username":'jason','csrfmiddlewaretoken':'{{ csrf_token }}'},#}
// 第三种 通用方式直接拷贝js代码并应用到自己的html页面上即可
data:{"username":'jason'}
function getCookie(name) {var cookieValue = null;if (document.cookie && document.cookie !== '') {var cookies = document.cookie.split(';');for (var i = 0; i < cookies.length; i++) {var cookie = jQuery.trim(cookies[i]);// Does this cookie string begin with the name we want?if (cookie.substring(0, name.length + 1) === (name + '=')) {cookieValue = decodeURIComponent(cookie.substring(name.length + 1));break;}}}return cookieValue;
}
var csrftoken = getCookie('csrftoken');function csrfSafeMethod(method) {// these HTTP methods do not require CSRF protectionreturn (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}$.ajaxSetup({beforeSend: function (xhr, settings) {if (!csrfSafeMethod(settings.type) && !this.crossDomain) {xhr.setRequestHeader("X-CSRFToken", csrftoken);}}
});
20-5 CSRF相关装饰器
"""
1.网站整体都不校验csrf,就单单几个视图函数需要校验
2.网站整体都校验csrf,就单单几个视图函数不校验
"""
from django.views.decorators.csrf import csrf_protect,csrf_exempt
from django.utils.decorators import method_decorator
"""
csrf_protect 需要校验针对csrf_protect符合我们之前所学的装饰器的三种玩法
csrf_exempt 忽视校验针对csrf_exempt只能给dispatch方法加才有效
"""'django.middleware.csrf.CsrfViewMiddleware'
## 打开以后网站对csrf进行校验,这个时候可以用@csrf_exempt装饰器忽略某些函数的校验'django.middleware.csrf.CsrfViewMiddleware'
## 注释掉以后,整个网站就不会进行校验,这时候我们用@csrf_protect 对个别视图函数进行校验# @csrf_exempt
# @csrf_protect
def transfer(request):if request.method == 'POST':username = request.POST.get('username')target_user = request.POST.get('target_user')money = request.POST.get('money')print('%s给%s转了%s元'%(username,target_user,money))return render(request,'transfer.html')from django.views import View# @method_decorator(csrf_protect,name='post') # 针对csrf_protect 第二种方式可以
# @method_decorator(csrf_exempt,name='post') # 针对csrf_exempt 第二种方式不可以
@method_decorator(csrf_exempt,name='dispatch')
class MyCsrfToken(View):# @method_decorator(csrf_protect) # 针对csrf_protect 第三种方式可以# @method_decorator(csrf_exempt) # 针对csrf_exempt 第三种方式可以def dispatch(self, request, *args, **kwargs):return super(MyCsrfToken, self).dispatch(request,*args,**kwargs)def get(self,request):return HttpResponse('get')# @method_decorator(csrf_protect) # 针对csrf_protect 第一种方式可以# @method_decorator(csrf_exempt) # 针对csrf_exempt 第一种方式不可以def post(self,request):return HttpResponse('post')
20-6 补充知识点
# 模块:importlib
import importlib
res = 'myfile.b'
ret = importlib.import_module(res) # from myfile import b
# 该方法最小只能到py文件名
print(ret)
20-7 重要思想
import settings
import importlibdef send_all(content):for path_str in settings.NOTIFY_LIST: #'notify.email.Email'module_path,class_name = path_str.rsplit('.',maxsplit=1)# module_path = 'notify.email' class_name = 'Email'# 1 利用字符串导入模块module = importlib.import_module(module_path) # from notify import email# 2 利用反射获取类名cls = getattr(module,class_name) # Email、QQ、Wechat# 3 生成类的对象obj = cls()# 4 利用鸭子类型直接调用send方法obj.send(content)
12-21 auth模块
21-1 Auth模块
"""
其实我们在创建好一个django项目之后直接执行数据库迁移命令会自动生成很多表django_sessionauth_user
django在启动之后就可以直接访问admin路由,需要输入用户名和密码,数据参考的就是auth_user表,并且还必须是管理员用户才能进入创建超级用户(管理员)python3 manage.py createsuperuser或者在工具里面run manager.py然后在下面输入createsuperuser依赖于auth_user表完成用户相关的所有功能
"""
21-2 方法总结
from django.contrib import auth# 1.比对用户名和密码是否正确
user_obj = auth.authenticate(request,username=username,password=password)
## 自动查找auth_user标签
## 自动给密码加密再比对
# 括号内必须同时传入用户名和密码
print(user_obj) # 用户对象 jason 数据不符合则返回None
print(user_obj.username) # jason
print(user_obj.password) # 密文# 2.保存用户状态
if user_obj:auth.login(request,user_obj) # 类似于request.session[key] = user_obj
# 只要执行了该方法 你就可以在任何地方通过request.user获取到当前登陆的用户对象
## 如果没有登录则拿到的是AnonymousUser# 3.判断当前用户是否登陆
request.user.is_authenticated()# 4.获取当前登陆用户
request.user# 5.校验用户是否登陆装饰器
from django.contrib.auth.decorators import login_required
# 局部配置
@login_required(login_url='/login/')
# 全局配置,在settings.py里配置即可
LOGIN_URL = '/login/'1.如果局部和全局都有 该听谁的?## 局部 > 全局2.局部和全局哪个好呢?## 全局的好处在于无需重复写代码 但是跳转的页面却很单一## 局部的好处在于不同的视图函数在用户没有登陆的情况下可以跳转到不同的页面# 6.比对原密码
request.user.check_password(old_password)if new_password == confirm_password:res = request.user.check_password(old_password) ## 自动加密比对密码## 返回值为true 或者 falseif res:## 修改密码request.user.set_password(new_password)## 一定要保存,要不然不会去操作数据库的request.user.save() # 7.修改密码
request.user.set_password(new_password) # 仅仅是在修改对象的属性
request.user.save() # 这一步才是真正的操作数据库# 8.注销
auth.logout(request) # 9.注册
from django.contrib.auth.models import User
# 操作auth_user表写入数据
User.objects.create(username=username,password=password) # 写入数据 但是密码是明文的,所以写入数据不能用create
# 创建普通用户
User.objects.create_user(username=username,password=password)
# 创建超级用户(了解):使用代码创建超级用户 邮箱是必填的 而用命令创建则可以不填
User.objects.create_superuser(username=username,email='123@qq.com',password=password)
21-3 如何扩展auth_user表
from django.db import models
from django.contrib.auth.models import User,AbstractUser
# Create your models here.# 第一种:一对一关系 不推荐
# class UserDetail(models.Model):
# phone = models.BigIntegerField()
# user = models.OneToOneField(to='User')# 第二种:面向对象的继承
class UserInfo(AbstractUser):"""如果继承了AbstractUser那么在执行数据库迁移命令的时候auth_user表就不会再创建出来了而UserInfo表中会出现auth_user所有的字段外加自己扩展的字段这么做的好处在于你能够直接点击你自己的表更加快速的完成操作及扩展前提:1.在继承之前没有执行过数据库迁移命令auth_user没有被创建,如果当前库已经创建了那么你就重新换一个库2.继承的类里面不要覆盖AbstractUser里面的字段名表里面有的字段都不要动,只扩展额外字段即可3.需要在配置文件中告诉django你要用UserInfo替代auth_user(******)AUTH_USER_MODEL = 'app01.UserInfo''应用名.表名'"""phone = models.BigIntegerField()"""
你如果自己写表替代了auth_user那么
auth模块的功能还是照常使用,参考的表页由原来的auth_user变成了UserInfo我们bbs作业用户表就是用的上述方式
"""
12-22 bbs项目
22-1 表设计
"""
一个项目中最最最重要的不是业务逻辑的书写
而是前期的表设计,只要将表设计好了,后续的功能书写才会一帆风顺bbs表设计1.用户表继承AbstractUser扩展phone 电话号码avatar 用户头像create_time 创建时间外键字段一对一个人站点表2.个人站点表site_name 站点名称site_title 站点标题site_theme 站点样式3.文章标签表name 标签名外键字段一对多个人站点4.文章分类表name 分类名外键字段一对多个人站点5.文章表title 文章标题desc 文章简介content 文章内容create_time 发布时间数据库字段设计优化(******)(虽然下述的三个字段可以从其他表里面跨表查询计算得出,但是频繁跨表效率)up_num 点赞数down_num 点踩数comment_num 评论数外键字段一对多个人站点多对多文章标签一对多文章分类6.点赞点踩表记录哪个用户给哪篇文章点了赞还是点了踩user ForeignKey(to="User") article ForeignKey(to="Article") is_up BooleanField()1 1 11 2 11 3 02 1 17.文章评论表记录哪个用户给哪篇文章写了哪些评论内容user ForeignKey(to="User") article ForeignKey(to="Article")content CharField()comment_time DateField()# 自关联parent ForeignKey(to="Comment",null=True) # ORM专门提供的自关联写法 parent ForeignKey(to="self",null=True)id user_id article_id parent_id1 1 1 2 2 1 1 根评论子评论的概念根评论就是直接评论当前发布的内容的子评论是评论别人的评论1.PHP是世界上最牛逼的语言1.1 python才是最牛逼的1.2 java才是根评论与子评论是一对多的关系"""
22-2 表设计代码
from django.db import models# Create your models here.from django.contrib.auth.models import AbstractUserclass UserInfo(AbstractUser):phone = models.BigIntegerField(verbose_name='手机号',null=True)# 头像avatar = models.FileField(upload_to='avatar/',default='avatar/default.jpg',verbose_name='用户头像')'''给avatar字段传文件对象,该文件会自动存储到avatar文件下,然后avatar字段只保存文件路径avatar/default'''create_time = models.DateField(auto_now_add=True)blog = models.OneToOneField(to='Blog',null=True)class Blog(models.Model):site_name = models.CharField(max_length=32,verbose_name='站点名称')site_title = models.CharField(verbose_name='站点标题',max_length=32)site_theme = models.CharField(verbose_name='站点样式',max_length=64) # 存储css或js的文件路径class Category(models.Model):name = models.CharField(verbose_name='文章分类',max_length=32)blog = models.ForeignKey(to='Blog',null=True)class Tag(models.Model):name = models.CharField(verbose_name='文章标签',max_length=32)blog = models.ForeignKey(to='Blog', null=True)class Article(models.Model):title = models.CharField(max_length=64,verbose_name='文章标题')desc = models.CharField(max_length=255,verbose_name='文章简介')content = models.TextField(verbose_name='文章内容')create_time = models.DateField(auto_now_add=True)up_num = models.BigIntegerField(default=0,verbose_name='点赞数')down_num = models.BigIntegerField(default=0,verbose_name='点踩数')comment_num = models.BigIntegerField(default=0,verbose_name='评论数')## 外键字段blog = models.ForeignKey(to='Blog', null=True)category = models.ForeignKey(to='Category')tags = models.ManyToManyField(to='Tag',through='Article2Tag',through_fields=('article','tag'))class Article2Tag(models.Model):article = models.ForeignKey(to='Article')tag = models.ForeignKey(to='Tag')class UpAndDown(models.Model):user = models.ForeignKey(to='UserInfo')article = models.ForeignKey(to="Article")is_up = models.BooleanField()class Comment(models.Model):user = models.ForeignKey(to='UserInfo')article = models.ForeignKey(to="Article")content = models.CharField(verbose_name='评论内容',max_length=255)comment_time = models.DateTimeField(verbose_name='评论时间',auto_now_add=True)## 自关联parent = models.ForeignKey(to='self',null=True)
22-3 注册功能
3-1 后端代码
注册页面的逻辑处理函数
"""
我们之前是直接在views.py中书写的forms组件代码
但是为了接耦合 应该将所有的forms组件代码单独写到一个地方如果你的项目至始至终只用到一个forms组件那么你可以直接建一个py文件书写即可myforms.py
但是如果你的项目需要使用多个forms组件,那么你可以创建一个文件夹在文件夹内根据
forms组件功能的不同创建不同的py文件myforms文件夹regform.pyloginform.pyuserform.pyorderform.py...
"""
## views.py
from django.shortcuts import render
from django.http import JsonResponse
from app01 import models
# Create your views here.
from app01.myforms import MyRegForm## 登录功能逻辑处理函数
def register(request):form_obj = MyRegForm()if request.method == "POST":# 校验数据是否合法form_obj = MyRegForm(request.POST)## 往前端放回的字典back_dic = {"code": "200", "msg": ""}# 判断数据是否合法if form_obj.is_valid():clean_data = form_obj.cleaned_data ## 将校验通过的数据字典赋值给一个变量# 将字典里面的confirm_password字段删除 为后续保存数据提供方便clean_data.pop('confirm_password')## 用户头像file_obj = request.FILES.get('avatar')## 针对用户头像一定要判断是否传值,不能直接添加到用户字典里去if file_obj:clean_data['avatar'] = file_obj## 操作数据库保存数据models.UserInfo.objects.create_user(**clean_data)back_dic['url'] = '/login/'back_dic['msg'] = '注册用户成功!'else:back_dic['code'] = 600back_dic['msg'] = form_obj.errorsreturn JsonResponse(back_dic)return render(request,'register.html',locals())
3-2 前端页面register.html代码
register.html 页面
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><meta name="viewport" content="width=device-width, initial-scale=1"><link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet"><script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script><script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container-fluid"><div class="row"><div class="col-md-8 col-md-offset-2"><h1 class="text-center">注册</h1><form id="myform">{% csrf_token %}{% for form in form_obj %}<div class="form-group" id="form-group"><label for="{{ form.auto_id }}">{{ form.label }}</label>{{ form }}<span style="color:red" class="pull-right">{{ form.errors.0 }}</span></div>{% endfor %}{% load static %}<div class="form-group"><label for="myfile">头像<img src="{% static 'image/default.jpg' %}" id="myimg" alt="" width="100" height="100" style="margin-left: 10px"></label><input type="file" id="myfile" name="avatar" style="display: none"></div><input type="button" class="btn btn-primary pull-right" value="注册" id="id_commit"></form></div></div>
</body>
<script>$("#myfile").change(function(){// 文件阅读器对象// 第一步 生成一个文件阅读器对象let myFileReaderObj = new FileReader();// 获取用户上传的头像文件let fileObj = $(this)[0].files[0]// 将文件对象交给阅读器对象读取myFileReaderObj.readAsDataURL(fileObj) // 异步操作 且IO操作// 所以等待文件阅读器异步加载完毕再进行下一步myFileReaderObj.onload = function(){// 利用文件阅读器将文件展示到前端页面,修改src属性$('#myimg').attr('src',myFileReaderObj.result)}})$("#id_commit").click(function(){// 发送Ajax请求// 我们发送的数据包含普通的键值对也包含文件let formDataObj = new FormData()// 添加普通的键值对form_arr = $('#myform').serializeArray() // [{},{},{},{},{},{}]// 遍历$.each(form_arr,function(index,obj){// 往文件对象里添加普通键值对formDataObj.append(obj.name,obj.value)})// 添加文件数据formDataObj.append('avatar',$('#myfile')[0].files[0])// 发送Ajax请求$.ajax({url:"",type:"post",data:formDataObj,// 需要指定两个关键性参数contentType:false,processData:false,success: function(res){if(res.code == 200){// 跳转到登录页面window.location.href = res.url}else{// 如何将对应的错误信息展示到对应的input框// forms组件渲染的标签ID值都是id_字段名$.each(res.msg,function(index,obj){let target_id = '#id_' + index$(target_id).next().text(obj[0]).parent().addClass('has-error')})}}})})// 直接给所有的input框绑定获取焦点事件$('input').focus(function(){$(this).next().text('').parent().removeClass('has-error')})
</script>
</html>
22-4 登录功能
4-1 后端代码
## 登录页面的逻辑处理函数
def login(request):if request.method == 'POST':back_dic = {'code':200,'msg':''}username = request.POST.get('username')password = request.POST.get('password')code = request.POST.get('code').upper()## 校验验证码是否正确(忽略大小写)local_code = request.session.get('code').upper()if local_code == code:## 校验用户名和密码是否正确user_obj = auth.authenticate(request,username=username,password=password)if user_obj:## 保存用户状态auth.login(request,user_obj)back_dic['url'] = '/home/'back_dic['msg'] = '登录成功!'else:back_dic['code'] = 600back_dic['msg'] = '用户名或密码错误'else:back_dic['code'] = 700back_dic['msg'] = '验证码错误'return JsonResponse(back_dic)return render(request,'login.html')## 图片相关的模块
'''
pip install pillowImage:生成图片
ImageDraw:能够在图片上乱涂乱画
ImageFont:控制字体样式
'''
def get_random():return random.randint(0,255),random.randint(0,255),random.randint(0,255)from PIL import Image,ImageDraw,ImageFont
from io import BytesIO,StringIO
'''
内存管理器模块
BytesIO:临时帮你存储数据 返回的时候数据是二进制
StringIO:临时帮你存储数据 返回的时候数据是字符串
'''
## 图片验证码
def get_code(request):## 推导步骤一:直接获取后端图片的二进制数据返回给前端# with open(r'static/image/default.jpg','rb') as f:# data = f.read()# return HttpResponse(data)## 推导步骤二:用pillow模块动态生成图片 缺点是IO操作频繁# img_obj = Image.new('RGB',(340,35),get_random())## ## 先将图片对象保存起来# with open('xxx.png','wb') as f:# img_obj.save(f,'png')## ## 再将图片对象读取出来# with open('xxx.png','rb') as f:# data = f.read()## return HttpResponse(data)## 推导步骤三:借助于内存管理器模块# img_obj = Image.new('RGB', (340, 35), get_random())# io_obj = BytesIO() ## 生成一个内存管理器对象 你可以看成是文件句柄## img_obj.save(io_obj,'png')# return HttpResponse(io_obj.getvalue()) ## 从内存管理器中读取二进制数据返回给前端## 最终步骤:写图片验证码img_obj = Image.new('RGB', (340, 35), get_random())img_draw = ImageDraw.Draw((img_obj)) ## 产生画笔对象img_font = ImageFont.truetype('static/font/222.ttf',30) ## 字体样式和大小## 五位数的随机验证码,包含数字,小写字母,大写字母code = ''for i in range(5):random_uppper = chr(random.randint(65,90)) ## 大写字母random_lower = chr(random.randint(97, 122)) ## 大写字母random_int = str(random.randint(0,9)) ## 数字## 从上面三个里面随机选择一个tmp = random.choice([random_uppper,random_lower,random_int])## 将产生的随机字符串写入到图片上"""为什么一个个写而不是生成好了之后再写因为一个个写能够控制每个字体的间隙 而生成好之后再写的话间隙就没法控制了"""img_draw.text((i*45+70,-2),tmp,get_random(),img_font)## 拼接随机字符串code += tmp## 随机验证码在登录的视图函数中需要用到 要比对 而且其他视图函数也能够拿到request.session['code'] = codeio_obj = BytesIO()img_obj.save(io_obj,'png')return HttpResponse(io_obj.getvalue())
4-2 前端login页面代码
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet"><script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script><script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
</head>
<body>
{% load static %}
<div class="container-fluid"><div class="row"><div class="col-md-8 col-md-offset-2"><h1 class="text-center">登录</h1><div class="form-group"><label for="username">用户名</label><span style="color:red" class="input_error pull-right"></span><input type="text" name="username" id="username" class="form-control"></div><div class="form-group"><label for="password">密码</label><span style="color:red" class="input_error pull-right"></span><input type="password" name="password" id="password" class="form-control"></div><div class="form-group"><label for="code">验证码</label><span id="error" style="color:red" class="pull-right"></span><div class="row"><div class="col-md-8"><input type="text" name="code" id="id_code" class="form-control"></div><div class="col-md-4"><img src="/get_code/" alt="" width="340" height="35" id="code_img"></div></div></div><div class="a-box"><a href="javascript:avoid;" id= "a-box" class="pull-right">看不清?换一张?</a></div><input type="button" class="btn btn-success" value="登录" id="id_commit"></div></div>
</div>
</body>
<script>$('#code_img').click(function(){// 获取标签之前的srclet oldval = $(this).attr('src')console.log(oldval)let newSrc = oldval += "?"console.log(newSrc)$(this).attr('src',newSrc)})$('#a-box').click(function(){// 获取标签之前的srclet oldval = $('#code_img').attr('src')let newSrc = oldval += "?"$('#code_img').attr('src',newSrc)})$('#id_commit').click(function(){$.ajax({url:'',type:'post',data:{'username':$("#username").val(),'password':$("#password").val(),'code':$("#id_code").val(),'csrfmiddlewaretoken':'{{ csrf_token }}'},success:function(res){if(res.code == 200){window.location.href = res.url}else{// 渲染错误信息if(res.msg == '验证码错误'){$('#error').text(res.msg)}else{$('.input_error').text(res.msg)}}}})})
</script>
</html>
Django 报错解决
1-1 错误一
module ‘collections‘ has no attribute ‘Iterable‘报错
在python3.9之后collections.Iterable被废弃,在更高版本的环境中使用时会出现module ‘collections’ has no attribute 'Iterable’报错。
可以直接添加语句collections.Iterable = collections.abc.Iterable完成修改
import collections
collections.Iterable = collections.abc.Iterable
1-2 错误二
‘DIRS’: [BASE_DIR / ‘templates’]
TypeError: unsupported operand type(s) for /: ‘str’ and ‘str’
setting 设置改为如下
'DIRS': [os.path.join(BASE_DIR,'templates')],