Django笔记(2万字)
Django基础
命令行操作:
创建django项目
"""
可以先切换到对应的文件路径下,默认在C盘
"""
django-admin startproject 项目名(Django_1)
创建好项目后:会在你的路径下生成一个Django_1文件夹:
在文件夹内有以下文件:
-manage.py
-Django_1文件夹
--__init__.py
-- setting.py
--urls.py
--wsgi.py
启动Django项目
"""
一定要切换到项目目录下
"""
python manage.py runserver
创建应用
python manage.py startapp app01
主要文件:
-Django_1项目文件夹
--Django_1文件夹
---setting.py 配置文件
---urls.py 路由与视图函数对应关系(路由层)
---wsgi.py wsgiref模块(不考虑)
--manage.py django入口文件
--db.sqlite3 django自带的sqlite3数据库(小型数据库 功能不是很大还有bug)
--app01文件夹
---admin django后台管理
---apps.py 注册使用
---migrations文件夹 数据库迁移记录
---models.py 数据库相关的模型类(orm)
---tests.py 测试文件
---views.py 视图函数(视图层)
使用pycharm创建,注意pycharm必须为专业版
命令行创建与pycharm创建的区别:
-
pycharm创建的会自带一个templates文件夹
-
在setting.py中
命令行创建的需要将以下地方修改:
TEMPLATES = [
{
'DIRS': [os.path.join(BASE_DIR , 'templates')],
},
]
Django必须三板斧
"""
HttpResponse
返回字符串
render
返回html文件
传值:
第一种:(更加,精确节省资源)
url_dict = {"data":1111,"test":222}
return render(request,'home.html',{"dict_data":url_dict})
接收:
在html文件中{{ dict_data }}
第二种:(当要传的数据特别多时使用)
return render(request,'home.html',locals())
locals()会将所有的变量全部传给页面
接收:
{{ url_dict }}
redirect
重定向:
return redirect('http://www.baidu.com/') 跳转外网要加前缀
return redirect('/home/') 跳转自己的则不需要前缀
"""
静态文件配置
我们将html文件放在templates文件夹中,默认将网站所需的静态文件放在static文件夹中,通常情况下,我们还会将static进行进一步的划分:
-static
--js
--css
--img
配置static路径:
在setting.py中的最后面STATIC_URL下,添加:
STATICFILES_DIRS = [
os.path.join(BASE_DIR,'static')
]
STATIC_URL = '/static/' 类似于访问静态文件的令牌
如果你想访问静态文件 你就必须以static开头
/static/bootstrap-3.4.1-dist/bootstrap-3.4.1-dist/js/bootstrap.min.js
/static/ 令牌
bootstrap-3.4.1-dist/bootstrap-3.4.1-dist/js/bootstrap.min.js 资源路径
令牌正确后django会从STATICFILES_DIRS列表中由上到下一次查找你要调用的资源路径,都没有才会报错
动态的获取静态文件令牌:静态文件动态解析
django提供了一种模板语法可以动态获取static不论令牌如何改变都会自动获取
在html页面中:
{% load static %}
<link rel="stylesheet" href="{% static 'bootstrap-3.4.1-dist/bootstrap-3.4.1-dist/css/bootstrap.min.css' %}">
<script src="{% static 'bootstrap-3.4.1-dist/bootstrap-3.4.1-dist/js/bootstrap.min.js' %}"></script>
request对象方法
form表单默认是get请求
form表单action参数:
- 不写,默认朝向当前所在的url提交数据
- 全写,指名道姓
- 只写后缀 /login/
get请求和post请求应该有不同的处理机制
request:请求相关的数据对象 里面有很多简易的方法
request.method 返回请求的方式,并且全是大写的字符串形式
request.POST 获取用户提交的post请求数据(不包含文件)
<querydict: {'username':="" ['crryg'],="" 'password':="" ['111111111']}="">
request.POST.get('username') 获取用户提交的post请求数据(不包含文件)
Crryg
注意get方法只会获取列表最后一个元素,可以使用request.POST.getlist('username')来取出全部数据,数据类型为列表
django链接MySQL
-
配置文件中配置
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } } """ 更改为: """ DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', #更改为mysql,默认sqlite3 'NAME':'javaweb', #你链接的数据库名称 'USER':'root', #用户 'PASSWORD':'password', #密码 'PORT':3306, #mysql端口号 'HOST':'127.0.0.1', #本地连接 'CHARSET':'utf8', #数据库编码 } -
代码声明
Django默认的是使用MySQLdb模块来链接MySQL,但是该模块兼容性不好 需要手动改为pymysql链接
#在项目名或者任意的应用名下的init文件中书写以下代码都可以: import pymysql pymysql.install_as_MySQLdb()
django ORM
ORM 对象关系映射
作用:能够让一个不会使用SQL语句的小白也能够通过python面向对象的代码简单快捷的操作数据库
不足:封装程度太高,有时候SQL语句的效率偏低 需要自己写SQL语句
类 ------》 表
对象 ------》 字段
对象属性 ------》 记录某个字段对应的值
models.py文件中
"""创建一个User表,即创建一个类"""class User(models.Model): #id int primary_key auto_increment (创建一个id记录[字段],数据类型为int的自增主键) id = models.AutoField(primary_key=True) #username varchar(32) (创建username记录[字段],数据类型为char,长度限制为32) username = models.CharField(max_length=32,verbose_name="用户名") """ CharField必须有max_length参数, verbose_name描述字段的 """ #password int (创建password记录[字段],数据类型为int) password = models.IntegerField()
创建好后要使用数据库迁移命令
python manage.py makemigrations (将操作记录记录到migrations文件夹)
python manage.py migrate (将操作同步进连接的数据库中)
字段增删改查
增加:
直接在models.py中添加即可,但有以下两种情况:
1. 表中有数据,添加后可在终端中直接输入默认值
2. 表中有数据,在字段中设置默认值:age = models.IntegerField(verbose_name='年龄',default=18) [直接设置默认值为18]
3. 表中有数据,在字段中设置默认值为空:hobby = models.CharField(max_length=32,verbose_name="兴趣爱好",null=True) [该字段可以为空]
删除:
直接在models.py中删除该字段即可,注意执行后里面的数据也会消失
修改:
直接在models.py中修改该字段即可
数据的增删改查
数据查询:
在views.py中:
from app01 import models (要先导入才能查询) res = models.User.objects.filter(username=username) #(等于select * from User where username='username') #返回值<queryset [<user:="" user="" object="">]> 可以看成是列表套数据对象的格式,支持索引取值,切片操作,但不支持负数索引 #Django不推荐使用索引的方式,推荐下面的代码形式: user_obj = models.User.objects.filter(username=username).first() #filter可以携带多个参数:models.User.objects.filter(username=username,password=password)等于select * from User where username='username' and password='password'
数据增加:
第一种:
res = models.User.objects.create(password=password,username=username)
返回值就是当前被创建的对象本身
第二种:
user_obj = models.User(password=password,username=username) #赋值
user_obj.save() #保存数据
数据删除:
models.User.objects.filter(id=id).delete()
数据修改:
查询需要编辑的对象
edit_obj = models.User.objects.filter(id=id).first()
修改数据
if request.method == "POST": username = request.POST.get('username') password = request.POST.get('password') #修改数据库中对应的数据 # 方式一 models.User.objects.filter(id=id).update(username=username,password=password) """ 将filter查询出来的列表中的所有的对象全部更新 批量操作 只修改被修改的字段 """ # 方式二: # edit_obj.username = username # edit_obj.password = password # edit_obj.save() """ 方法二当字段特别多的时候效率非常低 从头到尾将数据的所有字段全部更新一遍,无论该字段是否被修改 """
ORM创建表关系:
表与表之间的关系:
一对多
多对多
一对一
判断表关系的方法:换位思考
创建表关系 先将基础表创建出来 然后再添加外键字段
class Book(models.Model): title = models.CharField(max_length=32) price = models.DecimalField(max_digits=8,decimal_places=2) # 总共八位数 小数点占两位 """ 图书和出版社是一对多,并且书是多的一方,所以外键字段放在书的表中 """ publish = models.ForeignKey(to='Publish') #Book是Publish的外键,还有一个参数:to_field="",不写的话默认就是与关联表的主键字段做外键关联 """ 如果字段对应的是ForeignKey,那么orm会自动在字段后面加_id 如果你在后面加了_id,那么orm还会在后面加上_id """ """ 图书和作者是多对多的关系,外键字段建在任意一方即可,推荐建在查询频率较高的一方 """ authors = models.ManyToManyField(to='Author') #o_field="",不写的话默认就是与关联表的主键字段做外键关联 """ authors是一个虚拟字段,主要是用来告诉orm 书籍表和作者表是多对多的关系 让orm自动帮你创建第三张关系表 """ 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_detali = models.OneToOneField(to="AuthorDetail") """ OneToOneField也会自动给字段加_id后缀 """ class AuthorDetail(models.Model): phone = models.BigIntegerField() #因为IntegerField最多11位,所以我们可以BigIntegerField或者直接字符类型 addr = models.CharField(max_length=32)
Django生命周期图:

