6. 模型层

模型层是在视图和数据库之间的内容,根本任务是屏蔽了数据库的繁琐细节, 让程序员可以没有数据库的知识就能直接使用Django.

一般定义的文件位置在project_name/app_name/models.py文件内。

  • 负责跟数据库访问操作,数据的定义等
  • 每个模型是django.models.Model的子类, 可以认为是对应一张数据库table
  • Django提供一套自动生成的用于数据库访问的API
  • models.Type 负责定义字段类型,每个字段可以具有一定约束条件,且与数据库字段对应

为什么要用模型?

Model是MVC框架中重要的一部分,主要负责程序中用于处理数据逻辑的部分。 通常模型对象负责在数据库中存取数据。
模型层实现了数据模型与数据库的解耦,即数据模型的设计不需要依赖于特定的数据库, 通过简单的配置就可以轻松更换数据库。

6.1. ORM

  • 模型与数据库的关系:
    • 模型(Model)负责业务对象和数据库的关系映射(ORM)
  • ORM是“对象-关系-映射”的简称,主要任务是:
    • 根据对象的类型生成表结构
    • 将对象、列表的操作,转换为sql语句
    • 将sql查询到的结果转换为对象、列表

ORM最根本目的是让习惯OOP的程序员能够不懂SQL就能利用OOP来编程,在ORM中, 可以粗略把对应关系认为:

  • OOP的类对应SQL的表
  • OOP的属性(一般是一个描述字段的类)对应SQL的表中的字段
  • OOP的属性的属性对应SQL表字段的约束条件
  • OOP的函数对应SQL的操作, 比如增删查改

Django利用ORM实现了模型层。

6.2. Django中数据库的基本使用

Django有一个默认数据库sqlite3, 所以如果不配置数据库django也能运行,但是默认数据库基本不能 在生产环境中使用。

调试阶段可以直接利用django默认数据库sqlite3,等到调试完成可以无缝切换到mysql等其他关系型 数据库。

  • 配置Mysql数据库

    #Ubuntu中MySql的安装 sudo apt-get install mysql-server sudo apt install mysql-client sudo apt install libmysqlclient-dev

  • 在当前python环境中安装 pymysql

    pip install pymysql #在mysql中创建数据库 #此处需要注意的是一定要指定编码,否则可能对中文不太友好 create databases mydb default charset=utf8

  • 在Django项目中配置数据库
    Django中使用数据库需要对数据库进行简单的配置,一般需要配置两处:setttings和初始化文件。

    • 修改settings.py文件中的DATABASE配置项

        DATABASES = {
        #此处删除了默认数据库,其他数据库也可以并存
        'default': {
            'ENGINE': 'django.db.backends.mysql',
            'NAME': 'mydb',#选择数据库的名,请确认你的mysql中有这个库
            'USER': 'root',
            'PASSWORD': '123456',
            'HOST': 'localhost',
            'PORT': '3306',
            } }
      
    • 告诉Django在接下来的mysql操作中使用pymysql

        #打开mysite/__init__.py,写入以下代码导入pymysql:
        import pymysql
        pymysql.install_as_MySQLdb()
      

6.3. 初识模型:试验项目v4_model

  • 创建v4_model项目和tuling应用app

  • 在setting中设置INSTALLED_APPS: tuling

  • tuling/models.py 中添加模型类Student

      from django.db import models
      # Create your models here.
    
      class Student(models.Model):
          name = models.CharField(max_length=20)
          age = models.IntegerField(default=18)
          addr = models.CharField(max_length=100)
    
  • 迁移数据库:
    迁移数据库分两步:迁移准备和迁移实施。

      #迁移准备
      python manage.py makemigrations
      #迁移
      python manage.py migrate
    
  • 使用模型
    模型创建并迁移成功后就可以使用了,此时会看到相应的目录下生成了迁移文件tuling/migrations/..

注意: 有时候迁移出错,特别是更改后,可以把生成的数据库和迁移文件都删除再重新生成迁移
或者: python manage.py makemigrations appname 用来指定某个特定的app进行迁移

6.4. 定义模型

本章主要讲述如何定义一个模型类。

  • 在模型中定义属性,会生成表中的字段。
  • django会为表增加自动增长的主键列,每个模型只能有一个主键列。
  • 如果使用选项设置某属性为主键列后,则django不会再生成默认的主键列

属性命名限制:

  • 不能是python的保留关键字
  • 由于django的查询方式使用连续下划线,所以命名中不允许使用连续的下划线

6.4.1. 定义属性

定义属性时,需要字段类型,即每个属性的类型,智能书哟models下定义好的属性类型,或者你自己定义自己的属性类型。

字段类型被定义在django.db.models.fields目录下,为了方便使用,被导入到django.db.models

使用方式:

  • 导入from django.db import models
  • 通过models.Field创建字段类型的对象,赋值给属性

例如下面案例, 三个属性类型都是models.Field字类:

from django.db import models

# Create your models here.
class Student(models.Model):
    name = models.CharField(max_length=20)
    age = models.IntegerField(default=18)
    addr = models.CharField(max_length=100)

6.4.2. 常用属性类型

属性类型XXXField都在文件django\db\models\fields\__init__.py中定义,必要时可以查阅代码。

