Django框架

Django框架

Django是一个功能强大的web框架

前言:框架模式简介

1、MVC和MTV框架

MVC:Web服务器开发领域里著名的MVC模式,所谓MVC就是把Web应用分为模型(M),控制器(C)和视图(V)三层,结构说明如下:

M: models 数据库相关操作
V: views 视图,也就是业务逻辑相关操作
C: controller  控制器,也就是通过路径找到对应视图函数

MTV:Django的MTV模式本质上和MVC是一样的,也是为了各组件间保持松耦合关系,只是定义上有些许不同。

M: models 数据库相关操作
T: templates html文件相关操作(模板渲染等)
V: views 视图,也就是业务逻辑相关操作

加上一个url控制器,也就是通过路径找到对应视图函数
2、WSGI

WSGI(Web Server Gateway Interface)就是一种规范,它定义了web应用程序与web服务器程序之间的接口格式,实现web应用程序与web服务器程序间的解耦。

开发的项目分为两个部分的程序
1 服务器程序  socket相关功能的模块,wsgiref、uwsgi等等,负责接收网络请求并解析网络请求相关数据,分装加工成一些可用的数据格式,格式大家都是统一的,方便应用程序的开发

2 应用程序  就是使用接收到的网络请求相关数据,进行逻辑代码的开发,比如数据库相关操作,数据结构的调整、文件操作等等。。。

一、基本使用

1、下载的三种方式:
  • 直接在pycharm中的setting中进行下载
  • 在cmd中通过命令下载:pip install django==版本
  • 在pycharm的Terminal控制台中进行下载(下载时要注意路径问题)
2、创建项目

(1)通过cmd或pycharm控制台的命令创建项目

​ 先切换到要创建项目的目录下,然后执行创建项目命令:

django-admin startproject mysite  # mysite是项目名称

​ 创建项目后会生成如下的目录,当前目录下会生成mysite的工程,里面有个主目录和我们创建的项目目录同名,在项目目录下有个manage.py文件,在主目录下有settings.pyurls.pywsgi.py,每个文件的功能介绍如下:

manage.py ----- Django项目里面的工具,通过它可以调用django shell和数据库,启动关闭项目与项目交互等,不管你将框架分了几个文件,必然有一个启动文件,其实他们本身就是一个文件。
settings.py ---- 包含了项目的默认设置,包括数据库信息,调试标志以及其他一些工作的变量。
urls.py ----- 负责把URL模式映射到应用程序。
wsgi.py ---- runserver命令就使用wsgiref模块做简单的web server,后面会看到renserver命令,所有与socket相关的内容都在这个文件里面了,目前不需要关注它。

​ 一个django项目中可以有多个应用,每个应用完成项目的部分功能,这些功能相对于其他功能来说是相对独立的,但又同时存在于同一个项目中,每个应用的逻辑数据库等也都是相对独立的,每个应用都有属于自己的模块单位;开发的时候,都是通过应用来写逻辑

(2)通过pycharm创建django项目

  • 点击File --》New Project 选择第二项 Django

  • 在Location中选择选项目创建的地址和项目名

  • Project Interpreter:

    Project Interpreter中的Newenvironment using是创建项目执行的虚拟环境

    Project Interpreter中的Existing interpreter是使用本地的环境,也可以使用已创建好的虚拟环境

  • More Settings

    Template language:模板引擎;默认是Django的Template模板引擎

    如若下载jinja2模板引擎可进行切换,或者其他模板引擎

    注:django中的模板引擎未单独封装成模块;jinja2是模仿的的django的Template

    Templates folder:存放html文件的文件夹名

    Application name:是创建的应用的应用名

  • create创建完成后执行即可,通过控制台显示的链接即可访问

3、运行项目

启动项目命令:

python manage.py runserver 127.0.0.1:8080
ip和port可以不用写,不写时默认是 127.0.0.1:8000

运行成功后,会看到一个项目链接,在浏览器中输入此链接即可访问到创建的项目

4、创建应用Application

(1)创建项目时直接通过Application name创建应用

(2)pycharm中手动创建应用

  • cmd或pycharm控制器Terminal中创建应用
要在项目目录下执行命令进行创建应用
python manage.py startapp 应用名
经常用到的三个文件
models.py  数据库相关内容
views.py  视图,业务逻辑代码相关内容
tests.py 用来写一些测试代码,用来测试我们自己写的视图的,目前用不到
  • 手动创建文件夹(也可复制其它应用的文件,要注意修改配置)

    • 创建应用文件夹(鼠标右键使用python package创建)
    • 在settings.py中找到INSTALLED_APPS在其下面添加应用的配置信息
    • 应用名.apps.应用名Config(后面的应用名的首字母大写)
  • 最后一点可能需要配置一下template的路径

    • 在settings.py中找到TEMPLATES在其下面的'DIRS':[]中添加os.path.join(BASE_DIR, 'templates')

    • 结果显示:
      'DIRS': [os.path.join(BASE_DIR, 'templates')],
      
  • 注意

    • 手动创建应用的时候要把应用指定给当前项目
    • 在settings文件的INSTALLED_APPS的下面
      • 方式一:应用名.apps.应用名Config(后面的应用名的首字母大写)
      • 方式二:应用名 ( 这是简写方式)
    • 如若不指定应用,Django中的某些方法将无法使用
5、django项目的导入

请看博客:https://www.cnblogs.com/wylshkjj/p/11983596.html

6、windows中安装不同版本的python解释器

在python3.7及以上版本中,使用django框架的时候,django的版本要在2.0或以上,否则会出现问题;python3.6使用django的1.0版本。

当想即使用python3.7和python3.6针对django的不同版本进行创建项目时,python解释器的安装要注意,

解释器安装请参考博客:https://www.cnblogs.com/wylshkjj/p/13122349.html

二、url路由系统

​ 在django中,url中的路径写法是正则,正则里面有无名分组正则,有有名分组正则,那么对应django中的功能,我们称之为无名分组路由和有名分组路由

​ 在django的1.0版本中路由配置文件urls.py中使用的是url(),里面可以直接使用正则匹配路径的方式

​ 而在django的2.0版本中路由配置文件urls.py中使用的是path(),里面不能直接使用正则匹配路径,如需使用正则路径进行匹配就要使用re_path(),使用前要先导入

1、无命名分组路由

看写法,urls.py文件中内容如下

urlpatterns = [
		...
    url(r'^books/(d+)/(d+)/', views.book),
    #正则里面()分组正则,会将分组中的正则匹配到的内容作为返回值返回
]

简单分析,伪代码
'''
当用户请求的路径是它: /books/2019/8/

url(r'^books/(d+)/(d+)/', views.book), 里面做的事情如下

re.match(^books/(d+)/,/books/2019/)
2019 和 8 作为位置参数交给了要执行的book视图函数
视图函数book的写法
def book(request,xx,oo):
    xx = 2019
    oo = 8
    pass
'''

视图views.py文件中函数的写法

#位置传参,url中正则^books/(d+)/(d+)/,那么第一个()分组匹配到的数据,作为book函数的第二个参数,第二个()分组匹配到的数据,作为book的第三个参数
def book(request, year, month):
    print('year', year, 'month', month) #year 2020

    # return HttpResponse('%s所有书籍' % year)
    return HttpResponse('%s年%s月所有书籍' % (year, month))

使用url路由系统时需要注意几个点

1. urlpatterns中的元素按照书写顺序从上往下逐一匹配正则表达式,一旦匹配成功则不再继续。
2. 若要从URL中捕获一个值,只需要在它周围放置一对圆括号(正则分组匹配)。
3. 不需要添加一个前导的反斜杠(也就是写在正则最前面的那个/),因为每个URL 都有。例如,应该是^articles 而不是 ^/articles。
4. 每个正则表达式前面的'r' 是可选的但是建议加上。
5. ^articles$  以什么结尾,以什么开头,严格限制路径
2、有名分组路由

其实就玩得正则的有名分组,看示例

Urls.py文件中的写法

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    # /books/2020/6/
    url(r'^books/(?P<year>d+)/(?P<month>d+)/', views.book),
    # {'year':2020,'month':6},url类将有名分组正则匹配出来的数据,交给了book视图函数作为关键字参数来使用]

View.py文件中函数的写法如下

# ^books/(?P<year>d+)/(?P<month>d+)/
#获取到url中的有名分组正则匹配到的数据,那么函数形参名称必须和有名分组正则的那个名称相同才可以,也就是按照上面的url来看的话,函数的形参必须是year和month这两个名称,并且关键字传参不需要考虑函数形参的位置
def book(request, month, year):
    # print('year', year, 'month', month) #year 2020
    print(request.path) #/books/2020/6/
    # return HttpResponse('%s所有书籍' % year)
    return HttpResponse('%s年%s月所有书籍' % (year, month))
3、路径中的尾部斜杠问题

当用户通过浏览器访问django框架完整的项目中的某个路径时,如果用户在输入网址路径的最后,没有加上/斜杠,比如http://127.0.0.1:8000/test,那么django会发将用户输入的网址路径加上一个后置的/,也就会将路径变成这样http://127.0.0.1:8000/test/,然后给浏览器发送一个重定向的响应操作,状态码为301,那么浏览器拿到这个重定向操作之后,就会自动发起一个这样的路径请求http://127.0.0.1:8000/test/,所以当我们打开浏览器控制台的network功能查看请求过程时,会看到两个请求,一个没有后置的斜杠的,一个是有后置斜杠的。第二个请求才会走到我们的urs.py文件中的路径配合和分发对应视图的地方。

我们可以通过一个配置项,告诉django,不要自动加路径后面的斜杠了,但是需要注意的就是你自己写的url中的正则,也别加后面的斜杠,不然正则匹配不到。

配置项直接写在settings配置文件中,任意位置

APPEND_SLASH = False  #False表示告诉Django,不加路径后面的斜杠,默认值是True
5、视图函数中指定默认值

views.py文件:

# 在路由没有匹配任何参数的时候,num使用自己的默认值
# 当路由中有分组匹配数据的动作,比如url(r'^test/(d+)/', views.test),用户输入网址:http://127.0.0.1:8000/test/22/,那么22就被匹配到了,会作为参数传给我们的test函数,那么num的值就变成了22
def test(request, num=10):
    print('number>>>',num)
    return HttpResponse('test')

urls.py文件

# url(r'^test/', views.test),
	url(r'^test/(d+)/', views.test),
6、url反向解析

