Django测试开发学习笔记(一)

基础介绍

1. 简介

Django,发音为[dʒæŋɡəʊ],是用python语言写的开源web开发框架,并遵循MVC设计。Django的主要目:简便、快速的开发「数据库」驱动的网站。

学习网站:

官方网站: https://www.djangoproject.com/

github源码:https://github.com/django/django

django中文文档:https://yiyibooks.cn/xx/Django_1.11.6/index.html

2. 特点

  • 重量级框架

    对比Flask框架,Django原生提供了众多的功能组件,让开发更简便快速。

    • 提供项目工程管理 自动化脚本工具

    • 数据库ORM支持(对象关系映射,英语:Object Relational Mapping)

    • 模板

    • 表单

    • Admin管理站点

    • 文件管理

    • 认证权限

    • session机制

    • 缓存

  • MVT模式

    • M全拼为Model,与MVC中的M功能相同,负责和数据库交互,进行数据处理。
    • V全拼为View,与MVC中的C功能相同,接收请求,进行业务处理,返回应答。
    • T全拼为Template,与MVC中的V功能相同,负责封装构造要返回的html。
    image
![image](https://upload-images.jianshu.io/upload_images/12041448-2817315ecaf2b5e8?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

3. 环境搭建

  • 环境准备

    • python3.x
    • django 3.0.4
    • pycharm 专业版(专业版可以多一些django框架中界面操作的功能,社区版的话,需要用命令行去执行)
  • 安装django pip install django==3.0.4

4. 创建项目

  • 选择django框架+虚拟环境


    image
  • 指定项目运行的python解释器
    Perference->Project Interpreter->选择为虚拟环境中的python.exe

  • 项目中的文件

    • 最外层的:django_project: 项目的容器,可以随便命名。
    • manage.py: 一个让你用各种方式管理 Django 项目的命令行工具。
    • django_project/init.py:一个空文件,告诉 Python 这个目录应该被认为是一个 Python 包。
    • django_project/settings.py:Django 项目的配置文件。
    • django_project/urls.py:Django 项目的 URL 声明,就像你网站的“目录”。
    • django_project/wsgi.py:作为你的项目的运行在 WSGI 兼容的Web服务器上的入口。
  • 启动命令配置

    image
![image](https://upload-images.jianshu.io/upload_images/12041448-e15f6482897c18db?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
  • 访问127.0.0.1:8000,表示环境搭建成功

    image

5. settings.py配置文件

  • app路径配置

    INSTALLED_APPS = [
     'django.contrib.admin',
     'django.contrib.auth',
     'django.contrib.contenttypes',
     'django.contrib.sessions',
     'django.contrib.messages',
     'django.contrib.staticfiles',
     'app1.apps.App1Config', 
     # 默认已有 如果没有只要添加app名称即可 例如: 'app1'
     # 新建的应用都要在这里添加
    ]
    
  • 数据库配置

    # 如果使用django的默认sqlite3数据库则不需要改
    DATABASES = {
     'default': {
     'ENGINE': 'django.db.backends.sqlite3',
     'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
     }
    }
    
    # 如果使用mysql数据库需要将上述数据库注掉修改如下:
    
    DATABASES = {
     'default': {  
     'ENGINE': 'django.db.backends.mysql',
     'NAME': 'blog', #你的数据库名称 数据库需要自己提前建好
     'USER': 'root', #你的数据库用户名
     'PASSWORD': '', #你的数据库密码
     'HOST': '', #你的数据库主机,留空默认为localhost
     'PORT': '3306', #你的数据库端口
     }
    }
    
  • 静态文件目录

    STATIC_URL = ‘/static/’ #调用时目录
    STATICFILES_DIRS=[
    os.path.join(BASE_DIR,“static”), #具体路径
    ]
    
  • 中间件

    # 自己写的中间件,例如在项目中的md文件夹下md.py文件中的M1与M2两个中间件
    
    MIDDLEWARE = [
     'django.middleware.security.SecurityMiddleware',
     'django.contrib.sessions.middleware.SessionMiddleware',
     'django.middleware.common.CommonMiddleware',
     'django.middleware.csrf.CsrfViewMiddleware',
     'django.contrib.auth.middleware.AuthenticationMiddleware',
     'django.contrib.messages.middleware.MessageMiddleware',
     'django.middleware.clickjacking.XFrameOptionsMiddleware',
     'md.md.M1',
     'md.md.M2',
    ]
    # 注意自己写的中间件,配置要写在系统中的后面
    
  • session存储的相关配置

    # 数据库配置(默认)
    
    # Django默认支持Session,并且默认是将Session数据存储在数据库中,即:django_session 表中。
    # 配置 settings.py
     SESSION_ENGINE = 'django.contrib.sessions.backends.db' # 引擎(默认)
     SESSION_COOKIE_NAME = "sessionid"   # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串(默认)
     SESSION_COOKIE_PATH = "/"    # Session的cookie保存的路径(默认)
     SESSION_COOKIE_DOMAIN = None    # Session的cookie保存的域名(默认)
     SESSION_COOKIE_SECURE = False    # 是否Https传输cookie(默认)
     SESSION_COOKIE_HTTPONLY = True    # 是否Session的cookie只支持http传输(默认)
     SESSION_COOKIE_AGE = 1209600    # Session的cookie失效日期(2周)(默认)
     SESSION_EXPIRE_AT_BROWSER_CLOSE = False   # 是否关闭浏览器使得Session过期(默认)
     SESSION_SAVE_EVERY_REQUEST = False   # 是否每次请求都保存Session,默认修改之后才保存(默认)
     
     SESSION_CACHE_ALIAS = 'default' # 使用缓存的别名,此处设置成redis数据库配置的别名default
    
    
  • 缓存配置

    • 内存缓存配置

      # 配置缓存
       CACHES = {
       'default': {
        'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
        'LOCATION': 'unix:/tmp/memcached.sock',
        'KEY_PREFIX': 'lcfcn',
        'TIMEOUT': None
          }
       }
      
    • 文件缓存配置

      CACHES = {
          'default': {
              'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
              'LOCATION': '/var/tmp/django_cache', # 文件路径
          }
      }
      
    • 数据库缓存配置

      CACHES = {
          'default': {
              'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
              'LOCATION': 'my_cache_table', # 数据库表名,任意名字即可
          }
      }
      

      配置完之后记得在cmd中执行下列语句创建缓存表

      python manage.py createcachetable

    • redis缓存配置

      CACHES = { # 配置缓存数据为redis
          "default": {
              "BACKEND": "django_redis.cache.RedisCache",
              "LOCATION": "redis://127.0.0.1:6379",
              "OPTIONS": {
                  "CLIENT_CLASS": "django_redis.client.DefaultClient",
                  "PASSWORD":"12345"
              }
          }
      }
      
    • CACHES设置中有几个额外的参数:

      • TIMEOUT:缓存超时时间,默认为300s,可以设置为None,即永不超时。

      • OPTIONS : locmem, filesystem和database缓存系统这些有自己的剔除策略的系统有以下的参数:

      • MAX_ENTRIES : 缓存中存放的最大条目数,大于这个数时,旧的条目将会被删除,默认为300.

      • CULL_FREQUENCY:当达到MAX_ENTRIES的时候,被接受的访问的比率。实际的比率是1/cull_frequency,所以设置为2就是在达到max_entries时去除一半数量的缓存,设置为0意味着达到max_entries时,缓存将被清空。这个值默认是3。

      • KEY_PREFIX:一个会自动列入缓存key值的的字符串。

      • VERSION:缓存key值生成时使用的版本数字。

      • KEY_FUNCTION:key值最终生成所使用的方法。

6. Django常用命令

  • 创建项目

    django-admin startproject project_name

  • 新建app

    cd 项目根目录

    python manage.py startapp app_name

  • 创建数据库表 或 更改数据库表或字段

    生成迁移脚本 python manage.py makemigrations

    执行迁移脚本 python manage.py migrate

  • 使用开发服务器

    开发服务器,即开发时使用,一般修改代码后会自动重启,方便调试和开发,但是由于性能问题,建议只用来测试,不要用在生产环境。

    python manage.py runserver

    当提示端口被占用的时候,可以用其它端口:

    python manage.py runserver 8001

    监听机器所有可用 ip (电脑可能有多个内网ip或多个外网ip)

    python manage.py runserver 0.0.0.0:8000

    如果是外网或者局域网电脑上可以用其它电脑查看开发服务器

    访问对应的ip加端口,比如 http://172.16.20.2:8000

  • 清空数据库

    python manage.py flush

    此命令会询问是 yes 还是 no, 选择 yes 会把数据全部清空掉,只留下空表。

  • 创建超级管理员

    python manage.py createsuperuser

    按照提示输入用户名和对应的密码就好了邮箱可以留空,用户名和密码必填

    修改用户密码可以用:

    python manage.py changepassword username

  • 导出数据 导入数据

    python manage.py dumpdata appname > appname.json

    python manage.py loaddata appname.json

  • Django 项目环境终端

    python manage.py shell

    如果你安装了 bpython 或 ipython 会自动用它们的界面,推荐安装 bpython。

    这个命令和 直接运行 python 或 bpython 进入 shell 的区别是:你可以在这个 shell 里面调用当前项目的 models.py 中的 API,对于操作数据,还有一些小测试非常方便。

  • 数据库命令行
    python manage.py dbshell

    Django 会自动进入在settings.py中设置的数据库,如果是 MySQL 或 postgreSQL,会要求输入数据库用户密码。

    在这个终端可以执行数据库的SQL语句。如果您对SQL比较熟悉,可能喜欢这种方式。

  • 反向生成model

    python manage.py inspectdb

  • 更多命令

    python manage.py

    可以看到详细的列表,在忘记子名称的时候特别有用。

视图

1. 视图函数

  • 介绍

    一个视图函数,简称视图,是一个简单的Python函数,它接受Web请求并且返回Web响应。响应可以是一张网页的HTML内容,一个重定向,一个404错误,一个XML文档,或者一张图片,是任何东西都可以。无论视图本身包含什么逻辑,都要返回响应。代码写在哪里也无所谓,只要它在你的Python目录下面。除此之外没有更多的要求了——可以说“没有什么神奇的地方”。为了将代码放在某处,约定是将视图放置在项目或应用程序目录中的名为views.py的文件中。

    视图函数:

    一定包含两个对象:

    • request---->用户请求相关的所有信息(对象)

    • Httpresponse---->响应字符串

  • 简单的视图demo

    from django.http import HttpResponse # 导入了HttpResponse类
    
    # 我们定义了hello_world函数。它就是视图函数。
    # 每个视图函数都使用HttpRequest对象作为第一个参数,并且通常称之为 request。
    def helloworld(request):
        return HttpResponse("helloworld!hello,测开大佬!")
    
  • 配置路由

    主路由,即项目下的urls.py

    from django.contrib import admin
    from django.urls import path, include
    
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('test/',include('views_demo.urls'))
    ]
    

    子路由,即app下的urls.py

    from django.urls import path
    from views_demo import views
    
    urlpatterns = [
        path('helloworld/',views.helloworld),
    ]
    
  • 根据路由访问示例

    image

    展示了views.py中定义的helloworld函数的返回

2. 请求和响应

视图函数,围绕着两个对象进行:HttpResponse和HttpRequest

  • HttpRequest

    request---->请求信息

    request.path # 获取访问文件路径
    
    request.method属性 #获取请求中使用的HTTP方式(POST/GET)
    
    request.body #含所有请求体信息 是bytes类型
    
    request.GET #GET请求的数据(类字典对象)  请求头中的url中?后面拿值
    
    request.POST # POST请求的数据(类字典对象) 请求体里拿值
    
    request.COOKIES #包含所有cookies的标准Python字典对象;keys和values都是字符串。
    
    request.FILES:包含所有上传文件的类字典对象;FILES中的每一个Key都是<input type="file" name=""/>标签中name属性的值,FILES中的每一个value同时也是一个标准的python字典对象,包含下面三个Keys:
        - filename:上传文件名,用字符串表示
        - content_type:上传文件的Content Type
        - content:上传文件的原始内容
    
    request.user:是一个django.contrib.auth.models.User对象,代表当前登陆的用户。如果访问用户当前没有登陆,user将被初始化为django.contrib.auth.models.AnonymousUser的实例。你可以通过user的is_authenticated()方法来辨别用户是否登陆:if req.user.is_authenticated();只有激活Django中的AuthenticationMiddleware时该属性才可用
    
    request.session:唯一可读写的属性,代表当前会话的字典对象;自己有激活Django中的session支持时该属性才可用
    
    request.GET.get('name') 拿到GET请求里name的值
    
    如果某个键对应有多个值,则不能直接用get取值,需要用getlist,如:request.POST.getlist("hobby")
    
    请求url:http://127.0.0.1:8000/index.html/23?a=1
    
    request.path : 请求路径
    request.path结果为:/index.html/23
    
    request.get_full_path()
    request.get_full_path()结果为:/index.html/23?a=1
    

    可以自己在视图函数中打印下试试,可在控制台看到:

    def helloworld(request):
        print(request.path)
        print(request.GET)
        print(request.body)
        return HttpResponse("helloworld!hello,测开大佬!")
    
  • HttpResponse

    HttpResponse---->响应字符串

    对于HttpRequest请求对象来说,是由django自动创建的,但是,HttpResponse响应对象就必须我们自己创建。每个view请求处理方法必须返回一个HttpResponse响应对象。HttpResponse类在django.http.HttpResponse。

    常用属性:

    content:返回的内容。
    response = HttpResponse()
    response.content = '果芽软件'
    return response
    status_code:返回的HTTP响应状态码
    content_type:返回数据的mime类型
    

    常用方法:

    set_cookie:用来设置cookie信息
    delete_cookie:用来删除cookie信息。
    write:HttpResponse是一个类似于文件的对象,可以用来写入数据到数据体(content)中。
    
  • JsonResponse对象

    # 把字典或者列表当做抓换成json数据并返回
    
    def test_redirect(request):
        return JsonResponse([{"code":0000,"msg":'成功'}],safe=False) # 数据类型为列表,必须指定safe=False
        # return JsonResponse({"code":0000,"msg":'成功'},safe=False) # 数据类型为字典,不必指定safe
    
  • 其他常用响应函数和对象

    • render 函数(前后端分离开发,不需要掌握,了解即可)

      将指定页面渲染后返回给浏览器

      render(request, template_name[, context])

      结合一个给定的模板和一个给定的上下文字典,并返回一个渲染后的 HttpResponse 对象。

    • redirect 函数

      参数可以是:

      一个模型:将调用模型的get_absolute_url() 函数
      一个视图,可以带有参数:将使用urlresolvers.reverse 来反向解析名称
      一个绝对的或相对的URL,将原封不动的作为重定向的位置。

      默认返回一个临时的重定向;传递permanent=True 可以返回一个永久的重定向。

3. 类视图

>以函数的形式进行定义的视图就是函数视图,视图函数便于理解,但是遇到一个视图函数对应的路径提供了多种不同的HTTP请求方式的支持时(get,post,delete,put),需要在一个函数中写不同的业务逻辑,代码的可读性和复用性就很低, 所以,我们引入类视图进行解决。
  • 未使用类视图

     def register(request):
        """处理注册"""
     
        # 获取请求方法,判断是GET/POST请求
        if request.method == 'GET':
            # 处理GET请求,返回注册页面
            return render(request, 'register.html')
        else:
            # 处理POST请求,实现注册逻辑
            return HttpResponse('这里实现注册逻辑')
    
  • 使用类视图示例

    class DefineClassview(View):
        """演示类视图的定义和使用"""
    
        def get(self, request):
            """处理GET请求业务逻辑"""
            return HttpResponse('GET请求业务逻辑')
    
        def post(self, request):
            """处理POST请求业务逻辑"""
            return HttpResponse('POST请求业务逻辑')
    
        def put(self, request):
            pass
    
  • 类视图的使用

    • 定义类视图需要继承自的Django提供的父类的View

    • 导入

      • from django.views.generic import View
    • 示例 :views.py

      from django.http import HttpResponse
      from django.views import View #导入View
      class ViewDemo(View):
      
          def get(self,request):
              return HttpResponse("get方法被调用")
      
          def post(self,request):
              return HttpResponse("post方法被调用")
      
          def put(self,request):
              return HttpResponse("put方法被调用")
      
          def delete(self,request):
              return HttpResponse("delete方法被调用")
      
    • 配置路由时,需要使用类视图的as_view()方法来注册添加

      urlpatterns = [
      path('',view.ViewDemo.as_view()),# 使用as_view()方法把类视图注册为视图
      ]
      
    • 去掉禁用跨域访问的中间件

      若接口返回403 Forbidden,注释掉settings.py中的禁用跨域访问的中间件

      image

路由

1. 单一路由

就是一个路径对应一个视图函数或者视图类

```
urlpatterns = [
    path('hello/',view.hello_world),
    path('',view.ViewDemo.as_view()),# 使用as_view()方法把类视图注册为视图
]
```

2. 基于正则的路由

前边的写法,我们在访问一个接口的时候,接口地址最后边必须要带一个/但是这样看着极度不美观。我们就可以通过基于正则表达式的路由帮我们解决。

注意:

  • 基于正则表达式的路由必须使用re_path而不是我们之前用的path了
  • 正则表达式字符串的开头字母“r”。它告诉Python这是个原始字符串,不需要处理里面的反斜杠(转义字符)(2.0版本之后可以不用带了)

    urlpatterns = [
        re_path(r'^hello.?$',view.ViewDemo.as_view()),# .?代表0到1个任意字符,这样写我们在访问地址的时候就可以省略最后边的/了
    ]
    

3. 路由参数传递

  • 在Django中路由参数传递改变了一点写法

    urlpatterns = [
        path("args/<pk>/",view.args_demo),# 默认是匹配字符串
    ]
    
  • 视图函数参数列表中需要变量来接收url参数,且视图中的参数和上边的关键字一致

    def args_demo(request,pk):# 视图函数的参数列表中需要一个变量来接收url参数
        print(pk)
        return HttpResponse("pk的值为:{}".format(pk))
    
  • 注意:

    • 要捕获一段url中的值,需要使用尖括号
    • 可以转换捕获到的值为指定类型,比如例子中的int。默认情况下,捕获到的结果保存为字符串类型,不包含/这个特殊字符;
  • 还可以写成正则表达式的形式

    urlpatterns = [
        re_path(r"^args/(?P<pk>[a-zA-Z]+).?$",view.args_demo) # 正则表达式的写法,
    ]
    
    # (?P<关键字>) 指定正则表达式提取值的存放关键字
    # [a-zA-Z]+  表示匹配1到多个字母 匹配内容根据你正则表达式来定
    # (?P<pk>[a-zA-Z]+) 完整的解释就是匹配1到多个字母,存入pk这个关键字中
    
  • url参数转换器


    image
    urlpatterns = [
        path("args/<int:pk>/",view.args_demo),# 表示匹配 int类型的参数
    ]
    
  • 传递多个参数

    path("args/<int:page>/<int:size>/",view.args_demo)
    
  • 视图函数中也要对应声明多个参数来接收

    def args_demo(request,page,size):# 视图函数的参数列表中需要一个变量来接收url参数
        return HttpResponse("page的值为:{},size的值为:{}".format(page,size))
    
  • 参数传递默认值

    path("args/<int:page>/<int:size>/",view.args_demo,{"page":1,"size":5})
    

4. 路由分发

路由分发指的是一个请求过来之后,怎么通过一级一级的转发,给到对应的程序处理;在django中一般指的是,从主app分发到子app中

  • 创建子应用

    python manage.py startapp app01

  • settings.py中引入刚创建的app

image
  • 子应用中创建子路由文件
image
  • 子应用中写一个视图和路由信息,方便讲路由分发

views.py

from django.http import HttpResponse
from django.shortcuts import render

# Create your views here.

def hello_world(request):

    return HttpResponse('hello world')

urls.py

from django.urls import path, re_path

from . import views

urlpatterns = [
    re_path(r"^hello.?$",views.hello_world),
]
  • 使用include分发路由
from django.urls import path, re_path, include

from django_project import view

urlpatterns = [
    path("v01/",include("app01.urls")) # 主路由中使用include转发至子路由中
]
  • 重启服务之后,浏览器访问

5. url反向解析和命名空间(这个部分没懂,待补充,0524路由&视图)

在实际开发中,系统内部某些视图会通过redirect(url)重定向至其他的视图来处理,但是如果url地址被改掉了,我们通过url重定向就会失败,为了解决这个问题,引入了命名路由和命名空间的概念

ORM

  • ORM 概念

对象关系映射(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。 简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中。 ORM在业务逻辑层和数据库层之间充当了桥梁的作用

  • ORM 由来

让我们从O/R开始。字母O起源于"对象"(Object),而R则来自于"关系"(Relational)。 几乎所有的软件开发过程中都会涉及到对象和关系数据库。在用户层面和业务逻辑层面,我们是面向对象的。 当对象的信息发生变化的时候,我们就需要把对象的信息保存在关系数据库中。 按照之前的方式来进行开发就会出现程序员会在自己的业务逻辑代码中夹杂很多SQL语句用来增加、读取、修改、删除相关数据, 而这些代码通常都是极其相似或者重复的

  • ORM 优势

ORM解决的主要问题是对象和关系的映射。它通常将一个类和一张表一一对应,类的每个实例对应表中的一条记录, 类的每个属性对应表中的每个字段。 ORM提供了对数据库的映射,不用直接编写SQL代码,只需操作对象就能对数据库操作数据。 让软件开发人员专注于业务逻辑的处理,提高了开发效率。

  • ORM 劣势

ORM的缺点是会在一定程度上牺牲程序的执行效率。 ORM的操作是有限的,也就是ORM定义好的操作是可以完成的,一些复杂的查询操作是完成不了。 ORM用多了SQL语句就不会写了,关系数据库相关技能退化…

  • ORM 总结

ORM只是一种工具,工具确实能解决一些重复,简单的劳动。这是不可否认的。 但我们不能指望某个工具能一劳永逸地解决所有问题,一些特殊问题还是需要特殊处理的。 但是在整个软件开发过程中需要特殊处理的情况应该都是很少的,否则所谓的工具也就失去了它存在的意义。

  • ORM 与 DB 的对应关系

ORM 面向对象和关系型数据库的一种映射,通过操作对象的方式操作数据库数据,不支持对库的操作,只能操作表 对应关系: 类 --> 表 对象 --> 数据行 属性 --> 字段

Model 模块 在Django中model是你数据的单一、明确的信息来源。它包含了你存储的数据的重要字段和行为。通常, 一个模型(model)映射到一个数据库表。

  • 基本情况:

每个模型都是一个Python类,它是django.db.models.Model的子类。模型的每个属性都代表一个数据库字段。
综上所述,Django为您提供了一个自动生成的数据库访问API,详询官方文档

1. 安装mysql

2. django连接配置数据库

  • mysql中新建一个数据库

  • 安装msyql client

    pip install mysqlclient==1.4.6

  • settings.py文件中配置数据库链接信息

    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.mysql',
            'HOST': '127.0.0.1',  # 数据库主机
            'PORT': 3306,  # 数据库端口
            'USER': 'root',  # 数据库用户名
            'PASSWORD': 'root',  # 数据库用户密码
            'NAME': 'test'  # 数据库名字
        }
    }
    
  • 检查是否配置成功

    • pycharm terminal中执行python manage.py dbshell
    • 可进入mysql虚拟终端,表示配置成功