必要到时候,如果系统定义字段不能满足需求,可以自行定义字段属性。

每个字段类型都是Field的子类, Django可以根据字段类型确定以下信息:

  • 数据库中的类型
  • 渲染表单上用的HTML部件
  • 最低限度的验证需求

常用的Field类型如下:

  • AutoField:一个根据实际ID自动增长的IntegerField,通常不指定
    如果不指定,一个主键字段将自动添加到模型中

  • BooleanField:true/false 字段,此字段的默认表单控制是CheckboxInput

  • NullBooleanField:支持null、true、false三种值

  • CharField(max_length=字符长度):字符串,默认的表单样式是 TextInput

  • TextField:大文本字段,一般超过4000使用,默认的表单控件是Textarea

  • IntegerField:整数

  • DecimalField(max_digits=None, decimal_places=None):

    • 使用python的Decimal实例表示的十进制浮点数
    • DecimalField.max_digits:位数总数
    • DecimalField.decimal_places:小数点后的数字位数
  • FloatField:用Python的float实例来表示的浮点数

  • DateField[auto_now=False, auto_now_add=False]):

    • 使用Python的datetime.date实例表示的日期
    • 参数DateField.auto_now:每次保存对象时,自动设置该字段为当前时间,用于”最后一次修改”的时间戳,它总是使用当前日期,默认为false
    • 参数DateField.auto_now_add:当对象第一次被创建时自动设置当前时间,用于创建的时间戳,它总是使用当前日期,默认为false
    • auto_now_add, auto_now, and default 这些设置是相互排斥的,他们之间的任何组合将会发生错误的结果
    • 该字段默认对应的表单控件是一个TextInput. 在管理员站点添加了一个JavaScript写的日历控件,和一个“Today”的快捷按钮,包含了一个额外的invalid_date错误消息键
  • TimeField:使用Python的datetime.time实例表示的时间,参数同DateField

  • DateTimeField:使用Python的datetime.datetime实例表示的日期和时间,参数同DateField

  • FileField:一个上传文件的字段

  • ImageField:

    • 继承了FileField的所有属性和方法
    • 对上传的对象进行校验,确保它是个有效的image
  • 除ForeignKey,ManyToManyField,OneToOneField成员外, 其余定义第一个参数位置是verbose_name, 否则需要第二个参数位置用verbose_name指定

    first_name = models.CharField("person's first name", max_length=30)
    poll = models.ForeignKey(Poll, verbose_name="the related poll")
    sites = models.ManyToManyField(Site, verbose_name="list of sites")
    

6.4.3. 字段选项

通过字段选项,可以实现对字段的约束。数据库表或者字段在创建的时候有很多约束,用来对数据 进行校验。

使用是通过在字段对象定义的时候通过关键字参数指定。

常用的字段控制选项:

  • null:如果为True,Django 将空值以NULL存储到数据库中,默认值是 False

  • db_column:字段的名称,如果未指定,则使用属性的名称

  • db_index:若值为 True, 则在表中会为此字段创建索引

  • default:默认值

  • primary_key:若为 True, 则该字段会成为模型的主键字段

  • unique:如果为 True, 这个字段在表中必须有唯一值

  • blank: 是否允许字段值为空,默认False

  • null: 是否将DB中空值保存为NULL,默认是False

  • choices: 由二元相组成的可选迭代对象,用来给字段提供选项.例如:

    class Person(models.Model):
        SHIRT_SIZES = (
            ('S', 'Small'),
            ('M', 'Medium'),
            ('L', 'Large'),
        )
        name = models.CharField(max_length=60)
        shirt_size = models.CharField(max_length=1, choices=SHIRT_SIZES)
    

6.4.4. 元选项

在模型类中定义类Meta,用于设置元信息。 一般用于对数据库表整个表的属性进行定义。
常见的元信息如下:

  • db_table:定义数据表名称,推荐使用小写字母,
    数据表的默认名称:<app_name>_<model_name>

  • ordering:对象的默认排序字段,获取对象的列表时使用,接收属性构成的列表

      class BookInfo(models.Model):
          ...
          class Meta():
              ordering = ['id']
    
      #字符串前加-表示倒序,不加-表示正序
      class BookInfo(models.Model):
          ...
          class Meta():
              ordering = ['-id']
    
排序会增加数据库的开销

6.5. 模型的关系

本章探讨模型与模型的关系,常见关系数据库模型关系分为三种:

  • 一对一(1:1, OneToOne):
    • 一个有一个,即 has one: OneToOneField
    • 用一访问一: 对象.模型类小写(例如: hasband.wife)
  • 一对多(1:N, OneToMany):
    • 多个属于一个,即 belong to: ForeignKey,多个属于一个
    • 一个有很多个,即 has many: lots of A belong to B 与 B has many A, 在建立 ForeignKey 时,另一个表会自动建立对应的关系
    • 用一访问多: 对象.模型类小写_set(例如:course.student_set)
  • 多对多(N:N, ManyToMany): 一个既有很多个,又属于很多个,即has many and belong to: ManyToManyField, 同样只能在一个model类中说明,关联表会自动建立。