​ 由于将来项目中的不同功能对应的url路径可能会发生变化,所以我们在每个url路径上加上一个别名,将来通过别名反向解析来使用这个别名对应的路径,那么不管路径将来发生什么变化,只要别名不变,那么逻辑中使用这个路径的地方,都可以通过别名获取到

(1)url别名用法

urlpatterns = [
    ...
    url(r'^add_book/', views.add_book, name='add_book'),  #name属性对应的值,就是这个路径的别名

]

(2)views视图中使用url反向解析的方式

# 首先在视图中引入反向解析的方法
from django.urls import reverse  #url别名反向解析,通过name别名对应的数据,解析出我们的url路径

1 针对没有参数的别名 url(r'^add_book/', views.add_book, name='add_book'),
	反向解析:reverse('book_list')
2 针对含有无名分组的url:url(r'^book_list/v2/(d+)/(d+)/', views.book_list, name='book_list'),
	反向解析:reverse('book_list',args=(11,22))
3 针对含有有名分组的url:url(r'^book_list/v2/(?P<year>d+)/(?P<month>d+)/', views.book_list, name='book_list'),
	反向解析:reverse('book_list',kwargs={'year':2022,'month':11})

(3)html文件中使用url别名反向解析

  • 无参数:{% url 'add_book' %}
  • 有参数:{% url 'add_book' 2020 12 %}
针对无参数的路径:url(r'^add_book/', views.add_book, name='add_book'),
	反向解析:<a href="{% url 'add_book'%}" class="btn btn-primary">添加书籍</a>
这对有参数的路径:url(r'^add_book/(d+)/(d+)/', views.add_book, name='add_book'),
							 url(r'^add_book/(?P<year>d+)/(?P<month>d+)/', views.add_book, name='add_book'),
	反向解析: <a href="{% url 'add_book' 2020 12 %}" class="btn btn-primary">添加书籍</a>

三、视图

1、request的对象

常用的属性和方法

print(request)  # wsgirequest对象
print(request.path)  # 请求路径 #/index/
print(request.method)  # 请求方法
print(request.POST)  # post请求提交的数据 <QueryDict: {'username': ['root']}>
print(request.GET)  # 获取url中的查询参数 <QueryDict: {'a': ['1'], 'b': ['2']}>  #不是针对get请求的
print(request.body)  #获取http请求消息格式的请求数据部分的内容  b''
print(request.META)  #请求头信息
print(request.get_full_path())  # 获取完整路径(包含查询参数的) /index/?a=1&b=3

print(request.FILES)  # 上传的文件对象数据
print(request.FILES.get('file'))  # 上传的文件名

#<QueryDict: {'username': ['root'], 'sex': ['female'], 'hobby': ['2', '3']}>
print(request.POST.get('username'))  # 前端中传输的username的值
print(request.POST.get('sex'))  # 前端中单选传输的sex值
# 多选提交来的数据通过getlist来获取
print(request.POST.getlist('hobby'))  # ['2', '3']
2、response的响应

(1)常用方法

from django.shortcuts import render, HttpResponse, redirect

return HttpResponse('你好') #回复字符串
return render(request,'home.html') #回复html页面

#重定向方法,参数是个路径
return redirect('/home/')  #封装了302状态码,以及浏览器要重定向的路径

(2)添加响应头键值对

ret = render(request,'home.html') 
ret['a'] = 'b' #添加响应头键值对
return ret

(3)添加响应状态码

ret = render(request,'home.html', status=202) #render修改状态码还可以这样改
#ret['a'] = 'b' #添加响应头键值对
ret.status_code = 201 #添加响应状态码
return ret #回复html页面
3、CBV和FBV

两种视图逻辑的写法方法

FBV:全称function based view,就是基于函数来写视图逻辑

CBV:全称class based view,就是基于类来写视图

基于类的视图CBV写法,如下,views.py文件

from django.views import View

#登录需求
class LoginView(View):
    # get请求  获取login页面
    def get(self,request):
        return render(request,'login.html')
        
    # post请求,获取post请求提交的数据,并校验等等
    def post(self,request):
        print(request.POST)
        
        #<QueryDict: {'uname': ['chao'], 'pwd': ['123']}>
        return render(request,'login.html')

url路径的写法:urls.py文件

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^index/', views.index),
    url(r'^home/', views.home),

    # 类视图的url写法
    url(r'^login/', views.LoginView.as_view()),

]
4、cbv源码重点(反射)
from django.views import View
View里面的dispatch方法中的反射逻辑,实现了不同的请求方法,找到我们视图类中的对应方法执行
5、FBV和CBV加装饰器

FBV和普通函数加装饰器方式一样

示例:

#装饰器函数
def outer(f):
    def inner(request, *args ,**kwargs):
        print('前面')
        ret = f(request, *args ,**kwargs)
        print('后面')
        return ret
    return inner

#使用装饰器
@outer
def books(request):
    print('FBV执行了')
    return HttpResponse('book.html--ok')

CBV加装饰器

#装饰器函数
def outer(f):
    def inner(request, *args ,**kwargs):
        print('前面')
        ret = f(request, *args ,**kwargs)
        print('后面')
        return ret
    return inner


#使用装饰器
#1 引入django提供的装饰器方法method_decorator来配合装饰器函数的使用
from django.utils.decorators import method_decorator

@method_decorator(outer,name='get') #CBV加装饰器方式3
class BookView(View):
    #给类方法统一加装饰器,借助dispatch方法(父类的dispatch方法,就是通过反射来完成不同的请求方法找到并执行我们自己定义的视图类的对应方法)
    # 重写dispatch方法,dispatch方法是在其他请求方法对应的类方法执行之前执行的
    # @method_decorator(outer) #加装饰器方式2
    def dispatch(self, request, *args, **kwargs):
        # print('xxxxxx')
        ret = super().dispatch(request, *args, **kwargs)
        # print('oooooo')
        return ret

    #CBV加装饰器的方式1,给单独的方法加装饰器
    # @method_decorator(outer)
    def get(self, request, xx):
        print('CBV的get方法')
        return render(request, 'book.html')

    # @method_decorator(outer)
    def post(self,request, xx):
        print('CBV的post方法')
        return HttpResponse('ok')
  

四、Template模板

1、Template的基本使用

(1)通过{{ 变量 }}:获取单个变量值

(2)通过{% 逻辑 %}:获取逻辑渲染结果

2、变量的使用

Number数据类型,容器数据类型和对象都可以直接进行渲染

(1)返回前端页面的数据格式

  • 在return返回的时候可以直接写入参数,区别在于html中渲染的时候直接通过元素直接获取(使用原参数没有作用),容器数据类型才有效,number类型无效

    return render(request, "index.html", info)

  • 在return返回的时候也可以使用字典的方式,使用字典时,就是直接通过字典的键来进行相应的操作

    return render(request, "index.html", {'info': info})

(2)句点号的使用:

​ 在字典数据类型中需要使用句点号和索引搭配才能获取到相应的值

​ 同理对象的方法和属性的调用也是通过句点号,而且要注意调用方法不能加()

# views.py
from django.shortcuts import render
import datetime
# Create your views here.
class obj():
    pass
def index(request):
    pdd = '1234'
    info = {
        'name': '旋风奥利奥',
        'age': '18',
        'hobby': "girl",
        'dict': {'drink': '饮品', 'milk': '牛奶'},
        'list': ['面包', '包子'],
        'object': obj(),
        'size': 123456,
        'time': datetime.datetime.now()
    }
    return render(request, "index.html", info)
    # return render(request, "index.html", {'info': info})

# html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h3>{{ name }}</h3>
<p>{{ dict }}</p>
<p>{{ list }}</p>
<p>{{ pdd }}</p>
<hr>
{% for foo in list %}
<p>{{ foo }}</p>
{% endfor %}
<hr>
{% for k,v in dict.items %}
<p>{{ k }} : {{ v }}</p>
{% endfor %}
<hr>
{{ object.obk }}
</body>
</html>

# url.py
from app01 import views
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^index/', views.index)
]
3、Template过滤器
  • 通过过滤器对数据进行过滤处理显示
  • 使用方式:{{ 变量|过滤器:参数 }}
  • 过滤器支持链式操作,通过多个管道符和过滤器相配合实现层级过滤
  • 过滤器可以接收参数
  • 注意事项:管道符左右两遍不能留空格,否则不能识别;还有参数和:间也不能留空格;
3.1、内置过滤器

(1)default:默认值

​ 当变量获取的到值的时候显示获取的值,获取不到值或者获取的是一个布尔类型的False时就使用默认的default值:{{ value|default:"没有值"}}

(2)length:长度

​ 返回的是字符串或列表的长度:{{ value|length }}

(3)filesizeformat:文件大小的显示处理

​ 将值格式化为一个 “人类可读的” 文件尺寸 (例如 13 KB, 4.1 MB, 102 bytes, 等等)。

(4)slice:切片:{{ value|slice:"2:-1" }}

(5)date:时间显示处理

​ 对获取的时间进行过滤处理:{{ value|date:"Y-m-d H:i:s"}}

(6)safe:声明此变量值(或代码段)不转义

{'a_tag':'<a href="">百度</a>',}
渲染
<p>
    {{ a_tag|safe }} #生成标签效果
</p>

(7)truncatechars:指定字符串长度,超出部分以 ... 显示
{{ value|truncatechars:9}}:指定九个字符长度,也包括 ... 这三个字符

(8)truncatewords:以单词的形式指定字符串长度,超出部分以 ... 显示

​ {{ value|truncatewords:3}}:指三个单词长度,不包括 ... 部分

(9)cut:过滤字符串

​ {{ value|cut:' ' }}:过滤掉value变量中和参数中的空格相同的字符

(10)join:字符串拼接

​ {{ hobby|join:'+' }}:把列表数据通过加号拼接成一个字符串

3.2、自定义过滤器
  • 在应用文件夹中创建一个名为templatetags的文件夹(文件夹的名字必须是templatetags)
  • 在templatetags文件夹中创建任意 .py 文件,如:mytag.py
  • 在mytag.py文件中写上如下内容
from django import template
register = template.Library()  # 制作注册器,名字必须叫register
#过滤最多两个参数
@register.filter  # 注册过滤器,需要两个参数的
def add(v1, v2):  # v1表示管道符前面的,v2表示冒号后面的参数
    print(v1,v2)  # 100 50
    return v1 + v2

