Djiango视图层和模型层

时间:2024-03-11 14:25:33

Djiango 使用教程

目录
  • Djiango 使用教程
  • 一. 视图层
    • 1.1 HttpResponse,render,redirect
    • 1.2 JsonResponse
    • 1.3 form表单上传文件及后端保存
    • 1.4. request 方法
  • 二. FBV和CBV
    • 2.1 CBV语法格式
    • 2.2 CBV 实现原理
  • 三. 模版层
    • 3.1 模版语法传值
    • 3.2 过滤器
    • 3.3 转义
    • 3.4 标签逻辑系列
      • 1)for循环
      • 2)if判断
      • 3)if+for组合
      • 4)with别名
    • 3.5 自定义标签,过滤器,inclusion_tag
    • 3.6 模版的继承
    • 3.7 模版的导入
  • 四. 模型层-增删改查
    • 4.1 连接数据库
    • 4.2 创建表&准备测试环境
    • 4.3 单表增删改
    • 4.4 单表操作必会13条
    • 4.5 查看内部SQL语句
    • 4.6 双下划线查询
    • 4.7 多表操作&环境创建
    • 4.8 一对多外键增删改查
    • 4.9 多对多外键增删改查
    • 4.10 多表查询 正向和反向概念
    • 4.11 子查询(基于对象的跨表查询)
    • 4.12 联表查询(基于双下划线的跨表查询)
  • 五. 模型层-聚合函数
    • 5.1 聚合查询
    • 5.2 分组查询
    • 5.3 F和Q查询
    • 5.4 djiango中开启事务
    • 5.5 orm中常用字段及参数
  • 六. 数据库查询优化
    • 6.1 单表查询优化
    • 6.2 连表查询优化
  • 七. choices参数
    • 7.1 数据库字段设计常见
    • 7.2 创建环境
    • 7.3 取choices数据
  • 八. MTV与MVC模型
  • 九. 多对多三种创建方式

一. 视图层

1.1 HttpResponse,render,redirect

  • HttpResponse
  • render
  • redirect
"""
HttpResponse
	返回字符串类型
render
	返回html页面 并且在返回给浏览器之前还可以给html文件传值
redirect
	重定向
"""

#01 正常写法
def func(repuest):
    return HttpResponse('func')
    return render(request,'home.html')
  	return redirect('https://www.baidu.com/')
    return redirect('/home/')			#重定向本项目

  
#02 如何不返回则报错
def index(request):
    pass

#03 结论:视图函数必须要返回一个HttpResponse对象  正确   研究三者的源码即可得处结论
The view app01.views.index didn't return an HttpResponse object. It returned None instead.

视图app01.views.index未返回HttpResponse对象。它返回None。


#04 render简单内部原理
		from django.template import Template,Context
    res = Template('<h1>{{ user }}</h1>')
    con = Context({'user':{'username':'jason','password':123}})
    ret = res.render(con)
    print(ret)
    return HttpResponse(ret)


image-20240124115149177

1.2 JsonResponse

"""
json格式的数据有什么用?
	前后端数据交互需要使用到json作为过渡 实现跨语言传输数据

前端序列化
	JSON.stringify()					json.dumps()
	JSON.parse()							json.loads()
"""

#01 代码 json方式
import json
def ab_json(request):
    user_dict = {'username':'张宇宙 fufu','password':123445}
    #转为json 字符串  ensure_ascii=False  非ASCII字符直接保存到json格式中 显示中文 不被解析
    json_str = json.dumps(user_dict,ensure_ascii=False)
    #将字符串返回
    return HttpResponse(json_str)

  
  
#02 代码 JsonResponse方式传值 字典
from django.http import JsonResponse
def ab_json(request):
    user_dict = {'username':'张宇宙 fufu','password':123445}  
    #JsonResponse 方法 默认只能传字典 json_dumps_params = {'ensure_ascii':False} 显示中文不被解析
    
    return JsonResponse(user_dict,json_dumps_params = {'ensure_ascii':False})
  
 

#03 列表JsonResponse方式传值  
from django.http import JsonResponse
def ab_json(request):
    l = [111,222,444]  
    # 传列表需要设置 safe=False
    return JsonResponse(l, safe=False)

  
  
 ##默认只能序列化字典 序列化其他需要加safe参数	


1.3 form表单上传文件及后端保存

"""
form表单上传文件类型的数据
	1.method必须指定成post
	2.enctype必须换成formdata

"""

#01 视图层
def ab_file(request):
    if request.method == 'POST':
        print(request.POST)                     #只能获取普通的简直对数据 文件不行
        print(request.FILES)                    #获取文件数据

        # 获取文件对象  取出 file
        # <MultiValueDict: {'file': [<TemporaryUploadedFile: IMG_6543.JPG (image/jpeg)>]}>
        file_obj = request.FILES.get('file')
        #获取文件名称
        print(file_obj.name)

        #保存文件 ../file_obj.name 路径 path+文件名称 默认在当前html 同级目录
        with open(file_obj.name,'wb') as f:
            #.chunks() 切片保存,不写也一样
            for line in file_obj.chunks():
                f.write(line)

    return render(request,'file.html')

  
#02 前端页面
<body>
<form action="" method="post" enctype="multipart/form-data" >
    <p>username:<input type="text" name="username" ></p>
    <p>file:<input type="file" name="file"></p>
    <input type="submit" >
</form>
</body>


image-20240124152920332

1.4. request 方法

"""

request.method			#获取请求方法
request.POST				#获取post请求数据
request.GET					#获取get请求
request.FILES				#获取文件数据
request.body  			#原生的浏览器发过来的二进制数据  后面详细的讲
request.path 
request.path_info
request.get_full_path()  能过获取完整的url及问号后面的参数 

"""
    print(request.path)  						# /app01/ab_file/
    print(request.path_info)  			# /app01/ab_file/
    print(request.get_full_path())  # /app01/ab_file/?username=jason

二. FBV和CBV

2.1 CBV语法格式

#01 FBV是视图层的函数 以上皆为FBV 
def index(request):
  return HttpResponse('index')

#02 CBV是类函数
1)CBV路由层 MyLogin是类名称
url(r'^login/',views.MyLogin.as_view()),