下面是相应的示例代码,用来演示三种对应关系, 其中一个地址(Place)只能 开一个餐馆(Restaurant), 一个餐馆可以有多个服务员(Waiter):

试验代码本人用的是Django shell, 为了便于演示,具体源码参考项目v4_model:

6.5.1. OneToOne

一对一定义方式是在任一个类中使用OneToOneField定义。

    ```
    from django.db import models

    class Place(models.Model):
        name = models.CharField(max_length=50)
        address = models.CharField(max_length=80)

        def __str__(self):              
            return "%s the place" % self.name

    class Restaurant(models.Model):
        # Place:Restaurant=1:1
        pobject = models.OneToOneField(Place, primary_key=True)
        serves_hot_dogs = models.BooleanField(default=False)
        serves_pizza = models.BooleanField(default=False)

        def __str__(self):
            return "%s the restaurant" % self.place.name

    class Waiter(models.Model):
        #Restaurant:Waiter=1:N
        restaurant = models.ForeignKey(Restaurant)
        name = models.CharField(max_length=50)

        def __str__(self):  
            return "%s the waiter at %s" % (self.name, self.restaurant)

    ```

一对一关系中,访问OneToOne关系定义方可通过定义的变量访问,对方会自动 有个定义方同名的属性,可以直接使用。

访问方法伪代码如下, 以上面Place和Restaurant为例:

    #保存
    p = Place()
    r = Restaurant()
    r.pobject = p
    p.save()
    r.save()
      
    #访问
    
    #Place中自动添加一个一对一关系中另一方小写名字的属性,通过这个属性可以访问对方
    #Restaurant访问Place通过自己定义的属性pobject就可以 
    
    print(p.restaurant)
    print(r.pobject )

6.5.2. OneToMany

多对一使用django.db.models.ForeignKey定义,字段定义放在多的一边。

添加关系一般在多方,多方可以直接使用定义关系的变量。一方自动有 一个形如 partername_set 的变量名,此变量可以直接使用查询方法,例如 all, get等。

下面案例中,一个制造商可以生产很多汽车,在多的一端,即Car类中说明即可。

```
from django.db import models

class Manufacturer(models.Model):
    # ...
    pass

class Car(models.Model):
    manufacturer = models.ForeignKey(Manufacturer)
```

在项目v4_model中,我们定义模型类如下,试验代码以下面模型类为准:

    class Users(models.Model):
        username = models.CharField(max_length=20, null=True, blank=True, verbose_name=u'用户名')

        class Meta:
            db_table = 'Users'

    #用户详情
    class UserInfo(models.Model):
        # 在用户详情表中,关联用户表,让两个表的数据产生联系
        # 第一个参数:是被关联的模型名称
        # 第二个参数:当user用户表中的一条数据被删除的时候,与之对应的详情表数据也会被删除
        uid = models.OneToOneField(Users, on_delete=models.CASCADE)
        address = models.CharField(max_length=100, null=True)

        class Meta:
            db_table = 'UserInfo'


    # 班级
    class Class(models.Model):
        id = models.AutoField(primary_key=True)
        classname = models.CharField(max_length=32)
        class Meta:
            db_table = 'Class'

        def __str__(self):
            return self.classname

    # 学员
    class Stu(models.Model):
        id = models.AutoField(primary_key=True)
        sname = models.CharField(max_length=32)

        # 一对多
        cid = models.ForeignKey(to="Class", to_field="id", on_delete= models.CASCADE)

        class Meta:
            db_table = 'Stu'

        def __str__(self):
            return self.sname


    # 讲师信息表
    class Teacher(models.Model):
        id = models.AutoField(primary_key=True)
        tname = models.CharField(max_length=32)
        cid = models.ManyToManyField(to="Class")

        def __str__(self):
            return self.tname

参考以上模型,测试案例如下,具体代码请参考项目源码, 以下代码使用shell测试:

    In [12]: c = Class(classname="北图01班")
    In [13]: c.save()
    In [14]: c = Class(classname="北图02班")
    In [15]: c.save()

    In [16]: s = Stu(sname="刘大拿")
    In [17]: s.cid = c
    In [18]: s.save()

    In [19]: s = Stu(sname="刘二拿")
    In [20]: s.cid = c
    In [22]: s.save()

    In [23]: s = Stu(sname="刘二拿")
    IIn [24]: s.sname = "刘三拿"
    In [25]: s.cid = c
    In [26]: s.save()

    In [27]: c = Class.objects.all()[1]
    In [29]: c.classname
    Out[29]: '北图02班'


    In [34]: for s in Stu.objects.all():
        ...:     print(s.sname + " --- " + s.cid.classname)
    刘大拿 --- 北图02班
    刘二拿 --- 北图02班
    刘三拿 --- 北图02班

    In [35]: c
    Out[35]: <Class: 北图02班>

    In [40]: for s in c.stu_set.all():
        ...:     print(c.classname + " --- " + s.sname)
        ...:
    北图02班 --- 刘大拿
    北图02班 --- 刘二拿
    北图02班 --- 刘三拿

6.5.3. ManyToMany:

多对多定义可以在任一端采用ManyToMany定义,属性名称一般采用复数形式。

    ```
    from django.db import models

    class Topping(models.Model):
        # ...
        pass

    class Pizza(models.Model):
        # ...
        toppings = models.ManyToManyField(Topping)
    ```