@register.filter  # 注册过滤器,需要一个参数的
def xxx(v1):   # v1表示管道符前面的
    print(v1) 
    return 'xxx'
  • 在html文件中导入
{% load mytag %}  <!-- 首先通过load来加载一下mytag文件,不一定放在文件的开头,但是一定要放在使用过滤器的前面先进行引用 -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

</head>
<body>
<h1>base页面</h1>
  <!-- {% load mytag %} 放这里也是可以的 -->  
<div>
    {{ num|add:50}}  <!-- 使用过滤器,和django内置过滤器用法一样,这里生成的是add函数的返回值 -->
</div>
<div>
    {{ num|xxx }}
</div>
<!-- {% load mytag %} 放这里不行 -->  

</body>
</html>
4、Template标签

​ 使用方式:{% 标签 %} {%end标签%}

4.1、内置标签

(1)for 循环标签

​ {% for xx in hobby %}{% endfor %}

forloop的使用

​ 注:循环序号可以通过{{forloop}}显示,必须在循环内部用

  • forloop.counter 当前循环的索引值(从1开始),forloop是循环器,通过点来使用功能

  • forloop.counter0 当前循环的索引值(从0开始)

  • forloop.revcounter 当前循环的倒序索引值(从1开始)

  • forloop.revcounter0 当前循环的倒序索引值(从0开始)

  • forloop.first 当前循环是不是第一次循环(布尔值)

  • forloop.last 当前循环是不是最后一次循环(布尔值)

  • forloop.parentloop 本层循环的外层循环的对象,再通过上面的几个属性来显示外层循环的计数等

for循环的反向循环:

​ 可以利用{% for obj in list reversed %}反向完成循环。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
{% for i in dict %}
    <p>{{ i }}</p>
{% endfor %}
<hr>
{% for i in dict.values %}
    <p>{{ i }}</p>
{% endfor %}
<hr>
{% for k,v in dict.items %}
    <p>{{ k }} : {{ v }}</p>
{% endfor %}
<hr>
{% for foo in list %}
    <p>{{ forloop }} : {{ foo }}</p>
{% endfor %}
<hr>
{% for foo in list %}
    <p>{{ forloop.counter }} : {{ foo }}</p>
{% endfor %}
<hr>
{% for foo in list %}
    <p>{{ forloop.counter0 }} : {{ foo }}</p>
{% endfor %}
<hr>
{% for foo in list %}
    <p>{{ forloop.revcounter }} : {{ foo }}</p>
{% endfor %}
<hr>
{% for foo in list %}
    <p>{{ forloop.revcounter0 }} : {{ foo }}</p>
{% endfor %}
<hr>
{% for foo in list %}
    <p>{{ forloop.first }} : {{ foo }}</p>
{% endfor %}
<hr>
{% for foo in list %}
    <p>{{ forloop.last }} : {{ foo }}</p>
{% endfor %}
<hr>
{% for foo in list %}
    {% for foo in list %}
        <p>{{ forloop.parentloop.counter }} : {{ forloop.revcounter0 }} : {{ foo }}</p>
    {% endfor %}
{% endfor %}
<hr>

# 反向循环列表
{% for foo in list reversed %}
    <p>{{ foo }}</p>
{% endfor %}

</body>
</html>

(2)if 判断标签

​ {% if 判断条件 %}{% endif %}

  • if语句支持 and 、or、==、>、<、!=、<=、>=、in、not in、is、is not判断,注意条件两边都有空格。
  • 但是不支持连续判断操作:{% if a > b > c %}{% endif %}
  • {% if %}会对一个变量求值,如果它的值是“True”(存在、不为空、且不是boolean类型的false值),对应的内容块会输出。
{% if num > 100 or num < 0 %}
	<p>无效</p>  <!--不满足条件,不会生成这个标签-->
{% elif num > 80 and num < 100 %}
	<p>优秀</p>
{% else %}  <!--也是在if标签结构里面的-->
	<p>凑活吧</p>
{% endif %}

(3)with 起别名标签

​ 使用一个简单地名字缓存一个复杂的变量,多用于给一个复杂的变量起别名,当你需要使用一个“昂贵的”方法(比如访问数据库)很多次的时候是非常有用的;注意:等号左右不要加空格。

方式一:
{% with total=business.employees.count %}
    {{ total }} <!--只能在with语句体内用-->
{% endwith %}

方式二:
{% with business.employees.count as total %}
    {{ total }}
{% endwith %}

(4)for empty联合使用的情况

​ 当循环的hobby没有数据或为空的时候,就显示empty下面的内容

<ul>
    {% for xx in hobby %}
    <li>{{ xx }}</li>
    {% empty %}
        <h2>抱歉,没有查询到相关数据</h2>
    {% endfor %}
</ul>

(5)Django的模板语言中属性的优先级大于方法

​ 处理的字典数据中不要出现以方法名为键的键值对,因为默认会获取该键值对数据,而不是走方法去处理数据,导致得不到想要的数据结果。

def xx(request):
    d = {"a": 1, "b": 2, "c": 3, "items": "100"}
    return render(request, "xx.html", {"data": d})

​ 如上,我们在使用render方法渲染一个页面的时候,传的字典d有一个key是items并且还有默认的 d.items() 方法,此时在模板语言中:{{ data.items }}

​ 默认会取d的items key的值。

4.2、自定义标签
  • 在应用文件夹中创建一个名为templatetags的文件夹(文件夹的名字必须是templatetags)
  • 在templatetags文件夹中创建任意 .py 文件,如:mytag.py
  • 在mytag.py文件中写上如下内容
from django import template
register = template.Library()  # 制作注册器,名字必须叫register
@register.simple_tag
def atag(v1,v2):  # 没有参数个数限制
    print(v1,v2)
    return v1 + v2
  • 在html文件中导入
 {% load mytag %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

</head>
<body>
<h1>base页面</h1>
{% load mytag %}

<div>
    {% atag a b %}  <!-- 注意,是{%%} 来包裹使用,先是标签名称然后空格写参数,参数之间也是空格分隔的 -->
</div>

</body>

</html>
5、Templete模板继承

将一些页面公共的部分,可以抽离出来单独做成一个html页面,使用这些公用部分的其他html文件,只需要继承一下它就可以了,具体使用流程如下:

(1) 创建公用模板,比如内容如下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        body{
            padding: 0;
            margin: 0;
        }
        {% block css %}
        .nav{
            height: 60px;
            background-color: green;
        }
        {% endblock %}
        .nav a{
            color:white;
            text-decoration: none;
        }
        .left-menu{
             30%;
            background-color: rgba(0,0,0,0.5);
            float:left;
        }
        .menu .menu-title{
            text-align: center;
        }
        .main{
            float: right;
             65%;
            height: 300px;
            border: 1px solid red;
        }
    </style>
</head>
<body>

<div class="nav">
    <a href="/xx1/">首页</a>
    <a href="/person/">个人中心</a>
    <a href="/detail/">详情页</a>
</div>
<div class="left-menu">
    <div class="menu">
        <div class="menu-title">菜单1</div>
        <div class="menu-body">
            <div class="item">局部按摩</div>
            <div class="item">全身按摩</div>
            <div class="item">足底按摩</div>
            <div class="item">头部按摩</div>
        </div>
        <div class="menu-title">菜单2</div>
        <div class="menu-body">
            <div class="item">盲人按摩</div>
            <div class="item">推背</div>
            <div class="item">刮痧</div>
            <div class="item">精油、火罐</div>
        </div>
    </div>
</div>
<div class="main">
    {% block content %}
    公共页面
    {% endblock %}
</div>

</body>
{% block js %}
    
{% endblock %}

</html>

(2)将来如果说继承公用模板的html文件中需要修改公用模板中的一些内容,那么需要在公用模板中预留一些钩子,钩子的写法如下

{% block content %}  #block 后面的块名称随便起
公共页面
{% endblock %}
#也可以这样写 {% endblock content %}  #endblock指定名称

(3)继承公用模板需要在html文件中写如下内容:

{% extends 'xx.html' %}  <!-- 需要先继承一下公用模板,写法就是extends '公用模板文件名称',注意,必须写在第一行 -->

{% block css %}
.nav{
    height: 60px;
    background-color: pink;
}
{% endblock %}

{% block content %}
    <h1>首页</h1>
{% endblock %}


(4) 在使用公用模板的其他html文件中,如果需要更改公用模板里面的内容,只需要在html文件中写上相同的钩子,钩子里面写上自定义的内容,写法如下

{% block css %}
.nav{
    height: 60px;
    background-color: pink;
}
{% endblock %}

{% block content %}
    <h1>首页</h1>
{% endblock %}

(5)注意事项:

  • 如果你在模版中使用 {% extends %} 标签,它必须是模版中的第一个标签。其他的任何情况下,模版继承都将无法工作,模板渲染的时候django都不知道你在干啥。

  • 在base模版中设置越多的 {% block %} 标签越好。子模版不必定义全部父模版中的blocks,所以,可以在大多数blocks中填充合理的默认内容,然后,只定义你需要的那一个。多一点钩子总比少一点好。

  • 如果你发现你自己在大量的模版中复制内容,那可能意味着你应该把内容移动到父模版中的一个 {% block %} 中。

  • {{ block super }}的使用,在子模板中也展示出父模板原来钩子中的内容

    {% block content %}
        <h1>首页</h1>
        {{ block.super }}
    {% endblock %}
    
    
  • 为了更好的可读性,你也可以给你的 {% endblock %} 标签一个 名字 。例如:

{% block content %}
...
{% endblock content %}  

 在大型模版中,这个方法帮你清楚的看到哪一个 {% block %} 标签被关闭了。

  • 不能在一个模版中定义多个相同名字的 block 标签。

    #两个block都叫content,这种写法是不对的
    {% block content %}
    
        <h1>首页</h1>
        {{ block.super }}
    {% endblock %}
    {% block content %}
    
        <h1>首页</h1>
        {{ block.super }}
    {% endblock %}
    
6、要注意Template模板渲染实在浏览器解释之前执行的,模板渲染后才轮到浏览器器来执行解释

​ 与safe处理效果相同的mark_safe方法,mark_safr方法需要导入模块

  • mark_safr的使用方法
    • 导入模块:from django.utils.safestring import mark_safe
    • mark_safe(html代码)
  • safe的使用方法:
    • 通过管道符safe:变量|safe

mark_safe与|safe的优缺点:

  • mark_safe可直接返回大量的html标签,可用于自定义标签中,通过后端python代码返回实现
  • |safe每次只能解决一个变量的问题,在自定义标签中不能使用,通过前端模板渲染实现
7、拓展:XSS攻击(上面过滤|safe的拓展)

xss攻击:,全称跨站脚本攻击

​ Django的模板中在进行模板渲染的时候会对HTML标签和JS等语法标签进行自动转义,原因显而易见,这样是为了安全,django担心这是用户添加的数据,比如如果有人给你评论的时候写了一段js代码,这个评论一提交,js代码就执行啦,这样你是不是可以搞一些坏事儿了,写个弹窗的死循环,浏览器就不能用了,浏览器会一直弹出弹窗,这叫做xss攻击,所以浏览器中进行了一些转义处理。但是有的时候我们需要这些HTML元素不被转义,比如我们做一个内容管理系统,后台添加的文章中是经过修饰的,这些修饰可能是通过一个类似于FCKeditor编辑加注了HTML修饰符的文本,如果自动转义的话显示的就是保护HTML标签的源文件。为了在Django中关闭HTML的自动转义有两种方式,如果是一个单独的变量我们可以通过过滤器“|safe”的方式告诉Django这段代码是安全的不必转义。

8、django的UTC格林威治时间的处理

​ 在settings.py文件中,具体情况集体分析,主要修改settings配置文件的参数。

9、Template模板注意事项

在引入的javastript文件中使用模板渲染是不会生效的

​ Django对于html的Template渲染时是以字符串的形式先读取所有的html文件中的内容,然后执行模板渲染,此实javascript的导入部位代码还是字符串,文件还没有被引入,当模板渲染结束后返回给浏览器的时候,浏览器解析时异步执行javascript文件,而此时无法再进行模板渲染了,因为模板渲染是由后台执行的。浏览器并不会,也没有此功能。

​ 若想在javascript中使用模板渲染,那就不能使用文件引入的方式,只能在html文件中写javascript,并在里面使用模板渲染规则

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True
10、组件与动态组件的应用

(1)组件

组件就是一个html文件,其中封装了一些特定功能,比如就是一个导航栏,或者就是一个左侧菜单,相当我们将一些特定功能封装成了一个组件,将来任何其他html文件中如果使用这部分功能,可以直接引入使用。

在django模板渲染系统中使用组件的步骤

第一步:创建组件的html文件,名字随便取,比如叫做zujian.html,比如内容如下,做一个顶部导航栏组件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>zujian</title>
    <style>
        .nav{
            background-color: blue;
            height: 100px;
        }
        .nav a{
            color:white;

        }
    </style>
</head>
<body>

<div class="nav">
    <a href="">百度</a>
    <a href="">京东</a>
    <a href="">个人中心</a>
</div>


</body>

</html>

第二步:使用组件,需要借助下面这个标签语法

{% include '组件文件名称.html' %}

示例:比如我们需要某个html文件中使用,比如show.html文件中使用,show.html文件内容如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
<!-- <style> -->
<!-- 可以修改样式,但是注意权重问题 -->
<!--    .nav{  
            background-color: yellow!important;#}
        } 
-->
<!-- </style> -->
</head>
<body>

<h1>这是show页面</h1> 
<!-- include 'zujian.html' -->  <!-- 在哪里引入组件,组件就生成在页面的对应位置上 -->
</body>

</html>

(2)动态组件的应用

注意:在settings中的INSTALLED_APPS配置当前app,不然django无法找到自定义的标签

  1. 在app中创建templatetags文件夹(文件夹名只能是templatetags)

  2. 在templatetags文件夹中创建任意 .py 文件,如:mytag.py

  3. 在mytag.py文件中写上如下内容

from django import template
register = template.Library() #制作注册器,名字必须叫register
@register.inclusion_tag('zujian2.html')
def xiaolin(v1):
    #v1 = [11,22,33]
    return {'data': v1}
  1. 使用inclusion_tag,比如在show2.html文件中使用:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
{% load mytag %}
{% xiaolin d %}

<h1>这是show2页面</h1>
</body>

</html>
  1. 需要后台给show2.html传递数据,比如views.py文件写法如下
def show2(request):
    d = ['国产', '欧美', '日韩']
    return render(request,'show2.html',{'d': d})

五、静态文件配置

Django的常用的三种静态文件的配置方式

(1)settings配置引入静态文件

  • 在settings.py中配置

    STATICFILES_DIRS = [
        os.path.join(BASE_DIR, 'statics'),  # statics为创建的静态文件夹
    ]
    
  • 在前端文件导入的时候使用settings.py中的STATIC_URL = '/static/'的static路径引入

<!-- html代码 -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="/static/css/index.css">
</head>
<body>
<div class="c">hello world!</div>
</body>
</html>

<!-- inde.css代码 -->
.c{
    height: 100px;
     100px;
    border: 1px solid black;
    background-color: aqua;
    color: aliceblue;
}
# settings中的配置

STATIC_URL = '/static/'

STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'statics'),
]