2)视图层 需要import View模块
from django.views import View
class MyLogin(View):
    def get(self,request):
        return render(request,"file.html")
    def post(self,request):
        return HttpResponse("post方法")

 
##结论 方式该路径时候 CBV会自动判断方法类型 get会走get函数 post会走post函数

"""

FBV和CBV各有千秋
CBV特点
	能够直接根据请求方式的不同直接匹配到对应的方法执行
	
	内部到底是怎么实现的?
		CBV内部源码(******)

"""

2.2 CBV 实现原理

  • URL classonlymethod装饰方法
  • 反射机制
#01 路由层
url(r'^login/',views.MyLogin.as_view())

1)as_view代码
    @classonlymethod
    def as_view(cls, **initkwargs):
        """
        cls就是我们自己写的类   MyCBV
        Main entry point for a request-response process.
        """
        def view(request, *args, **kwargs):
            self = cls(**initkwargs)  # cls是我们自己写的类
            # self = MyLogin(**initkwargs)  产生一个我们自己写的类的对象
            return self.dispatch(request, *args, **kwargs)
            """
            以后你们会经常需要看源码 但是在看python源码的时候 一定要时刻提醒自己面向对象属性方法查找顺序
                先从对象自己找
                再去产生对象的类里面找
                之后再去父类找
                ...
            总结:看源码只要看到了self点一个东西 一定要问你自己当前这个self到底是谁
            
            这里调用了 dispatch 函数 (对象--对象类--父类里查找)
            """
        return view

"""

结论:发现 as_view是闭包函数 并被classonlymethod装饰过 并非返回 view函数名称
url(r'^login/',views.MyLogin.as_view()) 等同于  url(r'^login/',views.view)  FBV一模一样

"""



#02 dispatch方法
    def dispatch(self, request, *args, **kwargs):
    """
    获取当前请求的小写格式 然后比对当前请求方式是否合法
    get请求为例
    post请求
    内置8个方法 http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
    判断当前请求是否 包含在内置8个合法请求内
    
    """
        if request.method.lower() in self.http_method_names:
            handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
            """
            反射:通过字符串来操作对象的属性或者方法
                handler = getattr(自己写的类产生的对象,'get',当找不到get属性或者方法的时候就会用第三个参数)
                handler = 我们自己写的类里面的get方法
            """
        else:
            handler = self.http_method_not_allowed
        return handler(request, *args, **kwargs)
        """
        自动调用get方法
        """


三. 模版层

3.1 模版语法传值

  • {{}} 变量相关
  • {%%} 逻辑相关
#01 视图层 列表字段元祖集合 类对象等传值
def index(request):
    n = 123
    f = 11.11
    s = "我在想付付"
    b = True
    l = ['付付',123,'小爱同学']
    t = ('111','222','333')
    d = {'username': '付付','age':18,'info':'此情可待成追忆'}
    se = {'嘤嘤嘤','嘻嘻','哈哈哈'}

    def func():
        print('我被执行了')
        return '只是当时已惘然'

    class MyClass(object):
        def get_self(self):
            return 'self'

        @staticmethod
        def get_func():
            return 'func'

        @classmethod
        def get_class(cls):
            return 'cls'

        def __str__(self):
            return '嘻嘻嘻嘻'

    obj = MyClass()

    return render(request,'index.html',locals())


#02 前端取值 {{ 名称 }}
<p>{{ n }}</p>
<p>{{ f }}</p>
<p>{{ s }}</p>
<p>{{ b }}</p>
<p>{{ l }}</p>
<p>{{ t }}</p>
<p>{{ d }}</p>
<p>{{ se }}</p>
<p>传递函数名会自动加括号调用 但是模版语法不支持给函数传额外的参数:{{ func }}</p>
<p>传类名的时候也会自动加括号调用(实例化){{ MyClass }}</p>
<p>内部能够自动判断出当前的变量名是否可以加括号调用 如果可以就会自动执行  针对的是函数名和类名</p>
<p>{{ obj.get_class }}</p>
<p>{{ obj.get_func }}</p>
<p>{{ obj.get_self }}</p>


#注意:对象加点 可以直接调用内部方法运行,前端的结果是 return的值,传对象和类 会自动帮你加括号调用


#03 模版语法取值
1) 视图层
l = ['付付',123,'小爱同学']
d = {'username': '付付','age':18,'info':'此情可待成追忆','hobby':[111,222,{'info':'love'}]}

2)前端取值
<p>字典取值:{{ d.username }}</p>				#付付
<p>列表取值:{{ l.2 }}</p>								#小爱同学
<p>字典取值: {{ d.hobby.2.info }}</p>		 #love

#结论:django模版语法的取值 是固定的格式 只能采用“句点符” 即可以点键也可以点索引 还可以两者混用



3.2 过滤器

#01 语法格式
{{数据|过滤器:参数}}

#02 过滤器类型
<p>统计长度:{{ d|length }}</p>
<p>默认值(第一个参数布尔值是True就展示第一个参数的值否在展示冒号后面的值):{{ b|default:'前面是假的 会显示这个' }}</p>
<p>文件大小:{{ filesize | filesizeformat }}</p>
<p>时间格式化(时区相差6小时):{{ current_time | date:'Y-m-d H:i:s' }}</p>
<p>切片操作(支持步长 包含三个点):{{ l|slice:'0:8:2' }}</p>
<p>切取字符串(包含三个点):{{ info|truncatechars:9 }}</p>
<p>切取单词(中英文 默认分隔符是空格 3): {{ info|truncatewords:3 }}</p>
<p>切取单词(中英文 默认分隔符是空格 6): {{ egl |truncatewords:6 }}</p>
<p>移除特定字符(问好):{{ msg|cut:'?' }}</p>
<p>移除特定字符(空格):{{ msg|cut:' ' }}</p>
<p>拼接操作:{{ l|join:'$^' }}</p>
<p>拼接操作(数字相加){{ n|add:10 }}</p>
<p>拼接操作(字符串相加){{ s|add:'msg' }}</p>


