美文网首页Python 学习
【三】模型层ORM

【三】模型层ORM

作者: 一个无趣的人W | 来源:发表于2019-07-05 20:02 被阅读0次

    折腾,是对梦想的尊重。

    一、ORM简介

      MVC或者MVC框架中包括一个重要的部分,就是ORM,它实现了数据模型与数据库的解耦,即数据模型的设计不需要依赖于特定的数据库,在业务逻辑层和数据库层之间起了桥梁的作用。
      ORM(Object Relational Mapping)是“对象—关系—映射”的简称。

    ORM 对象-关系-映射

    二、前期准备

    1、在settings中配置数据库

    首先想将模型转为mysql数据库中的表,要在settings中配置。Django默认使用的是sqlite数据库,这里换成mysql。

    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.mysql',
            'NAME': 'db_name',   # 要连接的数据库,连接前需要创建好
            'HOST': '127.0.0.1',   # 连接主机,默认本机
            'USER': 'root',    # 连接数据库的用户名
            'PASSWORD': ' ',   # 连接数据库的密码
            'PORT': 3306   # 端口 默认3306
        }
    }
    

    🐷 tips: NAME即数据库的名字,在mysql连接前该数据库必须已经创建,而上面的sqlite数据库下的db.sqlite3则是项目自动创建 USER和PASSWORD分别是数据库的用户名和密码。
      设置完后启动项目会报错:no module named MySQLdb。这是因为django默认你导入的驱动是MySQLdb,可是MySQLdb 对于py3有很大问题,所以我们需要的驱动是PyMySQL,接下来去激活我们的mysql
    我们只需要找到项目名文件下的init,在里面写入:

    import pymysql
    pymysql.install_as_MySQLdb()
    

    2、在settings中配置日志Logging

    日志在程序开发中是少不了的,通过日志我们可以分析到错误在什么地方,有什么异常。在生产环境下有很大的用途,而且一旦涉及到对数据库的操作,更应该记录下来,数据是很重要的。在Java开发中通常用log4j,logback等第三方组件。那么在django中是怎么处理日志?django利用的就是Python提供的logging模块,但django中要用logging,还得有一定的配置规则,需要在setting中设置。

    (1) logging 模块
    logging模块为应用程序提供了灵活的手段记录事件、错误、警告和调试信息。对这些信息可以进行收集、筛选、写入文件、发送给系统日志等操作,甚至还可以通过网络发送给远程计算机。

    a、 日志记录级别
    logging模块的重点在于生成和处理日志消息。每条消息由一些文本和指示其严重性的相关级别组成。级别包含符号名称和数字值。

    级别 描述
    CRITICAL 50 关键错误/消息
    ERROR 40 错误
    WARNING 30 警告消息
    INFO 20 通知消息
    DEBUG 10 调试
    NOTSET 0 无级别

    b、记录器
    记录器负责管理日志消息的默认行为,包括日志记录级别、输出目标位置、消息格式以及其它基本细节。

    关键字参数 描述
    filename 将日志消息附加到指定文件名的文件
    filemode 指定用于打开文件模式
    format 用于生成日志消息的格式字符串
    datefmt 用于输出日期和时间的格式字符串
    level 设置记录器的级别
    stream 提供打开的文件,用于把日志消息发送到文件

    c、 format日志消息格式

    格式 描述
    %(name)s 记录器的名称
    %(levelno)s 数字形式的日志记录级别
    %(levelname)s 日志记录级别的文本名称
    %(filename)s 执行日志记录调用的源文件的文件名称
    %(pathname)s 执行日志记录调用的源文件的路径名称
    %(funcName)s 执行日志记录调用的函数名称
    %(module)s 执行日志记录调用的模块名称
    %(lineno)s 执行日志记录调用的行号
    %(created)s 执行日志记录的时间
    %(asctime)s 日期和时间
    %(msecs)s 毫秒部分
    %(thread)d 线程ID
    %(threadName)s 线程名称
    %(process)d 进程ID
    %(message)s 记录的消息

    d、内置处理器
    logging模块提供了一些处理器,可以通过各种方式处理日志消息。使用addHandler()方法将这些处理器添加给Logger对象。另外还可以为每个处理器配置它自己的筛选和级别。

    • handlers.DatagramHandler(host,port) 发送日志消息给位于制定host和port上的UDP服务器。
    • handlers.FileHandler(filename) 将日志消息写入文件filename。
    • handlers.HTTPHandler(host, url) 使用HTTP的GET或POST方法将日志消息上传到一台HTTP 服务器。
    • handlers.RotatingFileHandler(filename) 将日志消息写入文件filename。如果文件的大小超出maxBytes制定的值,那么它将被备份为filename1。
      ps: 由于内置处理器还有很多,如果想更深入了解,可以查看官方手册。

    (2) Django中使用logging记录日志
    a、在settings.py中进行配置

    • 完整版
    🌈 先导入模块
    import logging
    import django.utils.log
    import logging.handlers
    
    LOGGING = {
        'version': 1,   #  解析配置,目前为止,这是dictConfig 格式唯一的版本
        'disable_existing_loggers': True,  # 这是一个布尔型值,默认值为True(为了向后兼容)表示禁用已经存在的logger,除非它们或者它们的祖先明确的出现在日志配置中;如果值为False则对已存在的loggers保持启动状态。
        'formatters': {
           'standard': {
                'format': '%(asctime)s [%(threadName)s:%(thread)d] [%(name)s:%(lineno)d] [%(module)s:%(funcName)s] [%(levelname)s]- %(message)s'}  #日志格式 
        },
        'filters': {
        },
        'handlers': {
            'mail_admins': {
                'level': 'ERROR',
                'class': 'django.utils.log.AdminEmailHandler',
                'include_html': True,
            },
            'default': {
                'level':'DEBUG',
                'class':'logging.handlers.RotatingFileHandler',
                'filename': '/sourceDns/log/all.log',     #日志输出文件
                'maxBytes': 1024*1024*5,                  #文件大小 
                'backupCount': 5,                         #备份份数
                'formatter':'standard',                   #使用哪种formatters日志格式
            },
            'error': {
                'level':'ERROR',
                'class':'logging.handlers.RotatingFileHandler',
                'filename': '/sourceDns/log/error.log',
                'maxBytes':1024*1024*5,
                'backupCount': 5,
                'formatter':'standard',
            },
            'console':{
                'level': 'DEBUG',
                'class': 'logging.StreamHandler',
                'formatter': 'standard'
            },
            'request_handler': {
                'level':'DEBUG',
                'class':'logging.handlers.RotatingFileHandler',
                'filename': '/sourceDns/log/script.log', 
                'maxBytes': 1024*1024*5, 
                'backupCount': 5,
                'formatter':'standard',
            },
            'scprits_handler': {
                'level':'DEBUG',
                'class':'logging.handlers.RotatingFileHandler',
                'filename':'/sourceDns/log/script.log', 
                'maxBytes': 1024*1024*5, 
                'backupCount': 5,
                'formatter':'standard',
            }
        },
        'loggers': {
            'django': {
                'handlers': ['default', 'console'],
                'level': 'DEBUG',
                'propagate': False 
            },
            'django.request': {
                'handlers': ['request_handler'],
                'level': 'DEBUG',
                'propagate': False,
            },
            'scripts': { 
                'handlers': ['scprits_handler'],
                'level': 'INFO',
                'propagate': False
            },
            'sourceDns.webdns.views': {
                'handlers': ['default', 'error'],
                'level': 'DEBUG',
                'propagate': True
            },
            'sourceDns.webdns.util':{
                'handlers': ['error'],
                'level': 'ERROR',
                'propagate': True
            }
        } 
    }
    

    🐷tips:
    loggers类型为"django"这将处理所有类型日志;
    sourceDns.webdns.views 应用的py文件

    • 简化版
    import logging
    import django.utils.log
    import logging.handlers
    import logging.StreamHandler
    
    LOGGING = {
        'version': 1,
        'disable_existing_loggers': False,
        'formatters': {
           'standard': {
                'format': '%(asctime)s [%(threadName)s:%(thread)d] [%(name)s:%(lineno)d] [%(module)s:%(funcName)s] [%(levelname)s]- %(message)s'}  #日志格式 
        },
        'handlers': {
            'console': {
                'level': 'DEBUG',
                'class': 'logging.StreamHandler',
            },
        },
        'loggers': {
            'django.db.backends': {   # 数据库引擎
                'handlers': ['console'],
                'propagate': True,
                'level': 'DEBUG',
            },
        }
    }
    

    参数解析:
    【a】formatters:配置打印日志格式
    【b】handlers:用来定义具体处理日志的方式,可以定义多种,"default"就是默认方式,"console"就是打印到控制台方式,"导入StreamHandler",日志信息会输出到指定的stream中,如果stream为空则默认输出到sys.stderr。
    【c】loggers:用来配置用那种handlers来处理日志,比如你同时需要输出日志到文件、控制台。

    (3) 在settings中注册APP

    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'book'
    ]
    

    🐷 tips:如果出现如下报错

    django.core.exceptions.ImproperlyConfigured: mysqlclient 1.3.3 or newer is required; you have 0.7.11.None
    

    因为MySQLclient目前只支持到python3.4,如果使用的更高版本的python,需要修改如下:
    通过查找路径C:\Programs\Python\Python36-32\Lib\site-packages\Django-2.0-py3.6.egg\django\db\backends\mysql
    把这个路径文件中下面这两句话注释掉:

    if version < (1, 3, 3):
         raise ImproperlyConfigured("mysqlclient 1.3.3 or newer is required; you have %s" % Database.__version__)
    

    🐷 tips: 如果想打印orm转换过程中的sql,需要在settings中进行如下配置:

    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、在models中创建表

    (1) 创建模型表

    from django.db import models
    
    class Book(models.Model):
         id=models.AutoField(primary_key=True)
         title=models.CharField(max_length=32)
         state=models.BooleanField()
         pub_date=models.DateField()
         price=models.DecimalField(max_digits=8,decimal_places=2)
         publish=models.CharField(max_length=32)
    

    (2) ORM字段介绍
    Django提供了很多字段类型,比如URL/Email/IP/ 但是mysql数据没有这些类型,这类型存储到数据库上本质是字符串数据类型,其主要目的是为了封装底层SQL语句。
    每个字段有一些特有的参数,例如,CharField需要max_length参数来指定VARCHAR数据库字段的大小。还有一些适用于所有字段的通用参数。 这些参数在文档中有详细定义,这里我们只简单介绍一些最常用的:

    【常用字段】

    • AutoField
       int自增列,必须填入参数 primary_key=True。当model中如果没有自增列,则自动会创建一个列名为id的列( 如果你不指定主键的话,系统会自动添加一个主键字段到你的 model)。
       自定义一个主键:my_id=models.AutoField(primary_key=True)

    • CharField
       字符串类型,必须提供max_length参数,用于从数据库层和Django校验层限制该字段所允许的最大字符数。

    • IntegerField
       整数类型,范围在 -2147483648 to 2147483647

    • FloatField
       浮点数类型. 必须提供两个参数:
        max_digits 总位数(不包括小数点和符号)
        decimal_places 小数位数

      举例来说, 要保存最大值为 999 (小数点后保存2位),你要这样定义字段:models.FloatField(..., max_digits=5, decimal_places=2)
      要保存最大值一百万(小数点后保存10位)的话,你要这样定义:models.FloatField(..., max_digits=19, decimal_places=10)
      admin 用一个文本框(<input type="text">)表示该字段保存的数据.

    • DateField
       日期字段,日期格式 YYYY-MM-DD 年月日. 共有下列额外的可选参数:
        Argument 描述
        auto_now 当对象被保存时,自动将该字段的值设置为当前时间,每次操作该数据都会自动更新时间,通常用于表示 "last-modified" 时间戳
        auto_now_add 当对象首次被创建时,自动将该字段的值设置为当前时间,此后不再更新,通常用于表示对象创建时间
      🐷(仅仅在admin中有意义...)

    • DateTimeField
       日期时间字段,格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] 年月日时分秒。参数和datefield一样。

    • BooleanField
       A true/false field。admin 用 checkbox 来表示此类字段。

    • TextField
       一个容量很大的文本字段
       admin 用一个 <textarea> (文本区域)表示该字段数据(一个多行编辑框)。

    • EmailField
       一个带有检查Email合法性的 CharField,不接受 maxlength 参数

    • FileField
       一个文件上传字段
        要求一个必须有的参数: upload_to, 一个用于保存上载文件的本地文件系统路径。这个路径必须包含 strftime ,formatting,该格式将被上载文件的 date/time替换(so that uploaded files don't fill up the given directory)。
       admin 用一个<input type="file">部件表示该字段保存的数据(一个文件上传部件) 。
      🐷 注意:在一个model中使用FileField或ImageField需要以下步骤:
       (1) 在你的settings文件中,定义一个完整路径给MEDIA_ROOT以便让Django在此处保存上传文件(出于性能考虑,这些文件并不保存到数据库),定义MEDIA_URL作为该目录的公共URL,要确保该目录对web服务器用户账号是可写的。
       (2) 在你的model中添加FileField或ImageField,并确保定义了upload_to选项,以告诉Django使用MEDIA_ROOT的哪个子目录保存上传文件,你的数据库中要保存的只是文件的路径(相对于MEDIA_ROOT),出于习惯你一定很想使用Django提供的get_<fieldname>_url函数,举例来说,如果你的ImageField叫做mug_shot,你就可以在模板中以{{ object.get_mug_shot_url }}这样的方式得到图像的绝对路径。

    • FilePathField
       可选项目为某个特定目录下的文件名。 支持三个特殊的参数, 其中第一个是必须提供的。
      参数 | 描述
      path | 必需参数,一个目录的绝对文件系统路径FilePathField 据此得到可选项目。例如"/home/images"
      match | 可选参数,一个正则表达式, 作为一个字符串, FilePathField 将使用它过滤文件名。注意这个正则表达式只会应用到 base filename 而不是路径全名。例如"foo.*\.txt^",将匹配文件foo23.txt,却不匹配bar.txt或foo23.gif。
      recursive | 可选参数,要么True,要么False,默认是False,是否包括path下面的全部子目录。
      match | 仅应用于 base filename, 而不是路径全名。 那么,这个例子:FilePathField(path="/home/images", match="foo.*", recursive=True)...会匹配 /home/images/foo.gif 而不匹配 /home/images/foo/bar.gif。

    • ImageField
       类似 FileField, 不过要校验上传对象是否是一个合法图片。它有两个可选参数:height_field和width_field,如果提供这两个参数,则图片将按提供的高度和宽度规格保存。

    • URLField
       用于保存 URL, 若 verify_exists 参数为 True (默认), 给定的 URL 会预先检查是否存在( 即URL是否被有效装入且没有返回404响应)。
       admin 用一个 <input type="text"> 文本框表示该字段保存的数据(一个单行编辑框)。

    • NullBooleanField
       类似 BooleanField, 不过允许 NULL 作为其中一个选项,推荐使用这个字段而不要用 BooleanField 加 null=True
        admin 用一个选择框 <select> (三个可选择的值:"Unknown", "Yes" 和 "No" ) 来表示这种字段数据。

    • SlugField
        "Slug" 是一个报纸术语. slug 是某个东西的小小标记(短签), 只包含字母,数字,下划线和连字符。它们通常用于URLs。
        若你使用 Django 开发版本,你可以指定 maxlength.。若 maxlength 未指定, Django 会使用默认长度: 50。在以前的 Django 版本,没有任何办法改变50 这个长度。这暗示了 db_index=True。
        它接受一个额外的参数: prepopulate_from, which is a list of fields from which to auto-#populate the slug, via JavaScript,in the object's admin form: models.SlugField (prepopulate_from=("pre_name", "name"))prepopulate_from 不接受DateTimeFields。

    • XMLField
       一个校验值是否为合法XML的 TextField,必须提供参数: schema_path, 它是一个用来校验文本的 RelaxNG schema 的文件系统路径。

    • IPAddressField
       一个字符串形式的 IP 地址, (i.e. "24.124.1.30")

    • CommaSeparatedIntegerField
       用于存放逗号分隔的整数值,类似 CharField, 必须要有maxlength参数。


    详解
    (1) 字符串类(以下在数据库中本质都是字符串数据类型,此类字段只是在Django自带的admin中生效)
    🌈models.CharField 对应的是MySQL的varchar数据类型
    🐷tips:char 和 varchar的区别 :
     char和varchar的共同点是存储数据的长度,不能超过max_length限制;
     不同点是varchar根据数据实际长度存储,char按指定max_length( )存储数据,所有前者更节省硬盘空间;

    EmailField(CharField):
    IPAddressField(Field)
    URLField(CharField)
    SlugField(CharField)
    UUIDField(Field)
    FilePathField(Field)
    FileField(Field)
    ImageField(FileField)
    CommaSeparatedIntegerField(CharField)
    
    ------- 在模型表中的定义 -------
    class Publisher(models.Model):
        id = models.AutoField(primary_key=True)
        publishName = models.CharField(max_length=32)
        address = models.CharField(max_length=32)
        tele = models.CharField(max_length=32)
    

    (2) 时间字段

    models.DateTimeField(null=True)
    date=models.DateField()
    

    (3) 数字字段
    (max_digits=30,decimal_places=10) 总长度30,小数位10位

    num = models.IntegerField()
    num = models.FloatField() 浮点
    price=models.DecimalField(max_digits=8,decimal_places=3) 精确浮点
    

    (4) 枚举字段
     在数据库存储枚举类型,比外键有什么优势?
     无需连表查询性能低,省硬盘空间(选项不固定时用外键);
     在modle文件里不能动态增加(选项一成不变用Django的choice)

    choice=(
            (1,'男人'),
            (2,'女人'),
            (3,'其他')
        )
    lover=models.IntegerField(choices=choice) #枚举类型
    # 或者
     gender = models.CharField(verbose_name='性别', max_length=8, choices=(("male", "男"), ("female", "女")),default='female')
    

    (5) 其他字段

    db_index = True 表示设置索引
    unique(唯一的意思) = True 设置唯一索引
    
    ------- 联合唯一索引 -------
    class Meta:
        unique_together = (
              ('email','ctime'),
        )
    
    ------- 联合索引(不做限制)------
        index_together = (
             ('email','ctime'),
        )
    
    ------- 多对多操作 -------
    ManyToManyField(RelatedField)  
    

    (6) 自定义字段

    class UnsignedIntegerField(models.IntegerField):
        def db_type(self, connection):
            return 'integer UNSIGNED'
    

    自定义char类型字段:

    class FixedCharField(models.Field):
        """
        自定义的char类型的字段类
        """
        def __init__(self, max_length, *args, **kwargs):
            self.max_length = max_length
            super(FixedCharField, self).__init__(max_length=max_length, *args, **kwargs)
     
        def db_type(self, connection):
            """
            限定生成数据库表的字段类型为char,长度为max_length指定的值
            """
            return 'char(%s)' % self.max_length
     
     
    class Class(models.Model):
        id = models.AutoField(primary_key=True)
        title = models.CharField(max_length=25)
        # 使用自定义的char类型的字段
        cname = FixedCharField(max_length=25)
    

    附ORM字段与数据库实际字段的对应关系

    '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)',
    

    【常用字段参数】

    • null
      表示某个字段可以为空,如果为True,Django将用NULL在数据库中存储空值,默认是False。
    • blank
      如果为True,该字段允许不填,默认为False。
      🐷tips:这与null不同,null纯粹是数据库范畴的,而blank是数据验证范畴的。如果一个字段的blank=True,表单的验证将允许该字段是空值,如果字段的blank=False,该字段是必填的
    • default
      字段的默认值,可以是一个值或者可调用对象,如果可调用,每有新对象被创建它都会被调用。
    • primary_key
      如果为True,那么这个字段就是模型的主键,如果你没有指定任何一个字段的primary_key=True,Django就会自动添加IntegerField字段作为主键,所以除非你想覆盖默认的主键行为,否则没必要设置任何一个字段的primary_key=True。
    • unique
      如果该值设置为True,则这个数据字段的值在整张表中必须是唯一的。
    • required
      如果设置该值为True,则表示请求不能为空
    • choice
      由二元组组成的一个可迭代对象(例如:列表或元组),用来给字段提供选择项,如果设置了choices,默认的表单将是一个选择框而不是标准的文本框,而且这个选择框的选项就是choices中的选项。
    class User(models.Model):
           name=models.CharField(max_length=32)
           password=MyCharField(max_length=32)
           choices=((1,'重点本科'),(2,'普通本科'),(3,'专科'),(4,'其他'))
           education= models.IntegerField(choices=choices)  通过选择数字来选择相应的属性
    
        user_obj.education  拿到的是数字🌈
        uer_obj.get_education_display()  固定用法,获取choice字段对应的注释🌈
    
    • db_index
      如果db_index=True,则表示为此字段设置索引。
    • only与defer
      两者是相反的models.User.objects.only('name')🐷不走数据库

    [ DateField 和 DateTimeField中的参数 ]

    • auto_now_add
      配置auto_now_add=True,创建数据记录的时候会把当前时间添加到数据库。
    • auto_now
      配置auto_now=True,每次更新数据记录的时候会更新该字段。

    【关系字段及参数】
    🌈ForeignKey
     外键类型在ORM中用来表示外键关联关系,一般把ForeignKey字段设置在‘一对多中多的一方’。
     ForeignKey可以和其他表做关联关系同时也可以和自身做关联关系。

    • 字段参数
      to 设置要关联的表
      to_field 设置要关联的表的字段
      related_name 反向操作时使用的字段名,用于代替原反向查询时的'表名_set'

    举个例子🌰

    class Classes(models.Model):
        name = models.CharField(max_length=32)
    
    class Student(models.Model):
        name = models.CharField(max_length=32)
        theclass = models.ForeignKey(to="Classes")
    

    当我们要查询某个班级关联的所有学生(反向查询)时,我们会这么写models.Classes.objects.first().student_set.all()
    当我们在ForeignKey字段中添加了参数 related_name 后

    class Student(models.Model):
        name = models.CharField(max_length=32)
        theclass = models.ForeignKey(to="Classes", related_name="students")
    

    当我们要查询某个班级关联的所有学生(反向查询)时,我们会这么写models.Classes.objects.first().students.all()


     related_query_name 反向查询操作时,使用的连接前缀,用于替换表名
     on_delete 当删除关联表中的数据时,当前表与其关联的行为,即外键的删除


    on_delete属性:
    1、常见的使用方式(设置为null) >>> on_delete=models.SET_NULL
    2、关于别的属性的介绍
     models.CASCADE: 这就是默认的选项,级联删除,你无需显性指定它,删除关联数据,与之关联也删除。
     models.PROTECT: 保护模式,如果采用该选项,删除关联数据的时候,会抛出ProtectedError错误。
     models.SET_NULL: 置空模式,删除关联数据的时候,外键字段被设置为空,前提就是blank=True, null=True,定义该字段的时候,允许为空。
     models.SET_DEFAULT: 置默认值,删除关联数据的时候,外键字段设置为默认值,所以定义外键的时候注意加上一个默认值。
     models.DO_NOTHING: 删除关联数据,引发错误IntegrityError。
     models.SET: 删除关联数据
      a.与之关联的值设置为指定值,设置:models.SET(值)
      b.与之关联的值设置为可执行对象的返回值,设置:models.SET(可执行对象)

    def func():
        return 10
    
    class MyModel(models.Model):
        user = models.ForeignKey(
            to="User",
            to_field="id",
            on_delete=models.SET(func)
        )
    

     db_constraint 是否在数据库中创建外键约束,默认为True

    🌈OneToOneField
    一对一字段
    通常一对一字段用来扩展已有字段

    • 字段参数
       to 设置要关联的表
       to_field 设置要关联的字段
       on_delete 同Foreign字段

    🌈ManyToManyField
      用于表示多对多的关联关系,在数据库中通过第三张表来建立关联关系。

    • 字段参数
       to 设置要关联的表
       related_name 同ForeignKey字段
       related_query_name 同ForeignKey字段
       symmetrical 仅用于多对多自关联时,指定内部是否创建反向操作的字段,默认为True。
        举个例子🌰
    class Person(models.Model):
        name = models.CharField(max_length=16)
        friends = models.ManyToManyField("self")
    
    🌸此时person对象就没有person_set属性
    
    class Person(models.Model):
        name = models.CharField(max_length=16)
        friends = models.ManyToManyField("self", symmetrical=False)
    

     through 在使用ManyToManyField字段时,Django将自动生成一张表来管理多对多的关联关系。但我们也可以手动创建第三张表来管理多对多关系,此时就需要通过through来指定第三张表的表名。
     through_fields 设置关联字段
     db_table 默认创建第三张表时,数据库中表的名称

    🌈元信息
      ORM对应的类里面包含另一个Meta类,而Meta类封装了一些数据库的信息。主要字段如下:

    • db_table ORM在数据库中的表名默认是app_类名,可以通过db_table重写表名
    • index_together 联合索引
    • ordering 指定默认按什么字段排序。只有设置了该属性,我们查询到的结果才可以被reverse()

    三、单表操作

    1、前言

      orm使用方式:orm操作可以使用类实例化,obj.save的方式,也可以使用create( )的形式。
    QuerySet数据类型介绍
     ⚠️只要是queryset对象就可以无限的点queryset方法,比如```models.User.object.filter( ).filter( ).count( )
     QuerySet与惰性机制
     所谓惰性机制:publish.obj,all( )或者.filter( )等都只是返回了一个QuerySet(查询结果集对象),它并不会马上执行sql,而是当调用QuerySet的时候才执行。
     QuerySet特点:可迭代的;可切片;惰性计算和缓存机制

    def queryset(request):
        books=models.Book.objects.all()[:10]  #切片 应用分页
        books = models.Book.objects.all()[::2]
        book= models.Book.objects.all()[6]    #索引
        print(book.title)
        for obj in books:  #可迭代
            print(obj.title)
        books=models.Book.objects.all().  #惰性计算--->等于一个生成器,不应用books不会执行任何SQL操作
        # query_set缓存机制1次数据库查询结果query_set都会对应一块缓存,再次使用该query_set时,不会发生新的SQL操作;
        #这样减小了频繁操作数据库给数据库带来的压力;
        authors=models.Author.objects.all()
        for author in  authors:
            print(author.name)
        print('-------------------------------------')
        models.Author.objects.filter(id=1).update(name='陈某')
        for author in  authors:
            print(author.name)
        #但是有时候取出来的数据量太大会撑爆缓存,可以使用迭代器优雅得解决这个问题;
        models.Publish.objects.all().iterator()
        return HttpResponse('OK')
    

    拿出前面准备好的表:

    from django.db import models
    
    class Book(models.Model):
         id=models.AutoField(primary_key=True)
         title=models.CharField(max_length=32)
         state=models.BooleanField()
         pub_date=models.DateField()
         price=models.DecimalField(max_digits=8,decimal_places=2)
         publish=models.CharField(max_length=32)
    

    🌸 接下来使用单独的py文件测试ORM操作,在test.py中需要配置的参数如下:

    import os
    import sys
    
    if __name__ == "__main__":
          os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test.settings")
          import django
          django.setup()
          from app import models  🐷这句话必须在下面
    

    2、添加表记录(新增数据的三种方式)

    • 方法一
      基于create创建(create)
      表.objects.create()
    🐟create方法的返回值book_obj就是插入book表中python葵花宝典这本书籍记录对象
    book_obj = Book.objects.create(title="python葵花宝典",state=True,price=100,publish="苹果出版社",pub_date="2019-12-12")
    
    • 方法二
      基于对象的绑定方法创建(save)也就是类实例化
      obj=类(属性=xx) obj.save()
    book_obj = Book(title="python葵花宝典",state=True,price=100,publish="苹果出版社",pub_date="2012-12-12")
    book_obj.save()
    
    • 方法三
      自动识别时间格式字符串进行传参
    from datetime import datetime 
    ctime = datetime.now()
    models.Book.objects.create(title="python葵花宝典",state=True,price=100,publish="苹果出版社",pub_date=ctime)
    

    3、查询表记录

    • 基本查询
    查询API 作用
    all( ) 查询所有结果
    filter(**kwargs) 包含了与所给筛选条件相匹配的对象
    get(**kwargs) 返回与所给筛选条件相匹配的对象,返回结果有且只有一个,如果符合筛选条件的对象超过一个或者没有都会抛出错误
    exclude(**kwargs) 包含了与所给筛选条件不匹配的对象
    order_by(*field) 对查询结果排序
    reverse( ) 对查询结果反向排序,前面要先有排序才能反向
    count( ) 返回数据库中匹配查询(QuerySet)的对象数量
    first( ) 返回第一条记录
    last( ) 返回最后一条记录
    exists( ) 如果QuerySet包含数据,就返回True,否则返回False
    values(*field) 返回一个ValueQuerySet—一个特殊的QuerySet,运行后得到的并不是一系列model的实例化对象,而是一个可迭代的字典序列
    values_list(*field) 它与values( )非常相似,它返回的是一个元组序列,values返回的是一个字典序列
    distinct( ) 从返回结果中剔除重复记录,去重的对象必须是完全相同的数据才能去重

    例子们🌰:

    🍃 filter(**kwargs):
          models.User.objects.filter(name='wpr',age=18)  ——查出来是<QuerySet []>
          models.User.objects.filter(name='wpr',age=23)  ——查出来是<QuerySet [<User: User object>, <User: User object>]>
      🐷⚠️:filter里面可以放多个限制条件,但多个条件之间是and
    
    🍃 exclude(**kwargs):
          models.User.objects.exclude(name='wpr')
    
    🍃 order_by(*field):
          models.User.objects.order_by('-age')  ——可以在排序的字段前面加一个减号,就是降序
          models.User.objects.order_by('age')  ——默认是升序
    
    🍃 reverse():  
          models.User.objects.order_by('age').reverse()  ——要先有排序才能反向
    
    🍃 count():
          models.User.objects.count()
          models.User.objects.all().count()
    
    🍃 first():
          models.User.objects.all().first()
          models.User.objects.all()[0]  ——不支持负数的索引取值
    
    🍃 last():
          models.User.objects.all().last()
    
    🍃 exists():
          models.User.objects.all().exists()
          models.User.objects.filter(name='wpr',age=3).exists()
    
    🍃 values(*field):
          models.User.objects.values('name')  ——🐷列表套字典[{},{},{}]
    
    🍃 values_list(*field):
          models.User.objects.value_list('name','age')  ——🐷列表套元组[(),(),()]
    
    🍃 distinct():
          models.User.objects.values('name','age').distinct()  ——🐷去重的对象必须是完全相同的数据才能去重
          必须完全一样才可以去重(意味着带了id就没有意义了)
          models.Book.objects.all().values('name').distinct()  ——先查一个重复的值再去重
    
    • 基于双下划线的模糊查询
    ------- 数字查询 -------
    查询价格为100或200或300的书籍  models.Book.objects.filter(price__in=[100,200,300])
    查询年龄大于44岁的用户   models.User.objects.filter(age__gt=44)
    查询价格小于100的书籍   models.Book.objects.filter(price__lt=100)
    查询年龄大于等于44的用户   models.User.objects.filter(age__gte=44)
    查询年龄小于等于44的用户   models.User.objects.filter(age__lte=44)
    查询价格再100到200之间的书籍   models.Book.objects.filter(price__range=[100,200])
    查询年龄是否为空(0:is not null | 1:is null)   models.User.objects.filter(age__isnull=0|1)
    
    ------- 字符串查询 -------
    查询名字中包含字母n的用户   models.User.objects.filter(name__contains='n')
    查询名字中包含字母n的用户并且忽略大小写   models.User.objects.filter(name__icontains='n')
    查询书籍中以py开头的书籍   models.Book.objects.filter(title__startswith='py')
    查询书籍中以n结尾的用户   models.User.objects.filter(name__endswith='n')
    查询名字满足某个正则表达式的用户   models.User.objects.filter(name__regex='正则表达式')
    
    ------- 日期 -------
    查询注册时间是在2017年的用户   models.User.objects.filter(register_time__year=2017)
    

    🌸 tips:返回queryset对象的方法
      all ( )
      filter ( )
      exclude ( )
      order_by ( )
      reverse ( )
      distinct ( )
      values ( ) 返回一个可迭代的字典序列
      values_list ( ) 返回一个可迭代的元祖序列

    4、删除表记录

    • 基于对象
    user_obj = models.User.objects.filter(name='json').first()
    user_obj.delete()
    

      删除方法就是delete(),它运行时立即删除对象而不返回任何值,例如user_obj.delete()

    • 基于queryset
    models.User.objects.filter(name='wpr').delete()
    

      一次性删除多个对象,每个QuerySet都有一个delete()方法,它一次性删除QuerySet中所有的对象。例如,下面的代码将删除pub_date是2005年的Entry对象:
    Entry.objects.filter(pub_date__year=2005).delete()
    在Django删除对象时,会模仿SQL约束ON DELETE CASCADE的行为,换句话说,删除一个对象时也会删除与他相关联的外键对象,例如:

    b = Blog.objects.get(pk=1)
    # This will delete the Blog and all of its Entry objects.
    b.delete()
    

    要注意的是:delete()方法是QuerySet上的方法,但并不适用于Manager本身。这是一种保护机制,是为了避免意外地调用Entry.objects.delete()方法导致所有的记录被误删除。如果你确认要删除所有的对象,那么你必须显式地调用Entry.objects.all().delete()
    如果不想级联删除,可以设置为:

    pubHouse = models.ForeignKey(to='Publisher', on_delete=models.SET_NULL, blank=True, null=True)
    

    5、修改表记录

    • 基于对象,先获取用户对象,然后赋值,最后保存
    user_obj = models.User.objects.filter(name='wpr').first()
    user_obj.age = 17
    user_obj.save()
    
    • 基于queryset,queryset直接更新
        update()方法对于任何结果集(QuerySet)均有效,这意味着你可以同时更新多条记录,update()方法会返回一个整型值,表示受影响的记录的条数。
     models.User.objects.filter(name='wpr').update(age=77)
    

    三、多表操作

    1、Django多表ORM设计规则

    (1)关联的表之间建议建立外键,但可以取消关联关系(db_constraint=False)
    (2)关联表之间的外键字段建议采用对应类名的全小写
    (3) 采用关联表的主键或对象均能进行操作

    2、表与表之间的关系

    • 一对一(OneToOneField):一对一字段无论建在哪张关系表里都可以,但是推荐建在查询频率比较高的那张表中

    • 一对多(ForeignKey):一对多字段建在多的那一方

    • 多对多(ManyToManyField):多对多字段无论建在哪张关系表里都可以,但是推荐建在查询频率比较高的那张表中
      多对多:add() 添加  |  set() 修改  |  remove() 不能接收可迭代对象  |  clear() 清空,不用传参

    • 外键关系

    publish = models.ForeignKey(to='Publish',to_field='title')
    

    to 是设置要关联的表
    to_field 是设置要关联的表的字段,不设置默认关联id
    on_delete 是当删除关联表中的数据时,当前表与其关联的行的行为。在django2.0中要加models.CASCADE
    db_constraint 是否在数据库中创建外键约束,默认为True

    3、创建模型

    【作者Author】:一个作者有姓名,作者详情和作者之间是一对一关系(one-to-one)
    【作者详情AuthorDetail】:包含年龄、电话和信息
    【出版社Publish】:出版商有名称,地址
    【书籍Book】:书籍有书名、价格和出版日期,一本书可能会有多个作者,一个作者也可以写多本书,所以作者和书籍的关系就是多对多的关联关系(many-to-many);一本书只应该由一个出版商出版,所以出版商和书籍是一对多关联关系(one-to-many)

    from django.db import models
    
    class Author(models.Model):
        name=models.CharField( max_length=32)
        # 与AuthorDetail建立一对一的关系
        author_detail=models.OneToOneField(to="AuthorDetail",on_delete=models.CASCADE)
    
    class AuthorDetail(models.Model):
        age=models.IntegerField()
        telephone=models.BigIntegerField()
        info=models.CharField( max_length=64)
    
    class Publish(models.Model):
        name=models.CharField( max_length=32)
        address=models.CharField( max_length=32)
    
    class Book(models.Model):
        name = models.CharField( max_length=32)
        price=models.DecimalField(max_digits=5,decimal_places=2)
        publish_date=models.DateField()    
        # 与Publish建立一对多的关系,外键字段建立在多的一方
        publish=models.ForeignKey(to="Publish",to_field="nid",on_delete=models.CASCADE)
        # 与Author表建立多对多的关系,ManyToManyField可以建在两个模型中的任意一个,自动创建第三张表
        authors=models.ManyToManyField(to='Author',)
    

    4、一对多关系

    • 规则
      (1)关系中“多”依赖于“一”;
      (2)Django 1.x外键关联默认有级联删除,2.x需要手动明确外键的级联删除(on_delete=models.CASCADE)

    ① 增

    🌸方式一:外键为关联对象,这里外键是实际表的字段
    先有出版社,才有书籍
    publish = Publish.objects.create(name="南方出版社",address="上海)
    Book.objects.create(name='人间失格',price=88.88,publish_date='2018-8-8',publish=publish)
    
    🌸 方式二:外键字段为关联对象主键,这里外键不是实际表的字段
    id = Publish.objects.create(name="北方出版社", address="北京").id
    Book.objects.create(name='活着', price=37.70, publish_date='2043-8-8', publish_id=id)
    

    ② 删

    删除出版社,默认有级联删除,出版社出版的数据全会被删除
    Publish.objects.first().delete()
    

    ③ 改

    🌸方式一:save()
    book_obj=models.Book.objects.filter(pk=1).first()
    book_obj.publish=new_publish_obj
    book_obj.save()
    
    🌸方式二:update()
    models.Book.objects.filter(pk=1).update(publish_id=2)
    或 models.Book.objects.filter(pk=1).update(publish_id=publish_obj)
    

    5、一对一关系

    • 规则
      通过外键所在表决定依赖关系

    ① 增

    🌸遵循操作顺序
    author_detail = AuthorDetail.objects.create(age=18,telephone=1232331231,ingo="真帅)
    Author.objects.create(name='wpr',author_detail=author_detail)
    

    ② 删

    🌸拥有级联删除
    AuthorDetail.objects.first().delete()
    

    ③ 改
      一般不考虑该关联字段

    6、多对多关系

    • 规则
      (1) 多对多关系存在关系表,关系表建议采用ManyToManyField字段处理
      (2) 需要手动创建关系表时,在字段中明确through与through_field值

    ① 增 add( )

    为书籍添加作者的主键或对象们
    book.author.add(*args)
    

    ② 删 remove( )

    删除书籍已有作者的主键或对象们
    book.author.remove(*args)
    

    ③ 改 set( ) | 清空clear( )

    清空并添加作者的主键或对象/设置作者的主键或对象形式的列表
    book.authoe.clear()
    book.author.set([*args])
    

    🐷tips:add(),set(),remove()都可以支持传多个数字或对象,set必须接收一个可迭代对象

    7、跨表查询规则

    • 正向反向的概念
      关联字段在你当前这张表查询另一张表叫正向
      关联字段不在你当前这张表查询另一张表叫反向

    • 正向查询按字段名进行跨表查询

    • 反向查询按表名小写进行跨表查询

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

      在跨表查询的规则上,跨表查询的结果为多条数据时需要在字段后添加_set;还有如果返回的是None,只要加个all( )。

    • 一对一查询
      (1) 查询得到作者对象 author_obj=Author.object.first()
      (2) 基于作者对象跨表获取作者详情对象,正向通过字段名author_detail author_detail_obj=author_obj.author_detail
      (3) 基于作者详情对象跨表获取作者对象,反向通过表名小写author author_obj=author_detail_obj.author

    • 一对多查询
      (1) 查询得到书籍对象 book_obj=Book.objects.first()
      (2) 获取出版社对象,正向通过字段名publish publish_obj=book_obj.publish
      (3) 获取书籍对象们,反向通过表名小写book,多条数据添加_set,如果返回的结果是None,加个all() book_list_obj=publish_obj.book_set.all()

    • 多对多查询
      (1) 查询得到书籍对象 book_obj=Book.objects.first()
      (2) 获取作者对象们,正向通过字段名author author_list_obj=book_obj.author
      (3) 基于作者对象获取书籍对象们,反向通过表名小写book,多条数据添加_set

    author = Author.objects.first()
    book_list_obj=author.book_set
    

    🐷⚠️tips:如果在ForeignKey()和ManyToManyField的定义中设置related_name的值来复写FOO_set的名称。
    例如:在Article model中做一下更改

    publish = ForeignKey(Book,related_name='bookList')
    

    那么在“查询人民出版社出版过的所有书籍”时结果如下:

    publish=Publish.objects.get(name="人民出版社")
    book_list=publish.bookList.all()  🌸与人民出版社关联的所有书籍对象集合,这里使用all代替了_set
    
    • 多级跨表查询
    某作者出版的第一本书的出版社名字
    author.book_set.first().publish.name
    

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

      Django还提供了一种直观而高效的方式在查询(lookups)中表示关联关系,他能自动确认SQL JOIN联系。要做跨关系查询,就使用两个下划线来连接模型(model)间关联字段的名称,直到最终连接到想要的model为止。
      正向查询按字段,反向查询按表名小写用来告诉ORM引擎join哪张表。
      满足跨表查询规则。
      filter方法与values方法支持__查询规则。

    • 一对多查询
      (1) 正向查询,按字段名:publish
    queryResult=Book.objects.filter(publish__name="南方出版社").values_list("title","price")
    

      (2)反向查询,按表名小写:book

    queryResult=Publish.objects.filter(name="北方出版社").values_list("book__title","book__price")
    
    • 多对多查询
      (1) 正向查询,按字段名:authors
    queryResult=Book.objects.filter(authors__name="wpr").values_list("title)
    

      (2)反向查询,按表名小写:book

    queryResult=Author.objects.filter(name="wpr").values_list("book_list","book_price")
    
    • 一对一查询
      (1) 正向查询
    Author.objects.filter(name="wpr").values("author detail__telephone")
    

      (2) 反向查询

    AuthorDetail.objects.filter(author__name="wpr").values("telephone")
    
    • 两表关联
      查询所有小于18岁作者的名字与实际年龄
    authors_dic=Author.objects.filter(author_detail__id__gt=0).values('name','author_detail__age')
    
    • 多表关联(连续跨表)
      查询出版社在上海的出版过的所有书的作者姓名、作者电话、具体出版社名的相关信息
    info_dic=Book.objects.filter(publish__address__contains="上海").value('author__name','author__author_detail__telephone','publish__name')
    

    queryset对象.query能够查看内部对应的sql语句
      all()
      filter()
      values()
      value_list()
      order_by()
      reverse()
      distinct()
      exclude() 取反
      count() 计数
      exists() 布尔值
      get() 数据对象本身
      first()
      last()

    🌸 related_name
    反向查询时,如果定义了related_name,则用related_name替换表名,例如 publish=ForeignKey(Blog,related_name='bookList')
    那么在“查询人民出版社出版过的所有书籍的名字与价格(一对多)”,反向查询,不再按表名:book,而是related_name:bookList

    queryResult=Publish.objects.filter(name="人民出版社").values_list("boolList__title","bookList__price")
    

    🐷tips:配置文件配置参数查看所有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',
                },
            }
        }
    

    10、聚合查询与分组查询

    (1)聚合(aggregate)
      from django.db.models import Max,Min,Sum,Count,Avg
      aggregate(*args,**kwargs)
    举个例子🌰:计算所有图书的平均价格

    from django.db.models import Avg
    Book.objects.all().aggregate(Avg('price'))
    >>> {'price__avg': 34.35}  # 自动生成
    

    aggregate()是QuerySet的一个终止子句,意思是说,它返回一个包含一些键值对的字典。键的名字是聚合值的标识符,值是计算出来的聚合值。键的名字是按照字段和聚合函数的名称自动生成出来的。如果想要为聚合值指定一个名称,可以向聚合子句提供它。

    Book.objects.aggregate(average_price=Avg('price'))
    >>> {'average_price':34.35}
    

    如果想生成不止一个聚合,你可以想aggregate()子句中添加另一个参数,所以如果想知道所有图书价格的最大值和最小值,可以这样查询:

    from django.db.models import Avg, Max, Min
    Book.objects.aggregate(Avg('price'), Max('price'), Min('price'))
    >>> {'price__avg': 34.35, 'price__max': Decimal('81.20'), 'price__min': Decimal('12.99')}
    

    (2)分组(group by)
      annotate():依据前面的基表
      group_concat():特地用在分组
      concat():所有分组
    🐷tips:字符串拼接要用到concat和value

    • 单表分组查询

    emp表:

    id name age salary depends
    1 wpr 27 1000 销售部
    2 wpp 30 3000 人事部
    3 www all( ) 5000 人事部

    查询每一个部门名称以及对应的员工数
    sql语句:select dep,Count(*) from emp group by dep;
    ORM语句:emp.objects.values("dep").annotate(c=Count("id")

    • 多表分组查询

    emp表:

    id name age salary dep_id
    1 wpr 27 1000 1
    2 wpp 30 3000 2
    3 www all( ) 5000 2

    dep表:

    id name
    1 销售部
    2 人事部

    emp-dep表:

    id name age salary dep_id id name
    1 wpr 27 1000 1 1 销售部
    2 wpp 30 3000 2 2 人事部
    3 www all( ) 5000 2 2 人事部

    查询每一个部门名称以及对应的员工数
    sql语句:select dep.name,Count(*) from emp left join dep on emp.dep_id=dep.id group by dep.id
    ORM语句dep.objetcs.values("id").annotate(c=Count("emp")).values("name","c")

    class Emp(models.Model):
        name=models.CharField(max_length=32)
        age=models.IntegerField()
        salary=models.DecimalField(max_digits=8,decimal_places=2)
        dep=models.CharField(max_length=32)
        province=models.CharField(max_length=32)
    

      annotate()为调用的QuerySet中每一个对象都生成一个独立的统计值(统计方法用聚合函数)。
    总结:跨表分组查询本质就是将关联表join成一张表,再按单表的思路进行分组查询。

    11、F查询与Q查询

    from django.db.models import F,Q
    (1)F查询
      对两个字段的值做比较,Django提供F()来做这样的比较。F()的实例可以在查询中引用字段,来比较同一个model实例中两个不同字段的值。
      F可以取到表中某个字段对应的值来当作我的筛选条件,而不是自定义常量的条件,实现了动态比较的效果。
      Django支持F()对象之间以及F()对象和常数之间的加减乘除和取模的操作。

    🌸 比较两个不同字段的值
      查询评论数大于收藏数的书籍
      from django.db.models import F
      Book.objects.filter(commnetNum__lt=F('keepNum'))
    
    🌸 取到某个字段的值当作筛选条件
    字符串拼接要用到  concat和value
    将所有商品的名字后面加一个爆款后缀
    from django.db.models.functions import Concat
    from django.db.models import Value
    models.Product.objects.update(name=Concat(F('name'),Value('爆款')))
    
    🌸 通过字段名获取可以直接做运算的查询结果
    from Django.db.models import F
    案例一:将id为1的结果年龄增加1
    models.User.objects.filter(id=1).update(age=F('age')+1)
    案例二:查询id是年龄1/4的结果
    models.User.objects.filter(id=F('age')/4)
    

    (2)Q查询

    • filter()等方法中的关键字参数查询都是一起进行'AND'与的关系,如果要执行更复杂的查询(例如or或者not)的关系查询数据,可以使用Q对象。
    🌸 默认是and关系
    models.Product.objects.filter(Q(name='变形金刚'),Q(price=999.999))
    
    🌸 与普通过滤条件混合使用,完成逻辑运算方式的查询
    from Django.db.models import Q
    与[&]   models.User.objects.filter(Q(id=1)&Q(age=10))
    或[|]   models.User.objects.filter(Q(id=1)|Q(id=2))
    非[~]   models.User.objects.filter(~Q(id=1))
    
    当一个操作符在两个Q对象上使用时,它产生一个新的Q对象
    bookList=Book.objects.filter(Q(authors__name="wpr")|Q(authors__name="wpp"))
    等同于下面的SQL WHERE子句
    where name="wpr" OR name="wpp"
    
    同时,Q 对象可以使用~ 操作符取反,这允许组合正常的查询和取反(NOT) 查询:
    bookList=Book.objects.filter(Q(authors__name="wpr") & ~Q(publishDate__year=2017)).values_list("title")
    
    • 查询函数可以混合使用Q对象和关键字参数,所有提供给查询函数的参数(关键字参数或Q对象)都将'AND'在一起,但是,如果出现Q对象,它必须位于所有关键字参数的前面,例如:
    bookList=Book.objects.filter(Q(publishDate__year=2019) | Q(publishDate__year=2020),title__icontains="python" )
    
    • Q查询进阶操作——可以传入字符串
    先实例化一个Q对象
    q=Q()
    q.connector='or'
    q.children.append(('name','wpr'))
    
    Q对象支持直接放在filter括号内
    models.User.objects.filter(q)   q对象默认也是and关系
    

    12、事务(上下文管理)

      定义:将多个sql语句操作变成原子性操作,要么同时成功,有一个失败则里面回滚到原来的状态,保证数据的完整性和一致性(NoSQL数据库对于事务则是部分支持)
      事务的ACID:A(原子性) C(一致性) I(隔离性) D(持久性)

    from django.db import transaction
    with transaction.atomic():
            这里写多个数据库操作
        print('其他逻辑代码')
    

    13、浅谈ORM查询性能

    • 普通查询
    obj_list=models.Love.objects.all() 
        for row in obj_list:   #for循环10次发送10次数据库查询请求
            print(row.b.name)
    

    这种查询方式第一次发送查询请求,每for循环一次也会发送查询请求,频繁进行IO操作。

    • select_related:结果为对象,注意query_set类型的对象都有该方法
      原理:查询时主动完成连表形成一张大表,for循环时不用额外发请求。
      应用场景:节省硬盘空间,数据量少的时候使用,相当于只做了一次数据库查询
    obj_list=models.Love.objects.all().select_related('b')
        for row in obj_list:
            print(row.b.name)
    
    • prefetch_related:结果都是对象
      原理:select_related虽然好,但是做连表操作依然会影响查询性能,所以出现了prefetch_related,prefetch_related不做连表查询,多次单表查询外键表,去重之后显示,2次单表查询(有几个外键做1+N次单表查询)。
      应用场景:效率高,数据量大的时候适用
    obj_list=models.Love.objects.all().prefetch_related('b')
        for obj in obj_list:
            print(obj.b.name)
    
    • update()和对象.save()修改方式的性能比较
      update():
    models.Book.objects.filter(id=1).update(price=3)
    

      执行结果:

    (0.000) BEGIN; args=None
    (0.000) UPDATE "app01_book" SET "price" = '3.000' WHERE "app01_book"."id" = 1; args=('3.000', 1)
    (0.000) SELECT "app01_book"."id", "app01_book"."title", "app01_book"."price", "app01_book"."date", "app01_book"."publish_id", "app01_book"."classify_id" FROM "app01_book" WHERE "app01_book"."id" = 1; args=(1,)
    

      save():

    book_obj=models.Book.objects.get(id=1)
    book_obj.price=5
    book_obj.save()
    

      执行结果:

    (0.000) BEGIN; args=None
    (0.000) UPDATE "app01_book" SET "title" = '人间失格', "price" = '5.000', "date" = '1370-09-09', "publish_id" = 4, "classify_id" = 3 WHERE "app01_book"."id" = 1; args=('人间失格', '5.000', '1370-09-09', 4, 3, 1)
    

    总结:update()比obj.save()性能好。

    相关文章

      网友评论

        本文标题:【三】模型层ORM

        本文链接:https://www.haomeiwen.com/subject/mdtoqctx.html