(2)template渲染模式static引入静态文件

  • 通过模板渲染在html文件头部引入{% load static %}
  • 引入文件方式:<link rel="stylesheet" href="{% static "文件路径" %}">
<!-- html代码 -->
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="{% static "css/index.css" %}">
</head>
<body>
<div class="c">hello world!</div>
</body>
</html>

<!-- inde.css代码 -->
.c{
    height: 100px;
     100px;
    border: 1px solid black;
    background-color: aqua;
    color: aliceblue;
}

(3)template渲染模式get_static_prefix引入静态文件

  • 通过模板渲染在html文件头部引入{% load static %}
  • 引入文件方式:<link rel="stylesheet" href="{% get_static_prefix %}文件路径 ">
  • 注:路径和大括号之间不能有空格, %}文件路径,要无缝对接,否则无法引入文件
<!-- html代码 -->
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="{% get_static_prefix %}css/index.css ">
</head>
<body>
<div class="c">hello world!</div>
</body>
</html>

<!-- inde.css代码 -->
.c{
    height: 100px;
     100px;
    border: 1px solid black;
    background-color: aqua;
    color: aliceblue;
}

六、数据库的连接

这里只讲解两种数据库的连接模式:

  • Django自带的sqlite3小型数据库,实际应用不常用,多用于测试代码时使用

  • mysql数据库的连接操作

(1)sqlite3

​ Django默认自带的数据库,直接执行初始化创建数据库即可

(2)mysql

​ mysql数据库的连接需要配通过包来实现,在Java中叫连接池,不过python把这个封装成了包,很多包都可以实现,对于django1.0版本通常使用pymysql模块来实现连接,但是对于django2.0版本以上可能会出错,这时也可以使用mysqlclient模块来连接数据库,当然还有一些其它连接数据库的模块。

​ 这里主要介绍pymysql的连接方法

  • 下载好pymysql模块后

  • 在项目主目录的__init__.py中指定

    	import pymysql
    	pymysql.install_as_MySQLdb()
    
  • settings文件中的DATABASES里面配置数据库的连接信息

    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.mysql',  # 数据库类型
            'NAME': 'drawer',					   # 数据库名
            'USER': 'root',						   # 用户名
            'PASSWORD': '1234',					   # 密码
            'HOST': 'localhost',				   # ip
            'PORT': '3306',						   # 端口
            'OPTIONS': {
                "init_command": "SET sql_mode='STRICT_TRANS_TABLES'",
            }  # 这个可以不写
        }
    }
    
  • 注意:连接mysql数据库的时候要现在mysql中创建好数据库,不然连接时会报错,因为djang无法自动创建数据库

(3)初始化和执行数据库

​ 初始化命令:python manage.py makemigrations

​ 执行操作命令:python manage.py migrate

(4)通过pycharm的图形界面操作来操作数据库

​ 在pycharm的右边框位置有个database在里面可以创建sqlite和MySQL的连接实现对数据库的操作

(5)还有一些关于数据库操作中的问题,后面会总结,关注本人博客即可。

七、ORM(对象关系映射)

ORM全称:object relational mapping ---- 对象 关系 映射

主要完成的就是数据库操作。

  • MVC或者MVC框架中包括一个重要的部分,就是ORM,它实现了数据模型与数据库的解耦,即数据模型的设计不需要依赖于特定的数据库,通过简单的配置就可以轻松更换数据库,这极大的减轻了开发人员的工作量,不需要面对因数据库变更而导致的无效劳动
  • ORM是“对象-关系-映射”的简称。(Object Relational Mapping,简称ORM)(将来会学一个sqlalchemy,是和他很像的,但是django的orm没有独立出来让别人去使用,虽然功能比sqlalchemy更强大,但是别人用不了)
  • 类对象--->sql--->pymysql--->mysql服务端--->磁盘,orm其实就是将类对象的语法翻译成sql语句的一个引擎,明白orm是什么了,剩下的就是怎么使用orm,怎么来写类对象关系语句。

1、model类的属性参数

比如:models.CharField(null=True,blank=True)
(1)null
	如果null=True,Django将用NULL来在数据库中存储值。默认情况null=False
(2)blank
	如果blank=True,该字段允许不填,默认blank=False
	需要注意,null与default不同;null是数据库范畴,blank是数据验证范畴
(3)dfault
	设置字段默认值,可以是一个值或者可调用对象。
	如果可调用,每有新对象被创建它都会被调用,如果字段没有设置可以为空,那么当进行添加新字段操作时要加上default默认值,否则会有操作提示,让你添加默认值。
(4)primary_key
	主键,当primary_key=True就是为当前字段添加主键。
	如果表中没有设置primary_key=True,Django会自动添加一个IntegerField字段作为主键
	当表中设置了primary_key=True,就覆盖了默认添加主键的功能
(5)unique
	唯一属性(唯一索引),当字段设置unique=True就表示该字段在整张表中必须是唯一的
(6)choices
	由二元元组组成的一个可迭代对象(例:列表,元组),用来给字段提供选择选项。
	如果设置了choices,默认的表单是一个先择框而不是一个文本框,选择框中的选项就choice中的选项值
(7)db_index
	db_index=True时代表此字段设置数据库索引
(8)auto_now_add
	auto_now_add=True,在创建数据记录的时候会把当前时间添加到数据库相应字段中
(9)auto_now
	auto_now=True,每次更新数据记录时会自动更新该字段,标识该数据最后一次的修改时间
	注:只能在save方式时触发自动更新时间的动作
(10)DatetimeField、DateField、TimeField都可以使用auto_now_add和auto_now这两个属性
1.1、models中的FIeld类对应类型和对应的mysql的数据类型对比