3. ORM表模型

定义模型

在Django中,所有的模型必须继承from django.db.models import Model这个类,字段类型需要使用models模块中定义好的字段类型

from django.db import models

class Student(models.Model):  # 一个类对应一个表

# 类属性对应表中的字段,models.CharField这里对应字段的类型,这个是字符串类型
# max_length定义字段的最大长度
# 五大约束:非空约束(null=False,为默认,可不写)、主键约束(primary_key=True)、唯一约束(unique=True)、可空(null=True)、外键约束(通过models.ForeignKey()来指定)
# help_text添加该字段的备注信息,但没有加在数据库中,让自己看着比较清楚
# choices=((0, '男'), (1, '女')),表示该字段只有0,1两个值,0代表男,1代表女
#  models.DateTimeField 该字段是日期类型
# auto_now_add=True 在每一次数据被添加进去的时候,记录当前时间
# auto_now=True 在每一次数据被保存的时候,记录当前时间
    
    s_name = models.CharField(max_length=64,help_text="学生姓名")
    s_sex = models.IntegerField(choices=((0, '男'), (1, '女')), help_text="性别")
    s_phone = models.CharField(max_length=11, help_text="手机号")
    create_time = models.DateTimeField(auto_now_add=True)
    
    update_time = models.DateTimeField(auto_now=True)

    class Meta:
        db_table = 'student'  # 指定表名,如果不指定表名,会用app名+类名生成表名