多对多关系的访问一方通过定义的变量访问,对方自动增加一个xxx_set 的变量, 可以使用查询方法访问。

多对多的案例展示如下所示,具体参加源码:

# c是Class的实列
In [51]: t = Teacher(tname="刘大拿")
#需要注意的是,必须先save后才能add,否则报错,顺序很重要
In [54]: t.save()
In [55]: t.cid.add(c)
In [56]: t.save()

In [57]: c.classname
Out[57]: '北图py02'

In [59]: c1 = Class.objects.get(classname="北图py01")
In [60]: c2 = c

# 添加一个联系
In [61]: t.cid.add(c1)

In [63]: t.cid.all()
Out[63]: <QuerySet [<Class: 北图py01>, <Class: 北图py02>]>

In [64]: c1.teacher_set.all()
Out[64]: <QuerySet [<Teacher: 刘大拿>]>

In [65]: t2 = Teacher(tname="刘二拿")
In [66]: t2.save()
In [67]: t2.cid.add(c1)
In [68]: t2.cid.add(c2)

In [69]: t3 = Teacher(tname="刘d三拿")
In [71]: t3.save()

In [72]: c1.teacher_set.add(t3)

In [73]: c1.teacher_set.all()
Out[73]: <QuerySet [<Teacher: 刘大拿>, <Teacher: 刘二拿>, <Teacher: 刘d三拿>]>

In [74]: t3.delete()
Out[74]: (2, {'tuling.Teacher_cid': 1, 'tuling.Teacher': 1})

#从另一端访问,teacher_set是自动添加的属性
In [75]: c1.teacher_set.all()
Out[75]: <QuerySet [<Teacher: 刘大拿>, <Teacher: 刘二拿>]>

6.6. 模型高级知识

本章讨论模型的一些属性或者实例相关的函数和功能。

6.6.1. 模型的Manager属性(管理器)

模型的作用是跟数据库打交道,我们前面主要展示了模型的建立等,但模型究竟如何 和数据库交互依然没解释清楚,其实可以把模型看作是一个数据描述集合,具体跟 数据库的操作,比如增删查改等,是通过一个Manager类的实例作为代理人来实行的。

所以,操作类函数一般都在Manager的实例里,它是模型类的一个类属性。

  • Manager,是数据库查询操作的接口,用于从数据库获取示例

  • 如果没有自定义Manager,则默认名称是objects

  • 只能通过模型类访问,不能通过模型实例访问

  • 可以自行定义这个管理器类,常见于两种情况

    1. 向管理器类添加额外的方法
    2. 修改管理器返回的原始查询集合,重写 get_queryset()方法
  • 示例:

      class StudentManager(models.Manager):
          def get_queryset(self): 
              return super(StudentManager, self).get_queryset().filter(name="test") 
    
      class StudentInfo(models.Model):
          ...
          #此处自己定义了一个管理器,当然这个管理器是继承自Manager
          student_test = StudentManager()
    
  • Manager用于表范围的事物,属于”静态类方法”

6.6.2. 模型的方法

模型当然也可以带有自己的方法,但一般此类方法都是跟模型的 实例相关的内容,如下例,通过定义一些函数,在模型的 实例上执行:

 from django.db import models
 class Person(models.Model):
     first_name = models.CharField(max_length=50)
     last_name = models.CharField(max_length=50)
     birth_date = models.DateField()

     def baby_boomer_status(self):
         "Returns the person's baby-boomer status."
         import datetime
         if self.birth_date < datetime.date(1945, 8, 1):
             return "Pre-boomer"
         elif self.birth_date < datetime.date(1965, 1, 1):
             return "Baby boomer"
         else:
             return "Post-boomer"

     def _get_full_name(self):
         "Returns the person's full name."
         return '%s %s' % (self.first_name, self.last_name)
     full_name = property(_get_full_name)

6.6.3. 重写预定义的模型方法

必要的时候,也可以对模型预定义的方法进行重写,常见于对模型 执行预定义的方法的时候需要增加功能的时候,优点类似于类的属性(property)的需求 场景。

  • 代码示例:

    from django.db import models
    
    class Blog(models.Model):
        name = models.CharField(max_length=100)
        tagline = models.TextField()
    
        # 本例中,每次实例保存的时候我们都需要加点私货,
        # 注意写法, 对于正常的保存功能,完全可以调用父类的方法
        def save(self, *args, **kwargs):
            do_something()
            # Call the "real" save() method.
            super(Blog, self).save(*args, **kwargs) 
            do_something_else()
    

6.6.4. 模型的继承

模型的诞生是为了OOP精神? 那么继承是不能少的,需要注意的是,模型的继承 最终也要反映到数据库的表的继承上。

模型基础主要分三类:

  • 抽象继承
  • 多表继承
  • 代理继承

下面分别解释。

6.6.4.1. 抽象继承

抽象类的诞生,纯属是为了被继承, 子类只使用父类类持有信息,父类不会被单独使用。