(1)可以在pycharm中的Project项目区中的External Libraries中的Python解释器下的site-packages文件夹中的:site-packagesdjangodbackendsmysql中base.py文件查看(前提是你使用的IDE是pycharm)

(2)也可以在pip安装的django包中的路径找到对应关系的文件:/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/django/db/backends/mysql/base.py

(3)在创建的虚拟环境中的pip安装的django包中的路径找到对应关系的文件:/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/django/db/backends/mysql/base.py

(4)下面是对应关系

'AutoField': 'integer AUTO_INCREMENT',
'BigAutoField': 'bigint AUTO_INCREMENT',
'BinaryField': 'longblob',
'BooleanField': 'bool',
'CharField': 'varchar(%(max_length)s)',
'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
'DateField': 'date',
'DateTimeField': 'datetime',
'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
'DurationField': 'bigint',
'FileField': 'varchar(%(max_length)s)',
'FilePathField': 'varchar(%(max_length)s)',
'FloatField': 'double precision',
'IntegerField': 'integer',
'BigIntegerField': 'bigint',
'IPAddressField': 'char(15)',
'GenericIPAddressField': 'char(39)',
'NullBooleanField': 'bool',
'OneToOneField': 'integer',
'PositiveIntegerField': 'integer UNSIGNED',
'PositiveSmallIntegerField': 'smallint UNSIGNED',
'SlugField': 'varchar(%(max_length)s)',
'SmallIntegerField': 'smallint',
'TextField': 'longtext',
'TimeField': 'time',
'UUIDField': 'char(32)',
1.2、字段的choices属性

(1)使用

class Book(models.Model):
		...
  	# sex = models.CharField(max_length=12)
    sex_choices = ((1, '男性'),(0, '女性'))  #enum枚举 单选
    sex = models.IntegerField(choices=sex_choices, default=1)

(2)获取含有choices属性的字典数据方法

ret = models.Book.objects.get(id=5)
print(ret.sex)  #  1 获取到的是数据库中存储的字段数据
print(ret.get_sex_display())  # 男性 -- 能够帮我们获取到该字段数据对应的choices指定的元祖中的这个数据对应的文本内容
    # sex_choices = ((1, '男性'), (0, '女性'))  # enum枚举 单选
    # sex = models.IntegerField(choices=sex_choices, default=1)
    # 比如拿sex这个字段来说,数据库中存的数据是1,表示男性,如果我想获取到男性这个字符串数据,我直接通过模型类对象.get_sex_display()这个方法就能获取到,这个方法的语法是get_模型类属性名称_display()
1.3、auto_now_add和auto_now

(1)使用

class ShowTime(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=32)
    brithday = models.DateTimeField(auto_now_add=True)  # 添加记录时,自动添加创建时间
    bday = models.DateTimeField(auto_now=True)  # 添加记录时,也能自动添加创建时间,并且更新记录时,自动更新为当前时间

(2)操作

    # models.DateTimeField(auto_now=True)中的auto_now=True对update方法没有效果
    # models.ShowTime.objects.all().update(
    #     name='chao2',
    # )

    # auto_now=True只有在使用save方法来进行更新动作时,才生效,才会自动更新修改时间
    ret = models.ShowTime.objects.get(id=1)
    ret.name = 'zhen'
    ret.save()

2、ORM单表增删改查

2.1、增
  • 添加单条数据

    • 第一种添加方式

      obj = models.表(字段1=值1,字段2=值2,...)

      obj.save() # 一定要执行save否则不会执行插入数据操作

    • 第二种添加方式

      obj = models.表.objects.create(字段1=值1,字段2=值2,...)

    • 注意点:create添加完数据后会返回插入数据的类对象,通过这个类对象:对象.属性 可以获取到对应字段的数据

  • 添加多条数据

    • 通过bulk_create

      obj_list = []
      for i in range(1,10):
          obj = models.Book(
              title=f'水浒传{i}',
              state=True,
      
              pub_date=f'2018-11-{i}',
              price=11.11 + i,
              publish='出版社'
          )
          obj_list.append(obj)
      
      models.Book.objects.bulk_create(obj_list)
      
    • 也可以通过字典的方式添加多条数据,使用 **kwargs

2.2、删
  • 删除delete方法
# 调用者可以是model对象,也可以是QuerySet类型数据
    # 调用者可以是model对象,可以是querset类型数据
    # obj = models.Book.objects.get(id=3).delete()
    # print(obj) # (1, {'app01.Book': 1})

    # 返回结果其实还是受影响的行数

    # obj = models.Book.objects.filter(publish='出版社').delete()
    # print(obj)  # (2, {'app01.Book': 2})
    
    # 错误示例演示
    # obj = models.Book.objects.delete()
    # 报错'Manager' object has no attribute 'delete'
    # 控制器没有delete方法,原因就是怕你一下子全部删除了所有数据
    # 如果想删除所有数据,
2.3、改
  • 第一种:模型对象修改数据:get

    obj = models.Book.objects.get(id=1)
    # print(obj)
    obj.title = 'lol冠军之路'
    obj.price = 20
    obj.save()
    布尔值类型保存数据时,数据库中保存的是数字:1--True  0--False
    mysql中的bool使用tinyint(0)和tinyint(1)来表示的
    
  • 第二种:QuerySet对象结果集:update

    • 注:update方法,调用者可以是objects控制器,可以是queryset类型数据,但是不能是模型类对象
    (1)objects调用,下面示例是整列数据更新
    models.Book.objects.update(
        state=False,
        title='xx',
    )
    
    (2)queryset类型数据调用,下面也是更新整列数据
    models.Book.objects.all().update(
        state=True,
        # title='xx',
    )
    
    (3)queryset类型数据调用,更新部分记录数据
    # obj = models.Book.objects.filter(publish='刘伟出版社').update(
    #     # state=True,
    #     title='红楼懵',  # 别奇怪,就叫红楼懵,难道你看红楼梦,不懵吗?
    # )
    # print(obj) # 2 返回结果为受影响的行数
    
    (4)模型类对象不能直接调用update方法,错误示例如下
    models.Book.objects.get(id=1).update(
    	title='xx',
    )
    报错信息'Book' object has no attribute 'update'
    
