对象关系映射(英语:(Object Relational Mapping,简称ORM,或O/RM,或O/R mapping),是一种程序技术,用于实现面向对象编程语言里不一样类型系统的数据之间的转换 。从效果上说,它实际上是建立了一个可在编程语言里使用的--“虚拟对象数据库”。前端
简单一句话来讲:就是把数据库的表映射为一个个对象,对对象的操做会被映射成SQL语句,在数据库执行。
python
django默认支持sqlite,mysql, oracle,postgresql数据库。mysql
在django的项目中会默认使用sqlite数据库,在settings里有以下设置:git
DATABASES = { 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } }
下面是MySQL的配置sql
DATABASES = { 'default': { - 'ENGINE': 'django.db.backends.mysql', - 'NAME': 'books', # 你的数据库名称 - 'USER': 'root', # 你的数据库用户名 - 'PASSWORD': '', # 你的数据库密码 - 'HOST': '',- # 你的数据库主机,留空默认为localhost - 'PORT': '3306', # 你的数据库端口 } }
参数以下:数据库
django可使用以下mysql的驱动程序:django
MySQLdb(不完美支持Python3) mysqlclient (官方建议) PyMySQL(纯python的mysql驱动程序)
建议使用:编程
django基础配置部分已经描述了mysqlclient的安装,这里介绍如何使用pymysql,首先须要安装缓存
pip install pymysql
下一步只须要找到项目名文件下的__init__,在里面写入:
import pymysql pymysql.install_as_MySQLdb()
便可完成配置
咱们所说的ORM主要分为两种:
DB First
数据库里先建立数据库表结构,根据表结构生成类,根据类操做数据库Code First
先写代码,执行代码建立数据库表结构主流的orm都是code first。django 的orm也是code first,因此本质上分为两部分:
如今有一张表,主要字段以下:
对应的models中的映射类为:
from django.db import models # 导入models,django提供的对象包含不少建表的方法 # Create your models here. class UserInfo(models.Model): # 须要继承models.Model class Meta: db_table = 'userinfo' # 在数据库中生成的代表 id = models.AutoField(primary_key=True,null=False,verbose_name='ID') name = models.CharField(max_length=4,null=False,verbose_name='用户名') password = models.CharField(max_length=64,null=False,verbose_name='密码') email = models.EmailField(null=False,verbose_name='邮箱') # AutoField : 自增字段,相似于mysql的int字段加auto_increment属性 # CharField:可变长字段,相似于mysql的varchar类型,须要指定长度 # EmailField:邮件字段,仅仅提供给 django admin进行约束使用,映射到MySQL上,根本上也是字符串类型 # null:是否为空,通用参数,默认为否。 # verbose_name:django admin上对表操做时,显示的字段名称 # primary_key:主键 # max_length:针对于CharField字段,标示其长度
部分字段类型及说明以下:
字段名称 | 含义 |
---|---|
AutoField(Field) | int自增列,必须填入参数 primary_key=True |
BigAutoField(AutoField) | bigint自增列,必须填入参数 primary_key=True 注:当model中若是没有自增列,则自动会建立一个列名为id的列 |
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 |
URLField(CharField) | 字符串类型,Django Admin以及ModelForm中提供验证 URL |
SlugField(CharField) | 字符串类型,Django Admin以及ModelForm中提供验证支持 字母、数字、下划线、链接符(减号) |
CommaSeparatedIntegerField(CharField) | 字符串类型,格式必须为逗号分割的数字 |
UUIDField(Field) | 字符串类型,Django Admin以及ModelForm中提供对UUID格式的验证 |
FilePathField(Field) | 字符串,Django Admin以及ModelForm中提供读取文件夹下文件的功 |
FileField(Field) | 字符串,路径保存在数据库,文件上传到指定目录 |
ImageField(FileField) | 字符串,路径保存在数据库,文件上传到指定目录 |
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进制小数 |
BinaryField(Field) | 二进制类型 |
django中提供了不少参数对字段进行控制
通用类:
属性 | 含义 |
---|---|
null | 是否能够为空 |
default | 默认值 |
primary_key | 主键 |
db_column | 列名 |
db_index | 索引(bool) |
unique | 惟一索引 |
时间日期类:
属性 | 含义 |
---|---|
unique_for_date | 对日期字段来讲,表示只对时间作索引 |
unique_for_month | 对日期字段来讲,表示只对月份作索引 |
unique_for_year | 对日期字段来讲,表示只对年作索引 |
auto_now | 不管是你添加仍是修改对象,时间为你添加或者修改的时间。 |
auto_now_add | 为添加时的时间,更新对象时不会有变更。 |
django admin相关:
属性 | 含义 |
---|---|
choices | 在django admin中显示下拉框,避免连表查询(好比用户类型,能够在存在内存中) |
blank | 在django admin中是否能够为空 |
verbose_name | 字段在django admin中显示的名称 |
editable | 在django admin中是否能够进行编辑,默认是true |
error_message | 当在django admin中输入的信息不匹配时,字段的提示信息 |
help_text | 在django admin中输入框旁边进行提示 |
validators | 在django admin中自定义规则限制 |
django中提供了不少的字段类型,大部分都是提供给django admin 来作验证的,实际体如今数据库中的,大部分都是字符串类型。
GenericIPAddressField(Field)
:字符串类型,Django Admin以及ModelForm中提供验证 Ipv4和Ipv6
参数:
FilePathField(Field)
:字符串,Django Admin以及ModelForm中提供读取文件夹下文件的功能
参数:
FileField(Field)
:字符串,路径保存在数据库,文件上传到指定目录
参数:
ImageField(FileField)
:字符串,路径保存在数据库,文件上传到指定目录
参数:
DecimalField(Field)
:10进制小数
参数:
在类内部定义内部类Meta,因为设置映射的表的元数据信息,
class User(models.Model): class Meta: db_name = 'user' id... name...
这里的Meta的类属性db_name,表示生成的数据库表名称为'user',更多的参数有:
属性信息 | 含义 |
---|---|
db_tablespace | 有些数据库有数据库表空间,好比Oracle。你能够经过db_tablespace来指定这个模型对应的数据库表放在哪一个数据库表空间。 |
managed | 默认值为True,Django能够对数据库表进行 migrate或migrations、删除等操做,若是为False的时候,不会对数据库表进行建立、删除等操做。能够用于现有表、数据库视图等,其余操做是同样的。 |
ordering | 告诉Django模型对象返回的记录结果集是按照哪一个字段排序的 |
unique_together | 设置两个字段保持惟一性时使用 |
verbose_name | 给模型类起一个更可读的名字 |
经常使用的就是db_table,用于指定生成的表的名称
前面咱们已经编写了对应数据库表的类,这里咱们将进行实例化(建立数据库对应的表)。利用django提供的命令进行数据库的初始化工做.(以及其余对数据库表进行修改的动做,好比修改表结构,字段属性等,都须要执行以下步骤)
# 进入项目目录下执行 python manage.py makemigrations # 大体含义是:把类转换成对应格式的sql语句。 # 建立表 python manage.py migrate # 在数据库中执行生成的sql语句
注意:
若是没有生成对应的表文件,那么须要在settings.py中,注册你的app
# settings.py INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'app01.apps.App01Config', '你的app名称' ]
当咱们对已生成的表添加新字段时,会出现以下状况
ou are trying to add a non-nullable field 'code' to business 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:
因为咱们添加字段会影响以前的数据,因此这时django提醒咱们,不能只是单纯的添加一个字段,还须要为该字段指定对应的值,固然有几种解决办法:
接下来就须要利用orm来对数据库的表数据进行增删改查等基本操做了。
根据django的MTV架构,业务的处理是在views中完成的,那么对数据库的查询逻辑也应该在views中编写,而咱们定义的数据库对象在model.py中,那么在导入以后,才能进行表操做。
django提供了两种增长数据的方式:一、create,二、save
#------------------------------------- create ------------------------------------- from app01 import models # 从本身的应用中导入models模块 username = 'daxin' password = 'abc.123' email = 'daixin@qq.com' models.UserInfo.objects.create(name=username,password=password,email=email) # 表对象.objects.create() 用来新增数据 # name,password,email 表示表的字段 # 等号右边的表示数据 # 传递的key也能够是字典利用**dic进行传递,在后面利用form的时候会常用这种方式。 dict = {'usernane':'daxin','password':'abc.123',email='daxin@qq.com'} models.UserInfo.objects.create(**dict) #------------------------------------- save ------------------------------------- from app01 import models # 从本身的应用中导入models模块 username = 'daxin' password = 'abc.123' email = 'daixin@qq.com' userobj = models.UserInfo(name=username,password=password,email=email) userobj.save() # save的方式是利用对象实例化的传递参数后,调用save方法进行保存的 # 利用对象,咱们能够方便的进行数据修改后,再进行save # 好比 userobj.name = 'dahuaidan' # userobj.save()
注意:利用create新增数据时,会默认返回新增长的数据对象,咱们能够接受该对象来进行其余的操做,好比添加多对多关系等
比较经常使用的方式是 利用create进行数据的增长
想要对数据进行删除,首先须要匹配到数据,而后执行删除操做,那么就涉及两部分:查找,删除
models.UserInfo.objects.filter(id=1).delete() # 利用filter对数据惟一的字段进行过滤,匹配到删除的行,也能够用all()匹配全部数据,进行删除 # 利用delete方法进行删除操做
总体思路和删除是相同的,首先查找到数据,而后对字段进行修改
models.UserInfo.objects.filter(id=1).update(name='dachenzi') # 利用filter过滤到要修改的数据,而后更新它的name字段的值
查询会返回结果的集,它是django.db.models.query.QuerySet类型。是惰性求值,和sqlalchemy同样。结果就是查询的集。同时它也是一个可迭代对象。
返回结果集的方法:
名称 | 说明 |
---|---|
all() | 获取全部 |
filter() | 过滤,返回知足条件的数据 |
exclude() | 排除,排除知足条件的数据 |
order_by() | 以什么字段排序(在字段前面加减号 ,表示倒序) |
values() | 返回一个对象字典的列表,列表的元素是字典,字典内是字段和值的键值对 |
values_list() | 返回一个对象字典的列表,列表的元素是元组,字典内是字段和值的键值对 |
models.Userinfo.object.all().values('id','username') # 表示只取指定的id和username列 # 结果依旧返回的是QuerySet,可是数据不一样,体现的形式是一个字典 QuerySet({'id':1,'username':'daxin'},{'id':2,'username':'dachenzi'}......) # 使用value_list('id','username'),体现的形式是一个元组 QuerySet((1,'daxin'),(2,'dachenzi')......)
关于filter:
获取结果集的方法经过适当的组合能够链式编写。
返回单个值的方法:
名称 | 说明 |
---|---|
get() | 仅返回单个知足条件的对象 若是未能返回对象则抛出DoesNotExist异常; 若是能返回多条抛出MultipleObjectsReturned异常 |
count() | 返回当前查询的总条数 |
first() | 返回第一个对象 |
last() | 返回最后一个对象 |
exist() | 判断查询集中是否有数据,若是有则返回True |
# 获取表里全部的对象 models.UserInfo.objects.all() # 结果为一个QuerySet对象(django提供的),能够理解为一个列表 # 获取id为1的对象 models.UserInfo.objects.filter(id=1) # 相似于sql中的 where查询条件,结果也是一个QuerySet # 获取name字段包含da的记录 models.UserInfo.objects.filter(name__contains='da') # 这里涉及了万能的双下划线,在后续会进行说明 # get获取一条数据 models.UserInfo.objects.get(id=1) # 注意get不到数据,会直接抛出异常 # 除了id等于1的其余数据 models.UserInfo.objects.exclude(id=1)
注意:
PS:因为filter、all取到的数据默认是 QuerySet
格式,在某些场景下咱们只须要验证是否取到,咱们能够直接获取结果中的第一个数据便可,即在最后添加.first()便可,表示取第一个数据,或者使用.count()来统计匹配到的数据的个数。
module.UserInfo.filter(username='daxin').first()
查询集对象能够直接使用索引下标的方式(不支持负索引),至关于SQL语句中的limit和offset子句。
注意使用索引返回的新的结果集,依然是惰性求值,不会当即查询。
qs = User.objects.all()[20:40] # LIMIT 20 OFFSET 20 qs = User.objects.all()[20:30] # LIMIT 10 OFFSET 20
字段查询表达式能够做为filter()、exclude()、get()的参数,实现where子句。
语法:字段名称__比较运算符=值
,属性名和运算符之间使用双下划线
比较运算符以下
名称 | 举例 | 说明 |
---|---|---|
exact filter(isdeleted=False) |
filter(isdeleted__exact=False) | 严格等于,可省略不写 |
contains | exclude(title__contains='天') | 是否包含,大小写敏感,等价于like '%天%' |
statswith endswith |
filter(title__startswith='天') | 以什么开头或结尾,大小写敏感 |
isnull isnotnull |
filter(title__isnull=False) | 是否为null |
iexact icontains istartswith iendswith |
i的意思是忽略大小写 | |
in | filter(pk__in=[1,2,3,100]) | 是否在指定范围数据中 |
gt、gte lt、lte |
filter(id__gt=3) filter(pk__lte=6) filter(pub_date__gt=date(2000,1,1)) |
|
year、month、day week_day、hour minute、second |
filter(pub_date__year=2000) | 对日期类型属性处理 |
虽然Django提供传入条件的方式,可是不方便,它还提供了Q对象来解决。Q对象是django.db.models.Q,可使用&(and)、|(or)操做符来组成逻辑表达式。 ~ 表示not。
from django.db.models import Q User.objects.filter(Q(pk__lt=6)) # 不如直接写User.objects.filter(pk<6) User.objects.filter(pk__gt=6).filter(pk_lt=10) # 与 User.objects.filter(Q(pk_gt=6) & Q(pk_lt=10)) # 与 User.objects.filter(Q(pk=6) | Q(pk=10)) # 或 User.objects.filter(~Q(pk__lt=6)) # 非
可以使用&|和Q对象来构造复杂的逻辑表达式
咱们常说的表与表之间的关系有:一对1、一对多、多对多,下面分别说明
表示当前表的某个字段的值,来自于其余表,好比人员表和部门表,在人员表中利用一对多外键关系标明该员工属于哪一个部门。
user_type = models.ForeignKey('表名',to_field='字段') # 默认会自动关联对方表的ID字段(主键),手动指定的话必须是惟一列才行。
注意:
什么叫正向查询?仍是拿人员表和部门表举例,外键关系存在人员表中,那么咱们经过人员表利用表中的外键字段就能够查询到该人员的部门信息,我通常称之为正向查询。
一对多跨表查询例子:
# -------------------- models.py -------------------- from django.db import models class Business(models.Model): class Meta: db_table='business' caption = models.CharField(max_length=32) code = models.CharField(max_length=16,default='SA') class Host(models.Model): class Meta: db_table='host' nid = models.AutoField(primary_key=True) hostname = models.CharField(max_length=16) ip = models.GenericIPAddressField(protocol='ipv4',db_index=True) port = models.IntegerField() b = models.ForeignKey(to='Business',to_field='id') # 外键关联Business表 # -------------------- views.py -------------------- def host(request): v1 = models.Host.objects.all() return render(request,'host.html',{'v1':v1}) # -------------------- host.html -------------------- <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div> <p>主机信息</p> {% for row in v1 %} <p>{{ row.nid }} - {{ row.hostname }} - {{ row.ip }} - {{ row.port }} - {{ row.b_id }} - {{ row.b.caption }} - {{ row.b.code }}</p> # 经过b对象,进行跨表查询主机业务线相关信息 {% endfor %} </div> </body> </html>
这里经过row.b_id 和经过b对象来获取 row.b.id 结果是相同的,区别在于使用b对象,会多一次sql查询
PS:当多个表进行及联跨表,那么均可以经过表的外键字段使用点来进行跨表访问(或者使用双下划线)
双下划线和点跨表的不一样之处在于:
# -------------- 利用点进行跨表查询 -------------- # 若是我只想获取主机地址和所属业务线使用点跨表的话 v2 = models.Host.objects.all() return render(request,'host.html',{'v2':v2}) # 前端代码 {% for row in v2 %} <p>{{ row.hostname }} - {{ row.b.caption }}</p> {% endfor %} # 咱们只用了两个字段,却取出了相关的全部数据。 # 是否能够在查询阶段只取出所需字段便可?使用双下划线__便可 # -------------- 利用双下划线跨表查询 -------------- v3 = models.Host.object.all().values('hostname','b__caption') # 咱们能够看到在做为value的条件语句,经过b对象配合双下划线就进行了一次跨表查询 # 前端代码 <div> {% for row in v2 %} <p>{{ row.hostname }} - {{ row.b__caption }}</p> # 只须要向字典同样便可。 {% endfor %} </div>
在django内部,它之因此能识别__,是由于,它在处理values条件时,会使用__做为分隔符,分隔后再进行相关处理
不少时候咱们会有另外一种需求,查询一个部门下的全部员工,这时部门表中没有外间字段关联人员表啊,该怎么查?实际上是能够的,我把这个查询方式称之为反向查询。
在创建外键关系时,不只会在外键所在表中产生外键所关联表的对象,在所关联表中也会产生一个关联表的对象(可能有点绕,没找到更好的表达方式),这个对象通常有两种体现方式:
通常用在values,values_list看成条件作字段过滤
通常看成对象,看成字段处理
def index(request): users = models.User.objects.all() departments = models.Department.objects.values('user__name') # 跨到管理本表的user表中,查找对应的user的名称,利用的是join 格式 for dep in departments: print(dep) # {'user__name': 'daxin'} # {'user__name': 'dachenzi'} # {'user__name': 'hello'} # {'user__name': 'xiaoming'} # {'user__name': 'xiaochen'} return HttpResponse('ok')
对应的sql语句为:
def index(request): departments = models.Department.objects.filter(pk=1) for dep in departments: for username in dep.user_set.values('name'): # user_set指代的是关联的user表对象 print(username) # {'name': 'daxin'} # {'name': 'dachenzi'} # {'name': 'hello'} return HttpResponse('ok')
对应的sql语句为:
前面说了一对多的状况,这里还有一种状况叫多对对,好比一个主机能够关联多个业务线,不一样的业务线能够关联多个主机,因此这里,业务线和主机的关系为多对多,在多对多的状况下,有须要一张额外的表来表示对应关系,这里有两种状况来建立这张关系表。
手动建立,故名思议,咱们须要手动的建立一张关系表,而后建立两个ForeignKey字段(一对多),关联两张表便可。
# 业务线表 class Business(models.Model): caption = models.CharField(max_length=32) code = models.CharField(max_length=16,default='SA') # 主机表 class Host(models.Model): nid = models.AutoField(primary_key=True) hostname = models.CharField(max_length=16) ip = models.GenericIPAddressField(protocol='ipv4',db_index=True) port = models.IntegerField() # 多对多关系表 class Application(models.Model): h = models.ForeignKey(to='Host',to_field='nid') # 关联主机id,字段名为h_id,同时存在对象h,存放对应的Host信息 b = models.ForeignKey(to='Business',to_field='id') # 关联业务线id,字段名为b_id,同时存在对象b,存放对应的Business信息
PS:一共手动建立三张表,能够利用建立的Application关系表来直接操做多对多关系。
在Django中,还存在一种方式为自动建立, 经过django提供的ManyToMany关键字建立。
# 业务线表 class Business(models.Model): caption = models.CharField(max_length=32) code = models.CharField(max_length=16,default='SA') # 主机表 class Host(models.Model): nid = models.AutoField(primary_key=True) hostname = models.CharField(max_length=16) ip = models.GenericIPAddressField(protocol='ipv4',db_index=True) port = models.IntegerField() business = models.ManyToManyField('Business') # 经过manytomany字段建立多对多关系
注意:
手动建立关系表的状况下,因为含有第三张表对应的class,那么咱们能够直接使用这个class对关系表进行操做,可是多对多的状况下没有关系表的class,因此咱们须要经过其余办法来操做。
方法 | 含义 |
---|---|
add() | 添加关系 |
remove() | 删除关系 |
clear() | 清除全部关系 |
all() | 获取对应的全部关系对象(在查询时这里可使用all,filter,get等,就像查找过滤其余数据同样) |
models.Host.objects.filter(nid=1).first().business.add(1,2) # 添加两条多对多关系 1(host) --> 1(business),1(host) --> 2(business) models.Host.objects.filter(nid=1).first().business.remove(2) # 删除一条多对多关系 1 -x-> 2 models.Host.objects.filter(nid=1).first().business.clear() # 清除nid为1的全部的多对多关系
下面是一个小栗子:
# models.py class Bussiness(models.Model): class Meta: db_table = 'bussiness' bus_id = models.AutoField(primary_key=True) bus_name = models.CharField(max_length=64, null=False) class Host(models.Model): class Meta: db_table = 'host' host_id = models.AutoField(primary_key=True) host_name = models.CharField(max_length=64, null=False) host_ip = models.GenericIPAddressField(protocol='both') bus = models.ManyToManyField(Bussiness) # views.py def index(request): hosts = models.Host.objects.all() for host in hosts: for bus in host.bus.all(): # 经过bus对象获取它关联的全部bussiness实例记录 print(host.host_name, host.host_ip, bus.bus_name) return HttpResponse('ok')