路由匹配
url(r'test',views.test),url(r'testadd',views.testadd),"""url方法第一个参数是正则表达式 只要第一个参数正则表达式能够匹配到内容 那么就会立刻停止往下匹配 直接执行视图函数"""url(r'test/',views.test),url(r'testadd/',views.testadd),#你输入网址时会发现网页上会自带/ 是因为Django帮你自动加上去的 网页请求第一次没有成功 Django会在后面加上/ 重定向一次"""取消自动加/ 在setting.py文件中加上一句话: APPEND_SLASH = False 默认是True"""#上面的url会出现一个问题,当你输入http://127.0.0.1:8000/uwyqwgfiqwgfqitest/时,依旧能够访问test/#解决:url(r'^test/',views.test),url(r'^testadd/',views.testadd),""" "^"的作用就是匹配以什么开头 但修改以后还会出现一个问题,当你输入http://127.0.0.1:8000/testwqfwfwefw/时,依旧能够访问test/"""#解决:url(r'^test/$',views.test),url(r'^testadd/$',views.testadd),#"$"的作用是匹配规则以什么结尾#匹配首页url(r'^$', views.home),
无名分组
分组:就是给某一段正则表达式用小括号括起来
url(r'^test/(\d+)/$',views.test),
def test(request,xx): print(xx) return HttpResponse('test')
无名分组就是将括号内正则表达式匹配到的内容当做位置参数传递给后面的视图函数
有名分组
可以给正则表达式起一个别名
url(r'^testadd/(?P<year>\d+)/$',views.test),
def testadd(request,year): print(year) return HttpResponse('test')
有名分组就是将括号内正则表达式匹配到的内容当做关键字位置参数传递给后面的视图函数
无名,有名是否可以混合使用(先无名后有名):
不能混用 但是同一个分组可以使用N多次
url(r'^test/(\d+)/(\d+)/(\d+)/(\d+)/(\d+)/$',views.test),
反向解析
普通反向解析
通过一些方法得到一个结果该结果可以直接访问对应的url触发对应的视图函数
先给路由和视图函数起一个别名
url(r'^func/$',views.func,name='func')
反向解析
#后端反向解析reverse('func')
<!--前端反向解析--><a href="{% url 'func' %}">11</a><a href="/func/">22</a>
无名分组反向解析
url(r'^test/(\d+)/$',views.test,name='xxx),
前端:
```html {% url 'xxx' 123 %} ```
后端:
reverse('xxx',args=(1,))"""这个数字写代码的时候应该放什么? 数字一般情况下放的是数据的主键值,用来进行数据的编辑和删除功能"""
有名分组反向解析
url(r'^testadd/(?P<year>\d+)/$',views.test,name='ooo'),
前端:
写法一:{% url 'ooo' year=123 %}写法二:{% url 'ooo' 123 %}
后端:
写法一:reverse("ooo",kwargs={'year':123})写法二:reverse('ooo',args=(111,))
路由分发
Django的每一个应用都可以拥有自己的templates文件夹 urls.py static文件夹
正是上述特点,Django能够非常好的做到分组开发(每个人只写自己的app)
作为组长,只需要将下面的所有人写的app全部拷贝到一个新的Django项目中,然后在配置文件里面注册所有app再利用路由分发的特点将所有app整合起来
利用路由分发后,总路由不再干路由与视图函数的直接对应关系 而是做一个分发处理:识别当前url属于哪个应用下的,直接分发给对应的应用去处理
总路由:(总路由里面的url千万不能加$结尾)
form app01 import url as app01_urlform app02 import url as app02_urlurlpatterns = [ #写法一: url(r'^app01/', include(app01_url)), url(r'^app02/', include(app02_url)), #写法二: url(r'^app01/', include(‘app01.url)), url(r'^app02/', include(‘app02.url)), ]
子路由:
app01 urls.py
from django.conf.urls import urlfrom app01 import viewsurlpatterns = [ url(r'^reg/$',views.reg,name='reg'), ]
app02 urls.py
from django.conf.urls import urlfrom app02 import views urlpatterns = [ url(r'^reg/$',views.reg,name='reg'), ]
命名空间
当多个应用出现了相同的别名 我们研究反向解析会不会自动识别应用前缀:
正常情况下的反向解析是不会识别出来的
名称空间:
总路由:
url(r'^app01/', include(‘app01.url),namespace='app01'),url(r'^app02/', include(‘app02.url),namespace='app02'),
解析:
reverse('app01:reg'),reverse('app02:reg'),
{% url 'app01:reg' %}{% url 'app02:reg' %}
只要保证名字不冲突 就没必要使用名称空间
一般情况下 有多个app的时候我们在起别名的时候会加上app的前缀 这样就可以避免使用名称空间
伪静态的概念
静态网页:
数据是写死的 万年不便
伪静态:
将一个动态网页伪装成一个静态网页
为什要伪装?
伪装的目的在于增大本网站的seo查询力度、并且增加搜索引擎收藏本网上的概率
只需要在url后面加上.html
url(r'^reg.html/$',views.reg,name='reg'),
虚拟环境
在正常的开发环境 我们会给每一个项目配备该项目独有的解释器环境 该环境内只有该项目用到的模块 用不到的一概不装
虚拟环境:
你每创建一个虚拟环境就类似于重新下载了一个纯净的python解释器,但是虚拟环境不要创建太多,要消耗硬盘空间
Django版本区别
Django1.x路由层使用的是url方法,而在Django2.x和3.x版本中路由层使用的是path方法
url()第一个参数支持正则
path()第一个参数不支持正则 写什么就匹配什么
如果习惯使用url,可以导入re_path(re_path就是1.x的url)
虽然path不正则,但它支持五种转换器
除了有默认的五种,还支持自定义转换器(自行百度)
模型层里面1.x外键默认是级联更新删除的,2.x和3.x需要自己手动配置参数
models.ManyToManyField(to='Author')------1.x
models.ManyToManyField(to='Author',on_delete = models.CASCAD)-----2.x 3.x
JsonResponse对象
json格式的数据有什么用?
前后端数据交互需要使用json作为过渡 实现跨语言传输数据
from表单文件上传及后端如何处理
from表单上传文件:
<!--上传文件需要指定两个参数method="post" enctype="multipart/form-data"--><form action="" method="post" enctype="multipart/form-data"> <p>password:<input type="file" name="file" class="form-control"></p> <input type="submit" class="btn btn-danger btn-block"></form>
后端获取:
file_obj = request.FILES.get('file') #文件对象with open(file_obj.name,'wb') as f: for line in file_obj.chunks(): #推荐加上chunks()方法都试一行一行读取 f.write(line)
request对象补充
request.path 只能拿到路由,不能拿到后面的参数
request.path_info 只能拿到路由,不能拿到后面的参数
request.get_full_path() 能拿到路由,也能拿到后面的参数
request.body 原生的浏览器所发过来的二进制数据
FBV与CBV
视图函数既可以是函数也可以是类
FBV:
def index(request): return HttpResponse("Hello Django")
CBV:
from django.views import Viewclass MyLogin(View): def get(self,request): return HttpResponse('get') def post(self,request): return HttpResponse('post')
路由:
```pythonurl(r'^login/',view.MyLogin.as_view())```
FBV与CBV各有千秋
CBV特点:
能根据请求方式的不同直接匹配到对应的方法执行
CBV源码解析:
@classonlymethod def as_view(cls, **initkwargs): pass .......... def view(request, *args, **kwargs): pass .......... return view
url(r'^login/',views.MyLogin.as_view())
上述代码在启动Django的时候会立刻执行as_view方法 等价于:url(r'^login/',views.view) 与FBC一模一样,CBV与FBV在路由匹配上本质是一样的 都是有路由 对应 函数内存地址
模板语法传值
内部能够自动判断出当前的变量名是否可以加括号调用 如果可以就会自动执行 针对的是函数名和类名
可以传递的后端python数据类型:python的基本数据类型都可以传递
函数:
def func(): print('执行了函数') return '函数执行成功'
{{ func }} 传递函数名会自动加括号调用 但是模板语法不支持给函数传递额外的参数就不会执行该函数
类:
class MyClass(object): def get_self(self): return 'self' @staticmethod def get_func(): return 'func' @classmethod def get_class(cls): return 'cls'
{{ MyClass }} 传类名的时候也会自动加括号调用(实例化)
对象:
obj = MyClass()
{{ obj }}{{ obj.get_self }}{{ obj.get_func }}{{ obj.get_class }} 可以执行类里面的方法
Django模板语法的取值是固定的格式 只能采用句点符 既可以点, 也可以点键混用
{{ dict.username }}{{ list.0 }}
过滤器
过滤器类似于是一些模板语法内置的 内置方法,Django内置有60多个过滤器
基本语法:
后端传值:
import datetimecurrent_time = datetime.datetime.now()s = 'qweqweq' b = Truefile_size = 12332l = ['aaa','ccc','bbbb','dddd','eeeee','fffff']info = '人生苦短,我用python'egl = 'Hello word my name is xxx'hhhh = '<h1>Crryg</h1>'return render(request.'xxx.html',locals())
{{ 数据|过滤器:参数 }}{{ s|length }} 统计长度{{ b|default:"默认值" }} 默认值:第一个参数布尔值是True就展示,否则就展示冒号后面的值{{ file_size|filesizeformat }} 文件大小:自动计算内存{{ current_time|date:"Y-m-d H:i:s" }} 日期格式化,默认是UTC{{ l|slice:"0:4:2" }} 切片操作,支持步长{{ info|truncatechars:9 }} 切取字符(截取一部分字符串),人生苦短,我... 注意后面"..."也算在字符里面的{{ egl|truncatewords:4 }} 切取单词 Hello word my name..., 注意切取单词是...不算{{ egl|cut:" " }} 移除特定的字符,移除空格{{ l|join:"$" }} 拼接{{ file_size|add:10 }} 拼接(加法){{ egl|add:info }} 也可以拼接两个字符串{{ hhh }} 取消转义{{ hhh|safe }} 转义后端转义:from django.utils.safestring import mark_saferes = mark_safe(hhh){{ res }}
模板语法之标签
for 循环
{% for i in l %} {{ forloop }} <p>{{ i }}</p>{% endfor %}
if 判断
{% if b %} <p>11111</p>{% elif %} <p>333</p>{% else %} <p>222</p>{% endif %}
for if 混合使用
{% for i in l %} {% if forloop.first %} <p>第一次循环</p> {% elif forloop.last %} <p>最后一次循环</p> {% else %} <p>{{ i }}</p> {% endif %} {% empty %} <p>for循环的可迭代对象是空 根本没法循环</p>{% endfor %}
with起别名
{% with l.3 as lll %} <p>{{ lll }}</p> 在with语法内就可以通过as后面的别名快速的使用前面非常复杂获取数据的方式{% endwith %}
自定义过滤器,标签,inclusion_tag
先三步走:
-
在应用下必须创建一个templatetags文件
-
在该文件下创建任意名称的.py文件
-
在该py文件内必须写下面代码:
from django import templateregister = template.Library()
自定义过滤器,mytags.py
from django import templateregister = template.Library()#自定义过滤器(最多有两个参数)@register.filter(name='mytag')def my_sum(a,b): return a + b
使用:
{% load mytags %}{{ n|mytag:666 }}
自定义标签
@register.simple_tag(name='plus')def index(a,b,c,d): return "%s-%s-%s-%s"%(a,b,c,d)
使用:
{% load mytags %}注意多个参数用空格隔开{% plus 'aaa' 123 234 345 %}
自定义inclusion_tag
@register.inclusion_tag('xxxx.html')def left(n): data = ['第{}项'.format(i) for i in range(n)] #第一种传参 # return {'data':data} #将data传递给xxxx.html # 第二种传参 return locals() #将data传递给xxxx.html
使用:
{% load mytags %}{% left 5 %}
当html页面某一个地方的页面需要传参数才能动态的渲染出来,并且在多个页面上都需要使用当该局部,那么就可以考虑将该局部页面做成inclusion_tag
模板的继承
在新的html中导入xxx.html模板
{% extends 'xxx.html' %}
如何修改模板?
-
需要在模板中规定好区域
{% block content %} #将这个区域命名为content........{% endblock %} -
在新页面中
{% block content %} 在此区域内就可以来对模板相应的部分进行修改{% endblock %}
一般来说模板可修改的至少有三个部分,模板页面上划定的区域越多 那么该模板的扩展性越高 但是如果太多还不如自己重写
- css
- js
- html
模板的导入
将页面的局部当做模块的形式 哪个地方需要就可以直接导入使用即可
{% include 'xxx.html' %}
测试环境
当你只是想测试django中某一个py文件内容 那么你可以不要书写前后端交互的形式而是直接写一个测试脚本即可
测试环境准备:
在manage.py中copy走以下代码:
import osif __name__ == "__main__": os.environ.setdefault("DJANGO_SETTINGS_MODULE", "Django_demo_1.settings")#将上述的代码copy进test,.py文件中在添加以下代码(可以自己新建一个test文件,也可以使用应用中的test.py):import osif __name__ == "__main__": os.environ.setdefault("DJANGO_SETTINGS_MODULE", "Django_demo_1.settings") import django django.setup() #在这个代码块的下面就可以测试django里面的单个py文件了 #注意所有代码都必须在这下面,包括导包一类操作
模型层
必知必会:
-
all()
查询所有数据
-
filter()
带有过滤条件的查询
-
get()
直接拿数据对象,但如果条件不存在,直接报错 -
first()
拿queryset里面第一个元素 -
last()
拿queryset里面最后一个元素 -
values()
res = models.User.objects.values('name')
可以指定获取的数据字段 返回结果是列表套字典 -
values_list()
res = models.User.objects.values_list('name','age')
可以指定获取的数据字段 返回结果是列表套元组 -
distinct()
去重
去重一定要是一模一样的数据,如果带有主键那么肯定不一样 -
oeder_by()
排序
res = models.User.objects.oeder_by('name') 默认升序
res = models.User.objects.oeder_by('-name') 降序 -
reverse()
反转
反转前提是数据已经排过序了 -
count()
统计当前数据的个数 -
exclude()
排除对象在外 -
exists()
判断其是否存在 返回布尔值
双下划线查询:
eg:
-
查询年龄大于35岁的
res = models.User.objects.filter(age__gt=35) -
年龄小于35
res = models.User.objects.filter(age__lt=35) -
大于等于
res = models.User.objects.filter(age__gte=35) -
小于等于
res = models.User.objects.filter(age__lte=35) -
年龄是18或者30或者40
res = models.User.objects.filter(age__in=[18,30,40]) -
年龄在18至40岁之间 收尾都要
res = models.User.objects.filter(age__range=[18,40]) -
查询名字里含有n的
res = models.User.objects.filter(name__contains='n') #默认区分大小写 res = models.User.objects.filter(name__icontains='n') #不区分大小写 -
以什么开头的
res = models.User.objects.filter(name__startswith='g') -
以什么结尾
res = models.User.objects.filter(name__endswith='g') -
查询出注册时间是2020年1月份的
res = models.User.objects.filter(time__year='2020',time__moth='1')
多表操作:
外键增删改查:
一对多:
增:
-
直接写实际字段 id
models.Book.objects.create(title='三国演义',price=123.3,publish_id=1) -
虚拟字段 对象
publish_obj = models.Publish.objects.filter(pk=2).first() #pk:自动帮你识别主键models.Book.objects.create(title='三国演义',price=123.3,publish=publish_obj)
删:
models.Book.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)
多对多: 就是第三张关系表的增删改查
增:
book_obj = models.Book.objects.filter(pk=1).first()book_obj.authors #就类似于你已经来到了第三张表book_obj.authors.add(1) #给书籍id为1的书籍绑定一个主键为1的作者book_obj.authors.add(2,3) #给书籍id为1的书籍绑定一个主键为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,author_obj1,author_obj2)
删:
book_obj = models.Book.objects.filter(pk=1).first()book_obj.authors.remove(1,2)#同样支持对象author_obj = models.Author.objects.filter(pk=1).first()author_obj1 = models.Author.objects.filter(pk=2).first()book_obj.authors.remove(author_obj,author_obj1)
改(先删除,后新增):
book_obj = models.Book.objects.filter(pk=1).first()book_obj.authors.set([1,2])#同样也可以放对象
清空:
在第三张关系表中,清空书籍与作者的绑定关系book_obj = models.Book.objects.filter(pk=1).first()book_obj.authors.clear() #括号内不要加任何参数
正反向的概念:
外键字段在我手上,我查你就是正向,否则就是反向
正向查询按外键字段
反向查询按表名小写_set
跨表查询:
基于对象的跨表查询(子查询):
-
查询书籍主键为1的出版社名称
book_obj = models.Book.objects.filter(pk=1).first()#书查出版社 正向res = book_obj.publish 拿到了出版社对象print(res.name) -
查询书籍主键为1的作者
book_obj = models.Book.objects.filter(pk=1).first()#书查作者 正向res = book_obj.authors 返回结果为:app01.Author.Noneres = book_obj.authors.all() -
查询作者Crry的电话号码
author_obj = models.Author.objects.filter(name='Crry').first()res = author_obj.author_detetailprint(res.phone)在书写orm语句的时候跟写SQL语句是一样的
不要企图将orm一次性写完,如果比较复杂,就看一点写一点
什么时候加.all()?
当你的结果可能为多个时,就需要加.all()
如果是一个则直接拿到数据对象 -
查询出版社是东方出版社出版的书
publish_obj = models.Publish.objects.filter(name="东方出版社").first()#出版社查书 反向res = publish_obj.book_set.all() -
查询作者是Crry写过的书
author_obj = models.Author.objects.filter(name='Crry').first()#作者查书 反向res = author_obj.book_set.all() -
查询手机号是110的作者姓名
author_detetail_obj = models.AuthorDetetail.objects.filter(phone=110).first()res = author_detetail_obj.author什么时候需要加_set?
当你的查询结果可以有多个时就要加_set
基于双下划线的跨表查询(联表查询)
-
查询Crry的手机号(一行代码搞定)
#正向:res = models.Author.objects.filter(name='Crry').values('author_detatail__phone')#反向:models.AuthorDetetail.objects.filter(authors__name=='Crry') #拿到作者名为Crry的详细信息res = models.AuthorDetetail.objects.filter(authors__name=='Crry').values('phone') -
查询书籍主键为1的出版社和书的名称
#正向:res = models.Book.objects.filter(pk=1).values('title','publish__name')#反向:res = models.Publish.objects.filter(book__id=1).values('name','book__title') -
查询书籍主键为1的作者姓名
res = models.Book.objects.filter(pk=1).values('authors__name')
Django高阶
聚合查询:
聚合查询一般都是配合分组一起使用的
只要是跟数据库相关的模块 基本上都在django.db.models里面 如果没有那么应该在django.db里面
#先导入from django.db.models import Max,Min,Count,Avg,Sum#1. 所有书的平均价格res = modes.Book.objects.aggregate(Avg('price')) #因为聚合查询一般都是配合分组一起使用的,如果不想分组就要使用aggregate#2. 上述方法一次性使用res = modes.Book.objects.aggregate(Avg('price'),Max('price'),Min('price'),Sum('price'),Count('price'))
分组查询 必须由关键字指定 annotate(类似于MySQL中的Group By):
"""MySQL分组查询后的特点 分组之后默认只能获取到分组的依据 组内其他字段无法直接获取了 严格模式 ONLY_FULL_GROUP_BY """#1. 统计每一本书的作者个数res = modes.Book.objects.annotate() #models.后面是什么 就是按什么分组res = modes.Book.objects.annotate(author_num=Count('authors')).values('author_num') #正向查询按外键字段#注意 author_num 是自定义的变量名#2. 统计每个出版社卖的最便宜的书的价格#a. 先按出版社分组res = modes.Publish.objects.annotate()#b. 反向查询res = modes.Publish.objects.annotate(min_pirce=Min('book__price')).values('min_pirce')#3. 统计不止一个作者的图书#a. 先按图书分组#b. 再过滤出不止一个作者的图书res = modes.Book.objects.annotate(author_num=Count('authors')).filter(author_num__gt=1).values('title','author_num')"""只要你的orm语句得到的结果还是一个queryset对象 那么它就可以继续无限制的点query对象封装方法"""#4. 查询每个作者出版的书的总价格res = models.Authors.objects.annotate(sum_price=Sum('book__price')).values('name','sum_price')""" 如果想按照指定的字段分组该如何处理? models.Book.objects.values('price').annotate() 如果出现分组报错的情况: 你需要修改数据库严格模式"""
F与Q查询:
F查询
#1. 查询卖出数大于库存数的书籍#F查询:能够帮助你直接获取到 表中某个字段对应的数据from django.db.models import Fmodels.Book.objects.filter(maichu__gt=F('kucun'))#2. 将所有书籍的价格提升50models.Book.objects.update(price=F('price')+50)#3. 在所有书名后面加上 爆款 两个字""" 注意,在操作字符型的数据时 F不能直接做到字符串的拼接 需要导入一个模块 from django.db.models.functions import Concat from django.db.models import Value"""from django.db.models.functions import Concatfrom django.db.models import Valuemodels.Book.objects.update(title=Concat(F('title'),Value('爆款')))
Q查询:
#1. 查询卖出数大于100或者价格小于600的书籍from django.db.models import Qres = models.Book.objects.filter(Q(maichu__gt=100),Q(price__lt=600)) #Q包裹起来的条件之间如果用的是 逗号 来进行连接那么还是and关系res = models.Book.objects.filter(Q(maichu__gt=100)|Q(price__lt=600)) #Q包裹起来的条件之间如果用的是 | 来进行连接那么就是or的关系res = models.Book.objects.filter(~Q(maichu__gt=100)|Q(price__lt=600)) #~就是not关系,在相应的条件前加~则代表该条件为not
Q的高阶用法: 能够将查询条件的左边也编程字符串的形式
q = Q()q.connector = 'or' #来将查询条件的关系设置为orq.children.append(('maichu__gt',100))q.children.append(('price__lt',600))res = models.Book.objects.filter(q) #filter括号内也支持直接放Q对象,默认还是and关系
django中开启事务
事务:
A :原子性
不可分割的最小单位
C :一致性
跟原子性相辅相成
I :隔离性
事务之间互不干扰
D :持久性
事务一旦确认永久生效
事务的回滚:
rollback
事务的确认:
commit
目前只需要掌握Django中如何开启事务:
事务:
from django.db import transaction
开启事务:
with transaction.atomic():
SQL语句
在with书写的所有orm操作都是属于同一个事务
orm中常用字段及参数
AutoField
主键字段 primary_key=TrueCharField varchar
verbose_name 字段的注释
max_length 长度IntegerField int
BigIntegerField int
DecimalField
max_digits=8
decimal_places=2
EmailField varchar(254)
DateField date
DatetimeField datetime
auto_now:每次修改数据的时候都会自动更新当前字段
auto_now_add:只在创建时记录创建时间后面不会自动修改
BooleanField 布尔值
该字段传布尔值(False/True) 数据库存0/1
TextField 文本类型
该字段可以用来存大段文本内容(文章,博客等)没有字数限制
FileField 字符类型
upload_to = "/data" 给该字段传一个文件对象,会自动保存到/data目录下 然后将文件路径保存到数据库中
Django除了给你提供了很多字段类型外,还可以自定义字段
choices参数(数据库字段设计常见)
只要某个字段的可能性是可以列举完全的,那么一般情况下都会采用choices参数
#性别gender_choices = ( (1,'男'), (2,'女'), (3,'其他'))gender = models.IntegerField(choices=gender_choices)""" 该gender存的还是数字,但如果存的数字在上面元祖列举的范围之内 那么可以轻松获取到数字对应的真正内容 1. 如果gender存的数字不在上述元组之中 存的时候没有列举出来数字也可以存进去(范围还是按照字段类型决定) 2. 如果在,如何获取对应的文本信息 user_obj = models.User.object.filter(pk=1).first() 只要是choices参数的字段,如果想要获取对应的文本信息,只能使用固定的写法 get_字段名_display() print(user_obj.get_gender_display()) 3.如果没有对应关系,保存的是什么,展示的就是什么"""
MVC与MTV模型
MTV:Django号称是MTV模型
M:models
T:templates
V: viewsMVC:其实Django本质也是MVC
M:models
V:views
C:controller(控制器)
多对多三种创建方式
全自动:利用orm自动帮我们创建第三章关系表
class Book(models.Model): title = models.CharField(max_length=32) class Author(models.Model): name = models.CharField(max_length=32) """ 优点:代码不需要你自己写 非常分布 还支持orm提供操作第三张关系表的方法 缺点:第三张关系表的扩展性极差(没有办法额外添加字段) """
纯手动:
class Book(models.Model): title = models.CharField(max_length=32) class Author(models.Model): name = models.CharField(max_length=32) #手动的指定关系表:class Book2(models.Model): book_id = models.ForeignKey(to='Book') author_id = models.ForeignKey(to='Author')"""优点:可以扩展第三张关系表,完全取决于自己进行额外的扩展缺点:需要写代码较多,不能够再使用orm提供的简单方法不建议使用该方式"""
半自动(推荐使用)
class Book(models.Model): title = models.CharField(max_length=32) #告诉orm该表跟Author表是多对多的关系,通过我们自己写的表(Book2)来创建关系 authors = models.ManyToManyField(to='Author', through='Book2', through_fields=('book','author')) class Author(models.Model): name = models.CharField(max_length=32) class Book2(models.Model): book = models.ForeignKey(to='Book') author = models.ForeignKey(to='Author')"""through_fields字段先后顺序判断本质: 通过第三张表查询对应的表,需要用到哪个字段就把哪个字段放在前面 半自动:可以使用orm的正反查询,但是没法使用add,set,remove,clear四种方法"""
Ajax
异步提交
局部刷新
朝后端发送请求的方式:
- 浏览器地址栏直接输入 GET请求
- a标签href属性 GET请求
- form表单 GET请求和POST请求
- Ajax GET请求和POST请求
例子:
页面上有三个input框
在前两个框中输入数字 点击按钮 朝后端发送Ajax请求
后端计算出结果 再返回给前端动态展示到第三个框中
(整个过程页面不准刷新,也不能再前端计算)
<script> //先给按钮绑定一个点击事件 $('#btn').click(function (){ //朝后端发送Ajax $.ajax({ //指定朝哪个后端发送ajax url:'',//不写朝当前地址提交 //请求方式 type:'post',//不指定默认就是get 都是小写 //数据 data:{"d1":$('#d1').val(),"d2":$('#d2').val()}, //回调函数:当后端给你返回结果时,会自动触发 args形参接收后端的返回结果 success:function (args){ {#alert(args)#} //通过DOM操作动态渲染到第三个input里面 $('#d3').val(args) console.log(args) } }) })</script>
针对后端如果是使用的HttpResponse返回的数据 回调函数是不会自动反序列化的
如果后端使用的是JsonResponse返回的数据 回调函数会自动帮你反序列化
HttpResponse解决方式
- 后端序列化 json.dumps()
- 自己在前端利用JSON.parse()
- 在ajax里面配置一个参数
前后端编码格式
我们主要研究的是POST请求数据的编码格式
GEt请求数据就是直接在url后面的
url?username=aaa&password=123
可以朝后端发送post请求的方式
- from表单
- ajax请求
前后端传输数据的编码格式
urlencoded
fromdata
json
研究from表单
默认的数据编码格式是urlencoded
数据格式:username=aaa&password=123
跟get基本上一模一样
Django后端针对符合urlencoded的编码格式的数据都会自动帮你解析到request.POST中
如果你把编码格式改成formdata,那么针对普通的键值对还是解析到request.POST中而将文件解析到request.FILES中
form表单是不能发送json数据的
研究ajax
默认的数据编码格式是urlencoded
数据格式:username=aaa&password=123
Django后端针对符合urlencoded的编码格式的数据都会自动帮你解析到request.POST中
ajax发送json格式数据
前后端传输数据的时候一定要确保编码格式跟数据真正的格式是一致的
Django针对json格式的数据不会做任何处理
针对json格式数据需要自己手动处理
request对象方法补充
request.is_ajax():
可以判断是否是ajax,返回布尔值
前端:
<script> $('#d1').click(function (){ $.ajax({ url:'', type:"post", {#data:{'username':'aaa','age':18}, 注意,因为你已经将数据格式定为json,所以要对data进行转换#} data:JSON.stringify({'username':'aaa','age':18}), contentType:'application/json', //指定编码格式(json) success:function (){ } }) })</script>
后端:
if request.is_ajax(): json_bytes = request.body #只能通过request.body来获取ajax传输的数据 # print(json_bytes,type(json_bytes)) #b'{"username":"aaa","age":18}' <class 'bytes'=""> 拿到的是二进制的数据 # json_str = json_bytes.decode('utf-8') # print(json_str,type(json_str)) #{"username":"aaa","age":18} <class 'str'=""> 将二进制数据转成字符串 # json_dict = json.loads(json_str) # print(json_dict,type(json_dict)) #{'username': 'aaa', 'age': 18} <class 'dict'=""> 再将字符串转换成字典 #其实json.loads()会自动解码再反序列化 json_dict = json.loads(json_bytes) print(json_dict, type(json_dict)) #{'username': 'aaa', 'age': 18} <class 'dict'="">
ajax发送文件
ajax发送文件需要借助与js内置对象Formdata
前端:
<script>{# 按钮朝后端发送普通键值对和文件数据#} $('#d4').click(function (){ //第一步需要先利用FormData内置对象 let formDateObj = new FormData(); //第二步 添加普通的键值对 formDateObj.append('username',$('#d1').val()); formDateObj.append('password',$('#d2').val()); //第三步 添加文件对象 formDateObj.append('myfile',$('#d3')[0].files[0]); //第四步 将对象基于ajax发送给后端 $.ajax({ url:'', type:'post', data:formDateObj, //直接将对象放在data后面即可 //ajax发送文件必须要指定的两个参数 contentType:false, // 不需要使用任何编码 Django后端能够自动识别formdata对象 processData:false, // 告诉浏览器不要对数据进行任何处理 success:function (){ } }) })</script>
后端:
if request.is_ajax(): if request.method == 'POST': print(request.POST) print(request.FILES)
- 需要利用内置对象formdata、
//第二步 添加普通的键值对
formDateObj.append('username',$('#d1').val());
formDateObj.append('password',$('#d2').val());
//第三步 添加文件对象
formDateObj.append('myfile',$('#d3')[0].files[0]); - 需要指定两个参数
contentType:false, // 不需要使用任何编码 Django后端能够自动识别formdata对象
processData:false, // 告诉浏览器不要对数据进行任何处理 - Django后端能够直接识别到formdata对象并且能够将内部的普通键值对自动解析并封装到request.POST中,文件数据自动解析并封装带request.FILES中
Django自带的序列化组件(drf铺垫)
需求:在前端给我获取到后端用户表里面的所有数据,并且要是列表套字典的格式
from django.http import JsonResponsefrom django.core import serializersdef db_ser(request): user_qst = models.User1.objects.all() # user_list = [] # for user_obj in user_qst: # tmp = { # 'pk':user_obj.pk, # 'user':user_obj.username, # 'age':user_obj.ages, # 'gender':user_obj.gender, # } # user_list.append(tmp) # return JsonResponse(user_list,safe=False) # return render(request,'db_ser.html',locals()) """ [ {"pk": 1, "user": "aaa", "age": 18, "gender": 1}, {"pk": 2, "user": "bb", "age": 20, "gender": 2}, {"pk": 3, "user": "cc", "age": 25, "gender": 3}, {"pk": 4, "user": "dd", "age": 48, "gender": 1} ] 前后端分离的项目 作为后端开发我们只需要写代码将数据处理好 能够序列化返回给前端即可 再写一个接口文档 告诉前端每个字段代表的意思即可 """ res = serializers.serialize('json', user_qst) """ 会自动帮你将数据编程JSON格式的字符串,并且内部非常的全面 """ return HttpResponse(res)""" [{"model": "app01.user1", "pk": 1, "fields": {"username": "aaa", "ages": 18, "gender": 1}}, {"model": "app01.user1", "pk": 2, "fields": {"username": "bb", "ages": 20, "gender": 2}}, {"model": "app01.user1", "pk": 3, "fields": {"username": "cc", "ages": 25, "gender": 3}}, {"model": "app01.user1", "pk": 4, "fields": {"username": "dd", "ages": 48, "gender": 1}}]"""
写接口就是利用序列化组件渲染数据然后写一个接口文档 该交代的交代一下就可以了
ajax结合sweetalert
前端:
<meta charset="UTF-8"> <title>Title</title> <meta name="viewport" content="width=device-width,initial-scale=1"> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css"> <script src="https://cdn.staticfile.org/jquery/3.3.1/jquery.min.js"></script> {% load static %} <link rel="stylesheet" href="{% static 'sweetalert/sweetalert2.css' %}"> <script src="{% static 'sweetalert/sweetalert2.js' %}"></script><div class="container-fluid"> <h1 class="text-center">数据展示</h1> <div class="row"> <div class="col-md-8 col-md-2"> {% for users in user_obj %} {% endfor %} <table class="table-striped table table-hover"> <thead> <tr> <th>ID</th> <th>username</th> <th>age</th> <th>gender</th> <th>action</th> </tr> </thead> <tbody><tr> <td>{{ users.pk }}</td> <td>{{ users.username }}</td> <td>{{ users.ages }}</td> <td>{{ users.get_gender_display }}</td> <td> <button class="btn btn-primary btn-xs">编辑</button> <button class="btn btn-danger btn-xs del" delet_id="{{ users.pk }}">删除</button> </td> </tr></tbody> </table> </div> </div></div><script> $('.del').click(function (){ {#alert($(this).attr('delet_id'))#} //先将当前对象存储起来 let deletobj = $(this); //二次确认弹框 const swalWithBootstrapButtons = Swal.mixin({ customClass: { confirmButton: 'btn btn-success', cancelButton: 'btn btn-danger' }, buttonsStyling: false }) swalWithBootstrapButtons.fire({ title: '是否确定删除?', text: "请考虑清楚!", type: 'warning', showCancelButton: true, confirmButtonText: '是的,确认删除!', cancelButtonText: '取消删除!', reverseButtons: true, }).then((result) => { if (result.value) { //朝后端发送ajax请求删除数据后,再弹出确认框 $.ajax({ {#url:'delet/user/' + deletobj.attr('delet_id'), // 传递主键值方式1#} url:'/delet/user/', // 传递主键值方式2,放请求体里面 type: 'post', data:{'delet_id':deletobj.attr('delet_id')}, success:function (args){ //args {'code':'','msg':''} //判断响应状态码 然后做不同的处理 if (args.code === 1000){ swalWithBootstrapButtons.fire( '删除成功!', args.msg, 'success' ) // 删除数据后页面上的数据还在,此时需要刷新页面 // 方法1:重新加载页面 不推荐 当出现分页是,刷新会跑到第一页去 {#window.location.reload()#} //方法2:利用DOM操作,动态刷新 deletobj.parent().parent().remove() } else { swalWithBootstrapButtons.fire( '数据删除失败', '出现了不可预知的错误', 'info' ) } } }) } else if ( /* Read more about handling dismissals below */ result.dismiss === Swal.DismissReason.cancel ) { swalWithBootstrapButtons.fire( '取消', '数据很珍贵请慎重哦 :)', 'error' ) } }) })</script>
后端:
def delet_user(request): """ 前后端在使用ajax进行交互的时候 后端通常给ajax回调函数返回一个字典格式的数据 :param request: :return: """ if request.is_ajax(): if request.method == 'POST': back_dic = {'code':1000,'msg':''} delet_id = request.POST.get('delet_id') models.User1.objects.filter(pk=delet_id).delete() back_dic['msg'] = '数据删除成功' #我们需要告诉前端我们操作的结果 return JsonResponse(back_dic)
批量插入数据
def data_pl(request): # 先给Bokk插入一千条数据 for i in range(1000): models.Book.objects.create(title='第%s本书籍' % i) # # 再将所有数据查询并展示到前端页面 book_obj = models.Book.objects.all()
批量插入
book_list = [] for i in range(100000): book_obj1 = models.Book(title='第%s本书籍' % i) #先将数据存为对象 book_list.append(book_obj1) #再将数据对象放入列表中 models.Book.objects.bulk_create(book_list) #直接使用列表来新增数据 book_obj = models.Book.objects.all() """ 当你想要批量插入数据的时候,可以使用orm给你提供的bulk_create能够帮你节省大量的时间 """ return render(request,'data_pl.html',locals())
自定义分页器推导过程
后端:
#分页: 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 = 0 start_page = (current_page - 1) * per_page_num #终止位置 # end_page = 10 end_page = current_page * per_page_num # 计算出到底需要多少页 all_count = book_list.count() page_count, more = divmod(all_count, per_page_num) # divmod() # 函数把除数和余数运算结果结合起来,返回一个包含商和余数的元组(a // b, a % b)。 if more: #如果有余数,那么页数就加1 page_count += 1 #注意,前端没有range函数,所以我们要在后端完成所有页码的 page_html = '' xxx = current_page if current_page < 6: current_page =6 for 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_obj = models.Book.objects.all()[start_page:end_page] return render(request,'data_pl.html',locals())
前端:
<ul class="pagination"> <li> <a href="#" aria-label="Previous"> <span aria-hidden="true">«</span> </a> </li> {{ page_html | safe }}{# 转义#} <li> <a href="#" aria-label="Next"> <span aria-hidden="true">»</span> </a> </li> </ul>
Django中自带有分页器的模块,但书写很麻烦并且功能太简单
所以我们自己想方设法写自定义分页器
以上代码无需掌握,只需要知道流程逻辑
自定义分页器的拷贝及使用
当我们需要使用到非Django内置的第三方功能或组件代码的时候
我们一般会创建一个名为utils文件夹 在该文件夹下对模块进行功能性划分
utils可以在根目录下,也可以在应用下创建
我们到了后期封装代码的时候 不再局限于函数
还是尽量朝面向对象去封装
分页器代码:
class Pagination(object): def __init__(self, current_page, all_count, per_page_num=2, pager_count=11): """ 封装分页相关数据 :param current_page: 当前页 :param all_count: 数据库中的数据总条数 :param per_page_num: 每页显示的数据条数 :param pager_count: 最多显示的页码个数 """ try: current_page = int(current_page) except Exception as e: current_page = 1 if current_page < 1: current_page = 1 self.current_page = current_page self.all_count = all_count self.per_page_num = per_page_num # 总页码 all_pager, tmp = divmod(all_count, per_page_num) if tmp: all_pager += 1 self.all_pager = all_pager self.pager_count = pager_count self.pager_count_half = int((pager_count - 1) / 2) @property def start(self): return (self.current_page - 1) * self.per_page_num @property def end(self): return self.current_page * self.per_page_num def page_html(self): # 如果总页码 < 11个: if self.all_pager <= self.pager_count: pager_start = 1 pager_end = self.all_pager + 1 # 总页码 > 11 else: # 当前页如果<=页面上最多显示11/2个页码 if self.current_page <= self.pager_count_half: pager_start = 1 pager_end = self.pager_count + 1 # 当前页大于5 else: # 页码翻到最后 if (self.current_page + self.pager_count_half) > self.all_pager: pager_end = self.all_pager + 1 pager_start = self.all_pager - self.pager_count + 1 else: pager_start = self.current_page - self.pager_count_half pager_end = self.current_page + self.pager_count_half + 1 page_html_list = [] # 添加前面的nav和ul标签 page_html_list.append(''' <nav aria-label="Page navigation>" <ul="" class="pagination"> ''') first_page = '<li><a href="?page=%s">首页</a></li>' % (1) page_html_list.append(first_page) if self.current_page <= 1: prev_page = '<li class="disabled"><a href="#">上一页</a></li>' else: prev_page = '<li><a href="?page=%s">上一页</a></li>' % (self.current_page - 1,) page_html_list.append(prev_page) for i in range(pager_start, pager_end): if i == self.current_page: temp = '<li class="active"><a href="?page=%s">%s</a></li>' % (i, i,) else: temp = '<li><a href="?page=%s">%s</a></li>' % (i, i,) page_html_list.append(temp) if self.current_page >= self.all_pager: next_page = '<li class="disabled"><a href="#">下一页</a></li>' else: next_page = '<li><a href="?page=%s">下一页</a></li>' % (self.current_page + 1,) page_html_list.append(next_page) last_page = '<li><a href="?page=%s">尾页</a></li>' % (self.all_pager,) page_html_list.append(last_page) # 尾部添加标签 page_html_list.append(''' </nav> ''') return ''.join(page_html_list)
使用
后端:
def get_book(request): book_list = models.Book.objects.all() current_page = request.GET.get("page",1) all_count = book_list.count() page_obj = Pagination(current_page=current_page,all_count=all_count,per_page_num=10) page_queryset = book_list[page_obj.start:page_obj.end] return render(request,'booklist.html',locals())
前端:
<div class="container"> <div class="row"> <div class="col-md-8 col-md-offset-2"> {% for book in page_queryset %} <p>{{ book.title }}</p> {% endfor %} {{ page_obj.page_html|safe }} </div> </div></div>
forms组件
前戏:
写一个注册功能
获取用户名和密码 利用form表单提交数据
在后端判断用户名和密码是否符合一定的条件(不是要ajax)
如何符合条件需要你将提示信息展示到前端页面
前端:
<form action="" method="post"> <p>username: <input type="text" name="username"> <span style="color: red">{{ book_list.username }}</span> </p> <p>password: <input type="text" name="password"> <span style="color: red">{{ book_list.password }}</span> </p> <input type="submit" class="btn btn-info"></form>
后端:
def data_form(request): book_list = {"username":"","password":""} if request.method == 'POST': username = request.POST.get('username') password = request.POST.get('password') if 'qwer' in username: book_list['username'] = '违规名称' if len(password) < 4: book_list['password'] = '密码太短' """ 无论是POST还是GET页面都能获取到字典 GET请求来的时候字典是空的 POST请求字典可能有值 """ return render(request,'data_form.html',locals())
- 手动书写前端获取用户数据的html代码 渲染HTML代码
- 后端对用户数据进行校验 校验数据
- 对不符合要求的数据进行前端提示 展示提示信息
forms组件能够完成的事情:
1. 渲染HTML代码
2. 校验数据
3. 展示提示信息
forms组件类书写
from django import formsclass MyForm(forms.Form): #username字符串类型最小3为最大8为 username = forms.CharField(min_length=3,max_length=8) password = forms.CharField(min_length=4,max_length=12) #email字段必须符合邮箱格式:xxx@xxx.com email = forms.EmailField()
forms校验数据
forms组件渲染标签
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_table }} {{ form_obj.as_ul }}<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> label是字段的一个属性,默认展示的是字段的首字母大写的形式,username = forms.CharField(min_length=3,max_length=8,label='用户名')<p>第三种推荐使用</p> {% for forms in form_obj %} <p>{{ forms.label }} : {{ forms }}</p> {% endfor %}
forms组件展示错误信息
后端:
def index(request): #1. 先产生一个空对象 form_obj = MyForm() if request.method == 'POST': #3. 校验数据 form_obj = MyForm(request.POST) #4. 判断是否合法: if form_obj.is_valid(): #5. 如果合法 操作数据库存入数据 return HttpResponse('OK') #6. 如果不合法我们需要将错误信息展示给前端 #2. 直接将该空对象传给HTML页面 return render(request,'index.html',locals())
前端:
<p> {{ forms.label }} : {{ forms }} <span style="color: red">{{ forms.errors.0 }}</span></p>浏览器会帮你自动校验,但前端的校验弱不禁风如何让浏览器不做校验<form action="" method="post" novalidate=""> novalidate不做校验
必备条件 get请求和post请求给html页面的变量名必须一样
这样可以使得forms组件当你的数据不合法的情况下 会保留你上次的数据 让你基于之前的结果进行修改 更加人性化
如何定制报错信息:
username = forms.CharField(min_length=3,max_length=8,label='用户名', error_messages={ 'min_length' : '用户名不得小于3位', 'max_length' : '用户名最大为8位', 'required' : '用户名不能为空' } )email = forms.EmailField(error_messages={ 'required':'邮箱不能为空', 'invalid':'邮箱格式不正确' })
forms组件钩子函数
钩子函数(HOOK):在特定的节点自动完成触发完成响应操作
钩子函数在forms组件中就类似于第二道关卡,能够让我们自定义校验规则
在forms组件中有两类钩子:
- 局部钩子
当你需要给单个字段增加校验规则的时候使用 - 全局钩子
当你需要给多个字段增加校验规则的时候使用
案例:
- 校验用户名中不能含有666
- 检验密码和确认密码是否一致
钩子函数 直接在你写的forms类中书写代码即可
局部钩子
def clean_username(self): # 获取用户名 username = self.cleaned_data.get('username') # 判断 if '666' in username: # 提示错误信息 self.add_error('username','用户名中不能包含666') # 将钩子函数钩取的数据再放回去 return username
全局钩子
def clean(self): # 获取数据 password = self.cleaned_data.get('password') qr_password = self.cleaned_data.get('qr_password') # 判断 if not qr_password == password: # 添加错误信息 self.add_error('qr_password','两次密码不一致哦') # 将钩子函数钩取的数据再放回去 因为全局钩子是把所有数据都给你了,所以要全部返回 return self.cleaned_data
forms重要参数介绍
label 给字段起名字
error_messages 自定义报错信息
initial 默认值
required 控制字段是否必填
如何修改input的类型
password
text
......
使用:widget=forms.widgets.TextInput()
如下:
username = forms.CharField(min_length=3,max_length=8,label='用户名', error_messages={ 'min_length' : '用户名不得小于3位', 'max_length' : '用户名最大为8位', 'required' : '用户名不能为空' }, widget=forms.widgets.TextInput() )password = forms.CharField(min_length=4,max_length=12,widget=forms.widgets.PasswordInput())
如何添加样式?
widget=forms.widgets.TextInput(attrs={'class':'form-control'})
添加多个样式用空格隔开就可以了
widget=forms.widgets.TextInput(attrs={'class':'form-control c1 c2'})
第一道关卡
不是在钩子函数,还支持正则校验 在自己写的forms类中:
from django.core.validators import RegexValidatorphone = forms.CharField(validators=[RegexValidator(r'^[0-9]+$', '请输入数字'), RegexValidator(r'^159[0-9]+$', '数字必须以159开头')],)
[其他类型渲染](Django Form表单组件 - JasonJi - 博客园 (cnblogs.com))
cookie与session
cookie
服务端保存在客户端浏览器上的信息都可以称为cookie
它的表现形式一般都是K:V键值对(可以有多个)
session
数据是保存在服务端的并且它的表现形式一般都是K:V键值对(可以有多个)
总结:
cookie是保存在客户端浏览器上的信息
session是保存在服务端上的信息
session是基于cookie工作的(大部分的保存用户状态的操作都需要使用到cookie)
Django操作cookie
虽然cookie是服务端告诉客户端浏览器需要保存内容
但是客户端浏览器可以选择拒绝保存
视图函数的返回值
return HttpResponse()return render()return redirect()
obj1 = HttpResponse()#操作cookiereturn obj1obj2 = render()#操作cookiereturn obj2obj3 = render()#操作cookiereturn obj3"""如果要操作cookie,你就不得不利用obj对象"""
设置cookie
obj.set_cookie(key,value)
获取cookie
request.COOKIES.get(key)
在设置cookie的时候可以设置一个超时时间
#超时时间设置为5秒obj.set_cookie(key,value,max_age=5,expires=5) #max_age和expires都是设置超时时间的都是以秒为单位需要注意的是,expires是针对IE浏览器的
主动删除cookie
obj.delete_cookie(key)
session操作
设置
request.session['key'] = value
获取
request.session.get('key')"""在默认情况下操作session的时候需要django默认的一张django_session表 我们在执行数据库迁移命令的时候,django会帮我们创建许多表,django_session就是其中一张表 django默认的session过期时间是14天,但也可以人为的修改 django_session表中的数据条数是取决于浏览器的,同一个计算机上同一个浏览器,只会同时有一条数据生效 主要是为了节省服务端资源 当session过期的时候,有可能会出现多条数据对应一个浏览器,但是该现象不会持续很久,内部会自动识别过期的清楚,也可以通过代码清除"""
设置过期时间
request.session.set_expiry()"""括号内可以放四种类型的参数 整数 秒 日期对象 到指定日期就失效 0 一旦当浏览器窗口关闭立刻失效 不写 失效时间就取决于django内部默认的失效时间"""
清除session
request.session.delete() #只删服务端request.session.flush() #浏览器和服务端都清空,一般我们会使用这个
session保存位置
session是保存在服务端的,但是session的保存位置有多种选择
- 自己的数据库
- 文件
- redis
- ……
Django中间件
Django中间件是Django的门户
请求来的时候需要先经过中间件才能到达真正的Django后端
响应走的时候最后也需要经过中间件才能传递数据
Django的请求生命周期图

Django支持程序员自定义中间件并且暴露给程序员五个可以自定义的方法
- 必须掌握:
process_request
process_response - 了解即可:
process_view
process_template_response
process_exception
Django中间件必须掌握的方法
自定义中间件
-
在项目名或应用名下创建一个任意名称的文件夹
- 在该文件夹下建一个任意名称的py文件
- 在该py文件内需要书写类(这个类必须继承MiddlewareMixin)
- 然后在这个类里面就可以自定义五个方法了(这五个方法可以只写一个,用几个写几个)
- 在该py文件内需要书写类(这个类必须继承MiddlewareMixin)
- 在该文件夹下建一个任意名称的py文件
-
需要将类的路径以字符串的形式注册到配置文件中才能够生效
-
MIDDLEWARE = [ '自己写的中间件的路径', ]
-
必须掌握:
process_request(请求来的时候)
- 请求来的时候需要经过每一个中间件里面的process_request,顺序是按照你在配置文件中注册的中间件从上往下的顺序依次执行
- 如果中间件里面没有定义该方法,那么直接跳过执行下一个中间件
- 如果该方法返回了HttpResponse对象,那么请求将不再往后继续执行,而是直接原路返回(校验失败不允许访问)
process_request方法就是用来做全局相关的所有限制功能
csrf跨站请求伪造
- 钓鱼网站
- 我搭建一个跟正规网站一模一样的页面(中国银行)
- 用户不小小心进入到我们的网站,用户给某个人转钱
- 用户转钱操作确确实实提交给中国银行的系统,用户的钱也确确实实减少了,但唯一不同的是,接收钱的账号不是用户想转的目标账户,而是变成了一个莫名其妙的账户
- 内部本质
- 我们在钓鱼网站页面 针对对方账户 只给用户提供一个没有name属性的普通input框
- 然后我们自己隐藏一个已经写好name和value属性的input框
- 如何规避上个问题
- csrf跨站请求伪造校验
- 网站再给用户返回一个具有提交数据功能页面的时候,会给这个页面加一个唯一标识,当这个页面朝后端发送post请求时,后端会先校验这个唯一标识,如果标识不对直接拒绝(403),如果成功则正常执行
- csrf跨站请求伪造校验
csrf校验
form表单
在表单内部写上:
{% csrf_token %}
ajaxx
-
第一种:利用标签查找获取页面上的随机字符串
在data中加一个键值,'csrfmiddlewaretoken'😒('[name=csrfmiddlewaretoken]').val() 注意:key必须为 csrfmiddlewaretoken,
-
第二种:直接利用模板语法提供的快捷方式
'csrfmiddlewaretoken':{{ csrf_token }}
-
第三张:通用方式,只需要引入一个js文件
新建static文件夹,再新建一个js文件,将如下代码拷贝进去
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 protection return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));}$.ajaxSetup({ beforeSend: function (xhr, settings) { if (!csrfSafeMethod(settings.type) && !this.crossDomain) { xhr.setRequestHeader("X-CSRFToken", csrftoken); } }});
csrf相关装饰器
- 网站整体都不校验,就单单几个视图函数校验
- 网站整体都校验,,就单单几个视图函数不校验
需要在views.py中导入:from django.views.decorators.csrf import csrf_exempt,csrf_protect
csrf_exempt 忽视校验
csrf_protect 需要校验
直接在函数上使用装饰器
@csrf_exempt/@csrf_protect
auth模块(详细请看文件夹下的django_auth项目)
我们在创建一个Django项目后就可以直接访问admin,admin登录索要的密码账户,数据参考就是auth_user表,并且还必须是管理员用户才能进入
创建超级用户(管理员)
python manage.py createsuperuser
auth比对用户名和密码是否正确:
views.py:
from django.contrib import authres = auth.authenticate(request,username=username,password=password)print(res) #返回的是用户对象 数据不符合(用户名或密码错误)返回Noneprint(res.username) #crrygprint(res.password) #pbkdf2_sha256$36000$usZ6cMXP08vQ$00la/D16+zCR2x4SNz89oCyrRQ/7em3GnFQig2kl6/U= 已经加密好的密码""" 1. 自动查找auth_user表 2. 自动将密码加密再比对 该方法注意事项:括号内必须同时传入用户名和密码,不能只传一个参数"""
auth保存用户状态:
views.py:
auth.login(request,user=res) # 等于request.session[key] = res# 只要执行了该方法 你就可以在任何地方通过request.user获取到当前登录的用户对象
auth获取当前登录用户
views.py:
auth保存用户状态:
views.py:
request.user#返回的是用户对象 没有登录返回 AnonymousUser 匿名用户# 自动去django_session表中查找对应的用户对象给你封装到request.user中
校验用户是否登录装饰器
views.py:
from django.contrib.auth.decorators import login_required#局部配置:@login_required(login_url='/login/') #让用户在没有登录的情况下跳转到/login/网址(局部配置)#全局配置: 在settings.py中最后一行:LOGIN_URL = '/login/'
比对原密码:
views.py:
request.user.check_password(old_password)
修改密码:
views.py:
request.user.set_password(new_password) # 仅仅是在修改对象的属性request.user.save() # 这一步才是真正的操作数据库
注销:
views.py:
auth.logout(request)
注册:
views.py:
# 操作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) #注意使用代码创建超级用户邮箱是必填的,而用命令行创建可以不填
auth模块表扩展
models.py
from django.db import models
from django.contrib.auth.models import User,AbstractUser
# 第一种:一对一关系 不推荐
# class Useraaa(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'
应用名.表名
如果自己写表替代了auth_user那么
auth模块的功能还是能照常使用,参考的表也由原来的auth_user变成了Userinfo
"""
phone = models.BigIntegerField()
项目开发流程:
需求分析
项目设计
分组开发
测试
交付上线

浙公网安备 33010602011771号