#03 视图层
    n = 123
    f = 11.11
    s = "我在想付付"
    b = True
    l = ['付付',123,'小爱同学',333,444,555,666]
    t = ('111','222','333')
    d = {'username': '付付','age':18,'info':'此情可待成追忆','hobby':[111,222,{'info':'love'}]}
    se = {'嘤嘤嘤','嘻嘻','哈哈哈'}
    info = 'Django 框架系列 目录Django 框架系列一HttpResponse,render,red'
    egl = 'my name is zhangyuzhou my age is 18 i am from china'
    msg = 'fufu i love you is who?'
    n = 100
    
    
#04 页面结果


image-20240125153448529

3.3 转义

#01 语法结构 把html js语法转义到页面
{{ 变量名称	|safe }}


#03 后端转义
hhh = '<h2>付付</h2>'
sss = '<script>alert(123)</script>'

from django.utils.safestring import mark_safe
res = mark_safe('<h2>付付2.0</h2>')							#使用mark_safe方法 之前传值给前端即可

#02 前端转义 safe
<p>转义html:{{ hhh|safe }}</p>
<p>转义js:{{ sss|safe }}</p>
<p>后端转义:{{ res }}</p>


3.4 标签逻辑系列

1)for循环

#01 语法结构( for + table键 自动补全)
{% for name in l %}
    <p>{{ name }}</p>		#循环的元素
{% endfor %}


#02 for循环内置模版变量
{% for foo in l %}
    <p>{{ forloop }}</p>
{% endfor %}

{'parentloop': {}, 'counter0': 0, 'counter': 1, 'revcounter': 7, 'revcounter0': 6, 'first': True, 'last': False}

"""
forloop.counter 知识for循环已经循环了多少次,从 1 开始计数
forloop.counter()  和forloop.counter一样,只是从 0 开始计数
forloop.revcounter  从倒数开始数,循环的次数,从 1 开始计数
forloop.revcounter()从倒数开始数,循环的次数,从 0 开始计数 
forloop.first  是否是第一次循环,如果是返回 True 。否则返回 False
forloop.last  是否是最后一次循环,如果是返回 True 。否则返回 False
forloop.parentloop  嵌套循环时,是一个指向当前循环的上一级循环的forloop对象的引用。如 forloop.parentloop.counter 表示上一级循环循环了多少次

"""


image-20240125154327677

2)if判断

#01 语法格式(if+tab 自动补全)

{% if b %}						#判断是否为真
    <p>baby</p>
{% elif s %}					#判断
    <p>爱了</p>
{% else %}						#否侧
    <p>否侧你完了</p>
{% endif %}

#02 视图层数据
    s = "我在想付付"
    b = False

#03 结果
爱了


3)if+for组合

#01 语法结构
l = ['付付','付付2.0','小爱同学','付付3.0',444,555,666]

{% for foo in l %}
    {% if forloop.first %}
        <p>这是我的第一次</p>
    {% elif forloop.last %}
        <p>这是最后一次了</p>
    {% else %}
        <p>{{ foo }}</p>
    {% endif %}
    {% empty %}
        <p>for循环的可迭代对象内部没有元素,无法循环</p>
{% endfor %}


#解释下:empty for循环的可迭代对象内部没有元素,无法循环则会打印这个


###循环字典方法
d = {'username': '付付','age':18,'info':'此情可待成追忆','hobby':[111,222,{'info':'love'}]}


#03 默认循环的是 d.keys
{% for foo in d %}
    <p>{{ foo }}</p>
{% endfor %}
<hr>

#04 循环字典的key
{% for key in d.keys %}
<p>{{ key }}</p>
{% endfor %}

#05  循环字典的值
{% for value in d.values %}
<p>{{ value }}</p>
{% endfor %}

#06循环字典的k+v
{% for item in d.items %}
<p>{{ item }}</p>
{% endfor %}


  

image-20240125161716815

4)with别名

#01 语法结构 给love起别名(with+tab)
d = {'username': '付付','age':18,'info':'此情可待成追忆','hobby':[111,222,{'info':'love'}]}


{% with d.hobby.2.info as fufu %}
    <p>{{ fufu }}</p>
{% endwith %}

3.5 自定义标签,过滤器,inclusion_tag

#01 自定义过滤器(最多两个参数)
1)定义
"""
先三步走
	1.在应用下创建一个名字”必须“叫templatetags文件夹
	2.在该文件夹内创建“任意”名称的py文件 eg:mytag.py
	3.在该py文件内"必须"先书写下面两句话(单词一个都不能错)
		from django import template
		register = template.Library()
"""

2)语法格式
from django import template
register = template.Library()

3)案例数字相加 
@register.filter(name='fu')
def my_sum(v1, v2):
    return v1 + v2

4)示例:将文本转换为大写
@register.filter(name='my_filter')
def my_filter(value):
    return value.upper()


5)前端使用
{% load mytag %}
<p>{{ n |fu:666 }}</p>
<p>{{ 1000 |fu:666 }}</p>
<p>{{ "hello fuuf" | my_filter }}</p>



#02 自定义标签(参数可以有多个)
1) 后端
@register.simple_tag(name='puls')
def puls(a,s,d,f):
    return '%s-%s-%s-%s'%(a,s,d,f)

2)前端
{% load mytag %}
{% puls 'fufu' 123 'love' 334 %}

3)结果
fufu-123-love-334



#03 自定义inclusion_tag装饰器
1) 后端
@register.inclusion_tag('left.html')	#作用html页面 left
def left(n):
    data = [f'第{i}项' for i in range(n)]
    return locals()

2)前端作用页面 
<ul>
    {% for foo in data %}		# 接收data 并循环
        <li>{{ foo }}</li>
    {% endfor %}
</ul>

3)调用
{% load mytag %}
{% left 10 %}

  • Inclusion_tag

image-20240125172012298

3.6 模版的继承


"""
# 模版的继承 你自己先选好一个你要想继承的模版页面
{% extends 'home.html' %}

# 继承了之后子页面跟模版页面长的是一模一样的 你需要在模版页面上提前划定可以被修改的区域
{% block content %}
	模版内容
{% endblock %}

# 子页面就可以声明想要修改哪块划定了的区域
{% block content %}
	子页面内容	
{% endblock %}

"""

#01 案例书写个网站页面 要求导航条和侧边栏保持不动 主题内容根据功能改变

#02 路由层
url(r'^home/',views.home),
url(r'^loginu/', views.loginu),
url(r'^reg/', views.reg),


