可视化界面之数据增删改查
针对数据对象主键字段的获取可以使用更加方便的 obj.pk获取
在模型类中定义双下str方法可以在数据对象被执行打印操作的时候方便的查看
'''
form表单中能够触发调剂动作的按钮只有两个
<input type='submit'/>
<button></button>
'''
1.数据展示功能
开设接口、获取数据、传递页面、展示数据
2.数据添加功能
开设接口、获取数据、发送数据、校验数据、录入数据、重定向
3.数据编辑功能
开设接口、后端如何区分所要编辑的数据(问号携带参数)、后端获取用户数据、前端展示默认数据、获取用户并完成更新
4.数据删除功能
开设接口、问号携带参数、删除二次确认
django请求生命周期流程图
1.浏览器发送一个基于HTTP协议的请求
2.进入web服务网关接口将请求来的数据拆解成一个request大字典
3.进入django框架里的中间件,相当于保安负责django安全性
4.进入django后端路由层完成功能匹配
5.进入视图层执行核心业务逻辑
6.执行核心业务逻辑的时候可能需要用到html页面,这时候就需要用到模板层
7.用模板的时候也可能用到数据库的数据,这时候用到模型层,由模型层帮你去跟数据库交涉
8.由orm把数据库返回的数据封装成一个数据对象,返回到视图层做一个模板语法,模板渲染
9.由视图层直接返回到中间件再做一层校验,怕携带危险数据
10.没问题会交给web服务网关接口封装数据发给浏览器
- 学习流程
路由层、视图层、模板层、模型层、组件、BBS项目
django路由层
- 1.路由匹配(路由结尾的斜杠)
path('网址后缀',函数名)
只要网址后缀匹配上了就会自动执行后面的函数并结束整个路由的匹配。
django2.X及以上 path第一个参数写什么就匹配什么
django1.X第一个参数是正则表达式
默认的情况下不写斜杠django会做二次处理,第一次匹配不上就会让浏览器自动加上斜杠再次请求。
django配置文件中可以指定是否自动添加斜杠:
APPEND_SLASH = False # 取消斜杠二次处理机制
- 2.path转换器
正常情况下很多网站都会有很多相似的网址 如果我们每一个都单独开设路由不合理
django2.X及以上版本路由动态匹配有转换器(五种)
str:匹配除路径分隔符外的任何非空字符串。
int:匹配0或者任意正整数。
slug:匹配任意一个由字母或数字组成的字符串。
uuid:匹配格式化后的UUID。
path:能够匹配完整的URL路径
ps:还支持自定义转换器(自己写正则表达式匹配更加细化的内容)
- 基本使用:
urls.py
path('func/<int:year>/<str:info>/', views.func)
views.py
def func(request,year,info):
pass
# 转换器 将对应位置匹配到的数据转换成固定的数据类型
path('index/<str:info>/', views.index_func), # index_func(实参request对象,info='转换器匹配到的类型转换之后的内容')
path('index/<str:info>/<int:id>/', views.index_func) # index_func(实参request对象,info='转换器匹配到的类型转换之后的内容',id='转换器匹配到的类型转换之后的内容')
转换器匹配到的内容会当做视图函数的关键字参数传入,转换器有几个叫什么名字,那么视图函数的形参必须对应。
-
3.re_path正则匹配
re_path(正则表达式,函数名)
django2.X及以上版本有re_path 第一个参数是正则
匹配的本质是只要第一个正则表达式能够从用户输入的路由中匹配到数据就算匹配成功会立刻停止路由层其他的匹配直接执行对应的视图函数
re_patn('^test/$', views.test) #^是限制开头$是限制结尾
django1.X路由匹配使用的是url() 功能与django2.X及以上的re_path()一致
-
4.正则匹配之无名分组
re_path('^test/(?P<year>\d{4})/', views.test)
路由匹配成功之后就会调用视图函数默认情况下会自动给视图函数传递一个request位置参数
def test(request):
return HttpResponse('测试')
如果路由匹配中使用括号对正则表达式进行了分组,那么在调用视图函数的时候,会将括号内匹配到的内容当做位置参数传递给视图函数。
# test(request,括号内正则表达式匹配到的内容)
例子:正则匹配到的内容是123
def test(request,123):
return HttpResponse('测试')
-
5.正则匹配之有名分组
re_path('^test/(?P<year>\d+)/(?P<others>.*?)/',views.test)
给括号内的正则表达式起别名之后,匹配成功则会讲括号内匹配到的内容按照关键字参数传递给视图函数。
# testadd(request,user_id=括号内正则表达式匹配到的内容)
例子:正则匹配到的内容是123
def test(request,user_id=123):
return HttpResponse('测试')
- 两者是否可以混合使用?
url(r'^test/(\d+)/(?P<year>\d+)/$', views.testadd) # 不可以 无名有名分组不能混合使用!!! 单个可以重复使用
url(r'^test/(\d+)/(\d+)/$', views.testadd) # 可以
url(r'^test/(?P<a>\d+)/(?P<b>\d+)',views.testadd) # 可以
反向解析
a标签的href可以写网址的全称,也可以写后缀。
href='https://www.baidu.com'
href='/login/' # 自动补全当前服务的ip和port href='127.0.0.1:8000/login/'
页面上有很多a标签链接了其他路由,一旦路由发送变化会导致所有页面相关链接失效。
为了防止出现该问题,我们需要使用反向解析
反向解析:返回一个结果,该结果可以访问到对应的路由。
步骤一:路由对应关系起别名
path('register/', views.reg, name='reg_view')
步骤二:使用反向解析语法
html页面:
html页面上模板语法 {% url 'reg_view' %} # reg666/ 结果可以访问路由
例子:<a href="{% url 'reg_view' %}">reg_view</a>
后端:
from django.shortcuts import reverse
reverse('reg_view') # reg666/ 结果可以访问路由
-
无名有名反向解析
步骤一:路由对应关系起别名
path('reg/<str:info>/', views.reg, name='reg_view')
步骤二:使用反向解析语法
当路由中有不确定的匹配因素,反向解析的时候需要人为给出一个具体的值
html页面:
html页面上模板语法 {% url 'reg_view' 'jason' %}
后端:
from django.shortcuts import reverse reverse('reg_view', args=('jason',))
路由分发
如果一个django项目特别庞大,里面有很多应用,每个应用下有很多对应关系。那么django自带的路由层里面的代码就会非常非常的多。
我们可以根据应用的不同拆分路由对应关系到不同的应用中。django支持每个应用都可以有自己独立的路由层、模板层、静态文件、视图层(默认)、模型层(默认)。所以所有人都可以在应用中开发完整的项目功能,最后汇总到一个空的django项目中,然后通过路由分发整合所有人的应用。
路由分发的作用:能够让基于django开发的多个应用完全独立,便于小组开发
- 1.创建多个应用,并去配置文件中注册
INSTALLED_APPS = [
'app01',
'app02'
]
- 2.路由分发:总路由
# 正规写法
from django.urls import path, include
from app01 import urls as app01_urls
from app02 import urls as app02_urls
urlpatterns = [
path('app01/',include(app01_urls)),
path('app02/',include(app02_urls))
]
# 简便写法
# 同名文件夹下urls.py文件内
from django.urls import path, include
urlpatterns = [
path('app01/', include('app01.urls')),
path('app02/', include('app02.urls')),
]
- 3.子路由
# app01应用文件夹下urls.py文件内
urlpatterns = [
path('after/', views.after) # app01
]
# app02应用文件夹下urls.py文件内
urlpatterns = [
path('after/', views.after) # app02
]
应用场景:当项目特别大,应用特别多的时候,可以使用路由分发,非常方便。
名称空间
在路由分发场景下多个应用在涉及到反向解析别名冲突的时候无法正常解析。默认情况下是无法直接识别应用前缀的
解决方法1:名称空间
总路由添加名称空间。
path('app01/', include(('app01.urls', 'app01'), namespace='app01')),
path('app02/', include(('app02.urls', 'app02'), namespace='app02')),
应用反向解析自动提示
# app01应用文件夹下urls.py文件内
urlpatterns = [
path('after/', views.after, name='after_view'),
]
# app01应用文件夹下views.py文件内
reverse('app01:after_view')
# app02应用文件夹下urls.py文件内
urlpatterns = [
path('after/', views.after, name='after_view'),
]
# app02应用文件夹下views.py文件内
reverse('app02:after_view')
#html页面
{% url 'app01:after_view' %}
{% url 'app02:after_view' %}
解决方法2:别名不冲突即可
路由分发:总路由
path('app01/', include('app01.urls')),
path('app02/', include('app02.urls')),
子路由
# app01应用文件夹下urls.py文件内
urlpatterns = [
path('after/', views.after, name='app01_after_view'),
] # 给别名加个前缀,保证别名不冲突
# app01应用文件夹下views.py文件内
reverse('app01_after_view')
# app02应用文件夹下urls.py文件内
urlpatterns = [
path('after/', views.after, name='app02_after_view'),
]
# app02应用文件夹下views.py文件内
reverse('app02_after_view')
#html页面
{% url 'app01_after_view' %}
{% url 'app02_after_view' %}
虚拟环境
项目1需要使用:django1.11 python38
项目2需要使用:django2.22 pymysql requests python38
项目3需要使用:django3.22 request_html flask urllib3 python38
实际开发项目中我们只会给项目配备所需的环境,不需要的一概不配!!!
虚拟环境:能够针对相同版本的解释器创建多个分身 每个分身可以有自己独立的环境
pycharm创建虚拟环境:(每创建一个虚拟环境就相当于重新下载了一个全新的解释器)
命令行的方式: python -m venv pyvenv38
注意:python命令此处不支持多版本共存的操作 python27 python36 python38
激活
activate
关闭
deactivate
pip install --index-url http://mirrors.aliyun.com/pypi/simple/ django==1.11.11 --trusted-host mirrors.aliyun.com
视图层之必会三板斧
视图函数的返回值
视图函数必须返回一个HttpResponse对象。(HttpResponse其实是一个类)
HttpResponse
class HttpResponse(HttpResponseBase):
return HttpResponse()
render
def render():
return HttpResponse()
return render()
redirect
def redirect(to, *args, permanent=False, **kwargs):
redirect_class = HttpResponsePermanentRedirect if permanent else HttpResponseRedirect # 三元表达式
相当于 redirect_class = 类(祖先有个类是HttpResponse)
return redirect_class(resolve_url(to, *args, **kwargs))
JsonResponse对象
视图函数返回json格式数据
将下列数据用json格式展示到网页
user_dict = {'name': 'jason老师', 'pwd': 123, 'hobby': ['read', 'run', 'music']}
json方法
def obj_json(request):
json_str = json.dumps(user_dict, ensure_ascii=False)
return HttpResponse(json_str)
JsonResponse方法
from django.http import JsonResponse
def obj_json(request):
return JsonResponse(user_dict, json_dumps_params={'ensure_ascii':False}, safe=False)
'''序列化非字典类型的数据还需要指定safe参数为False'''
JsonResponse源码解析
from django.http import JsonResponse
class JsonResponse(HttpResponse):
def __init__(self, data, encoder=DjangoJSONEncoder, safe=True,
json_dumps_params=None, **kwargs):
if json_dumps_params is None:
json_dumps_params = {}
data = json.dumps(data, cls=encoder, **json_dumps_params)
super().__init__(content=data, **kwargs)
视图层之request对象获取文件
from表单携带文件数据
form表单上传的数据中如果含有文件,那么需要做以下几件事。
第一:method属性值必须是post。
第二:enctype属性值必须修改为multipart/form-data,默认是application/x-www-form-urlencoded。
第三:后端需要使用request.FILES获取,然后就可以通过句点符和with open等查看和下载文件信息。
视图层之FBV与CBV
FBV:基于函数的视图
def index(request):
return HttpResponse()
path('index/', views.index)
CBV:基于类的视图
from django import views
class MyView(views.View):
def get(self, request):
return HttpResponse('我是CBV里面的get方法')
def post(self, request):
return HttpResponse('我是CBV里面的post方法')
path('func/', views.MyView.as_view())
"""
CBV会自动根据请求方式的不同匹配类中定义的方法并自动执行
"""
CBV源码分析(重要)
源码分析入口
path('func/', views.MyView.as_view())
1.切入点:路由匹配
类名点属性as_view并且还加了括号
as_view可能是普通的静态方法
as_view也可能是绑定给类的方法
2.对象查找属性的顺序
先从对象自身开始、再从产生对象的类、之后是各个父类
MyView.as_view()
先从我们自己写的MyView中查找,没有再去父类Views中查找
3.CBV路由匹配本质:跟FBV是一致的
项目已启动就会执行as_view方法 查看源码返回了一个闭包函数名view
def as_view(cls):
def view(cls):
pass
return view
path('func/', views.view)
# 函数名加括号执行优先级最高
4.路由匹配成功之后执行view函数(访问func触发view执行)
def view(...):
obj = cls() # cls我们直接写的类,加括号产生一个自己写的类的对象
return obj.dispatch(request, *args, **kwargs)
'''涉及到对象点名字 一定要确定对象是谁 再确定查找顺序'''
5.研究dispatch方法
def dispatch(...):
# 利用getattr反射(通过字符串操作对象的属性和方法)
#参数:obj自己写的类产生的对象,当前的请求方法。 handler就等于自己写的类里的请求方法
handler = getattr(obj, request.method.lower())
# 返回的时候加括号传承调用自己写的类里的请求方法
return handler(request, *args, **kwargs)
ps:查看源码也可以修改,但是尽量不要这么做,很容易产生bug。
模板层
模板语法传值
django提供的模板语法只有两个符号:
{{}}:主要用于数据值相关;{%%}:主要用于逻辑
django的模板语法是自己写的 跟jinja2不一样
1.针对需要加括号调用的名字 django模板语法会自动加括号调用你只需要写名字就行
2.模板语法的注释前端浏览器是无法查看的 {##}
传值的两种方式
# 方式1:指名道姓的传,适用于数据量较少的情况,节省资源
return render(request, 'modal.html', {'name':name})
# 方式2:打包传值(关键字locals()),将整个局部名称空间中的名字全部传入,适用于数据量较多的情况,简单快捷
return render(request,'modal.html',locals())
模板语法传值的特性
django的模板语法在操作容器类型的时候只允许使用句点符
data1 = {'info':{'pro':[11, 22, 33, {'name':'jason','msg':'努力就有收获'}]}}
{{ data1.info.pro.3.msg }} # 既可以点key也可以点索引 django内部自动识别
模板语法过滤器(类似于python内置函数)
<p>统计长度:{{ s|length }}</p>
<p>加法运算:{{ i|add:123 }}</p>、<p>加法运算:{{ s|add:'heihei' }}</p>
<p>日期转换:{{ s|date:'Y-m-d H:i:s' }}</p>
<p>文件大小:{{ file_size|filesizeformat }}</p>
<p>数据切片:{{ l|slice:'0:10' }}</p>
<p>字符截取(三个点算一个):{{ s1|truncatechars:6 }}</p>
<p>单词截取(空格):{{ s1|truncatewords:6 }}</p>
# 在前端使用 |safe
<p>语法转义:{{ script_tag|safe }}</p> # safe:将后端写的代码可以在前端被识别被转换
# 在后端使用mark_safe
from django.utils.safestring import mark_safe
script_tag1 = '<script>alert(666)</script>'
res = mark_safe(script_tag1)
<p>{{ res }}</p>
ps:有时候html页面上的数据不一定非要在html页面上编写了 也可以后端写好传入
模板层之标签(类似于python流程控制)
在django模板语法中写标签的时候,只需要写关键字然后tab键就会自动补全。
1.if判断
{% if 条件 %} # 条件一般是模板语法传过来的数据 直接写名字使用即可
条件成立执行的代码
{% elif 条件1 %}
条件1成立执行的代码
{% else %}
条件都不成立执行的代码
{% endif %}
2.for循环
{% for k in t1 %}
{% if forloop.first %}
<p>这是我的第一次循环{{ k }}</p>
{% elif forloop.last %}
<p>这是我的最后一次循环{{ k }}</p>
{% else %}
<p>这是中间循环{{ k }}</p>
{% endif %}
{% empty %}
<p>你给我传的数据是空的无法循环取值(空字符串、空列表、空字典)</p>
{% endfor %}
forloop关键字:{'parentloop': {}, 'counter0': 0, 'counter': 1, 'revcounter': 4, 'revcounter0': 3, 'first': True, 'last': False}
# counter0:从0开始计数;counter:从一开始计数;first': 是否是第一次循环;last是否是最后异常循环
补充:如果数据是字典,同样提供了keys、values、items方法。
a={'name':jason,'age':18}
{{ for i in a.keys}}
<p>{{ i }}</p> # name age
{% endfor %}
{{ for i in a.values}}
<p>{{ i }}</p> # jason 18
{% endfor %}
{{ for i in a.items}}
<p>{{ i }}</p> # ('name',jason) ('age',18)
{% endfor %}
django模板语法取值操作>>>:只支持句点符
句点符既可以点索引也可以点键
{{ d1.hobby.2.a1 }}
{% with d1.hobby.2.a1 as h %} 复杂数据获取之后需要反复使用可以起别名
<a href="">{{ h }}</a>
{% endwith %}
自定义过滤器、标签及inclusion_tag(了解)
"""
如果想要自定义一些模板语法 需要先完成下列的三步走战略
1.在应用下创建一个名字必须叫templatetags的目录
2.在上述目录下创建任意名称的py文件
3.在上述py文件内先编写两行固定的代码
from django import template
register = template.Library()
"""
自定义过滤器(最大只能接收两个参数)
@register.filter(name='myfilter') # 括号内可以通过name给过滤器取别名
def my_add(a, b):
return a + b
# 在html页面使用过滤器需要先导入创建的py文件模块
{% load mytag %}
{{ i|myfilter:1 }}
自定义标签:可以接收任意参数(参数没有限制)
@register.simple_tag(name='mytag')
def func2(a, b, c, d, e):
return f'{a}-{b}-{c}-{d}-{e}'
# 在html页面
{% load mytags %}
{% mytag 'jason' 'kevin' 'oscar' 'tony' 'lili' %} # 参数之间空格隔开即可
自定义inclusion_tag(局部的html代码)
@register.inclusion_tag(filename='it.html')
def index(n):
html = []
for i in range(n):
html.append('第%s页'%i)
return locals()
#it.html里内容
<ul>
{% for i in html %}
<li>{{ i }}</li>
{% endfor %}
</ul>
'''该方法需要先作用于一个局部html页面 之后将渲染的结果放到调用的位置 类似于封装了一个能够快速产生html代码的方法'''
{% load mytag %}
{% index 10 %}
模板的继承与导入
类似于面向对象的继承:继承了某个页面就可以使用该页面上所有的资源。
如果我们想要继承获取一个页面的大部分只修改一部分代码,我们也可以用继承来实现。
方式1:传统的复制粘贴
方式2:模板的继承
母页面
1.在模板中使用block划定子板以后可以修改的区域
{% block 区域名称 %}
{% endblock %}
子页面
{% extends 'html文件名' %} # html文件名是母页面的HTML文件名,继承了母页面
{% block 名字 %} # 这里写的内容可以直接修改母页面里可以被修改的区域
子板内容
{% endblock %}
'''子板中还可以使用母板的内容 {{ block.super }} '''
模板上最少应该有三个区域可以让子页面继承:css区域、内容区域、js区域。子页面就可以有自己独立的css、js、内容
{% block css %}
css修改内容
{% endblock %}
{% block 名字 %}
子板内容
{% endblock %}
{% block js %}
js修改内容
{% endblock %}
子页面还可以重复使用父页面的内容
{{ block.super }}
模板的导入(了解)
将某个html的部分提前写好 之后很多html页面都想使用就可以导入
{% include 'myform.html' %}
模型层之前期准备
表查询数据准备及测试环境搭建
django自带一个sqlite3小型数据库,该数据库功能非常有限并且针对日期类型的数据兼容性很差。所以我们习惯切换成常见的数据库比如MySQL django orm并不会自动帮你创建库 所以需要提前准备好
日期字段:modles.DateField()
# 年月日
参数:atuo_now 每次操作数据并保存都会自动更新当前时间
参数:auto_now_add 在数据被创建出来的那一刻会自动记录当前时间,之后在不人为修改的情况下保存不变
edit_time = modles.DateField(atuo_now=True或auto_now_add=True)
1.django切换MySQL数据库
2.定义模型类
class User(models.Model):
uid = models.AutoField(primary_key=True, verbose_name='编号') # verbose_name是注释起名字
name = models.CharField(max_length=32, verbose_name='姓名')
age = models.IntegerField(verbose_name='年龄')
join_time = models.DateTimeField(auto_now_add=True)
3.执行数据库迁移命令(模型类>>>表)
python38 manage.py makemigrations
python38 manage.py migrate
4.模型层测试环境准备(单独测试django某个功能层)
默认不允许单独测试某个py文件 如果想要测试某个py文件(主要models.py)
测试环境1:pycharm提供的python console:
python console 命令行测试环境
python console自带测试环境
测试环境2:自己搭建(自带的test或者自己创建)
1.拷贝manage.py前四行
2.自己再加两行
import django
django.setup()
import os
def main():
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'moxingceng.settings')
import django
django.setup()
from app01 import models
models.User.objects.filter()
if __name__ == '__main__':
main()
查看ORM底层SQL语句
方式1:
如果是Queryset对象,那么可以直接点query查看SQL语句
方式2:
配置文件配置、打印所有的ORM操作对应的SQL语句、直接拷贝使用即可
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console':{
'level':'DEBUG',
'class':'logging.StreamHandler',
},
},
'loggers': {
'django.db.backends': {
'handlers': ['console'],
'propagate': True,
'level':'DEBUG',
},
}
}
ORM常用关键字
创建数据
1.create() 创建数据并直接获取当前创建的数据对象
models.User.objects.create(name='jason',age=18)
方式2:还可以利用类实例化对象然后调用save方法创建
user_obj = models.User(name='jason', age=18)
user_obj.save() # 保存对象到User表中
查询数据
需要注意事项:
①当需要查询数据主键字段值的时候可以使用pk忽略掉数据字段真正的名字
②在模型类中可以定义一个__ str__方法便于后续数据对象被打印展示的是查看方便
③Queryset中如果是列表套对象那么直接for循环和索引取值但是索引不支持负数
④虽然queryset支持索引但是当queryset没有数据的时候索引会报错、推荐使用first
# __str__方法是对象被执行打印(print、页面展示、数据查询)操作的时候自动触发
def __str__(self):
return f'对象:{self.name}' # __str__方法必须要返回一个字符串类型的数据
ORM执行SQL语句
由于有时候ORM的操作效率可能偏低,我们是可以自己编写SQL的
方式1:
models.User.objects.raw('select* from app01_user;')
方式2:
from django.db import connection
cursor = connection.cursor()
cursor.execute('select name from app01_user;')
print(cursor.fetchall())
神奇的双下划线查询
'''
只要还是queryset对象就可以无限制的点queryset对象的方法
queryset.filter().values().filter().values_list().filter()...
'''
ORM外键字段的创建
复习MySQL外键知识。
ORM中的外键创建和mysql几乎一样,我们以创建图书表,出版社表,作者表和作者详情表来举例子。
ps:三个关键字里面的参数,to用于指定跟哪张表有关系,自动关联主键。to_field\to_fields,也可以自己指定关联字段。
ORM中有三种创建多对多字段的方式 models.ManyToManyField()
方式1:直接在查询频率较高的表中填写字段即可 自动创建第三张关系表
方式2:自己创建第三张关系表
方式3:自己创建第三张关系表 但是还是要orm多对多字段做关联
ManyToManyField不会在表中创建实际的字段,而是告诉django orm自动创建第三张关系表。ForeignKey、OneToOneField会在字段的后面自动添加_id后缀,如果你在定义模型类的时候自己添加了该后缀那么迁移的时候还会添加,所以不要自己加下划线id后缀。
针对一对多和一对一同步到表中之后会自动加_id的后缀
publish = models.ForeignKey(to='Publish',on_delete=models.CASCADE)
author_detail = models.OneToOneField(to='AuthorDetail', on_delete=models.CASCADE)
针对多对多 不会在表中有展示 而是创建第三张表
authors = models.ManyToManyField(to='Author')
注意:
django1.X 针对 models.ForeignKey() models.OneToOneField()不需要on_delete
django2.X 3.X 则需要添加on_delete参数
创建的具体代码:
from django.db import models
# Create your models here.
class Book(models.Model):
"""图书表"""
title = models.CharField(max_length=32,verbose_name='书名')
price = models.DecimalField(max_digits=8,decimal_places=2,verbose_name='价格')
publish_time = models.DateTimeField(auto_now_add=True,verbose_name='出版日期')
# 创建书籍与出版社的一对多外键字段
publish = models.ForeignKey(to='Publish',on_delete=models.CASCADE)
'''django1.x版本外键字段默认都是级联更新删除 django2.x及以上需要自己申明 on_delete=models.XXX '''
# 创建书籍与作者的多对多外键字段
authors = models.ManyToManyField(to='Author')
'''多对多字段为虚拟字段 用于告诉ORM自动创建第三张表'''
def __str__(self):
return f'数据对象:{self.title}'
class Publish(models.Model):
"""出版社表"""
name = models.CharField(max_length=32,verbose_name='名称')
address = models.CharField(max_length=64,verbose_name='地址')
def __str__(self):
return f'出版社对象:{self.name}'
class Author(models.Model):
"""作者表"""
name = models.CharField(max_length=32, verbose_name='姓名')
age = models.IntegerField(verbose_name='年龄')
# 创建作者与作者详情的一对一外键字段
author_detail = models.OneToOneField(to='AuthorDetail',on_delete=models.CASCADE)
def __str__(self):
return f'作者对象:{self.name}'
class AuthorDetail(models.Model):
"""作者详情表"""
phone = models.BigIntegerField(verbose_name='手机号')
address = models.CharField(max_length=64,verbose_name='家庭住址')
def __str__(self):
return f'详情对象:{self.address}'
外键字段相关操作
一对多和一对一
models.ForeignKey(to='Publish',on_delete=models.CASCADE)
models.OneToOneField(to='AuthorDetail', on_delete=models.CASCADE)
方式1:直接给实际字段添加关联数据值
models.Book.objects.create(title='python从入门到放弃',price=29800.88,publish_id=1)
方式2:间接使用外键虚拟字段添加数据对象
publish_obj = models.Publish.objects.filter(pk=2).first()
models.Book.objects.create(title='django从入门到入土',price=19888.88,publish=publish_obj)
多对多
models.ManyToManyField(to='Author')
第三张关系表添加数据
book_obj = models.Book.objects.filter(pk=1).first()
book_obj.authors.add() # 括号内即可以填写数字值也可以填写数据对象 支持多个
第三张关系表修改数据
book_obj.authors.set() # 括号内必须是一个可迭代对象,元素同样支持主键值或者数据对象
第三张关系表删除数据
book_obj.authors.remove() # 括号内即可以填写数字值也可以填写数据对象 支持多个
第三张关系表清空指定数据
book_obj.authors.clear() # 括号内无需传值 直接清空当前表在第三张关系表中的绑定记录
add()\remove() 多个位置参数(数字 对象)
set() 可迭代对象(元组 列表) 数字 对象
clear() 情况当前数据对象的关系
ORM跨表查询
"""
复习MySQL跨表查询的思路
子查询
分步操作:将一条SQL语句用括号括起来当做另外一条SQL语句的条件
连表操作
先整合多张表之后基于单表查询即可
inner join 内连接
left join 左连接
right join 右连接
"""
正反向概念(重要)
正向查询
由外键字段所在的表数据查询关联的表数据 正向
反向查询
没有外键字段的表数据查询关联的表数据 反向
ps:正反向的核心就看外键字段在不在当前数据所在的表中
ORM跨表查询的口诀(重要)
正向查询按外键字段
反向查询按表名小写
基于对象的跨表查询(子查询)
基于对象的正向跨表查询
1.查询主键为1的书籍对应的出版社
1.先根据条件查询数据对象(先查书籍对象)
book_obj = models.Book.objects.filter(pk=1).first()
2.以对象为基准,思考正反向概念(书查出版社 外键字段在书表中 所以是正向查询)
print(book_obj.publish)
2.查询主键为4的书籍对应的作者姓名
先根据条件获取数据对象
book_obj = models.Book.objects.filter(pk=4).first()
再判断正反向的概念 由书查作者 外键字段在书所在的表中 所以是正向查询
print(book_obj.authors) # app01.Author.None
print(book_obj.authors.all())
print(book_obj.authors.all().values('name'))
3.查询jason的电话号码
author_obj = models.Author.objects.filter(name='jason').first()
print(author_obj.author_detail.phone)
基于对象的正向跨表查询
4.查询北方出版社出版过的书籍
publish_obj = models.Publish.objects.filter(name='北方出版社').first()
print(publish_obj.book_set) # app01.Book.None
print(publish_obj.book_set.all())
5.查询jason写过的书籍
author_obj = models.Author.objects.filter(name='jason').first()
print(author_obj.book_set) # app01.Book.None
print(author_obj.book_set.all())
6.查询电话号码是110的作者姓名
author_detail_obj = models.AuthorDetail.objects.filter(phone=110).first()
print(author_detail_obj.author)
print(author_detail_obj.author.name)
基于上下划线的跨表查询(连表操作)
基于双下划线的正向跨表查询
1.查询主键为1的书籍对应的出版社名称
res = models.Book.objects.filter(pk=1).values('publish__name','title')
print(res)
2.查询主键为4的书籍对应的作者姓名
res = models.Book.objects.filter(pk=4).values('title', 'authors__name')
print(res)
3.查询jason的电话号码
res = models.Author.objects.filter(name='jason').values('author_detail__phone')
print(res)
基于双下划线的反向跨表查询
4.查询北方出版社出版过的书籍名称和价格
res = models.Publish.objects.filter(name='北方出版社').values('book__title','book__price','name')
print(res)
5.查询jason写过的书籍名称
res = models.Author.objects.filter(name='jason').values('book__title', 'name')
print(res)
6.查询电话号码是110的作者姓名
res = models.AuthorDetail.objects.filter(phone=110).values('phone', 'author__name')
print(res)
进阶操作
1.查询主键为1的书籍对应的出版社名称
res = models.Publish.objects.filter(book__pk=1).values('name')
print(res)
2.查询主键为4的书籍对应的作者姓名
res = models.Author.objects.filter(book__pk=4).values('name','book__title')
print(res)
3.查询jason的电话号码
res = models.AuthorDetail.objects.filter(author__name='jason').values('phone')
print(res)
4.查询北方出版社出版过的书籍名称和价格
res = models.Book.objects.filter(publish__name='北方出版社').values('title','price')
print(res)
5.查询jason写过的书籍名称
res = models.Book.objects.filter(authors__name='jason').values('title')
print(res)
6.查询电话号码是110的作者姓名
res = models.Author.objects.filter(author_detail__phone=110).values('name')
print(res)
补充:
查询主键为4的书籍对应的作者的电话号码
res = models.Book.objects.filter(pk=4).values('authors__author_detail__phone')
print(res)
res = models.AuthorDetail.objects.filter(author__book__pk=4).values('phone')
print(res)
res = models.Author.objects.filter(book__pk=4).values('author_detail__phone')
print(res)
聚合查询
聚合函数有Max, Min, Sum, Avg, Count。
聚合查询:
from django.db.models import Max, Min, Sum, Avg, Count
res = models.Book.objects.aggregate(Max('price'), Min('price'), Sum('price'), Avg('price'), Count('pk'))
print(res)
'''没有分组之前如果单纯的使用聚合函数、需要关键字aggregate、默认整体就是一组'''
分组查询
分组有一个特性,默认只能够直接获取分组的字段,想要获取其他字段需要使用方法,我们也可以忽略掉该特性,将sql_mode strict mode 中only_full_group_by配置移除即可。
分组两种方式:
1.按照整条数据分组
models.Book.objects.annotate() # 按照一条条书籍记录分组
2.按照表中某个字段分组
models.Book.objects.values('title').annotate() # 按照annotate之前values括号中指定的字段分组
res = models.Book.objects.values('publish_id').annotate(count_pk=Count('pk')).values('publish_id', 'count_pk')
print(res)
示例1:统计每一本书的作者个数
res = models.Book.objects.annotate(author_num=Count('authors__pk')).values('title', 'author_num')
print(res)
示例2:统计出每个出版社卖的最便宜的书的价格
res = models.Publish.objects.annotate(min_price=Min('book_price') ).values('name','min_price')
print(res)
示例3:统计不止一个作者的图书
1.先统计每本书的作者个数
res = models.Book.objects.annotate(author_num=Count('authors__pk'))
2.筛选出作者个数大于1的数据
res = models.Book.objects.annotate(author_num=Count('authors__pk')).filter(author_num__gt=1).values('title','author_num')
print(res)
'''filter在annotate前面则是where(筛选)、在annotate后面则是having(过滤)'''
示例4:查询每个作者出的书的总价格
res = models.Author.objects.annotate(总价=Sum('book__price'),count_book=Count('book__pk')).values('name','总价','count_book')
print(res)
F与Q查询
F查询:当查询条件不是明确的 也需要从数据库中获取 就需要使用F查询 查询条件不是自定义的而是来自于表中其他字段'''
当表中已经有数据的情况下,添加额外的字段,需要指定默认值(default)或者可以为null。
方式1:设置字段值允许为空
IntegerField(verbose_name='销量',null=True)
方式2:设置字段默认值
IntegerField(verbose_name='销量',default=1000)
方式3:在终端中直接给出默认值
使用F查询需要先导入模块
from django.db.models import F
①查询库存数大于卖出数的书籍
res = models.Book.objects.filter(kucun__gt=F('maichu'))
print(res)
②将所有书籍的价格上涨800块
models.Book.objects.update(price=F('price') + 800)
③将所有书的名称后面追加爆款
#models.Book.objects.filter(pk=5).update(title=F('title') + '爆款') # 针对字符串数据无法直接拼接
from django.db.models.functions import Concat
from django.db.models import Value
res = models.Book.objects.filter(pk=5).update(title=Concat(F('title'), Value('爆款')))
Q查询:可以改变filter括号内多个条件之间的逻辑运算符
示例:查询主键是1或者价格大于2000的书籍
res = models.Book.objects.filter(pk=1, price__gt=2000) # 逗号默认是and关系
from django.db.models import Q
res = models.Book.objects.filter(Q(pk=1), Q(price__gt=2000)) # 逗号是and
res = models.Book.objects.filter(Q(pk=1) | Q(price__gt=2000)) # |是or
res = models.Book.objects.filter(~Q(pk=1) | Q(price__gt=2000)) # ~是not
print(res.query)
标签:总结,__,name,models,res,django,objects
From: https://www.cnblogs.com/wxlxl/p/16990489.html