此类继承当然不会产生父类相应的数据库表。

  • 父类中属性abstract需要abstract=True

  • 不生成任何数据表

  • 子类继承Meta后,abstract=False,可以手动更改

  • 示例代码如下:

    from django.db import models
    
    class CommonInfo(models.Model):
        # ...
        class Meta:
            # 抽象类
            abstract = True
            ordering = ['name']
    
    class Student(CommonInfo):
        # ...
        class Meta(CommonInfo.Meta):
            db_table = 'student_info'
    
  • related_name 问题:

    • 名称中包含 ‘%(app_label)s’和 ‘%(class)s’

    • 代码示例:

      from django.db import models
      
      class Base(models.Model):
          m2m = models.ManyToManyField(OtherModel,
              related_name="%(app_label)s_%(class)s_related")
          class Meta:
              abstract = True
      
      class ChildA(Base):
          pass
      
      class ChildB(Base):
          pass
      

6.6.5. 多表继承

子类继承父类后,子类会拥有父类的属性,但在数据库中父类 和子类不是一个表格,通过一个OneToOne的关系进行链接, 此时字类可以自由访问包含父类的所有属性。

  • 示例如下:

    from django.db import models
    
    class Place(models.Model):
        name = models.CharField(max_length=50)
        address = models.CharField(max_length=80)
    
    class Restaurant(Place):
        serves_hot_dogs = models.BooleanField(default=False)
        serves_pizza = models.BooleanField(default=False)
    
  • 查询的时候可以通过子类的小写形式得到其对象

    >>> Place.objects.filter(name="Bob's Cafe")
    >>> Restaurant.objects.filter(name="Bob's Cafe")
    >>> p = Place.objects.get(id=12)
    #如果p同时也是个Restaurant类,则可以通过p.restaurant访问
    #否则会引发异常
    >>> p.restaurant
    <Restaurant: ...>
    
  • 一般情况子类不继承父类的Meta,在受限情况下,可有特例

6.6.6. 代理继承

代理继承是对原始模型的一个代理,可以创建,删除,更新代理model的实例, 不同之处在于,可以在代理model中改变默认的排序设置 和默认的manager,不会对原始model产生影响

  • 示例如下:

    from django.db import models
    
    class Person(models.Model):
        first_name = models.CharField(max_length=30)
        last_name = models.CharField(max_length=30)
    
    class MyPerson(Person):
        class Meta:
            #代理继承
            proxy = True
    
        def do_something(self):
            # ...
            pass
    
  • MyPerson 和Person操作同一个数据表

6.7. 模型层查询

本章讲述模型的相关具体操作,主要通过Manager的实例来操作。

  • 实验代码:

      from django.db import models
    
      class Blog(models.Model):
          name = models.CharField(max_length=100)
          tagline = models.TextField()
    
          def __str__(self):              # __unicode__ on Python 2
              return self.name
    
      class Author(models.Model):
          name = models.CharField(max_length=50)
          email = models.EmailField()
    
          def __str__(self):              # __unicode__ on Python 2
              return self.name
    
      class Entry(models.Model):
          blog = models.ForeignKey(Blog)
          headline = models.CharField(max_length=255)
          body_text = models.TextField()
          pub_date = models.DateField()
          mod_date = models.DateField()
          authors = models.ManyToManyField(Author)
          n_comments = models.IntegerField()
          n_pingbacks = models.IntegerField()
          rating = models.IntegerField()
    
          def __str__(self):              # __unicode__ on Python 2
              return self.headline
    
  • save: 保存, 无返回值,直接执行insert操作

  • ForeignKey类型的变量:

    • 直接对ForeignKey赋值即可

    • 代码如下

      >>> from blog.models import Entry
      >>> entry = Entry.objects.get(pk=1)
      >>> cheese_blog = Blog.objects.get(name="Cheddar Talk")
      >>> entry.blog = cheese_blog
      >>> entry.save()
      
  • ManyToMany属性的处理:

    • 需要用到add函数来添加关联关系的记录

    • 代码示例:

        >>> from blog.models import Author
        >>> joe = Author.objects.create(name="Joe")
        >>> entry.authors.add(joe)
      

      为了在一条语句中,向ManyToManyField添加多条记录,可以在调用add()方法时传入多个参数,像这样:

        >>> john = Author.objects.create(name="John")
        >>> paul = Author.objects.create(name="Paul")
        >>> george = Author.objects.create(name="George")
        >>> ringo = Author.objects.create(name="Ringo")
        >>> entry.authors.add(john, paul, george, ringo)
      

6.7.1. 管理器

管理器就是ClassName.objects, 用来对数据库表进行具体操作。

管理器只可以通过模型的类访问,而不可以通过模型的实例访问, 目的是为了强制区分表级别的操作和记录级别的操作。

返回结果是复数的,一般是一个QuerySet结果,单数的一般是一个object。

  • 查询集(QuerySet):
    表示从数据库中取出来的对象的集合。它可以含有零个、一个或者多个过滤器。

6.7.2. 过滤器

过滤器就是返回查询集的方法,即结果是QuerySet。

  • all: 所有结果
  • filter: 过滤
  • exclude:不包括
  • order_by:排序
  • values:一个对象构成一个字典,然后构成一个列表返回