#03 视图层
def home(request):
    return render(request,'home.html')
def loginu(request):
    return render(request,'loginu.html')
def reg(request):
    return render(request,'reg.html')

#04 html文件 给需要修改的区域添加 标签 block confufu

{% block css %}

{% endblock %}


{% block confufu %}
                             <div class="panel-body">
                           <div class="jumbotron">
                          <h1>Hello, world!</h1>
                          <p>...</p>
                          <p><a class="btn btn-primary btn-lg" href="#" role="button">Learn more</a></p>
                </div>
                  </div>
{% endblock %}



{% block js %}

{% endblock %}

#05 模版的继承
{% extends 'home.html' %}

{% block confufu %}
<h1>登入页面</h1>
    <form action="">
        <p>username: <input type="text" class="form-control"></p>
        <p>password: <input type="text" class="form-control"></p>
        <input type="submit" class="btn btn-success">
    </form>
{% endblock %}


#06 一般情况下模版页面上应该至少有三块可以被修改的区域
	1.css区域
  2.html区域
  3.js区域
  {% block css %}

	{% endblock %}
  
  {% block content %}

	{% endblock %}
  
  {% block js %}

	{% endblock %}
  # 每一个子页面就都可以有自己独有的css代码 html代码 js代码
  

-------案例------

{% extends 'home.html' %}

{% block css %}
    <style>
    .h {
        color: #31708f;
    }
    </style>
{% endblock %}


{% block confufu %}
<h1 class="h">注册页面</h1>
    <form action="">
        <p>username: <input type="text" class="form-control"></p>
        <p>password: <input type="text" class="form-control"></p>
        <input type="submit" class="btn btn-danger">
    </form>
    {% include 'love.html' %}
{% endblock %}


{% block js %}
    <script>
       alert(1234)
    </script>
{% endblock %}
  • 图示

image-20240126095853589

3.7 模版的导入

"""
将页面的某一个局部当成模块的形式
哪个地方需要就可以直接导入使用即可
"""
{% include 'wasai.html' %}

四. 模型层-增删改查

4.1 连接数据库

#01 修改配置文件 settings.py
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'day64',
        'HOST': '192.168.5.9',
        'USER': 'root',
        'PASSWORD': '0x00NF2001',
        'POST': 3306,
        'CHARSET': 'utf8'
    }
}


#02 主项目下 __init__文件内声明该模块
import pymysql
pymysql.install_as_MySQLdb()

 
#03 配置文件templates
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR,'templates')]

##其它方式连接信息(和本代码无关 只单纯记录)
conn = pymysql.connect(
    user='root',
    host='192.168.5.9',
    password='0x00NF2001',
    port=3306,
    database='note',
    charset='utf8',
    autocommit=True
)

4.2 创建表&准备测试环境

#01 创建表
class User(models.Model):
    name = models.CharField(max_length=32)
    age = models.IntegerField()
    register_time = models.DateField()      			#年月日
    uptime = models.DateTimeField(auto_now=True)	#年月日时分秒

    """
    DateTimeField  年月日 时分秒
    DateField      年月日
        两个重要参数
        auto_now: 每次操作数据的时候 该字段会自动将当前时间更新
        auto_now_add: 在创建数据的时候会自动将当前时间记录下来  之后只要不人为修改 那么一直不变

    """

#02 运行manage任务 创建表
makemigrations
migrate


#03 准备测试环境 tests.py
import os
if __name__ == "__main__":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "day64.settings")
    import django
    django.setup()
		
    #下方可以写Djiango 查询语句,需要查什么模版 导入即可
    from app01 import models
    models.User.objects.all()		#查询
    
"""
当你只是想测试django中的某一个py文件内容 那么你可以不用书写前后端交互的形式
而是直接写一个测试脚本即可

脚本代码无论是写在应用下的tests.py还是自己单独开设py文件都可以

测试环境的准备 去manage.py中拷贝前四行代码 然后自己写两行
"""


4.3 单表增删改

#01 增加数据
#1.1 方式1
models.User.objects.create(name='付付',age='18',register_time='2000-11-06')
    

#1.2 方式2  
import datetime
ctime = datetime.datetime.now()
obj = models.User(name='zhangyuzhou',age='20',register_time=ctime)  #支持变量方式
print(obj)
obj.save()


#02 删除数据
#2.1 方式1
models.User.objects.filter(pk=2).delete()
models.User.objects.filter(age=18).delete()

"""
pk=当前表的主键值
pk会自动查找到当前表的主键字段 指代的就是当前表的主键字段

filter(过滤条件)
    
"""

#2.2 方式2 获取当前数据对象
user_obj = models.User.objects.filter(pk=6).first()
print(user_obj)
user_obj.delete()


#03 修改数据
#3.1 方式1
res = models.User.objects.filter(pk=5).update(name='love')

#3.2 方式2
user_obj = models.User.objects.filter(pk=5).first()
user_obj.name = '爱情'
user_obj.save()

#3.3 方式3
user_obj = models.User.objects.get(pk=5)
user_obj.age = 19
user_obj.save()


"""
get方法返回的直接就是当前数据对象
但是该方法不推荐使用
一旦数据不存在该方法会直接报错
而filter则不会
所以我们还是用filter
"""

image-20240126111222986

4.4 单表操作必会13条

#01 all() 查询所有数据
    res = models.User.objects.all()
    print(res)

#02 filte()  带有过滤条件的查询
    res = models.User.objects.filter(pk=5).first()
    print(res)

#03 get()  直接拿数据对象 但是条件不存在直接报错
    user_obj = models.User.objects.get(pk=5)
    user_obj.age = 19
    user_obj.save()

#04 拿queryset里面第一个元素
    res = models.User.objects.all().first()
    print(res)

#05 last() 拿queryset里面最后第一个元素
    res = models.User.objects.all().last()
    print(res)

#06 values()  可以指定获取的数据字段  select name,age from ...     列表套字典
    res = models.User.objects.values('name','age')
    print(res)
    print(res.query)	#查看SQL语句 只针对QuerySet对象
   
    <QuerySet [{'name': 'zhangyuzhou', 'age': 20}, {'name': '爱情', 'age': 19}]>
    