2.4、查
  • 查询功能的13个API接口

    1. all()

      • all查询所有的数据,返回结果为QuerySet类型数据,QuerySet类似于列表,里面存放的是model类的实例化对象,是一个对象结果集,每个对象表示一条记录,对象中的对应数据有着该行记录的字段数据
      • QuerySet类似于列表,但是比列表还多一些其他的功能,这是orm给咱们封装出来的新的数据类型
    2. filter()

      • 过滤查询,结果为QuerySet类型数据,里面存放的是model类的实例化对象,是一个对象结果集,每一项也是模型类对象
      • 当查不到任何内容时,返回空的queryset
    3. get(**kwargs)

      • 过滤查询,结果有且只能有一条数据,结果不是QuerySet数据类型,是模型类对象数据,model类的实例化对象
      • 查询结果不止一个,多于一个了,就报这个错误类似,其中的部分错误数据是根据表中数据变化的
        get() returned more than one Book -- it returned 9!
      • 查询不到任何数据时,也会报错,错误如下,其中的表名是根据操作的表来决定的
        Book matching query does not exist.
      • get无法和update串联操作使用
      • 示例:
        Book.objects.get(id=1)
        models.Book.objects.all().get(id=8)
    4. exclude(**kwargs)

      • 排除的情况,包含了与所给筛选条件不匹配的对象
      • orm映射中没有不等于的操作,可以通过exclude来操作,也可以使用Q查询来操作
      • exclude的返回值类型是QuerySet数据类型,调用者可以是QuerySet类型数据,或者object控制器
      • 示例:
        Book.objects.exclude(id=6),返回id不等于6的所有的对象,
        或者在queryset基础上调用,Book.objects.all().exclude(id=6)
    5. order_by(*field)

      • 对查询结果进行排序,返回值是QuerySet类型数据,调用者可以是queryset类型数据,也可以是objects控制器

      • 除了使用order_by进行查询数据后排序,还可以在models.py中进行Meta属性设置默认排序规则,不过要注意此操作会对distinct去重造成影响

        方式1:
        	order_by(*field)方法进行排序
        方式: 在模型类中通过Meta类来执行排序规则
        class Book(models.Model):
        
            id = models.AutoField(primary_key=True)  # 自增、主键
            title = models.CharField(max_length=64,null=True)
            state = models.BooleanField(default=True)
            pub_date = models.DateField(null=True)
            # price = models.DecimalField(max_digits=20,decimal_places=5,null=True)
            price = models.DecimalField(max_digits=8,decimal_places=2,null=True)
            publish = models.CharField(max_length=32)
        
            def __str__(self):
                return self.title + '价格' + str(self.price)
        
            class Meta:
                ordering = ['id',]  #制定了它之后,所有的本表的查询结果,都按照id进行升序排列,还可进行多天剑排序规则的指定
        
        reverse()翻转,必须在上面两者的基础上,才能进行结果顺序翻转
        
      • 示例:
        models.Book.objects.order_by('price') # 获取所有数据,并且按照price字段升序排列
        models.Book.objects.order_by('-price') # 获取所有数据,并且按照price字段降序排列
        models.Book.objects.all().order_by('-price') # queryset类型数据调用

      • 多条排序示例:
        models.Book.objects.all().order_by('price','id') # 直接写price,默认是按照price升序排列,按照字段降序排列,就写个负号就行了order_by('-price'),order_by('price','id')是多条件排序,按照price进行升序,price相同的数据,按照id进行升序

    6. reverse()

      • 对查询结果反向排序,可以是QuerySet类型的数据来调用,也可以是objects控制器调用
      • 示例:
        obj_list = models.Book.objects.all().order_by('id').reverse()
        obj_list = models.Book.objects.reverse() # 注意:如果每给Book模型类执行Meta中的ordering属性,那么reverse()默认是不生效的。具体看官方文档的详细介绍
    7. count()

      • 返回数据库中匹配查询的(QuerySet)的对象数量,结果是个数字,可以QuerySet类型的数据来调用,也可以是objects控制器调用
      • 示例:
        obj_list = models.Book.objects.count() # 默认统计的整表的所有数据量
        obj_list = models.Book.objects.all().count()
    8. first()

      • 返回第一条记录对象,结果得到的都是model对象,不是queryset,可以QuerySet类型的数据来调用,也可以是objects控制器调用
      • 示例:
        Book.objects.all().first() #同:Book.objects.all()[0]
        Book.objects.first()
    9. last()

      • 返回最后一条记录对象,结果得到的都是model对象,不是queryset,可以QuerySet类型的数据来调用,也可以是objects控制器调用
      • 示例:
        Book.objects.all().last() #同:Book.objects.all()[-1] ,但是负数索引取值会报错,错误信息为: Negative indexing is not supported. queryset类型数据,不支持负数索引取值的形式
        Book.objects.last()
    10. exists()

      • 如果QuerySet包含数据,就返回True,否则返回False,可以QuerySet类型的数据来调用,也可以是objects控制器调用

      • 示例:
        obj_list = models.Book.objects.exists() # 判断表中是否有数据
        obj_list = models.Book.objects.filter(id=100).exists() # 判断查询结果集中是否有数据,有得到True,没有得到False

      • 还有个点需要注意一下:

          空的queryset类型数据布尔值为False,但是一般不用它来判断数据库里面是不是有数据,如果有大量的数据,你用它来判断,那么就需要查询出所有的数据,效率太差了,用count或者exits

      • 示例:
        'select * from book where name="xx";'
        if obj_list: # 会将满足条件的所有数据进行一次查询,效率低

        ​ select count(id) from book where name='xx';

        ​ if obj_list.count(): # 效率较高,按照查询结果对象的id值进行个数统计,

        ​ select id from book where name='xx' limit 1; # 查询一条数据,不用扫描所有数据

        ​ if obj_list.exists(): # 效率高

          例:all_books = models.Book.objects.all().exists() # 翻译成的sql是SELECT (1) AS a FROM app01_book LIMIT 1,就是通过limit 1,取一条来看看是不是有数据

    11. values(*filed)

      • 返回一个特殊的QuerySet,运行后得到的并不是一系列model的实例化对象,返回的queryset类型,里面的元素是字典数据,既然是queryset类型数据,那么就可以继续链式调用QuerySet类型的其他的查找方法,其他方法也是一样的;可以QuerySet类型的数据来调用,也可以是objects控制器调用

      • 示例:

        ​ obj_list = models.Book.objects.values() # 默认获取的表中所有记录的字典数据,字典中的键是字段名称(模型类属性名称),值是每个字段的对应数据

        # obj_list = models.Book.objects.all().values()
        # 取指定字段数据
        obj_list = models.Book.objects.all().values('id','title') 
        # <QuerySet [{'id': 5, 'title': '水浒传2'}, {'id': 6, 'title': '西游记'},...]>
        
    12. values_list(*filed)

      • 它与values()非常相似,它返回的是一个包含元组queryset序列,values返回的是一个包含字典QuerySet序列;可以QuerySet类型的数据来调用,也可以是objects控制器调用

      • 示例:

        ​ obj_list = models.Book.objects.all().values_list('id','title')
        ​ 结果:#<QuerySet [(5, '水浒传2'), (6, '西游记'),...]>

        ​ 不太好的一点,就是数据不知道对应的是哪个字段

    13. distinct()

      • 去重,values和values_list得到的queryset类型的数据来调用,从返回结果中剔除重复记录

      • 注意点:

        class Book(models.Model):
        
            id = models.AutoField(primary_key=True) #自增、主键
        		...
            publish = models.CharField(max_length=32)
        
            def __str__(self):
                return self.title + '价格' + str(self.price)
        		
            
            class Meta:
                ordering = ['id','publish',]
        
        
        obj_list = models.Book.objects.all().order_by('publish').values('publish').distinct()
        
        
        # 当模型类中制定了默认排序字段,那么当我们使用distinct方法进行去重时,默认会按照我们指定的排序字典进行去重,会导致去重结果不是我们想要的,所以要么我们在模型类中不指定排序字段,如果制定了排序字段,我们在使用distinct方法前面加上order_by方法,并且order_by方法中的字段就是我们要去重的字段数据
        
        官方文档中的下面这种写法
        Entry.objects.order_by('pub_date').distinct('pub_date')
        只适用于PostgreSQL数据库,mysql不支持distinct方法里面写字段
        
  • 基于双下划线的模糊查询
    Book.objects.filter(price__in=[100,200,300]) # price值等于这三个里面的任意一个的对象
    	示例:
      	ret = models.Book.objects.filter(price__in=['20.11', '1.5'])  # 针对decimal字段要用字符串
        ret = models.Book.objects.filter(price2__in=[20, 18])  # 针对float或者int类型要用数字
    
    Book.objects.filter(price__gt=100)  # 大于,大于等于是price__gte=100,别写price>100,这种参数不支持
    
    Book.objects.filter(price__lt=100)  # 小于,小于等于是price__lte=100
    
    Book.objects.filter(price__range=[100,200])  # sql的between and,大于等于100,小于等于200
    
    # 针对一些字符串数据操作:
    # 找到包含某些字符串的数据
    Book.objects.filter(title__contains="python")  # title值中包含python的
    Book.objects.filter(title__icontains="python")  # 不区分大小写
    
    # 找到以某些字符串开头或者结尾的数据
    Book.objects.filter(title__startswith="py")  # 以什么开头,istartswith  不区分大小写
    Book.objects.filter(title__istartswith='p')  
    Book.objects.filter(title__endswith="py")  # 以什么结尾,iendswith  不区分大小写
    Book.objects.filter(title__iendswith='p') 
    
    # 日期时间类型数据的操作
    # 按照年份查
    Book.objects.filter(pub_date__year=2012)
    Book.objects.filter(pub_date__year='2012')
    
    # 查某年某月的
    models.Book.objects.filter(pub_date__year=2018,pub_date__month=12)
    
    # 查某年某月某日
    models.Book.objects.filter(pub_date__year=2018,pub_date__month=11,pub_date__day=7)
    
    # 只查某月
    models.Book.objects.filter(pub_date__month=7)
    
    # 查询某个字段为空的数据
    models.Book.objects.filter(title__isnull=True) #正规的
    models.Book.objects.filter(title=None)  
    
    # 原生sql语句:
    # SELECT `app01_book`.`id`, `app01_book`.`title`, `app01_book`.`state`, `app01_book`.`pub_date`, `app01_book`.`price`, `app01_book`.`price2`, `app01_book`.`publish`, `app01_book`.`sex` FROM `app01_book` WHERE `app01_book`.`title` IS NULL ORDER BY `app01_book`.`id` ASC, `app01_book`.`publish` ASC
    

    查看某个orm语句的原生sql语句方法

    print(models.Book.objects.filter(title__isnull=True).query)
    
2.5、动态操作
  • 更新或添加:Update_or_create()

    • 有就更新,没有就添加update_or_create()

      # 有就更新的操作示例:
      models.Book.objects.update_or_create(
          id=6,
          defaults={
              'title':'西游记',
              'state':True,
              'pub_date':'2018-11-11',
              'price':1.5,
              'publish':'武大郎出版社'
          }
      )
      
      # 没有就创建(添加)的示例:
      models.Book.objects.update_or_create(
          id=100,
          defaults={
              'title': '红楼梦',
              'state': True,
              'pub_date': '2018-11-11',
              'price': 1.5,
              'publish': '武大郎出版社'
          }
      )
      
      # 查询为多条记录的演示示例:报错,因为update_or_create内部进行查询时,使用的是get方法查询
      models.Book.objects.update_or_create(
      	publish='武大郎出版社',
          defaults={
              'title': '红楼梦2',
              'state': True,
              'pub_date': '2018-11-11',
              'price': 1.5,
              'publish': '武大郎出版社'
          }
      )
      
  • 查询或添加:get_or_create()

    • 查询或创建,能够查询到就返回查询结果,查询不到就添加记录,查询时内部使用的还是get方法

      # 查询到结果的示例:
      ret = models.Book.objects.get_or_create(
          id=100,
          defaults={
              'title': '西游记',
              'state': True,
              'pub_date': '2018-11-11',
              'price': 1.5,
              'publish': '武大郎出版社'
          }
      )
      print(ret)  # (<Book: 红楼梦>, False)  # 如果没创建,那么返回结果元组中的第二个元素为Flase
      
      # 查询不到,自动添加记录的示例
      ret = models.Book.objects.get_or_create(
          id=102,
          defaults={
              'title': '西游记',
              'state': True,
              'pub_date': '2018-11-11',
              'price': 1.5,
              'publish': '武大郎出版社'
          }
      )
      print(ret)  # (<Book: 西游记>, True)  # 如果创建了新纪录,那么返回结果元祖中的第二个元素为True
      
2.6、关键字传参的两种方式:
方式1
filter(id=5, publish='出版社')
create(id=5, publish='出版社')
...

方式2
filter(**{'id':5, 'publish':'出版社'})
...

3、ORM多表操作增删改

3.1、表的基本结构
from django.db import models

# Create your models here.

class Author(models.Model):
    """作者表"""
    # id = models.AutoField(primary_key=True)
    # 其实模型类中的id主键字段不需要我们手动指定,django的orm默认会给每张表都添加一个id字段并且为主键,如果我们自己指定了主键,以我们自己指定的为准,就不会自动帮你添加主键字段了
    name = models.CharField(max_length=32)
    age = models.IntegerField()
    # ad = models.ForeignKey(to='AuthorDetail',to_field='id',on_delete=models.CASCADE)
    # ad = models.ForeignKey('AuthorDetail', on_delete=models.CASCADE)  # 只是单纯的外键关系,需要手动指定唯一约束才可以,在orm中提供了一对一关系的类,叫做OneToOneField
    ad = models.OneToOneField('AuthorDetail') #foreign + unique
    # books = models.ManyToManyField('Book')
    
    # models.ForeignKey(AuthorDetail)这里面的外键关联的表,可以不写引号,但是如果不写引号,那么这个外键关联的表必须在写这个外键字段的表上面,一般我们都写上引号,这样就不用考虑哪个表写在上面,哪个表写在下面了
    # ad = models.ForeignKey(AuthorDetail, on_delete=models.CASCADE)
    # to=可以用写,to_field='id'也可以不用写,自动找到是to=那张表的id字段
    # django 1.11 版本的foreignkey 默认有on_delete=models.CASCADE这个属性,所以如果要做这种级联删除模式,可以不用写这个属性,但是django2.0之后,必须要自己手动加上这个属性

    # 关于级联,级联有很多,这里用到的就是下面这个,还有些其他的下面会讲解
    # models.CASCADE 级联删除  # 没办法设置级联更新,要做级联更新,自己通过原生sql去加上,就是修改表

