Django之ORM
1. ORM介绍
1.1 ORM概念
对象关系映射(Object Relational Mapping,简称ORM)模式是一种味了解决面向对象与关系型数据库存在的互不匹配的现象的技术。
简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中。
ORM在业务逻辑层和数据库之间充当了桥梁的作用。
1.2 ORM由来
让我们从O/R开始。字母O起源于“对象”(Object),而R则来自于“关系”(Relational)。
几乎所有的软件开发过程中都会涉及到对象和关系型数据库。在用户层面和业务逻辑层面,我们是面向对象的。当对象的信息发生变化的时候,我们就需要把对象的信息保存在关系型数据库中。
按照之前的方式来进行开发就会出现程序员会在自己的业务逻辑代码中夹杂着很多SQL语句用来增删改查相关数据,而这些代码通常都是极其相似或者重复的。
1.3 ORM的优势
-
ORM解决的主要问题是对象和关系的映射。它通常将一个类和一张表做一一对应,类中的每个实例对应表中的一条记录,类的每个属性对应表中的每个字段。
-
ORM提供了对数据库的映射,不用直接编写SQL代码,只需要操作对象就能对数据库操作数据。
-
让软件开发人员专注于业务逻辑的处理,提高了开发效率。
1.4 ORM的劣势
- ORM在一定程度上牺牲了程序的执行效率(原生SQL效率会更高一些);
- ORM的操作是有限的,也就是ORM定义好的操作是可以完成的,一些复杂的查询操作是完成不了的;
- ORM用多了,SQL语句就不会写了,关系数据库相关技能退化…
1.5 ORM总结
ORM只是一种工具,工具确实能解决一些重复、简单的劳动。这是不可否认的
但是我们不能指望某个工具能一劳永逸地解决所有问题,一些特殊问题还是需要特殊处理
但是在整个软件开发过程中需要特殊处理的情况应该都是很少的,否则所谓的工具也就失去了它存在的意义
##2. Django中的ORM
2.1 Django使用ORM快速入门
1) Django中mysql的配置
配置settings.py文件中关于数据库的配置信息,达到连接数据库的目的
# 配置db信息
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'djangoblog',
'HOST': '127.0.0.1',
'PORT': 3306,
'USER': 'root',
'PASSWORD': 'root'
}
}
# 用pymysql替换MySQLdb模块进行数据库的连接(因为mysqldb模块只能在py2.X版本用)
# 注:以下部分,通常放在项目的setting.py文件同级目录的__init__.py文件中
import pymysql
pymysql.install_as_MySQLdb()
init.py文件
# 用pymysql替换MySQLdb模块进行数据库的连接(因为mysqldb模块只能在py2.X版本用)
import pymysql
pymysql.install_as_MySQLdb()
2)定义ORM模式的表结构
在modesl.py文件中定义表结构(ORM模式的)
下面定义了一个User模型,包含username和password两个属性/字段
from django.db import models
# 定义一个user表,里面有两个字段(username、password),且字段的类型是varchar,最大长度是32
class User(models.Model):
username = models.CharField(max_length=32) # varchar(32)
password = models.CharField(max_length=32) # varchar(32)
username和password是模型的字段。每个字段都被指定为一个类属性,每个属性映射到一个数据库列/字段(即类的属性对应着表的字段)
上面的User模型将会对应如下SQL语句进行数据表的创建
CREATE TABLE app01_user (
"id" serial NOT NULL PRIMARY KEY,
"username" varchar(32) NOT NULL,
"password" varchar(32) NOT NULL
);
3)数据库迁移
即将表结构的变化同步到数据库中
# 制作迁移文件,表示之后要做的操作;会在migrations文件夹下生成一个0001_initial.py的文件
$ python3 manage.py makemigrations
# 执行具体的迁移动作
$ python3 manage.py migrate
# 数据库中会生成一个app01_user的表
mlgdeMacBook-Pro:mysite01 mlg$ python3.5 manage.py makemigrations
Migrations for 'app01':
app01/migrations/0001_initial.py
- Create model User
mlgdeMacBook-Pro:mysite01 mlg$ python3.5 manage.py migrate
Operations to perform:
Apply all migrations: admin, app01, auth, contenttypes, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying app01.0001_initial... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying sessions.0001_initial... OK
mlgdeMacBook-Pro:mysite01 mlg$
注意:
如果在数据库迁移的时候出现如下警告
WARNINGS:
?: (mysql.W002) MySQL Strict Mode is not set for database connection 'default'
HINT: MySQL's Strict Mode fixes many data integrity problems in MySQL, such as data truncation upon insertion, by escalating warnings into errors. It is strongly recommended you activate it.
那么,在配置中多加一个OPTIONS参数:
'OPTIONS': {
'init_command': "SET sql_mode='STRICT_TRANS_TABLES'"},
Django官方解释:https://docs.djangoproject.com/en/1.11/ref/databases/#setting-sql-mode
4)查看生成的表结构
mysql> desc app01_user;
+----------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| username | varchar(32) | NO | | NULL | |
| password | varchar(32) | NO | | NULL | |
+----------+-------------+------+-----+---------+----------------+
3 rows in set (0.01 sec)
mysql>
说明:
- 表app01_user的名称是自动生成的,如果你要自定义表名,需要在model的Meta类中指定db_table参数;强烈建议使用小写表名,特别是使用MySQL作为数据库时
- id字段是自动添加的,如果你想要指定自定义主键,只需要在其中一个字段中指定
primary_key=True
即可。如果Django发现你已经明确设置了Field.primary_key
,它将不会添加自动ID列 - 本示例中的CREATE TABLE SQL使用的是PostgreSQL语法进行转译/格式化,但是,实际情况中,Django是会根据配置文件中指定的数据库类型来生成相应的SQL语句
- Django支持MySQL5.5及更高版本
2.2 Model
在Django中model是你数据的单一、明确的信息来源。它包含了你存储的数据的重要字段和行为。通常,一个模型(model)映射到一个数据库表
基本情况:
- 每个模型都是一个Python类,它是
django.db.models.Model
的子类 - 一个模型映射一个数据库表
- 模型的每个属性都代表一个数据库字段
- 综上所述,Django为您提供了一个自动生成的数据库访问API
官方资料:https://docs.djangoproject.com/en/1.11/topics/db/queries/
2.3 字段
1)常用字段
-
AutoField
自增的整型字段,必填参数
primary_key=True
,则成为数据库的主键。无该字段时,django自动创建。一个model不能有两个AutoField字段。
-
IntegerField
一个整数类型。数值范围:-2147483648 ~ 2147483647。
-
CharField
字符类型,必须提供
max_length
参数.max_length表示字符的长度。 -
DateField
日期类型,日期格式为
YYYY-MM-DD
,相当于Python中的datetime.date的实例参数:
auto_now:每次修改时修改为当前日期时间
auto_now_add:新创建对象时自动添加当前日期时间
Auto_now和auto_now_add和default参数时互斥的,不能同时设置。
-
DatetimeField
日期时间字段,格式为
YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]
,相当于Python中的datetime.datetime的实例
字段相关介绍官网链接:https://docs.djangoproject.com/en/1.11/ref/models/fields/#field-types
字段补充
AutoField(Field)
- int自增列,必须填入参数 primary_key=True
BigAutoField(AutoField)
- bigint自增列,必须填入参数 primary_key=True
注:当model中如果没有自增列,则自动会创建一个列名为id的列
from django.db import models
class UserInfo(models.Model):
# 自动创建一个列名为id的且为自增的整数列
username = models.CharField(max_length=32)
class Group(models.Model):
# 自定义自增列
nid = models.AutoField(primary_key=True)
name = models.CharField(max_length=32)
SmallIntegerField(IntegerField):
- 小整数 -32768 ~ 32767
PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)
- 正小整数 0 ~ 32767
IntegerField(Field)
- 整数列(有符号的) -2147483648 ~ 2147483647
PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)
- 正整数 0 ~ 2147483647
BigIntegerField(IntegerField):
- 长整型(有符号的) -9223372036854775808 ~ 9223372036854775807
BooleanField(Field)
- 布尔值类型
NullBooleanField(Field):
- 可以为空的布尔值
CharField(Field)
- 字符类型
- 必须提供max_length参数, max_length表示字符长度
TextField(Field)
- 文本类型
EmailField(CharField):
- 字符串类型,Django Admin以及ModelForm中提供验证机制
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 上传图片的宽度保存的数据库字段名(字符串)
DateTimeField(DateField)
- 日期+时间格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]
DateField(DateTimeCheckMixin, Field)
- 日期格式 YYYY-MM-DD
TimeField(DateTimeCheckMixin, Field)
- 时间格式 HH:MM[:ss[.uuuuuu]]
DurationField(Field)
- 长整数,时间间隔,数据库中按照bigint存储,ORM中获取的值为datetime.timedelta类型
FloatField(Field)
- 浮点型
DecimalField(Field)
- 10进制小数
- 参数:
max_digits,小数总长度
decimal_places,小数位长度
BinaryField(Field)
- 二进制类型
2)示例
在app02的models.py下定义表
from django.db import models
# Create your models here.
class User(models.Model):
uid = models.AutoField(primary_key=True) # 自定义uid字段为自增、主键
name = models.CharField(max_length=32) # 定义name为varchar(32)的字段
age = models.IntegerField() # 定义age为整数
birth = models.DateTimeField(auto_now_add=True) # auto_now_add=True;新增数据时保存当前时间
执行数据库迁移操作
# 制作迁移文件,表示之后要做的操作;会在migrations文件夹下生成一个0001_initial.py的文件
$ python3 manage.py makemigrations
# 执行具体的迁移动作
$ python3 manage.py migrate
表结构
mysql> desc app02_user;
+-------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+----------------+
| uid | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(32) | NO | | NULL | |
| age | int(11) | NO | | NULL | |
| birth | datetime(6) | NO | | NULL | |
+-------+-------------+------+-----+---------+----------------+
4 rows in set (0.00 sec)
mysql>
插入数据
通过pycharm的底部的Python Console窗口,执行插入数据操作
PyDev console: starting.
Python 3.5.4 (v3.5.4:3f56838976, Aug 7 2017, 12:56:33)
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Django 1.11.25
>>> from app02 import models
>>> models.User.objects.create(name='tom',age=18)
<User: User object>
>>>
查看插入结果
mysql> select * from app02_user;
+-----+------+-----+----------------------------+
| uid | name | age | birth |
+-----+------+-----+----------------------------+
| 1 | tom | 18 | 2020-02-23 04:24:10.216507 |
+-----+------+-----+----------------------------+
1 row in set (0.00 sec)
mysql>
说明:uid为自增主键,所以不需要插入;birth时间定义了(auto_now_add=True),默认是数据新增时的时间(UDC时间,和北京时间差8小时),不需要单独插入
查询表数据
>>> tom = models.User.objects.get(pk=1)
>>> tom
<User: User object>
>>> tom.age
18
>>>
更改age值,观察birth变化
>>> tom.age = 28
>>> tom.save()
>>>
mysql> select * from app02_user;
+-----+------+-----+----------------------------+
| uid | name | age | birth |
+-----+------+-----+----------------------------+
| 1 | tom | 28 | 2020-02-23 04:24:10.216507 |
+-----+------+-----+----------------------------+
1 row in set (0.00 sec)
mysql>
可知,age字段值已更改,但是birth未变化,是由于auto_now_add=True
是数据新增的时间,因此在该条数据有更新时,birth字段的值并不会发生变化
因此,引入auto_now=True
,参数,该参数表示当该条数据新增或修改时,该时间值均会发生变化
2.4 自定义字段
自定义一个cahr类型字段
class MyCharField(models.Field):
"""
自定义的char类型的字段类
"""
def __init__(self, max_length, *args, **kwargs):
self.max_length = max_length
super(MyCharField, self).__init__(max_length=max_length, *args, **kwargs)
def db_type(self, connection):
"""
限定生成数据库表的字段类型为char,长度为max_length指定的值
"""
return 'char(%s)' % self.max_length
使用自定义的char类型字段
在原来model基础上增加新的phone字段
class User(models.Model):
uid = models.AutoField(primary_key=True) # 自定义uid字段为自增、主键
name = models.CharField(max_length=32) # 定义name为varchar(32)的字段
age = models.IntegerField() # 定义age为整数
birth = models.DateTimeField(auto_now_add=True) # auto_now_add=True;新增数据时保存当前时间
phone = MyCharField(max_length=11) # 使用自定义的char类型
数据库迁移
malingangdeMBP:bookmanager malingang$ python3.5 manage.py makemigrations
You are trying to add a non-nullable field 'phone' to user without a default; we can't do that (the database needs something to populate existing rows).
Please select a fix:
1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
2) Quit, and let me add a default in models.py
Select an option: 1
Please enter the default value now, as valid Python
The datetime and django.utils.timezone modules are available, so you can do e.g. timezone.now
Type 'exit' to exit this prompt
>>> '12345678912'
Migrations for 'app02':
app02/migrations/0002_user_phone.py
- Add field phone to user
malingangdeMBP:bookmanager malingang$ python3.5 manage.py migrate
Operations to perform:
Apply all migrations: admin, app01, app02, auth, contenttypes, sessions
Running migrations:
Applying app02.0002_user_phone... OK
malingangdeMBP:bookmanager malingang$
由于之前该表中已存在一条数据,那么在更新表结构时,会提示原来的那条数据中,新增字段的值应该怎么处理,选择1,可直接填写默认值即可
查看数据库表结构及内容
mysql> desc app02_user;
+-------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+----------------+
| uid | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(32) | NO | | NULL | |
| age | int(11) | NO | | NULL | |
| birth | datetime(6) | NO | | NULL | |
| phone | char(11) | NO | | NULL | |
+-------+-------------+------+-----+---------+----------------+
5 rows in set (0.00 sec)
mysql> select * from app02_user;
+-----+------+-----+----------------------------+-------------+
| uid | name | age | birth | phone |
+-----+------+-----+----------------------------+-------------+
| 1 | tom | 28 | 2020-02-23 04:24:10.216507 | 12345678912 |
+-----+------+-----+----------------------------+-------------+
1 row in set (0.00 sec)
mysql>
由上结果可知,phone是一个char(11)
类型的字段
2.5 字段参数
字段参数,官网文档链接:https://docs.djangoproject.com/en/1.11/ref/models/fields/#field-options
null 数据库中字段是否可以为空;False代表不能为空,True代表可以为空
db_column 数据库中字段的列名
default 数据库中字段的默认值
primary_key 数据库中字段是否为主键
db_index 数据库中字段是否可以建立索引;即该字段是否加索引
unique 数据库中字段是否可以建立唯一索引
unique_for_date 数据库中字段【日期】部分是否可以建立唯一索引
unique_for_month 数据库中字段【月】部分是否可以建立唯一索引
unique_for_year 数据库中字段【年】部分是否可以建立唯一索引
verbose_name Admin中显示的字段名称
blank Admin中是否允许用户输入为空;表单输入层面的限制、校验
editable Admin中是否可以编辑
help_text Admin中该字段的提示信息
choices Admin中显示选择框的内容,用不变动的数据放在内存中从而避免跨表操作
如:gf = models.IntegerField(choices=[(0, '何穗'),(1, '大表姐'),],default=1)
error_messages 自定义错误信息(字典类型),从而定制想要显示的错误信息;
字典健:null, blank, invalid, invalid_choice, unique, and unique_for_date
如:{'null': "不能为空.", 'invalid': '格式错误'}
validators 自定义错误验证(列表类型),从而定制想要的验证规则
from django.core.validators import RegexValidator
from django.core.validators import EmailValidator,URLValidator,DecimalValidator,\
MaxLengthValidator,MinLengthValidator,MaxValueValidator,MinValueValidator
如:
test = models.CharField(
max_length=32,
error_messages={
'c1': '优先错信息1',
'c2': '优先错信息2',
'c3': '优先错信息3',
},
validators=[
RegexValidator(regex='root_\d+', message='错误了', code='c1'),
RegexValidator(regex='root_112233\d+', message='又错误了', code='c2'),
EmailValidator(message='又错误了', code='c3'), ]
)
说明
null
与blank
两个字段参数,其中null是从数据库层面表示该字段是否为空,而blank是从用户输入层面(Django通过form组件进行校验)来限制该字段是否为空;因此使用时要配合好使用
db_clolumn
字段参数,可以指定数据表中字段的名称,而不一定要和类中定义的属性名一致;比如数据库已经建好的情况,字段名都是固定的不会再修改,可是研发又不想用字段的名称来命名这个model中的属性,就可以采用db_clolumn
参数
2.6 表的参数(Model Meta 参数)
这个不是很常用,如果你有特殊需要可以使用。
官网资料链接:https://docs.djangoproject.com/en/1.11/ref/models/options/#model-meta-options
class UserInfo(models.Model):
nid = models.AutoField(primary_key=True)
name = models.CharField(max_length=32)
age = models.IntegerField()
class Meta:
# 数据库中生成的表名称 默认 app名称 + 下划线 + 类名
db_table = "table_name"
# admin中显示的表名称
verbose_name = '个人信息'
# verbose_name加s
verbose_name_plural = '所有用户信息'
# 联合索引
index_together = [
("name", "age"), # 应为两个存在的字段
]
# 联合唯一索引
unique_together = (("name", "age"),) # 应为两个存在的字段
models.py示例
from django.db import models
# Create your models here.
class MyCharField(models.Field):
"""
自定义的char类型的字段类
"""
def __init__(self, max_length, *args, **kwargs):
self.max_length = max_length
super(MyCharField, self).__init__(max_length=max_length, *args, **kwargs)
def db_type(self, connection):
"""
限定生成数据库表的字段类型为char,长度为max_length指定的值
"""
return 'char(%s)' % self.max_length
class User(models.Model):
uid = models.AutoField(primary_key=True)
name = models.CharField(verbose_name='用户名',max_length=32,db_column='username',unique=True)
age = models.IntegerField(verbose_name='年龄',blank=True,null=True)
birth = models.DateTimeField(verbose_name='生日',auto_now_add=True)
phone = MyCharField(11,verbose_name='手机号')
gender = models.BooleanField(choices=((True,'男'),(False,'女')))
class Meta:
# 数据库中生成的表名称 默认 app名称 + 下划线 + 类名
db_table = "table_name"
# admin中显示的表名称
verbose_name = '个人信息'
3. ORM基本操作
# 增
models.Tb1.objects.create(c1='xx', c2='oo') # 增加一条数据,可以接受字典类型数据 **kwargs
obj = models.Tb1(c1='xx', c2='oo')
obj.save()
# 查
models.Tb1.objects.get(id=123) # 获取单条数据,不存在则报错(不建议)
models.Tb1.objects.all() # 获取全部
models.Tb1.objects.filter(name='seven') # 获取指定条件的数据
models.Tb1.objects.exclude(name='seven') # 去除指定条件的数据
# 删
# models.Tb1.objects.filter(name='seven').delete() # 删除指定条件的数据
# 改
models.Tb1.objects.filter(name='seven').update(gender='0') # 将指定条件的数据更新,均支持 **kwargs
obj = models.Tb1.objects.get(id=1)
obj.c1 = '111'
obj.save() # 修改单条数据