#07 values_list()  列表套元祖
    res = models.User.objects.values_list('name','age')
    print(res)

		<QuerySet [('zhangyuzhou', 20), ('爱情', 19), ('付付', 18), ('汤姆', 33)]>
    
 
#08 distinct()  去重 并非从表内删除
    res = models.User.objects.values('name','age').distinct()
    print(res)
    
    """
    去重一定要是一模一样的数据
    如果带有主键那么肯定不一样 你在往后的查询中一定不要忽略主键
    
    """
        
    
    
#09 order_by() 排序
    res = models.User.objects.order_by('age')       #正序
    res_re = models.User.objects.order_by('-age')   #倒序
    print(res)
    print(res_re)
    

#10 reverse()  反转的前提是 数据已经排过序了  order_by()
    res =  models.User.objects.order_by('age').reverse()
    print(res)
    
    
#11 count() 统计当前数据个数 可以加条件filter()
    res = models.User.objects.filter(name='付付').count()
    res = models.User.objects.count()
    print(res)    
    
    
#12 排除 exclude()  排除在外
    res = models.User.objects.exclude(name='付付')
    print(res) 
    
    
#13. exists()  基本用不到因为数据本身就自带布尔值  返回的是布尔值
    res = models.User.objects.filter(pk=1).exists()
    print(res)
    
    

4.5 查看内部SQL语句

#01 方式一 queryset对象才能够点击query查看内部的sql语句
res = models.User.objects.values('name','age')
print(res)

结果如下:
<QuerySet [{'name': 'zhangyuzhou', 'age': 20}, {'name': '爱情', 'age': 19}]>


print(res.query)	#查看SQL语句 只针对QuerySet对象

结果如下:
SELECT `app01_user`.`name`, `app01_user`.`age` FROM `app01_user`



#02 修改配置文件 匹配所有查询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',
        },
    }
}


查询会自动打印SQL语句
res = models.User.objects.values('name','age')
print(res)

结果如下:
(0.005) SELECT `app01_user`.`name`, `app01_user`.`age` FROM `app01_user` LIMIT 21; args=()

4.6 双下划线查询

#01 年龄大于20
    res = models.User.objects.filter(age__gt=20)
    print(res)

#02 年龄大于等于20
    res = models.User.objects.filter(age__gte=20)
    print(res)

#03 年龄小于20
    res = models.User.objects.filter(age__lt=20)
    print(res)

#04 年龄小于等于20 并且name等于付付的
    res = models.User.objects.filter(age__lte=20,name='付付')
    print(res)

#05 年龄是 16或者19 或者33的 (在列表) 并按照年龄排序
    res = models.User.objects.filter(age__in=[16,19,33]).order_by('age')
    print(res)

#06 年龄在18到33之间的 并且只取name,age字段  首尾都要
    res = models.User.objects.filter(age__range=[18,33]).values('name','age')

#07 查出名字内包含 F的
    res = models.User.objects.filter(name__contains='F')    	#__contains   区分大小写
    res1 = models.User.objects.filter(name__icontains='F')  	#__icontains  忽略大小写


#08 查询以什么开头或者结尾的
    res = models.User.objects.filter(name__istartswith='F')			#__startswith  区分大小写 查询以什么开头
    res1 = models.User.objects.filter(name__startswith='F')			#__istartswith 忽略大小写 查询以什么开头

    res = models.User.objects.filter(name__endswith='U')				#__endswith  区分大小写 查询以什么结尾
    res1 = models.User.objects.filter(name__iendswith='U')			#__endswith  忽略大小写 查询以什么结尾
    
#09 日期查询 查询出注册时间是 2000 11月
    res = models.User.objects.filter(register_time__month=11,register_time__year=2000)
    print(res)
    
   """
    register_time__month = 月
    register_time__year = 年
    register_time__day = 天
   """

4.7 多表操作&环境创建

#图书表
class Book(models.Model):
    title = models.CharField(max_length=32)
    price = models.DecimalField(max_digits=8,decimal_places=2)
    publish_data = models.DateField(auto_now_add=True)

    #书籍和出版社关系表 一对多
    publish = models.ForeignKey(to='Publish')

    #多对多 书籍和作者 多个书籍可以有多个作者
    authors = models.ManyToManyField(to='Author')


#出版社
class Publish(models.Model):
    name = models.CharField(max_length=32)
    addr = models.CharField(max_length=64)
    email = models.EmailField()                #varchar(254) 该字段不是给model看的 而是用于校验组件


#作者表
class Author(models.Model):
    name = models.CharField(max_length=32)
    age = models.IntegerField()

    #一对一 作者对应作者详情
    author_dateil = models.OneToOneField(to='AuthorDetail')


#作者详情表
class AuthorDetail(models.Model):
    phone = models.BigIntegerField()        #电话号码
    addr = models.CharField(max_length=64)

      
##运行语句
makemigrations
migrate



4.8 一对多外键增删改查

#01 增加数据 书籍名称 价格 对应的出版社
#1.1 方式1 直接写出版社 ID主键
    models.Book.objects.create(title='论语',price=899.11,publish_id=1)
    models.Book.objects.create(title='大学',price=799.22,publish_id=2)
    models.Book.objects.create(title='中庸',price=699.33,publish_id=1)

#1.2 方式2 虚拟字段对象, 先查出出版社数据对象 然后在书籍表内添加
    publish_obj = models.Publish.objects.filter(pk=2).first()
    models.Book.objects.create(title='红楼梦',price=19.98,publish=publish_obj)

    
#02 删除操作 删除出版社id=1的数据
    models.Book.objects.filter(publish_id=1).delete()
    
 
#03 修改数据
# 3.1 方式一 修改主键是12的书籍表 修改内容:publish_id=2
    models.Book.objects.filter(pk=12).update(publish_id=2)

# 3.2 方式二 查出出版社对象 然后在书籍表内更新
    obj = models.Publish.objects.filter(pk=1).first()
    models.Book.objects.filter(pk=13).update(publish=obj)
    
    
    

4.9 多对多外键增删改查

#01 通过虚拟字段获取第三张表的数据对象
    book_obj = models.Book.objects.filter(pk=12).first()
    print(book_obj.authors)																	#就类似于你已经到了第三张关系表了