返回单个值的函数:

  • get:
    • 返回单个满足条件的对象
    • 如果未找到会引发”模型类.DoesNotExist”异常
    • 如果多条被返回,会引发”模型类.MultipleObjectsReturned”异常
  • count:返回当前查询的总条数
  • first:返回第一个对象
  • last:返回最后一个对象
  • exists():判断查询集中是否有数据,如果有则返回True

6.7.3. 查询集

查询集具有一些特性,在此做一个总结:

  • 链式过滤:

    • 如果查询集的筛选结果本身还是查询集,所以可以将筛选语句链接在一起。

    • 代码如下:

        >>> Entry.objects.filter(
        ...     headline__startswith='What'
        ... ).exclude(
        ...     pub_date__gte=datetime.date.today()
        ... ).filter(
        ...     pub_date__gte=datetime(2005, 1, 30)
        ... )
      
  • 每次过滤后结果相互独立不受彼此影响,如下q1,q2,q3相互独立:

      >>> q1 = Entry.objects.filter(headline__startswith="What")
      >>> q2 = q1.exclude(pub_date__gte=datetime.date.today())
      >>> q3 = q1.filter(pub_date__gte=datetime.date.today())
    
  • 查询集的惰性问题:
    下面代码只有在执行 print(q) 语句才会真实查询数据库

      >>> q = Entry.objects.filter(headline__startswith="What")
      >>> q = q.filter(pub_date__lte=datetime.date.today())
      >>> q = q.exclude(body_text__icontains="food")
      >>> print(q)
    
  • 查询集可以使用切片操作,

    • 同样惰性,不会引发真实查询

      Entry.objects.all()[:5]

    • 但如果指定step则真实查询

      Entry.objects.all()[:10:2]

    • 不支持负索引,比如

      Entry.objects.all()[-1]

  • get获取一个单一的对象

    • get和filter()[0]有区别
      • get如果查询不到会引发DoesNotExist异常
      • get如果查询结果多余1会引发MultipleObjectsReturned异常

6.7.4. 查询

SQL在查询的时候往往有很多条件限定,我们ORM本意是做一个完全的从OOP到 SQL的映射,所以带条件的查询是少不了的。

6.7.4.1. 查询参数

如果使用查询参数,待条件的查询使用方法如下pramater__querypara, 比如要查询 年龄(age)小于18岁的人, 则age__lt=18

常见的带条件的查询如下:

  • exact: 精准等于

      >>> Entry.objects.get(headline__exact="Man bites dog")
    
  • iexact: 大小写不敏感

      >>> Blog.objects.get(name__iexact="beatles blog")
    
  • contains: 大小写敏感的包含关系

      Entry.objects.get(headline__contains='Lennon')
      大体翻译成
      SELECT ... WHERE headline LIKE '%Lennon%';
    
  • icontains

  • istartwith

  • iendwith

  • gt: 大于

  • get: 大于等于

  • lt: 小鱼

  • lte: 小于等于

  • range: 范围

  • year: 年份

  • isnull:

6.7.4.2. 跨关系查询

跨关联关系的查询, 即查询之前定义的的表关系, 比如OneToOne, OneToMany之类的关系。

  • 下面这个例子获取所有Blog 的name 为’Beatles Blog’ 的Entry 对象

      # Blog:Entry = 1:N
      >>> Entry.objects.filter(blog__name='Beatles Blog')
    
  • 还可以反向工作。若要引用一个“反向”的关系,只需要使用该模型的小写的名称

      >>> Blog.objects.filter(entry__headline__contains='Lennon')
    

6.7.4.3. F表达式

F表达式用来表示同一模型中不同字段的比较。

使用的是需要先导入F查询:

from djaong.db.models import F

比如:

  • 查找comments 数目多于pingbacks 的Entry

      >>> from django.db.models import F
      >>> Entry.objects.filter(n_comments__gt=F('n_pingbacks'))
    
  • Django 支持对F() 对象使用加法、减法、乘法、除法、取模以及幂计算等算术操作

  • 查找comments 数目比pingbacks 两倍还要多的Entry

      >>> Entry.objects.filter(n_comments__gt=F('n_pingbacks') * 2)
    
  • 查询rating 比pingback 和comment 数目总和要小的Entry

      >>> Entry.objects.filter(rating__lt=F('n_comments') + F('n_pingbacks'))
    
  • 在 F 对象中使用双下划线标记来跨越关联关系。 带有双下划线的 F 对象将引入任何需要的join 操作以访问关联的对象

  • 要获取author 的名字与blog 名字相同的Entry

      >>> Entry.objects.filter(authors__name=F('blog__name'))
    
  • 对于date 和date/time 字段,你可以给它们加上或减去一个timedelta 对象。

      >>> from datetime import timedelta
      >>> Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))
    
  • F() 对象支持.bitand() 和.bitor() 两种位操作

      >>> F('somefield').bitand(16)
    

6.7.4.4. 查询快捷方式pk

Django 提供一个查询快捷方式pk ,它表示“primary key” 的意思。

  • 在Blog 模型示例中,主键是id 字段,所以下面三条语句是等同的

      >>> Blog.objects.get(id__exact=14) # Explicit form
      >>> Blog.objects.get(id=14) # __exact is implied
      >>> Blog.objects.get(pk=14) # pk implies id__exact
    
  • pk 的使用不仅限于__exact 查询, 任何查询类型都可以与pk 结合来完成一个模型上对主键的查询

      # Get blogs entries with id 1, 4 and 7
      >>> Blog.objects.filter(pk__in=[1,4,7])
      # Get all blog entries with id > 14
      >>> Blog.objects.filter(pk__gt=14)
    