class AuthorDetail(models.Model):
    """作者详细信息表"""
    birthday = models.DateField()
    telephone = models.CharField(max_length=24)
    address = models.CharField(max_length=64)
    #通过手机号查找某人  手机号151开头

class Publish(models.Model):
    name = models.CharField(max_length=64)
    city = models.CharField(max_length=64)

class Book(models.Model):
    title = models.CharField(max_length=64)
    pub_date = models.DateField()
    price = models.DecimalField(max_digits=10,decimal_places=2)
    pub = models.ForeignKey('Publish')
    # ForeignKey这个类,生成数据库字段的时候,会自动将该属性名称_id作为我们的数据库字段名称
    authors = models.ManyToManyField('Author')
    # authors在执行数据库同步指令之后,不会生成字段,而是会帮我们生成一个第三张表,这个表就是书籍表和作者表的多对多关系表

# class BookToAuthor(models.Model):
额外拓展:**{}打散和**request.POST打散
print(request.POST.dict())  # dict()方法能将QueryDict类型数据转换为普通字典类型数据

# 传数据时,可以用**{}打散的方式来传输入,但是如果碰到models中有decimal类型的字段数据,那么update更新时,会对提交的数据进行decimal类型数据转换,
# 发现有Decimal数据要存储,会将提交的数据转换为Decimal类型来存储,所有个数据类型强转的过程,导致如果我们直接写**request.POST,会报错,所以引入了request.POST.dict()这个方法,其实如果说不涉及到强制类型转换失败的请求,参数直接写**request.POST就可以
obj_list.update(
  **request.POST.dict()
)

<!-- 模型类中的pk属性能够自动帮我们找到模型类中的主键字段 -->

<form action="/edit_book/{{ id }}/" method="post">
<form action="{% url 'edit_book' id %}" method="post">
<form action="{% url 'edit_book' obj.id %}" method="post">
<form action="{% url 'edit_book' obj.pk %}" method="post">
3.2、一对一
  1. 创建一对一关系字段的一些属性

    to
        设置要关联的表。
    
    to_field
        设置要关联的字段。
        
    on_delete
        同ForeignKey字段。这个是级联属性,django2.0之后需要手动添加,之前默认就有的内置属性
    
  2. 一对一关系表的增删改查操作

    • # 一对一
          # 如果使用的是模型类的关系属性名称来添加数据,那么属性名称对应的值必须是关联表中的某条记录的模型类对象
          # au_obj = models.AuthorDetail.objects.get(id=1)
          # models.Author.objects.create(
          #     name='eric',
          #     age=26,
          #     ad=au_obj  # 其实存在数据库中的还是au_obj的id值
          # )
      
          # 如果使用数据库表字段的形式来创建关系记录数据,那么需要使用数据库中表字段名称来指定数据(用的居多)
          # au_obj = models.AuthorDetail.objects.get(id=1)
          # models.Author.objects.create(
          #     name='pdd',
          #     age=16,
          #     ad_id=2  # 直接使用关联表中的某条记录的id值的方式
          # )
      
    • #删除
          # delete方法
          # models.Author.objects.filter(id=1).delete()
          # models.AuthorDetail.objects.filter(id=1).delete()
          # 一对一和一对多删除一样
      
    • # 修改
          # 在一对一和一对多关系时,和单表操作是一样的
          # 一对一
          # models.Author.objects.filter(id=1).update(name='xx',ad=模型类对象)
          # models.Author.objects.filter(id=1).update(name='xx',ad_id=2)
      
          # pub_obj = models.Publish.objects.get(id=2)
          # 一对多
          # models.Book.objects.filter(id=1).update(pub=pub_obj)
          # models.Book.objects.filter(id=2).update(title='第二部',pub_id=2)
      
    • 查询功能内容比较多,后面分出一块详细补充

3.3、一对多(多对一)
  1. 创建一对多关系字段的一些参数

    to
        设置要关联的表
    
    to_field
        设置要关联的表的字段
    
    related_name
        反向操作时,使用的字段名,用于代替原反向查询时的'表名_set'。
        
    related_query_name
        反向查询操作时,使用的连接前缀,用于替换表名。
    
    on_delete
        当删除关联表中的数据时,当前表与其关联的行的行为。
    
  2. 一对多(多对一)表的增删改查

    • # 一对多
          # book表和publish表是多对一的关系
          # 添加记录和上面的一对一一样
          # 写法1
          # publish_obj = models.Publish.objects.get(id=1)
          # models.Book.objects.create(
          #     title='红楼梦',
          #     pub_date='2008-09-09',
          #     price=88.88,
          #     pub=publish_obj  # 某个出版社的模型类对象
          # )
          # 写法2
          # models.Book.objects.create(
          #     title='金鳞',
          #     pub_date='2008-09-09',
          #     price=82.82,
          #     pub_id=1  # 某个出版社记录的id值
          # )
      
    • #删除
          # delete方法
          # models.Author.objects.filter(id=1).delete()
          # models.AuthorDetail.objects.filter(id=1).delete()
          # 一对一和一对多删除一样
      
    • # 修改
          # 在一对一和一对多关系时,和单表操作是一样的
          # 一对一
          # models.Author.objects.filter(id=1).update(name='xx',ad=模型类对象)
          # models.Author.objects.filter(id=1).update(name='xx',ad_id=2)
      
          # pub_obj = models.Publish.objects.get(id=2)
          # 一对多
          # models.Book.objects.filter(id=1).update(pub=pub_obj)
          # models.Book.objects.filter(id=2).update(title='第二部',pub_id=2)
      
    • 查询功能内容比较多,后面分出一块详细补充

3.4、多对多
  1. 创建多对多关系字段的一些参数

    ### 多对多的参数:
    to
    	设置要关联的表
    
    related_name
    	同ForeignKey字段。
    
    related_query_name
    	同ForeignKey字段。
    	
    through
    	在使用ManyToManyField字段时,Django将自动生成一张表来管理多对多的关联关系。
    
    ### 但我们也可以手动创建第三张表来管理多对多关系,此时就需要通过        
    through来指定第三张表的表名。
    
    through_fields
    	设置关联的字段。
    
    db_table
    	默认创建第三张表时,数据库中表的名称。       
    	示例:authors = models.ManyToManyField('Author',db_table='xx')
    
  2. 创建第三张表时的三种方式

    • 方式1

      手动创建第三张表(没办法使manytomanyfield提供的操作第三张表数据时的方法)
      # 想操作第三张,就需要自己写sql或者直接对第三张表来添加数据,
      # 比如models.Author2Book.objects.create(author_id=1,book_id=1,xx='oo')
        class Book(models.Model):
            title = models.CharField(max_length=32, verbose_name="书名")
      
        class Author(models.Model):
            name = models.CharField(max_length=32, verbose_name="作者姓名")
      
        # 自己创建第三张表,分别通过外键关联书和作者
        class Author2Book(models.Model):
            author = models.ForeignKey(to="Author")
            book = models.ForeignKey(to="Book")
            xx = models.CharField(max_length=32)  # 拓展字段
            class Meta:
                unique_together = ("author", "book")
      
    • 方式2

      # 中介模型,orm提供的有些方法可以用,有些用不了,比如add添加数据的方法
      # 手动创建第三张表,并通过ManyToManyField来指定一下这个关系表
      class Book(models.Model):
          title = models.CharField(max_length=32, verbose_name="书名")
      
      # 自己创建第三张表,并通过ManyToManyField指定关联
      class Author(models.Model):
          name = models.CharField(max_length=32, verbose_name="作者姓名")
          books = models.ManyToManyField(to="Book", through="Author2Book", through_fields=("author", "book"))
          # through_fields接受一个2元组('field1','field2'):
          # 其中field1是定义ManyToManyField的模型外键的名(author),field2是关联目标模型(book)的外键名。
      
      # 比如author_obj的id为1
      author_obj.books.add(1,2)
      '''
      Author2Book
      id author_id  book_id  xx
      1   1          1       怎么添加
      2   1          2       怎么添加  add方法搞不定
      '''
      class Author2Book(models.Model):
          author = models.ForeignKey(to="Author")
          book = models.ForeignKey(to="Book")
          # 可以扩展其他的字段了
          xx = models.CharField(max_length=32)  # 拓展字段
          class Meta:
              unique_together = ("author", "book")
      
    • 方式3

      通过ManyToManyField自动生成第三张表
      class Book(models.Model):
          title = models.CharField(max_length=32, verbose_name="书名")
      
      # 通过ORM自带的ManyToManyField自动创建第三张表
      class Author(models.Model):
          name = models.CharField(max_length=32, verbose_name="作者姓名")
          books = models.ManyToManyField(to="Book", related_name="authors")  #自动生成的第三张表我们是没有办法添加其他字段的
      
  3. 多对多表的增删改查

    • # 多对多 
          # 作者表和书籍表是多对多关系
          # title1  --  name1和name2
          # book_obj = models.Book.objects.create(
          #     title='title1',
          #     pub_date='2020-07-10',
          #     price=2,
          #     pub_id=1
          # )
      
          # author_obj1 = models.Author.objects.create(
          #     name='name1',
          #     age=16,
          #     ad_id=3  # 直接使用关联表中的某条记录的id值的方式
          # )
          # author_obj2 = models.Author.objects.create(
          #     name='name2',
          #     age=16,
          #     ad_id=4  # 直接使用关联表中的某条记录的id值的方式
          # )
      
          # 方式1
          # book_obj.authors.add(author_obj1,author_obj2)  # 写对应作者的模型类对象
          # 方式2
          # book_obj.authors.add(1, 4)  # 直接写作者记录的id值
          # 方式3
          book_obj = models.Book.objects.get(id=1)
          book_obj.authors.add(*[2, 3])  # 直接写作者记录的id值
          '''
          app01_book_authors
          id  book_id  author_id
          1   3        3
          2   3        4
          '''
      
    • 改和删

      # 多对多更新和删除
          # 针对关系记录的操作
          # book_obj = models.Book.objects.get(id=1)
      
          # author_obj = models.Author.objects.get(id=2)
          # 删除remove  在多对多关系表中删除了书籍id为1的,作者id为2的记录删除
          # book_obj.authors.remove(2)  # 删除单条
          # book_obj.authors.remove(2, 3)  # 删除多条
          # book_obj.authors.remove(author_obj)  # 按照模型类对象为参数进行删除
      
          # 清空clear
          # book_obj.authors.clear()  # 将当前书籍对应的所有作者在多对多关系表中的关系记录,全部删除
      
          # 更新(修改)
          # book_obj = models.Book.objects.get(id=4)
          # book_obj.authors.set('3')  # 参数是可迭代类型数据
          # book_obj.authors.set([3, ])  # 更新多个记录
      
          # book_obj = models.Book.objects.get(id=1)
          # book_obj.authors.set([4, ])
          # set动作有两步:1 先执行clear   2 再执行add添加
      
    • 查询功能内容比较多,后面分出一块详细补充