#02 给书籍增加作者     无则添加,有则不动
    book_obj.authors.add(1)																				#直接写ID方式
    book_obj.authors.add(1,2)

    
    authors_obj = models.Author.objects.filter(pk=1).first()			#虚拟对象方式
    authors_obj1 = models.Author.objects.filter(pk=2).first()
    authors_obj2 = models.Author.objects.filter(pk=3).first()
    book_obj.authors.add(authors_obj)
    book_obj.authors.add(authors_obj1,authors_obj2)

	  """
    add给第三张关系表添加数据
    括号内既可以传数字也可以传对象 并且都支持多个
    """

#03 删除 书籍表内作者ID等于1的 数据
	book_obj.authors.remove(1)
	book_obj.authors.remove(2,3)

		# 删除书籍ID=12 对着作者ID=2的
		# 获取作者ID
    author_obj = models.Author.objects.filter(pk=2).first()
   
    # 调用方法 在书籍表内删除对应ID是2的作者
    book_obj.authors.remove(author_obj)

	  """
    remove
        括号内既可以传数字也可以传对象 并且都支持多个
    """
  
  
#04 修改 修改书籍表ID是12的关系表 对应作者ID是 1和1 ,有则修改 无则添加 多则删除
    book_obj.authors.set([1,2])
    book_obj.authors.set([3])

    author_obj = models.Author.objects.filter(pk=2).first()
    author_obj1 = models.Author.objects.filter(pk=3).first()
    book_obj.authors.set([author_obj,author_obj1]) 						 # 括号内必须给一个可迭代对象
    
    """
    set
        括号内必须传一个可迭代对象,该对象内既可以数字也可以对象 并且都支持多个
    """
    
#05 清空 在第三张关系表中清空某个书籍与作者的绑定关系
    book_obj.authors.clear()
  
    """
    clear
        括号内不要加任何参数
    
    """
    

4.10 多表查询 正向和反向概念

# 正向
# 反向
	外键字段在我手上那么,我查你就是正向
  外键字段如果不在手上,我查你就是反向
  
  book >>>外键字段在书那儿(正向)>>> publish
  publish	>>>外键字段在书那儿(反向)>>>book
  
  一对一和多对多正反向的判断也是如此
  
"""
正向查询按字段
反向查询按表名小写
				_set
				...
"""


4.11 子查询(基于对象的跨表查询)

"""
    在书写orm语句的时候跟写sql语句一样的
    不要企图一次性将orm语句写完 如果比较复杂 就写一点看一点
    
    正向什么时候需要加.all()
        当你的结果可能有多个的时候就需要加.all()
        如果是一个则直接拿到数据对象
            book_obj.publish
            book_obj.authors.all()
            author_obj.author_detail
"""

    
------------正向查询按照外键字段---------------

#01 查询书籍主键为11的出版社 正向查询
    book_obj = models.Book.objects.filter(pk=11).first()
    res = book_obj.publish
    print(res)

#02 查询书籍主键为12的作者 
    # 这里结果有多个 需要加all()
    book_obj = models.Book.objects.filter(pk=12).first()
    res = book_obj.authors.all()
    for i in res:
        print(i.name)

#03 查询作者  fufu 的电话号码
		"""
		author_obj.name  作者名称
		res.phone				 电话号码 
		"""
    author_obj = models.Author.objects.filter(name='fufu').first()
    res = author_obj.author_dateil						#外键字段 author_dateil
    print(author_obj.name,res.phone)


    
    
------------反向查询按照表名---------------

##反向查询
#01 查询出版社是东方出版社的书
    publish_obj = models.Publish.objects.filter(name='东方出版社').first()
    res = publish_obj.book_set.all()      #这里用出版社对象.书籍表名_set.all()
    print(res)                            #结果是多个 需要在表名称后加_set.all()


#02 查询fufu作者 写过的书
    author_obj = models.Author.objects.filter(name='fufu').first()
    res = author_obj.book_set.all()
    for i in res:
        print(author_obj.name,i.title)

#03 查询手机号是1314的作者姓名
    author_detail_obj = models.AuthorDetail.objects.filter(phone=1314).first()
    res = author_detail_obj.author      #这里结果只有一个 所以不需要加 _set.all()
    print(res.name)


"""
基于对象 
    反向查询的时候
    当你的查询结果可以有多个的时候 就必须加_set.all()
    当你的结果只有一个的时候 不需要加_set.all()
"""

4.12 联表查询(基于双下划线的跨表查询)


------------------正向查询基于字段----------------------


# 01 查询fufu的手机号和 作者姓名 地址(正向查询 作者表-->>作者详情表)
    res = models.Author.objects.filter(name='fufu').values(
        'author_dateil__addr',
        'author_dateil__phone',
        'name')
    print(res)

#02 查询书籍名称是红楼梦的 出版社姓名和作者
    """
    用书籍表去查询 出版社表和作者表
    """
    res = models.Book.objects.filter(title='红楼梦').values('publish__name','authors__name','title')
    print(res)

#03 查询书籍主键是12的 书籍名称 作者姓名和电话 
    res = models.Book.objects.filter(pk=12).values(
        'title',
        'authors__name',
        'authors__author_dateil__phone')


  
------------------反向查询基于表名----------------------

#01 查询手机号是 1314的作者
    """
    根据作者详情反向查询作者
    反向查询 写表名称
    """
    res = models.AuthorDetail.objects.filter(phone=1314).values(
        'author__name','phone')
    print(res)
    

#02 出版社发布过红楼梦的 出版社名称和书的名称 及书的作者
    """
    根据出版社表 反向查询 书籍表和作者表
    反向查询 写表名称
    """
    res = models.Publish.objects.filter(book__title='红楼梦').values(
        'name','book__title','book__authors__name')
    print(res)
    

#03 查询书籍主键为12的作者姓名和地址
    res = models.Author.objects.filter(book__id=12).values(
        'book__title','name','author_dateil__addr')
    print(res)


#04  查询书籍主键为11的作者姓名和地址 book主主要查询
    """
    book关联作者表
    作者表关联作者详情
    authors__author_dateil__phone
    """
    res = models.Book.objects.filter(pk=12).values(
        'authors__author_dateil__phone','authors__name','title')
    print(res)

五. 模型层-聚合函数

5.1 聚合查询

  • 单独查询需要使用:aggregate
  • Max,Min,Count,Avg,Sum