6.7.4.5. 转义LIKE语句中的百分号和下划线

SQL语句中的百分号和下划线自动被转义:

    >>> Entry.objects.filter(headline__contains='%')
    上面可以转化成下面语句的SQL
    SELECT ... WHERE headline LIKE '%\%%';

6.7.4.6. Q查询

  • filter() 等方法中的关键字参数查询都是一起进行“AND” 的

  • Q 对象可以使用& 和| 操作符组合起来。当一个操作符在两个Q 对象上使用时,它产生一个新的Q 对象。

  • 下面的语句产生一个Q 对象,表示两个”question__startswith” 查询的“OR”

      Q(question__startswith='Who') | Q(question__startswith='What')
      它等同于一下SQL Where语句:
      WHERE question LIKE 'Who%' OR question LIKE 'What%'
    
  • 可以组合& 和| 操作符以及使用括号进行分组来编写任意复杂的Q 对象。
    同时,Q 对象可以使用~ 操作符取反,这允许组合正常的查询和取反(NOT) 查询

      Q(question__startswith='Who') | ~Q(pub_date__year=2005)
    
  • 每个接受关键字参数的查询函数(例如filter()、exclude()、get())都可以传递一个或多个Q 对象作为位置(不带名的)参数。如果一个查询函数有多个Q 对象参数,这些参数的逻辑关系为“AND”

      Poll.objects.get(
          Q(question__startswith='Who'),
          Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))
          )
    

6.7.4.7. 比较对象

为了比较两个模型实例,只需要使用标准的Python 比较操作符,即双等于符号:==。

在后台,它会比较两个模型主键的值。

如果模型的主键不叫id,也没有问题。比较将始终使用主键,无论它叫什么

6.7.4.8. 删除对象

删除对象使用delete函数。

  • 无返回值
  • 是唯一没有在管理器 上暴露出来的查询集方法
  • 当Django 删除一个对象时,它默认使用SQL ON DELETE CASCADE 约束

6.7.4.9. 拷贝模型实例

虽然没有内建的方法用于拷贝模型实例,但还是很容易创建一个新的实例并让它的所有字段都拷贝过来。

  • 只需要将pk 设置为None

      blog = Blog(name='My blog', tagline='Blogging is easy')
      blog.save() # blog.pk == 1
    
      blog.pk = None
      blog.save() # blog.pk == 2
    
  • 如果你用继承,那么会复杂,由于继承的工作方式,你必须设置pk 和 id 都为None

      class ThemeBlog(Blog):
          theme = models.CharField(max_length=200)
    
      django_blog = ThemeBlog(name='Django', tagline='Django is easy', theme='python')
      django_blog.save() # django_blog.pk == 3
    
      django_blog.pk = None
      django_blog.id = None
      django_blog.save() # django_blog.pk == 4
    
  • 这个过程不会拷贝关联的对象。如果你想拷贝关联关系,你必须编写一些更多的代码

      entry = Entry.objects.all()[0] # some previous entry
      old_authors = entry.authors.all()
      entry.pk = None
      entry.save()
      entry.authors = old_authors # saves new many2many relations
    

6.7.4.10. 一次更新多个对象

  • 有时你想为一个查询集中所有对象的某个字段都设置一个特定的值,then update()

     # Update all the headlines with pub_date in 2007.
     Entry.objects.filter(pub_date__year=2007).update(headline='Everything is the same')
    
  • 若要更新ForeignKey 字段,需设置新的值为你想指向的新的模型实例

      >>> b = Blog.objects.get(pk=1)
      # Change every Entry so that it belongs to this Blog.
      >>> Entry.objects.all().update(blog=b)
    

6.7.4.11. 关联的对象的查询

  • 个Entry 对象e 可以通过blog 属性e.blog 获取关联的Blog 对象
  • Django 还会创建API 用于访问关联关系的另一头, 即从关联的模型访问定义关联关系的模型。
    例如,Blog 对象b 可以通过entry_set 属性 b.entry_set.all()访问与它关联的所有Entry 对象