下图是mysql数据库字段和Models中定义类型的关系


image
模型中常用属性
image
django提供的一些特殊属性
EmailField(CharField):
    - 字符串类型,Django Admin以及ModelForm中提供验证机制
    - e_mail = models.EmailField(max_length=255, unique=True)

IPAddressField(Field)
    - 字符串类型,Django Admin以及ModelForm中提供验证 IPV4 机制

GenericIPAddressField(Field)
    - 字符串类型,Django Admin以及ModelForm中提供验证 Ipv4和Ipv6
    - 参数:
        protocol,用于指定Ipv4或Ipv6, 'both',"ipv4","ipv6"
        unpack_ipv4, 如果指定为True,则输入::ffff:192.0.2.1时候,可解析为192.0.2.1,开启此功能,需要protocol="both"

URLField(CharField)
    - 字符串类型,Django Admin以及ModelForm中提供验证 URL

SlugField(CharField)
    - 字符串类型,Django Admin以及ModelForm中提供验证支持 字母、数字、下划线、连接符(减号)

CommaSeparatedIntegerField(CharField)
    - 字符串类型,格式必须为逗号分割的数字

UUIDField(Field)
    - 字符串类型,Django Admin以及ModelForm中提供对UUID格式的验证

FilePathField(Field)
    - 字符串,Django Admin以及ModelForm中提供读取文件夹下文件的功能
    - 参数:
            path,                      文件夹路径
            match=None,                正则匹配
            recursive=False,           递归下面的文件夹
            allow_files=True,          允许文件
            allow_folders=False,       允许文件夹