## 导入聚合模块
from django.db.models import Max,Min,Count,Avg,Sum
from app01 import models
"""
聚合查询通常情况下都是配合分组一起使用的
只要是跟数据库相关的模块 
     基本上都在django.db.models里面
     如果上述没有那么应该在django.db里面
"""


#01 统计书籍的平均价格
    res = models.Book.objects.aggregate(Avg('price'))
    print(res)
    
    
#02 统计所有书籍 最大值 最小值 个数 平均值 求和
    res = models.Book.objects.aggregate(
        Avg('price'),Min('price'),Max('price'),
        Sum('price'),Count('price'))
    print(res)

    
 
 {'price__avg': 604.41, 'price__min': Decimal('19.98'), 'price__max': Decimal('899.11'), 'price__sum': Decimal('2417.64'), 'price__count': 4}

5.2 分组查询

  • annotate
##聚合查询 导入功能模块
from django.db.models import Max,Min,Count,Avg,Sum

    
#01 统计每一本书的作者数量
    #res = models.Book.objects.annotate()		# models后面点什么 就是按什么分组
    res = models.Book.objects.annotate(authors_num=Count('authors')).values('title','authors_num')
    print(res)
    
    """
    authors_num 是我们自己定义的字段 用接收聚合的结果 
    authors     书籍表的外键字段 对应的是作者表
    values			取值展示
    
    """

#02 统计每个出版社卖的最便宜的书的价格, 每个出版社 最便宜的书价格
    res = models.Publish.objects.annotate(min_price=Min('book__price')).values('name','min_price')
    print(res)

    
#03 找出图书的作者是多个的 
	"""
	先找出多个作者的书籍
	然后进行过滤 找出大于1的
	 
	 只要你的orm语句得出的结果还是一个queryset对象
   那么它就可以继续无限制的点queryset对象封装的方法
	
	"""
    res = models.Book.objects.annotate(count_author=Count('authors')).filter(count_author__gt=1).values(
        'title','count_author'
    )
    print(res)

#04 查询每个作者出的书的 总价格(作者名称 书籍价格)
    res= models.Author.objects.annotate(sum_book=Sum('book__price')).values('name','sum_book','count_book')
    print(res)
    
	
  
  
"""
    如果我想按照指定的字段分组该如何处理呢?
        models.Book.objects.values('price').annotate()
    后续BBS作业会使用
    
    
    你们的机器上如果出现分组查询报错的情况
        你需要修改数据库严格模式
""" 
  

5.3 F和Q查询

  • F查询
## F 查询需要先倒入功能模块

    """
    能够帮助你直接获取到表中某个字段对应的数据
    在操作字符类型的数据的时候 F不能够直接做到字符串的拼接
 	 	"""
  
##字段数据准备
#图书表
class Book(models.Model):
    # 库存
    kucun = models.IntegerField(default=1000)
    # 卖出
    maichu = models.IntegerField(default=100)

  	def __str__(self):
      return self.title  
    
    
#01 查询卖出大于库存的书籍
    from django.db.models import F
    res = models.Book.objects.filter(maichu__gt=F('kucun'))
    print(res)


#02 将书籍的价格提升500块
    res = models.Book.objects.update(price=F('price') + 500)

#03 将所有书名称加上爆款两个字
    """
    在操作字符类型的数据的时候 F不能够直接做到字符串的拼接
    需要使用
    from django.db.models.functions import Concat
    from django.db.models import Value
    
    """
    from django.db.models.functions import Concat
    from django.db.models import Value
    res = models.Book.objects.update(title=Concat(F('title'),Value('爆款')))



  • Q查询
## Q查询 导入功能模块
from django.db.models import Q

#01 查询书籍卖出数量大于100 或者小于600的
    # res = models.Book.objects.filter(Q(price__gt=100),Q(price__lt=600))     # , 默认是 and关系     与
    # res = models.Book.objects.filter(Q(price__gt=100)|Q(price__lt=600))     # | 是或者 or 关系     或
    # res = models.Book.objects.filter(~Q(price__gt=100)|Q(price__lt=600))    # ~ NOT 前面条件取反   非
    print(res)


#02 Q的高阶用法  能够将查询条件的左边也变成字符串的形式
    q = Q()
    q.connector = 'or'											#默认是 default = AND 并且关系
    q.children.append(('maichu__gt',100))
    q.children.append(('price__lt', 600))
    res = models.Book.objects.filter(q)
    print(res)

    

5.4 djiango中开启事务

##事务的特点
"""
事务
	ACID
		原子性
			不可分割的最小单位
		一致性
			跟原子性是相辅相成
		隔离性
			事务之间互相不干扰
		持久性
			事务一旦确认永久生效
	
	msyql事务的回滚 
		rollback
	msyql事务的确认
		commit
"""



#开启事务 需要导入功能模块  transaction
    from django.db import transaction
    try:
        with transaction.atomic():
            """
            sql1
            sql2
            在with代码快内书写的所有orm操作都是属于同一个事务
            """
            pass
    except Exception as e:
        print(e)
    print('执行其他操作')



5.5 orm中常用字段及参数

AutoField
	主键字段 primary_key=True
  
CharField				varchar
	verbose_name	字段的注释
  max_length		长度
  
IntegerField			int
BigIntegerField		bigint

DecimalField
	max_digits=8
  decimal_places=2

EmailFiled				varchar(254)

DateField					date
DateTimeField			datetime
	auto_now:每次修改数据的时候都会自动更新当前时间
  auto_now_add:只在创建数据的时候记录创建时间后续不会自动修改了
    
BooleanField(Field)				- 布尔值类型
	该字段传布尔值(False/True) 	数据库里面存0/1

TextField(Field)					- 文本类型
	该字段可以用来存大段内容(文章、博客...)  没有字数限制
  后面的bbs作业 文章字段用的就是TextField


FileField(Field)					- 字符类型
   upload_to = "/data"
  给该字段传一个文件对象,会自动将文件保存到/data目录下然后将文件路径保存到数据库中
  /data/a.txt
  后面bbs作业也会涉及