6.7.4.11.1. 一对多关系
  • 前向查询

    • 如果一个模型具有ForeignKey,那么该模型的实例将可以通过属性访问关联的(外部)对象

        >>> e = Entry.objects.get(id=2)
        >>> e.blog # Returns the related Blog object.
      
    • 你可以通过外键属性获取和设置。和你预期的一样,对外键的修改不会保存到数据库中直至你调用save()

        >>> e = Entry.objects.get(id=2)
        >>> e.blog = some_blog
        >>> e.save()
      
    • 如果ForeignKey 字段有null=True 设置(即它允许NULL 值),你可以分配None 来删除对应的关联性

        >>> e = Entry.objects.get(id=2)
        >>> e.blog = None
        >>> e.save() # "UPDATE blog_entry SET blog_id = NULL ...;"
      
    • 一对多关联关系的前向访问在第一次访问关联的对象时被缓存。以后对同一个对象的外键的访问都使用缓存

        >>> e = Entry.objects.get(id=2)
        >>> print(e.blog)  # Hits the database to retrieve the associated Blog.
        >>> print(e.blog)  # Doesn't hit the database; uses cached version.
      
    • select_related() 查询集方法递归地预填充所有的一对多关系到缓存中

        >>> e = Entry.objects.select_related().get(id=2)
        >>> print(e.blog)  # Doesn't hit the database; uses cached version.
        >>> print(e.blog)  # Doesn't hit the database; uses cached version.
      
  • 反向查询

    • 如果模型I有一个ForeignKey,那么该ForeignKey 所指的模型II实例可以通过一个管理器返回前面有ForeignKey的模型I的所有实例。默认情况下,这个管理器的名字为foo_set,其中foo 是源模型的小写名称。该管理器返回的查询集可以用上一节提到的方式进行过滤和操作

         >>> b = Blog.objects.get(id=1)
         >>> b.entry_set.all() # Returns all Entry objects related to Blog.
      
         # b.entry_set is a Manager that returns QuerySets.
         >>> b.entry_set.filter(headline__contains='Lennon')
         >>> b.entry_set.count()
      
    • 你可以在ForeignKey 定义时设置related_name 参数来覆盖foo_set 的名称。例如,如果Entry 模型改成blog = ForeignKey(Blog, related_name=’entries’),那么上面的示例代码应该改成这样

        >>> b = Blog.objects.get(id=2)
        >>> b.entries.all() # Returns all Entry objects related to Blog.
      
        # b.entries is a Manager that returns QuerySets.
        >>> b.entries.filter(headline__contains='Lennon')
        >>> b.entries.count()
      
6.7.4.11.2. 处理关联对象的其它方法
  • add(obj1, obj2, …): 添加一指定的模型对象到关联的对象集中。
  • remove(obj1, obj2, …): 从关联的对象集中删除指定的模型对象
  • lear(): 从关联的对象集中删除所有的对象
6.7.4.11.3. 多对多关系
  • 对多关系的两端都会自动获得访问另一端的API。这些API 的工作方式与上面提到的“方向”一对多关系一样

  • 唯一的区别在于属性的名称:定义 ManyToManyField 的模型使用该字段的属性名称,而“反向”模型使用源模型的小写名称加上’_set’ (和一对多关系一样)

  • 例子:

      e = Entry.objects.get(id=3)
      e.authors.all() # Returns all Author objects for this Entry.
      e.authors.count()
      e.authors.filter(name__contains='John')
    
      a = Author.objects.get(id=5)
      a.entry_set.all() # Returns all Entry objects for this Author.
    
  • 类似ForeignKey,ManyToManyField 可以指定related_name

6.7.4.11.4. 一对一关系
- 如果你在模型中定义一个OneToOneField,该模型的实例将可以通过该模型的一个简单属性访问关联的模型
- 例如:

        class EntryDetail(models.Model):
            entry = models.OneToOneField(Entry)
            details = models.TextField()

        ed = EntryDetail.objects.get(id=2)
        ed.entry # Returns the related Entry object.
- 在“反向”查询中有所不同。一对一关系中的关联模型同样具有一个管理器对象,但是该管理器表示一个单一的对象而不是对象的集合

        e = Entry.objects.get(id=2)
        e.entrydetail # returns the related EntryDetail object

6.8. models API

本章主要记录一些Django的API常见的问题。

  • save发生了什么

    1. 发出一个pre-save 信号: 发送一个django.db.models.signals.pre_save 信号, 以允许监听该信号的函数完成一些自定义的动作
    2. 预处理数据: 如果需要,对对象的每个字段进行自动转换。
    3. 准备数据库数据: 要求每个字段提供的当前值是能够写入到数据库中的类型。
    4. 据到数据库中: 将预处理过、准备好的数据组织成一个SQL 语句用于插入数据库。
    5. 出一个post-save 信号: 发送一个django.db.models.signals.post_save 信号,以允许监听听信号的函数完成一些自定义的动作。
  • Django如何分辨是Update还是Insert

    • 一个求值为True 的值(例如,非None 值或非空字符串),Django 将执行UPDATE。
    • 如果对象的主键属性没有设置或者UPDATE 没有更新任何记录,Django 将执行INSERT。
  • 强制使用Insert或者Update

    • 可以传递force_insert=True 或 force_update=True 参数给save() 方法。
  • 其他模型实例方法

    • unicode(): python3废弃

    • str: 对对象调用str的时候调用

    • get_FOO_display: 对于每个具有choices 的字段,每个对象将具有一个get_FOO_display() 方法, 其中FOO 为该字段的名称。这个方法返回该字段对“人类可读”的值。

        from django.db import models
      
        class Person(models.Model):
            SHIRT_SIZES = (
                ('S', 'Small'),
                ('M', 'Medium'),
                ('L', 'Large'),
            )
            name = models.CharField(max_length=60)
            shirt_size = models.CharField(max_length=2, choices=SHIRT_SIZES)
      
        >>> p = Person(name="Fred Flintstone", shirt_size="L")
        >>> p.save()
        >>> p.shirt_size
        'L'
        >>> p.get_shirt_size_display()
        'Large'