FileField(Field)
    - 字符串,路径保存在数据库,文件上传到指定目录
    - 参数:
        upload_to = ""      上传文件的保存路径
        storage = None      存储组件,默认django.core.files.storage.FileSystemStorage

ImageField(FileField)
    - 字符串,路径保存在数据库,文件上传到指定目录
    - 参数:
        upload_to = ""      上传文件的保存路径
        storage = None      存储组件,默认django.core.files.storage.FileSystemStorage
        width_field=None,   上传图片的高度保存的数据库字段名(字符串)
        height_field=None   上传图片的宽度保存的数据库字段名(字符串)
自定义字段(了解即可)

示例:我们发现上边没有固定长度的字符串类型,但是手机号字段一般都是定长11位的字符串,所以我们需要自定义一些字段类型



from django.db import models

# Create your models here.

class PhoneField(models.Field):  # 自定义的char类型的字段类

    def __init__(self, max_length, *args, **kwargs):
        self.max_length = max_length
        super(PhoneField, self).__init__(max_length=max_length, *args, **kwargs)

    def db_type(self, connection):  # 限定生成数据库表的字段类型为char,长度为max_length指定的值的字符串
        return 'char(%s)' % self.max_length
Field的常用参数
image
模型中Meta配置:

对于一些模型级别的配置。我们可以在模型中定义一个类,叫做Meta。然后在这个类中添加一些类属性来控制模型的作用。

比如我们想要在数据库映射的时候使用自己指定的表名,而不是使用模型的名称。那么我们可以在Meta类中添加一个db_table的属性。示例代码如下:

class Book(models.Model):
    name = models.CharField(max_length=20,null=False)
    desc = models.CharField(max_length=100,name='description',db_column="description1")

    class Meta:
        db_table = 'book_model'
image
外键和表关系