# 更多字段
直接参考博客: https://www.cnblogs.com/Dominic-Ji/p/9203990.html

    
# django除了给你提供了很多字段类型之外 还支持你自定义字段
class MyCharField(models.Field):
    def __init__(self,max_length,*args,**kwargs):
        self.max_length = max_length
        # 调用父类的init方法
        super().__init__(max_length=max_length,*args,**kwargs)  # 一定要是关键字的形式传入

    def db_type(self, connection):
        """
        返回真正的数据类型及各种约束条件
        :param connection:
        :return:
        """
        return 'char(%s)'%self.max_length

# 自定义字段使用
myfield = MyCharField(max_length=16,null=True)



# 外键字段及参数
unique=True
	ForeignKey(unique=True)   ===			OneToOneField()
  # 你在用前面字段创建一对一 orm会有一个提示信息 orm推荐你使用后者但是前者也能用
  
db_index
	如果db_index=True 则代表着为此字段设置索引
  (复习索引是什么)

to_field
	设置要关联的表的字段  默认不写关联的就是另外一张的主键字段

on_delete
	当删除关联表中的数据时,当前表与其关联的行的行为。
  """
  django2.X及以上版本 需要你自己指定外键字段的级联更新级联删除
  """

六. 数据库查询优化

6.1 单表查询优化

  • only与defer
"""
only与defer	
select_related与prefetch_related

orm语句的特点:
	惰性查询
		如果你仅仅只是书写了orm语句 在后面根本没有用到该语句所查询出来的参数
		那么orm会自动识别 直接不执行

"""


#获取书籍表的所有书的名称
#01 正常写法
    res = models.Book.objects.values('title')
    for i in res:
        print(i.get('title'))

    
    res = models.Book.objects.all()
    print(res)
    for i in res:
        print(i.title)					#all不需要走了
        
        
#02 only写法
    res = models.Book.objects.only('title')     #得到的是个对象
    for i in res:
        print(i.title)          #点击only括号内的字段 不会走数据库
        print(i.price)          #点击only括号内没有的字段 会重新走数据库查询而all不需要走了

#03 defer 写法
    res = models.Book.objects.defer('title')
    for i in res:
        print(i.title)
        print(i.price)
    """
    defer与only刚好相反
        defer括号内放的字段不在查询出来的对象里面 查询该字段需要重新走数据
        而如果查询的是非括号内的字段 则不需要走数据库了
    """


6.2 连表查询优化

  • select_related与prefetch_related
"""
select_related与prefetch_related
"""
## 查询书籍的出版社名称
#01 正常写法
    res = models.Book.objects.all()
    for i in res:
        print(i.publish.name)           #all() 每循环一次都会走一次数据库查询

#02 select_related 连表查询 写法
    res = models.Book.objects.select_related('publish')     #INNER JOIN 连表查询 只会查询一次数据库
    for i in res:
        print(i.publish.name)

    """
    select_related内部直接先将book与publish连起来 然后一次性将大表里面的所有数据
    全部封装给查询出来的对象
        这个时候对象无论是点击book表的数据还是publish的数据都无需再走数据库查询了
    select_related括号内只能放外键字段    一对多 一对一
        多对多也不行
    """

#03 prefetch_related 子查询
    res = models.Book.objects.prefetch_related('publish')
    for i in res:
        print(i.publish.name)

    """
    prefetch_related该方法内部其实就是子查询
        将子查询查询出来的所有结果也给你封装到对象中
        给你的感觉好像也是一次性搞定的
    """


七. choices参数

7.1 数据库字段设计常见

"""
用户表	
	性别
	学历
	工作经验
	是否结婚
	是否生子
	客户来源
	...
针对某个可以列举完全的可能性字段,我们应该如何存储

只要某个字段的可能性是可以列举完全的,那么一般情况下都会采用choices参数

7.2 创建环境

#01 创建表
from django.db import models
class User(models.Model):
    username = models.CharField(max_length=32)
    age = models.IntegerField()
    #性别元祖
    gender_choices = (
        (1,'男'),
        (2,'女'),
        (3,'其它'),
    )
    gender = models.IntegerField(choices=gender_choices)
    """
    IntegerField 如果元素第一个是数字 则使用数字字段 
    
    该gender字段存的还是数字 但是如果存的数字在上面元祖列举的范围之内
    那么可以非常轻松的获取到数字对应的真正的内容
    
    1.gender字段存的数字不在上述元祖列举的范围内容
    2.如果在 如何获取对应的中文信息
  
    """

    #评级 字符串字段  保证字段类型跟列举出来的元祖第一个数据类型一致即可
    score_choices = (
        ('A','优秀'),
        ('B','良好'),
        ('c','及格'),
        ('B','不合格'),
    )
    score = models.CharField(choices=score_choices,null=True,max_length=32)
    
makemigrations
migrate



#02 tests文件 测试数据
from django.test import TestCase

import os
import sys

if __name__ == "__main__":
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "day66.settings")
    import django
    django.setup()
    from app01 import models
    

7.3 取choices数据

"""
只要是choices参数的字段 如果你想要获取对应信息 固定写法 get_字段名_display()

如果没有对应关系 那么字段是什么还是展示什么
"""
    #01 创建数据
    models.User.objects.create(username='付付',age=18,gender=2)
    models.User.objects.create(username='宇宙',age=19,gender=1)
    models.User.objects.create(username='东方不败',age=100,gender=3)
    models.User.objects.create(username='金木',age=90,gender=4)

    #02 获取choices 数据
    obj = models.User.objects.filter(gender=1).first()
    print(obj.get_gender_display())

    #03 如果没有对应关系 那么字段是什么还是展示什么
    obj = models.User.objects.filter(gender=4).first()
    print(obj.get_gender_display())

    
    ##04 添加数据
    models.User.objects.filter(gender=1).update(score='A')
    models.User.objects.filter(gender=2).update(score='B')
    models.User.objects.filter(gender=3).update(score='C')
    models.User.objects.filter(gender=4).update(score='D')

    #03 取数据 
    obj = models.User.objects.filter(score='A').first()
    print(obj.get_score_display())

   
  
男
4
优秀

八. MTV与MVC模型

# MTV:Django号称是MTV模型
M:models
T:templates
V:views
# MVC:其实django本质也是MVC
M:models
V:views
C:controller
  
# vue框架:MVVM模型

九. 多对多三种创建方式