Python / 17 Django 中内嵌的 ORM 模型

Django 中内嵌的 ORM 模型

本小节将详细为大家介绍 Django 中内嵌的 ORM 模型及其使用,这里我会结合源码的方式为大家展示 Django 内部 ORM 模型的实现原理。

1. ORM 介绍

ORM 的概念如下:

对象关系映射(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。

简单的说,ORM 是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中。ORM 在业务逻辑层和数据库层之间充当了桥梁的作用。ORM 解决的主要问题是对象和关系的映射。它通常把一个类和一个表一一对应,类的每个实例对应表中的一条记录,类的每个属性对应表中的每个字段,具体如下图所示。ORM 提供了对数据库的映射,不用直接编写 SQL 代码,只需像操作对象一样从数据库操作数据。让软件开发人员专注于业务逻辑的处理,提高了开发效率

图片描述

ORM 模式也是有一定缺点的,它会在一定程度上牺牲程序的执行效率。此外,还存在许多复杂场景是 ORM 模式无法解决的,同样还是需要手动编写 SQL 语句完成。

2. Django 内嵌的 ORM 模型

2.1 Django 中的模型说明

在 Django 中,一个模型(model)会映射到一个数据库表。每个模型都是一个Python 类,它是django.db.models.Model 的子类,模型的每个属性都代表一个数据库字段。例如下面的代码中,我们定义了一个 Member 类。每个 model 会属于 Django 中的一个应用,我们通常会将每个应用的 models 写到该应用目录下的 models.py 中。

# first_django_app/hello_app/models.py

from django.db import models

class Member(models.Model):
    sex_choices = (
        (0, '男'),
        (1, '女'),
    )
    name = models.CharField('姓名', max_length=30)
    age = models.CharField('年龄', max_length=30)
    sex = models.SmallIntegerField('性别', choices=sex_choices, default=0)
    occupation = models.CharField('职业', max_length=30)
    phone_num = models.CharField('手机号', max_length=14, null=True)
    email = models.EmailField('邮箱', blank=True)
    city = models.CharField('城市', max_length=30)
    register_date = models.DateTimeField('注册时间', auto_now=True)
    
    def __str__(self):
        return "<%s, %s>" % (self.name, self.phone_num)

    class Meta:
        # 通过db_table自定义数据表名
        db_table = 'member'
    

上面模型类的定义中,我们看到几个和模型相关的字段类,比如 CharField、SmallIntegerField 等。Django 中定义了许多类似的字段类,这些字段类和数据库中字段的类型是一一映射的。以 MySQL 为例:

# 源码地址 django/db/backends/mysql/base.py
# ...

class DatabaseWrapper(BaseDatabaseWrapper):
    vendor = 'mysql'
    display_name = 'MySQL'
    # This dictionary maps Field objects to their associated MySQL column
    # types, as strings. Column-type strings can contain format strings; they'll
    # be interpolated against the values of Field.__dict__ before being output.
    # If a column type is set to None, it won't be included in the output.
    data_types = {
        'AutoField': 'integer AUTO_INCREMENT',
        'BigAutoField': 'bigint AUTO_INCREMENT',
        'BinaryField': 'longblob',
        'BooleanField': 'bool',
        'CharField': 'varchar(%(max_length)s)',
        'DateField': 'date',
        'DateTimeField': 'datetime(6)',
        '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(6)',
        'UUIDField': 'char(32)',
    }
    
    # ...
    
# ...

从上面这部分源码可以看到,Django 中定义的这些字段类都会和 MySQL 中的字段的类型是一一对应的。接下来介绍常用的 Field 类型以及相关的属性选项。

2.2 Django 中常用的 Field types

在 Django 模型中,每个字段都应该是相应 Field 类的实例,以此决定该表在数据库中保存字段的数据类型。在上面的源码中我们看到 Django 内部是定义了25个字段类,其中常用的 Field 类型如下:

  • AutoField:int 自增列,必须填入参数 primary_key=True。当 model 中如果没有自增列,则自动会创建一个列名为 id 的列;

  • BooleanField:布尔类型 (True/False),这个Field不接受null参数,要想使用可以为 null 的布尔类型的字段,就要使用 NullBooleanField;

  • CharField:最常用的字段类,映射到数据库中会转换成 varchar 类型,使用时必须传入 max_length 属性以定义该字符串的最大长度,如果超过254个字符,就不建议使用 CharField 了,此时建议使用 TextField;

  • DateField 和 DateTimeField:都是日期时间的字段类,注意前者只到天,后者可以精确到毫秒。使用这两个 Field 可以传递以下几个参数:

    • auto_now=True:在每次这个数据保存的时候,都使用当前的时间;
    • auto_now_add=True:在每条数据第一次被添加进去的时候,都使用当前的时间;

    此外要注意的是 auto_add_now,auto_now 与 default 是互斥的

  • DecimalField:处理浮点类型的 Field。从上面的源码可以看到,它有两个必须填入的参数:

    • max_digits:数字允许的最大位数;

    • decimal_places:小数的最大位数;

  • FloatField:也是处理浮点类型的 Field。它和 DecimalField 的区别就是 Python 中 float 和 decimal 的区别;

  • IntegerField /BigIntegerField/SmallIntegerField:都是处理整数类型的 Field;

  • TextField:长文本类型 Field,对应 MySQL 中的 longtext 类型。

2.3 Django 中的 Field options

每种 Field 类会有一些特定的 Field 选项,比如 CharField 必须要传入 max_length 属性值。但是下面这些属性对于所有 Field 类都是有效的:

  • null:默认为 False。如果为 True 则表明在数据库中该字段可以为 null;
  • blank:默认为 False。如果为 True 则表明在数据库中该字段可以为不填;
  • choice:设置可选项,表明该字段的值只能从 choice 中选择,例如上面 Member 表中定义的 sex 字段,只能为 0 或者 1,代表的含义分别为男或者女;
  • default:设置字段的默认值;
  • help_text:设置说明信息;
  • primary_key:如果为 True,表明设置该字段为主键。此时 Django 便不会再为我们添加默认的 id 主键了;
  • unique:设置该字段的值在表中唯一。

2.4 数据库中生成模型表

接下来,我们需要使用 Django 给我们提供的两个命令来在数据库中生成 hello_app 应用下定义的数据模型。注意: Member 类映射的表名默认是【应用名_类名小写】,然而在前面的模型代码中我们通过 model 的 Meta 类中的 db_table 参数改写了数据库的具体名称,所以最后数据库中生成的表名为 member,而不是 hello_app_member。

(django-manual) [root@server first_django_app]# python manage.py makemigrations hello_app
Migrations for 'hello_app':
  hello_app/migrations/0001_initial.py
    - Create model Member
(django-manual) [root@server first_django_app]# python manage.py migrate hello_app
Operations to perform:
  Apply all migrations: hello_app
Running migrations:
  Applying hello_app.0001_initial... OK

执行完成后,此时 hello_app 应用下的所有 model 就会被映射到 MySQL 数据库中,且会对应生成相应的模型表(此外还有一个迁移记录表 django_migrations):

MySQL [django_manual]> show tables;
+-------------------------+
| Tables_in_django_manual |
+-------------------------+
| django_migrations       |
| member                  |
| user                    |
+-------------------------+
3 rows in set (0.00 sec)

我们还可以通过 show create table 表名 命令显示表的创建语句:

MySQL [django_manual]> show create table member;
+--------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table  | Create Table                                                                                                                                                                                                                                                                                                                                                                                          |
+--------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| member | CREATE TABLE `member` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(30) NOT NULL,
  `age` varchar(30) NOT NULL,
  `sex` smallint(6) NOT NULL,
  `occupation` varchar(30) NOT NULL,
  `phone_num` varchar(14) NOT NULL,
  `email` varchar(254) NOT NULL,
  `city` varchar(30) NOT NULL,
  `register_date` datetime(6) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 |
+--------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

3. 小结

本小结中我们介绍了 ORM 的基本概念,然后讲解了 Django 中的 model 相关知识,介绍了模型层中常见的字段类型和字段选项。最后实战演示了如何通过 Django 提供的命令在数据库中生成模型层定义的表。接下来我们会在生成的表中使用 Django 给我们提供的 ORM 模型对表进行增删改查操作。