4. ORM模型迁移

生成迁移脚本
  • python manage.py makemigrations
![image](https://upload-images.jianshu.io/upload_images/12041448-8322b9155e8bc7fa?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
  • 会在model_demo下的migrations文件夹下生成一个0001_initial.py文件,如下:

    from django.db import migrations, models
    
    
    class Migration(migrations.Migration):
    
        initial = True
    
        dependencies = [
        ]
    
        operations = [
            migrations.CreateModel(
                name='Student',
                fields=[
                    ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
                    ('s_name', models.CharField(help_text='学生姓名', max_length=64)),
                    ('s_sex', models.IntegerField(choices=[(0, '男'), (1, '女')], help_text='性别')),
                    ('s_phone', models.CharField(help_text='手机号', max_length=11)),
                    ('create_time', models.DateTimeField(auto_now_add=True)),
                    ('update_time', models.DateTimeField(auto_now=True)),
                ],
                options={
                    'db_table': 'student',
                },
            ),
        ]
    
    

    比我们自己写的model,多了一个id字段,这是django自动生成的,对所有的表都会添加这样一个字段,所以不用特别去定义。

    迁移脚本生成时,数据库还没有这个表。

执行迁移标本,数据库中创建新表
  • python manage.py migrate
image

因为之前还做了别的操作,只想执行model_demo下的迁移脚本,所以可以在命令后面加上app的名字。

在数据库中创建了student表,django_migrations表存储了的是django的迁移记录。

image
image

可以看看这些字段的约束和我们定义的一样:

image
迁移命令详解
  • makemigrations:将模型生成迁移脚本。模型所在的app,必须放在settings.py中的INSTALLED_APPS中。这个命令有以下几个常用选项:

    • app_label:后面可以跟一个或者多个app,那么就只会针对这几个app生成迁移脚本。如果没有任何的app_label,那么会检查INSTALLED_APPS中所有的app下的模型,针对每一个app都生成响应的迁移脚本。

    • -name:给这个迁移脚本指定一个名字。

    • -empty:生成一个空的迁移脚本。如果你想写自己的迁移脚本,可以使用这个命令来实现一个空的文件,然后自己再在文件中写迁移脚本。

  • migrate:将新生成的迁移脚本。映射到数据库中。创建新的表或者修改表的结构。以下一些常用的选项:

    • app_label:将某个app下的迁移脚本映射到数据库中。如果没有指定,那么会将所有在INSTALLED_APPS中的app下的模型都映射到数据库中。
      app_label migrationname:将某个app下指定名字的migration文件映射到数据库中。
    • –fake:可以将指定的迁移脚本名字添加到数据库中。但是并不会把迁移脚本转换为SQL语句,修改数据库中的表。
    • –fake-initial:将第一次生成的迁移文件版本号记录在数据库中。但并不会真正的执行迁移脚本。
  • showmigrations:查看某个app下的迁移文件。如果后面没有app,那么将查看INSTALLED_APPS中所有的迁移文件。

  • sqlmigrate:查看某个迁移文件在映射到数据库中的时候,转换的SQL语句。

5. abstract创建模型父类

在创建模型的时候,其实有好多字段都是模型中共同存在的,例如createtime,updatetime,mark等等,这些字段我们需要在每个模型中重复创建,这就造成了工作量的浪费,我们可以通过创建一个公共的模型父类来,子类集成该父类,这样我们就不需要重复写这些字段了

class PublicModel(models.Model):
    createtime = models.DateTimeField(auto_now_add=True,verbose_name="创建日期")
    updatetime = models.DateTimeField(auto_now=True,verbose_name = '修改日期')
    status = models.IntegerField(verbose_name = '状态')
    class Meta:
        abstract=True  # 父类中必须指定该属性,否则该类会被创建到数据库


# 子类继承PublicModel类,就不需要重复写createtime,update,status三个字段的定义了
class Student(PublicModel):
    s_name = models.CharField(max_length=64,help_text="学生姓名")
    s_sex = models.IntegerField(choices=((0, '男'), (1, '女')), help_text="性别")
    s_phone = models.CharField(max_length=11, help_text="手机号")

    class Meta:
        db_table = 'student'
  • model修改,重新迁移
    • 删掉migrations文件夹下迁移脚本
    • 删掉django_migrations表中的迁移记录
    • 删掉student表
    • 重新生成迁移脚本
    • 执行迁移脚本

6. ORM操作

ORM操作准备
  • 以这个模型来进行后续的ORM操作练习

    class Student(models.Model):  
        s_name = models.CharField(max_length=64,help_text="学生姓名")
        s_sex = models.IntegerField(choices=((0, '男'), (1, '女')), help_text="性别")
        s_phone = models.CharField(max_length=11, help_text="手机号")
        create_time = models.DateTimeField(auto_now_add=True)
        update_time = models.DateTimeField(auto_now=True)
    
        class Meta:
            db_table = 'student'  
    
  • 进入django的虚拟终端

    image
  • 导入要操作的模型类

    image
新增数据
  • 插入单条数据-save
    s = Student(s_name="李白",s_sex=0,s_phone='18103909786')
    s.save()
    
![image](https://upload-images.jianshu.io/upload_images/12041448-51f8e1f7d480c0a8?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

数据库新增了一条记录:

![image](https://upload-images.jianshu.io/upload_images/12041448-294c9a2b6642c6f2?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
  • 插入单条数据-create

    使用模型对象(objects)操作数据库

    Student.objects.create(s_name='鲁班七号',s_sex=0,s_phone='15855586589')
    
    image

    数据库新增了一条记录:


    image
  • 批量插入多条数据-bulk_create

    s1 = Student(s_name="王二锤",s_sex=0,s_phone='18103909782')
    s2 = Student(s_name="王三锤",s_sex=0,s_phone='18103909783')
    s3 = Student(s_name="王四锤",s_sex=0,s_phone='18103909784')
    
    student_list = [s1,s2,s3]
    Student.objects.bulk_create(student_list)
    

    数据库新增了多条数据:

    image
查找数据

查找数据都是通过模型下的objects对象来实现的

  • 查找所有数据

    使用Student.objects.all()student表的所有数据,等同于select * from student

    image

    通过上边的例子我们可看出,我们使用Student.objects.all()方法查询出了数据所有的数据,返回了一个QuerySet的list对象,list里边存的都是Student对象,所以我们可以通过students[0]拿到list中第一个Student对象,然后通过Student.s_name就可以查看Student对象中,s_name的值同理,我们可以用类似的方法查看s_sex,s_phone的值

数据过滤
  • 单条件过滤

    在查找数据的时候,有时候需要对一些数据进行过滤。那么这时候需要调用objects的filter方法。

    类似与sql语句中的where,查询出s_name='鲁班七号'的数据,返回的是一个list,所以需要加上下标访问。

    image

    搜索出来的结果返回是一个QuerySet,QuerySet常用的内置方法values()first()

![image](https://upload-images.jianshu.io/upload_images/12041448-6a67a6b89e507f30?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

`values()`展示详细的数据,更易读
`first()` 展示第一条数据
  • 其他过滤条件(双下划线查询)

    # 查询姓名中包含'二'字的学生信息
    s = Student.objects.filter(s_name__contains='王')
    s[0].s_name
    
    # 查询id>2的学生信息
    s=Student.objects.filter(id__gt=2).values()
    s
    
    image
![image](https://upload-images.jianshu.io/upload_images/12041448-92b1ad44c8ac97fe?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

常用的符号

![image](https://upload-images.jianshu.io/upload_images/12041448-b2798ac9c8f03abb?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
  • 日期过滤

    settings.py文件中修改USE_TZ = False

    # 查询日期是2020-07-05创建的用户
    s=Student.objects.filter(create_time__date='2020-07-05')
    
    image
  • 日期的比较

    # 查询2020-07-05 00:00:00之后创建的用户
    import datetime
    date = datetime.datetime(2020,4,2)
    student = Student.objects.filter(create_time__gte=date)
    
其他查询方式
  • get查询

    获取满足条件的一个数据,获取不到或者多个都报错。一般通过主键查询。

    s = Student.objects.get(pk=1) ,pk就是主键的意思,可以看到get方法返回的是一个Student对象

    image
  • exclude

    获取不满足条件的所有数据,exclude返回的是一个QuerySet类型的对象列表

    # 获取主键不等于1的所有数据
    s = Student.objects.exclude(pk=1)
    
    image
  • values

    查询的对象转为字典。

    s = Student.objects.exclude(pk=1) # 先根据条件筛选出部分数据,返回结果是一个QuerySet类型的对象列表
    s.values('s_name','s_sex') # 再使用values()确定展示字段
    Student.objects.values('s_name','s_sex') # #查询出全部数据,并确定展示字段
    
    image
  • values_list()

    它与values()非常相似,它返回的是一个元组序列,相对字典要节省空间。

    Student.objects.values_list('s_name','s_sex')
    
    image
  • first

    取第一条元素。没有元素返回None,有返回第一条记录。相当于Student.objects.exclude(pk=1)[0],但是Student.objects.exclude(pk=1)[0]没有值时会报错。

    Student.objects.exclude(pk=1).first()

  • last

    取最后一条元素。没有元素返回None,有返回最后一条记录。与first同理

    Student.objects.exclude(pk=1).last()
    
  • exists

    如果QuerySet包含数据,就返回True,否则返回False

    Student.objects.filter(pk=1).exists()
    
  • 嵌套查询

    -- 查询性别和id=2的学生相同的所有学生信息
    SELECT * from student where s_sex in (SELECT s_sex from student where id=2)
    

    ORM实现:

    student=Student.objects.filter(pk=2)
    Student.objects.filter(s_sex=student[0].s_sex)
    
![image](https://upload-images.jianshu.io/upload_images/12041448-a93aa961db53fe28?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
  • F查询

    (针对自己单表中字段的比较和处理)

    在上面所有的例子中,我们构造的过滤器都只是将字段值与某个常量做比较。如果我们要对两个字段的值做比较,那该怎么做呢?

    # 查询创建之后被修改了的用户信息
    from django.db.models import F
    Student.objects.filter(update_time__gt=F("create_time"))
    
多条件查询
  • 并列关系

    filter默认就是并列关系,逗号分割就是并列关系

    Student.objects.filter(s_name='鲁班七号',s_sex=0)
    
    image
  • Q查询

    使用Q查询,先要导入Q查询模块

    from django.db.models import Q
    
    • &

      stu=Student.objects.filter(Q(s_name='鲁班七号')&Q(s_sex=0))
      
      image
- 或 `|`
    ```
    # 筛选s_name='鲁班七号'或者s_sex=1
    stu=Student.objects.filter(Q(s_name='鲁班七号')|Q(s_sex=1))
    ```

    ![image](https://upload-images.jianshu.io/upload_images/12041448-05954b20d16bdd4c?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

- 非 `~`
    ```
    stu=Student.objects.filter(~Q(s_sex=0))
    ```


    ![image](https://upload-images.jianshu.io/upload_images/12041448-502a2ec8d476aa90?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
数据聚合-aggregate

sql中聚合函数有:max()、min()、count()、avg()、sum(),在ORM中如何表示?

使用聚合函数对数据进行统计操作,就需要使用到aggregate方法,返回的是一个字典。

aggregate是从整个查询结果集生成统计数据。

  • 导入聚合函数的类

    from django.db.models import Avg, Sum, Max, Min, Count
    
  • 用法:先筛选,再聚合

    image
  • 修改字段名

    image
排序

排序使用order_by()

image

# 按id字段正序排列,默认为正序,由小到大
stu=Student.objects.filter(Q(s_name='鲁班七号')|Q(s_sex=0)).order_by("id")
# 按id字段倒序排列,加个符号
stu=Student.objects.filter(Q(s_name='鲁班七号')|Q(s_sex=0)).order_by("-id")
数据去重

去重使用.valuse("去重字段").distinct

# s_sex字段去重
stu=Student.objects.filter(s_sex=0).values("s_sex").distinct()
image
分页

使用django自带的分页器Paginator来对数据进行分页操作

  • 导入分页器

    from django.core.paginator import Paginator
    
  • 分页

    # 导入模块
    from django.core.paginator import Paginator
    # Paginator第一个参数是要分页的数据,第二个参数per_page是每页的数据条数
    p = Paginator(Student.objects.all(),per_page=2)
    # get_page()表示获取第几页的数据,返回一个Page对象,object_list获取对象中的数据列表
    print(p.get_page(1).object_list)
    

    当获取的页数,没有数据时,会返回最后一页的数据。

    image
    # 判断是否有下一页,有下一页返回True,没有返回False
    p.page(1).has_next()
    
    image
    # 判断是否有前一页
    p.page(1).has_previous()
    

    其他的一些分页操作:

    image
分组
  • sq中的分组

    select s_sex,count(*) from student group by s_sex;
    

    sql中的分组有语法约定,展示的字段,只能是被分组的字段和聚合函数。

    在ORM中,我们也是重点关注,展示字段分组字段

  • ORM的分组

    Student.objects.all().values('s_sex').annotate(Count('id')).values('s_sex','id__count')
    

    第一个.values('s_sex')是确定分组的字段,annotate(Count('id'))是聚合函数,.values('s_sex','id__count')是展示字段,回想之前学习Count时,count('id'),返回的字段是id__count,所以这里展示字段要写id__count

![image](https://upload-images.jianshu.io/upload_images/12041448-a9aed20f130328f8?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

所以,它还可以这样写,使用`count=Count('id')`
```
Student.objects.all().values('s_sex').annotate(count=Count('id')).values('s_sex','count')
```
![image](https://upload-images.jianshu.io/upload_images/12041448-5221b6a94c135025?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
  • having怎么实现

    在annotate之后使用filter,相当于having

    Student.objects.all().values('s_sex').annotate(count=Count('id')).filter(count=1).values('s_sex')
    
    image
修改数据

get和filter查询出来的数据结构不一样,get查询出来的结果是一个对象,filter、exclude等查询出来的结果是一个查询集,他们的修改操作是不一样的。

  • 对对象的修改操作

    # 把id=3的性别改为1
    stu=Student.objects.get(id=3) # stu是一个对象
    stu.s_sex=1 # 修改对象中的属性值,此时,数据库值还没有改变
    stu.save() # 使用.save()来触发对数据库的操作
    
  • 对查询集的修改操作

    #  把性别为1的学生手机号改为13800000000
    student=Student.objects.filter(s_sex=1) # student是一个查询集
    student.update(s_phone='13800000000') # 使用.update()来修改值
    # 修改多个值使用逗号分割
    student.update(s_phone='13800000000',s_name='王小二')
    
    image
删除数据

在查找到数据后,便可以进行删除了。删除数据非常简单,只需要调用这个对象的delete方法即可。

stu = Student.objects.get(id='6')
stu.delete()
  • 物理删除&逻辑删除

    image
事务和回滚
  • 什么是事务和回滚

    事务:多个数据逻辑操作的集合

    回滚:多个逻辑中某个操作出错,回到初始状态

    事务的原子性要求事务要么全部完成,要么全部不完成,不可能停滞在某个中间状态

  • 什么情况下需要使用事务

    修改多个ORM模型做时需要使用事务和回滚操作

    对结果要求严格一致(要么全部成功,要么全部失败)

  • django中怎么使用事务

    • 自动进行事务提交和回滚
      # 自动进行事务提交和回滚
      
      from django.db import transaction
      
      # 使用@transaction.atomic实现自动进行事务提交和回滚
      @transaction.atomic
      def test(request):
          # 事务提交
          Student.objects.filter(id=1).update(s_sex=1) # 若没有事务,这次操作是会成功的
          stu = Student.objects.get(id=0) # 查询不到id=0的学生,get方法会报错
          stu.s_sex=1
          stu.save()
          Student.objects.filter(id=2).update(s_sex=1)# 失败
          return HttpResponse("ok")
          
      def test(request):
          # 使用上下文管理器实现自动进行事务提交和回滚
          with transaction.atomic():
              Student.objects.get(id=1).update(s_sex=1) # 若没有事务,这次操作是会成功的
              stu = Student.objects.get(id=0) # 查询不到id=0的学生,get方法会报错
              stu.s_sex=1
              stu.save()
              Student.objects.filter(id=2).update(s_sex=1)# 失败
              return HttpResponse("ok")
      
    • 手动进行事务提交和回滚
      # 手动进行事务提交和回滚
      def test(request):
      # 事务提交
          try:
              transaction.set_autocommit(False)# 关闭django自动提交
              Student.objects.get(id=1).update(s_sex=1) # 若没有事务,这次操作是会成功的
              stu = Student.objects.get(id=0) # 查询不到id=0的学生,get方法会报错
              stu.s_sex=1
              stu.save()
              Student.objects.filter(id=2).update(s_sex=1)# 失败
              transaction.commit() # 操作成功,事务提交
          except:
              print("操作失败,事务回滚")
              transaction.rollback()  # 操作失败,事务回滚
              return HttpResponse("ok")
      

7. ORM多表关联

image
关联关系
  • 一对一

    student <----> student_info

  • 多对一

    score --------> student

    score --------> course

    course --------> teacher

  • 多对多

    student <----------------------> course

关联模型创建
  • 一对一 models.OneToOneField()

    关联关系放在使用频率较高的表

    image
  • 多对一 models.ForeignKey()

    关联关放在“多”的表里

    image
  • 多对多 models.ManyToManyField()

    关联关放在使用频率较高的表

![image](https://upload-images.jianshu.io/upload_images/12041448-9c7c7f28130527bb?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
  • 关系表级联删除 on_delete

    image
代码示例

models.py

from django.db import models


# Create your models here.

# 学生表
class Stu(models.Model):
    s_name = models.CharField('学生姓名', max_length=64, null=False, help_text='学生姓名')
    s_sex = models.IntegerField('性别', max_length=1, choices=((0, '男'), (1, '女')), help_text='性别')
    s_age = models.PositiveIntegerField('年龄', null=False, default=0, help_text='年龄')
    s_dept = models.CharField("专业", max_length=64, null=False, help_text="专业")
    # 一对一关系,用models.OneToOneField(),关联放在使用频率较高的表中
    """
        models.OneToOneField()有三个参数:
            to:设置要关联的表名(类),
            to_field:设置要关联的表字段(类属性),
            on_delete:当关联字段删除时,s_id的值设置成什么,常用设置为models.CASCADE,表示当关联字段删除时,它也删除
    """
    """
        related_name:一般可以使用当前表_关联表来命名
            正向查询时,通过s_id可以查询student_info表中的数据,
            反向查询(从student_info表查询student表数据时)使用related_name进行查询
    """
    # db_column指定表字段列名
    si_id = models.OneToOneField(to='StudentInfo', to_field="id", on_delete=models.CASCADE, db_column='si_id',
                                 related_name="stu_stu_info")
    
    # 多对多关系会自动生成一张中间表,db_table是对中间表的命名
    course = models.ManyToManyField(to="Course", db_table="stu_course", related_name="course_student")

    class Meta:
        db_table = 'stu'

# 学生信息表
class StudentInfo(models.Model):
    s_card = models.CharField("身份证号", max_length=18, null=False, unique=True)
    s_phone = models.CharField(max_length=11, null=False, unique=True, help_text="手机号")
    s_addr = models.CharField("家庭住址", max_length=128, help_text="家庭住址")

    class Meta:
        db_table = "student_info"

# 课程表
class Course(models.Model):
    c_name = models.CharField('课程名',max_length=64,null=False,help_text="课程名")
    t_id = models.ForeignKey(to="Teacher",to_field="id",on_delete=models.CASCADE,related_name='teacher_course',db_column='t_id',help_text="老师编号")

    class Meta:
        db_table="course"

# 成绩表
class Score(models.Model):
    s_id = models.ForeignKey(to="Stu", to_field="id", on_delete=models.CASCADE, related_name='stu_score',
                             db_column='s_id', help_text="学生编号")
    c_id = models.ForeignKey(to="Course", to_field="id", on_delete=models.CASCADE, related_name='course_score',
                             db_column='c_id', help_text="学生编号")
    score = models.PositiveIntegerField("成绩",null=False,default=0,help_text="成绩")

    class Meta:
        db_table = "score"

# 老师表
class Teacher(models.Model):
    t_name = models.CharField("老师姓名", max_length=64,null=False,help_text="老师姓名")

    class Meta:
        db_table = "teacher"

数据迁移:

python manage.py makemigrations
python manage.py migrate multi_table
关联模型操作

增删改,都是单表操作,只有查询涉及多表,我们就主要讲下查询的多表操作

  • 数据准备

    -- 数据准备sql 
    insert into `student_info` (`id`, `s_card`, `s_phone`, `s_addr`) values('1','410422200205061358','18103909786','河南省鲁山县');
    insert into `student_info` (`id`, `s_card`, `s_phone`, `s_addr`) values('2','310112199003074772','13853444534','上海闵行区');
    insert into `student_info` (`id`, `s_card`, `s_phone`, `s_addr`) values('3','31011520020907641X','15520979867','上海市浦东新区');
    
    insert into `stu` (`id`, `s_name`, `s_sex`, `s_age`, `s_dept`, `si_id`) values('2','薛小磊','0','18','计算机科学','1');
    insert into `stu` (`id`, `s_name`, `s_sex`, `s_age`, `s_dept`, `si_id`) values('3','王大锤','1','30','生物科学','2');
    insert into `stu` (`id`, `s_name`, `s_sex`, `s_age`, `s_dept`, `si_id`) values('4','薛大磊','0','18','外贸金融','3');
    
    insert into `teacher` (`id`, `t_name`) values('1','张三');
    insert into `teacher` (`id`, `t_name`) values('2','李四');
    insert into `teacher` (`id`, `t_name`) values('3','王五');
    
    insert into `course` (`id`, `c_name`, `t_id`) values('1','数学','1');
    insert into `course` (`id`, `c_name`, `t_id`) values('2','语文','2');
    insert into `course` (`id`, `c_name`, `t_id`) values('3','英语','3');
    
    insert into `score` (`id`, `score`, `c_id`, `s_id`) values('1','80','1','2');
    insert into `score` (`id`, `score`, `c_id`, `s_id`) values('2','90','2','2');
    insert into `score` (`id`, `score`, `c_id`, `s_id`) values('3','85','3','2');
    insert into `score` (`id`, `score`, `c_id`, `s_id`) values('4','60','1','3');
    insert into `score` (`id`, `score`, `c_id`, `s_id`) values('5','55','2','3');
    insert into `score` (`id`, `score`, `c_id`, `s_id`) values('6','70','3','3');
    insert into `score` (`id`, `score`, `c_id`, `s_id`) values('7','30','1','4');
    insert into `score` (`id`, `score`, `c_id`, `s_id`) values('8','50','2','4');
    

    tips:当模型改变时,需要刷新一下Python Console

    image
  • 新增数据

    学生表和中间表stu_course是多对多的关系,中间表是django自动生成的,是没有数据的。中间表的数据,只能通过一张表来生成另一张表的数据,我们从stu表生成stu_course表的数据。

    # 查询出一条数据,返回结果是Stu对象
    stu = Stu.objects.filter(s_name='薛小磊').first()
    # 使用add方法往第三张表中添加三条和薛小磊关联的数据,`.course`是字段名
    # 通过stu_id增加course_id,stu_id=2的学生,选了3门课,课程id是1,2,3
    stu.course.add(1,2,3)
    # stu_id为3的学生,选了课程id是2,3的两门课
    Stu.objects.get(pk=3).course.add(2,3)
    Stu.objects.get(pk=4).course.add(1,2)
    
    image
  • 删除数据

    # 查询出一条数据
    stu = Stu.objects.filter(s_name='薛小磊').first()
    # 使用remove方法删除中间表中的和薛小磊关联的数据
    stu.course.remove(1,2,3)
    
  • 重置数据

    #无则加,有则删除
    stu = Stu.objects.filter(s_name='薛小磊').first()
    # 添加编号2 3的选课记录
    stu.course.add(2, 3)
    # 使用set方法重置选课记录为1,3
    stu = Stu.objects.filter(s_name='薛小磊').first()
    stu.course.set([1,3])
    
  • 清空

    # 清空薛小磊所有的选课记录
    stu = Stu.objects.filter(s_name='薛小磊').first()
    stu.course.clear()
    
  • 一对一查询

    # 导入model
    from multi_table.models import Stu
    from multi_table.models import StudentInfo
    
    # 正向查询:关联student_info表查询第一条学生的身份证号
    student=Stu.objects.first()
    stu_detail=student.si_id
    stu_detail.s_card
    
    image
    # 反向查询:查询身份证号为410422200205061358的学生姓名
    stu_info=StudentInfo.objects.filter(s_card='410422200205061358').first()
    stu=stu_info.stu_stu_info # 通过related_name进行反向查询
    stu.s_name
    
    image
  • 多对一查询

    # 导入model
    from multi_table.models import Course
    from multi_table.models import Teacher
    
    # 正向查询:通过课程表查找任课老师
    course=Course.objects.filter(c_name='语文').first()
    teacher=course.t_id
    teacher.t_name
    
    image
    # 反向查询
    
    # 通过通过related_name进行反向查询,course返回了一个model的对象
    course=Teacher.objects.first().teacher_course 
    # 对象转为查询集
    courses=course.all() 
    # 通过for循环编辑结果
    for c in courses:
        print(c.c_name)
    
    
![image](https://upload-images.jianshu.io/upload_images/12041448-80e0c140a41b85fe?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
  • 多对多

    # 多对多正向查询- 根据属性course查询
    
    # 查询 薛小磊同学选修的课程名
    stu = Stu.objects.filter(s_name='薛小磊').first()
    courses = stu.course.all()
    for c in courses:
        print(c.c_name)
    
    # 多对多反向查询-根据related_name查询
    
    # 查询选修了数学课程的学生姓名
    course = Course.objects.filter(c_name='数学').first()
    students = course.course_student.all()
    for s in students:
        print(s.s_name)
    

8. QuerySet常用API

我们通常做查询操作的时候,都是通过模型名字.objects的方式进行操作。其实模型名字.objects是一个django.db.models.manager.Manager对象,而Manager这个类是一个“空壳”的类,他本身是没有任何的属性和方法的。他的方法全部都是通过Python动态添加的方式,从QuerySet类中拷贝过来的。

所以我们如果想要学习ORM模型的查找操作,必须首先要学会QuerySet上的一些API的使用。

返回新的QuerySet的方法
  • filter

  • exclude

  • annotate

  • order_by

  • values

  • values_list

    类似于values。只不过返回的QuerySet中,存储的不是字典,而是元组。

  • all

  • select_related

    在提取某个模型的数据的同时,也提前将相关联的数据提取出来。可以减少数据库查询的次数。selected_related只能用在一对多或者一对一中,不能用在多对多或者多对一中。

    article = Article.objects.get(pk=1)
     >> article.author # 重新执行一次查询语句
     article = Article.objects.select_related("author").get(pk=2)
     >> article.author # 不需要重新执行查询语句了
    
  • prefetch_related

    这个方法和select_related非常的类似,就是在访问多个表中的数据的时候,减少查询的次数。这个方法是为了解决多对一和多对多的关系的查询问题。

  • defer

    在一些表中,可能存在很多的字段,但是一些字段的数据量可能是比较庞大的,而此时你又不需要,因此这时候我们就可以使用defer来过滤掉一些字段。这个字段跟values有点类似,只不过defer返回的不是字典,而是模型。

  • only

    跟defer类似,只不过defer是过滤掉指定的字段,而only是只提取指定的字段。

  • get

  • create

  • get_or_create

    根据某个条件进行查找,如果找到了那么就返回这条数据,如果没有查找到,那么就创建一个。

  • bulk_create

  • count

  • first & last

  • aggregate

  • exists

  • distinct

  • update

  • delete

  • 切片操作

什么时候Django会将QuerySet转换为SQL去执行
  • 迭代
  • 使用步长做切片操作
  • 调用len函数
  • 调用list函数
  • 判断

9. 多数据库配置

(暂用不到,知道可以配置多数据库,要用的时候再学吧)

10. 执行sql语句

Manager.raw(raw_query, params=None, translations=None)

image

带参数的SQL

image
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349