3.5、创建模型类时的一些元信息配置
元信息
    ORM对应的类里面包含另一个Meta类,而Meta类封装了一些数据库的信息。主要字段如下:
class Author2Book(models.Model):
    author = models.ForeignKey(to="Author")
    book = models.ForeignKey(to="Book")
    class Meta:
        unique_together = ("author", "book")

db_table
    ORM在数据库中的表名默认是 app_类名,可以通过db_table可以重写表名。db_table = 'book_model'

index_together
    联合索引。

unique_together
    联合唯一索引。

ordering
    指定默认按什么字段排序。
    ordering = ['pub_date',]
    只有设置了该属性,我们查询到的结果才可以被reverse(),否则是能对排序了的结果进行反转(order_by()方法排序过的数据)
3.6、db_column指定列名称
title = models.CharField(max_length=64,db_column='xx')
author = models.ForeignKey(to="Author",db_column='ss')  
3.7、on_delete级联模式参数
on_delete
当删除关联表中的数据时,当前表与其关联的行的行为。

models.CASCADE
删除关联数据,与之关联也删除

models.DO_NOTHING
删除关联数据,引发错误IntegrityError

models.PROTECT
删除关联数据,引发错误ProtectedError

models.SET_NULL
删除关联数据,与之关联的值设置为null(前提FK字段需要设置为可空)
# pub = models.ForeignKey('Publish',on_delete=models.SET_NULL, null=True)

models.SET_DEFAULT
删除关联数据,与之关联的值设置为默认值(前提FK字段需要设置默认值)

models.SET

删除关联数据,
a. 与之关联的值设置为指定值,设置:models.SET(值)
b. 与之关联的值设置为可执行对象的返回值,设置:models.SET(可执行对象)
3.8、ForeignKey的db_contraint参数
  • 关系和约束要分清:在不加外键的时候也可以表示两个表中的关系,但是我们就不能使用ORM外键相关的方法了,所以我们单纯的将外键换成一个其他字段类型,只是单纯的存着另外一个关联表的主键值是不能使用ORM外键方法的。
  • db_constraint=False只加两者的关系,没有强制约束的效果,并且ORM外键相关的接口(方法)还能使用,所以如果将来公司让你建立外键,并且不能有强制的约束关系,那么就可以将这个参数改为False
    customer = models.ForeignKey(verbose_name='关联客户', to='Customer',db_constraint=False)

4、ORM多表操作的查询

4.0、配置日志查看sql语句
1 query
2 在settings配置文件中写上如下内容,就能够自动打印出我们执行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',
        },
    }
	} 

3 通过django配置的连接mysql的管道来查看(pymysql)
from app01 import models

def add_book(request):
    '''
    添加表记录
    :param request: http请求信息
    :return:
    '''
    book_obj = models.Book(title='python',price=123,pub_date='2012-12-12',publish='人民出版社')
    book_obj.save()
    from django.db import connection  #通过这种方式也能查看执行的sql语句
    print(connection.queries)
    return HttpResponse('ok')
4.1、子查询:基于对象的跨表查询
  • 正向查询:关系属性写在哪个表里面,那么通过这个表的数据,去查询关联的另外一张表的数据,就叫做正向查询,反之就是反向查询
  • 反向查询:通过模型类名小写_set使用反向查询
# 查询
# 一对一查询
    # 正向查询使用关联属性名称
    # 查询一下**这个作者的手机号
    # author_obj = models.Author.objects.get(name='**')
    # # author_obj.ad  # 找到了author_obj关联的作者详细信息表里面对应记录
    # print(author_obj.ad.telephone) # 120

    # 反向查询
    # 反向查询用关联它的模型类的名称小写
    # 查询一下地址在上海的那个作者是谁
    # author_detail_obj = models.AuthorDetail.objects.filter(address='上海').first()
    # print(author_detail_obj.author.name)

# 一对多

    # 正向查询
    # 使用关联属性查询
    # 查询一下红楼懵是哪个出版社出版的
    # book_obj = models.Book.objects.get(title='红楼懵')
    # print(book_obj.pub.name)

    # 反向查询
    # 模型类名小写_set
    # 查询一下**出版社出版了哪些书
    # pub_obj = models.Publish.objects.get(name='**出版社')
    # pub_obj.book_set  # 可能为多条记录,所以模型类名小写_set
    # 类似于objects控制器
    # print(pub_obj.book_set.all().values('title'))
    # 查询结果不会自动去重

# 多对多
    # 正向查询
    # 使用属性来查
    # 查询一下**第二部这本书是谁写的
    # book_obj = models.Book.objects.get(title='**第二部')
    # print(book_obj.authors.all().values('name'))

    # 反向查询
    # 使用模型类名小写_set
    # 查询一下**写了哪些书
    # author_obj = models.Author.objects.get(name='**')
    # print(author_obj.book_set.all().values('title'))
    
4.2、连表查询:基于双下划线的跨表查询
# 基于双下划线的跨表查询
    # 连表操作
    # select * from t2 inner join t1 on t1.t2_id=t2.id
    # select * from t1 inner join t2 on t1.t2_id=t2.id
# 一对一
    # 查询一下***这个作者的家庭住址
    # 正向操作,使用属性
    # ret = models.Author.objects.filter(name='***').values('ad__address')
    # <QuerySet [{'ad__address': '美国'}]>
    # 方向操作,表名小写
    # ret = models.AuthorDetail.objects.filter(author__name='***').values('address')
    # <QuerySet [{'address': '美国'}]>

# 一对多
    # 查询一下少年是哪个出版社出版的
    # 正向操作  使用关联属性
    # ret = models.Book.objects.filter(title='少年').values('pub__name')
    #<QuerySet [{'pub__name': '橘子成人出版社'}]>

    # 反向操作
    # ret = models.Publish.objects.filter(book__title='少年').values('name')
    #<QuerySet [{'name': '橘子成人出版社'}]>


# 多对多
    # 查询一下第二部这本书是谁写的
    # 正向操作  使用关联属性
    # ret = models.Book.objects.filter(title='第二部').values('authors__name')
    # <QuerySet [{'authors__name': '***'}, {'authors__name': 'pdd'}]>
    # ret = models.Author.objects.filter(book__title='第二部').values('name')
    # <QuerySet [{'name': '***'}, {'name': 'pdd'}]>
    # print(ret)
4.3、聚合查询
# 聚合查询aggregate
    # 统计一下所有书籍的平均价格 max min avg count sum
    # ret = models.Book.objects.aggregate(Avg('price'))
    # print(ret) # 结果是普通字典类型:{'price__avg': 43.925}
    # ret = models.Book.objects.all().aggregate(a=Avg('price'), b=Max('price'))
    # print(ret) # {'price__avg': 43.925, 'price__max': Decimal('88.88')} -- {'a': 43.925, 'b': Decimal('88.88')}
    # aggregate方法可以看为是orm语句的结束语句,结果为普通字典类型,不能在继续调用queryset或者模型类对象提供的方法了
4.4、分组查询
# 分组查询

    # 查询一下每个出版社出版书的平均价格
  
    # ret = models.Book.objects.values('pub_id').annotate(a=Avg('price')) # 只能获取到values指定的字段和统计结果数据
    
    # ret = models.Book.objects.values('pub_id','id').annotate(a=Avg('price')) # 多条件分组pub_id和id值相同的算为1组
    
    # ret = models.Book.objects.values('pub__name').annotate(a=Avg('price')) # 以出版社名称分组
    
    # print(ret)
    
    # ret = models.Publish.objects.annotate(a=Avg('book__price'))  # 返回结果是Publish的模型类对象,这个模型类对象里面包含Publish的所有属性数据,还有annotate的统计结果数据
    
    ret = models.Publish.objects.annotate(a=Avg('book__price')).values('name', 'a')
    # print(ret)
    
		# 原生sql,伪代码
    # select publish.name,Avg(book.price) from publish inner join book on publish.id=book.pub_id group by publish.id;

    # select avg(price) from book group by pub_id;
4.5、F查询
# F查询
    from django.db.models import F
    # 查询一下点赞数大于评论数的书籍
    # models.Book.objects.filter(dianzan__gt=comment)
    # obj_list = models.Book.objects.all().values()
    # a = []
    # for i in obj_list:
    #     if i.dianzan > i.comment:
    #         a.append(i)
    # print(a)
    # F查询可以用来做本表不同字段之间的一个比较
    # ret = models.Book.objects.filter(dianzan__gt=F('comment'))
    # print(ret)
    # F可以用来对本表数据进行一些统一操作(四则运算都支持)
    # 将所有的书籍上调10块钱
    models.Book.objects.all().update(price=F('price')+10)
    # models.Book.objects.all().update(price=100)
4.6、Q查询
from django.db.models import Q
#查询书名中包含少年两个字的并且评论数大于20的书籍
# ret = models.Book.objects.filter(title__contains='少年',comment__gt=20)  #filter中逗号分隔的条件,默认是and的关系
# <QuerySet [<Book: 少年2>]>

# 想进行或的关系查询需要借助到我们Q查询Q
# 查询书名中包含少年两个字的或者评论数大于20的书籍Q
# ret = models.Book.objects.filter(Q(title__contains='少年') | Q(comment__gt=20))

# 查询书名中包含少年两个字的或者评论数大于20的,并且点赞数大于等于80的
# ret = models.Book.objects.filter(Q(title__contains='少年') | Q(comment__gt=20), dianzan__gte=80)
# 注意,如果结合逗号来进行and的关系查询,那么必须将没有Q包裹的查询条件放在Q包裹的查询条件后面

#下面这种方式也可以,Q查询可以多层嵌套使用
# ret = models.Book.objects.filter(Q(Q(title__contains='少年') | Q(comment__gt=20)) & ~Q(dianzan__gte=80))  #~ 条件取反 ,&--and关系 ,|--or关系
# print(ret)

八、

原文地址:https://www.cnblogs.com/wylshkjj/p/13302949.html