Django对各种数据库都提供了很好的支持,而且为这些数据库提供了统一的调用API,这些API统称为ORM框架。通过使用Django内置的ORM框架可以实现数据库连接和读写操作。
构建模型
ORM框架是一种程序技术,用于实现面向对象编程语言中不同类型系统的数据之间的转换。从效果上说,其实是创建了一个可在编程语言中使用的“虚拟对象数据库”,通过对虚拟对象数据库操作,从而实现对目标数据库的操作,虚拟对象数据库与目标数据库是相互对应的。在Django中,虚拟对象数据库也称为模型,通过模型实现对目标数据库的读写操作。实现方法如下:
- 需要在项目的settings.py中设置数据库信息,具体参考前面的章节。
- 构建虚拟对象数据库,在App的models.py文件中以类的形式定义模型。
- 通过模型在目标数据库中创建相应的数据表。
- 在视图函数中通过对模型操作实现目标数据库的读写操作。
以mysql数据库为例,在settings.py中配置如下:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql', #mysql引擎
'NAME': 'mysite', #数据库名
'USER':'root', #用户名
'PASSWORD':'123456', #密码
'HOST':'127.0.0.1', #主机地址,默认本机
'PORT':'3306', #默认端口
}
}
在setting.py中配置好数据库后,就可以到项目app的models.py文件中定义模型,以前文的index为例:
from django.db import models
#创建产品分类表
class Type(models.Model):
id = models.AutoField(primary_key=True)
type_name = models.CharField(max_length=20)
#创建产品信息表
class Product(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=50)
weight = models.CharField(max_length=20)
size = models.CharField(max_length=20)
type = models.ForeignKey(Type, on_delete=models.CASCADE)
完成模型的定义后,需要通过执行makemigrations和migrate指令来创建数据表,命令如下:
#项目根目录下cmd
python manage.py makemigrations
python manage.py migrate
执行完以上两个指令后,就可以在数据库中生成相对应的数据表index_product和index_type,这两个表分别对应模型Product和Type。其他数据表是Django内置功能所使用的数据表。
上面的例子中,在模型Product和Type中定义的字段类型有整型和字符串类型,在实际开发中,我们需要定义不同的数据类型来满足各种需求,Django划分了多种不同的数据类型:
表字段 | 说明 |
---|---|
models.AutoField | 默认生成一个名为id的字段并未Int类型 |
models.CharField | 字符串类型 |
models.BooleanField | 布尔类型 |
models.ComaSeparatedIntegerField | 用逗号分隔的整数类型 |
models.DateField | 日期(date)类型 |
models.DateTimeField | 日期(datetime)类型 |
models.Decimal | 十进制小数类型 |
models.EmailField | 字符串类型(正则表达式邮箱) |
models.FloatField | 浮点类型 |
models.IntegerField | 整数类型 |
models.BigIntegerField | 长整数类型 |
models.IPAddressField | 字符串类型(IPv4正则表达式) |
models.GenericIPAddressField | 字符串类型,参数protocol可以是:both、IPv4和IPv6,验证IP地址 |
models.NullBooleanField | 允许为空的布尔类型 |
models.PositiveIntegerField | 正整数的整数类型 |
models.PositiveSmallIntegerField | 小正整数类型 |
models.SlugField | 包含字母、数字、下划线和连字符的字符串,常用语URL |
models.SmallIntegerField | 小整数类型,取值范围(-32,768~+32767) |
models.TextField | 长文本类型 |
models.TimeField | 时间类型,显示时分秒HH:MM[:ss[.uuuuuu]] |
models.URLField | 字符串,地址为正则表达式 |
models.BinaryField | 二进制数据类型 |
除了表字段类型之外,每个表字段还可以设置相应的参数,使得表字段更加完善。
参数 | 说明 |
---|---|
Null | 如为True,字段是否可以为空 |
Blank | 如为True,设置在Admin站点管理中添加数据时可允许空值 |
Default | 设置默认值 |
primary_key | 如为True,将字段设置为主键 |
db_column | 设置数据库中的字段名称 |
Unique | 如为True,将字段设置成唯一属性,默认为False |
db_index | 如为True,为字段添加数据库索引 |
verbose_name | 为Admin站点管理设置字段的显示名称 |
related_name | 关联对象反向引用描述符,用于多表查询,可解决一个数据表有两个外键同时指向另一个数据表而出现重名的问题 |
数据表的关系
表与表之间有三种关系:一对一、一对多和多对多。
- 一对一关系通常是一个数据表有太多字段,将常用字段抽取出来并组成一个新的数据表。在模型中可以通过OneToOneField来构建数据表的一对一关系。
- 一对多关系是最常见的表关系,在模型中可以通过ForeignKey来构建数据表的一对多关系。
- 多对多关系存在于在两个或两个以上的数据表中,第一个表的某一行数据可以与第二个表的一到多行数据进行关联,同时在第二个表中的某一行数据也可以与第一个表的一到多行数据进行关联。在多对多关系中,需要使用新的数据表来管理两个表的数据关系。在模型中可以通过ManyToManyField来构建数据表的多对多关系。
代码如下:
#models.py
#一对一关系
class Performer(models.Model):
id = models.IntegerField(primary_key=True)
name = models.CharField(max_length=20)
nationality = models.CharField(max_length=20)
masterpiece = models.CharField(max_length=50)
class Performer_info(models.Model):
id = models.IntegerField(primary_key=True)
performer = models.OneToOneField(Performer,on_delete=models.CASCADE)
birth = models.CharField(max_length=20)
#一对多关系
class Performer(models.Model):
id = models.IntegerField(primary_key=True)
name = models.CharField(max_length=20)
nationality = models.CharField(max_length=20)
class Performer_info(models.Model):
id = models.IntegerField(primary_key=True)
performer = models.ForeignKeyField(Performer,on_delete=models.CASCADE)
birth = models.CharField(max_length=20)
#多对多关系
class Performer(models.Model):
id = models.IntegerField(primary_key=True)
name = models.CharField(max_length=20)
nationality = models.CharField(max_length=20)
class Performer_info(models.Model):
id = models.IntegerField(primary_key=True)
name = models.CharField(max_length=20)
performer = models.ManyToManyField(Performer)
数据表的读写
数据库的读写操作主要是对数据进行增、删、改、查。
增
在Django中,向数据表中插入数据有以下几种方法:
方法一,通过实例化模型,再对对象属性逐一赋值,最后保存
>>> from index.models import *
>>> p = Product()
>>> p.name = '荣耀V9'
>>> p.weight = '111g'
>>> p.size = '120*75*7mm'
>>> p.type_id = 1
>>> p.save()
方法二,通过Django的ORM框架提供的API实现,使用create方法实现数据插入
>>> from index.models import *
>>> Product.objects.create(name='荣耀 V9', weight='111g', size='120*75*7mm',type_id = 1)
<Product: Product object (13)>
方法三,在实例化时直接设置属性值
>>> from index.models import *
>>> p = Product(name='荣耀 V9', weight='111g', size='120*75*7mm',type_id = 1)
>>> p.save()
改
如果要对数据进行更新,实现步骤与数据插入的方法大致相同,唯一的区别是在模型实例化后,要更新数据,需要先进行一次数据查询,将查询结果以对象的形式赋给p,最后对p的属性重新赋值就能实现数据的更新。
>>> p = Product.objects.get(id=14)
>>> p.name = '华为荣耀 V9'
>>> p.save()
除此以外,还可以使用update方法实现单条或多条数据的更新。
查询单条并更新
>>> Product.objects.filter(id=13).update(name='华为荣耀 V9')
1
查询多条并更新
>>> Product.objects.filter(name='华为荣耀 V9').update(name='华为荣耀 V10')
2
不查询,对全表数据进行更新
>>> Product.objects.update(name='华为荣耀 V9')
14
删
如果要对数据进行删除,也有三种方法:
删除一条id为1的数据
>>> Product.objects.get(id=1).delete()
(1, {'index.Product': 1})
删除多条数据
>>> Product.objects.filter(type_id=2).delete()
(2, {'index.Product': 2})
删除表中全部数据
>>> Product.objects.all().delete()
(11, {'index.Product': 11})
查
数据查询是数据库操作中最为复杂且内容最多的部分。
全表查询,等同于SQL语句 select * from index_product,返回数据的列表
>>> from index.models import *
>>> p = Product.objects.all()
>>> p[1].name
'HUAWEI nova 2s'
查询前5条数据,等同于 select * from index_product LIMIT 5
>>> p = Product.objects.all()[:5]
>>> p[4].name
'PORSCHE DESIGN'
>>> p[5].name
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "C:\Users\USER\AppData\Local\Programs\Python\Python37\lib\site-packages\django\db\models\query.py", line 303, in __getitem__
return qs._result_cache[0]
IndexError: list index out of range
查询某个字段,等同于Select name from index_product
values方法,以列表形式返回数据,列表元素以字典格式表示
>>> p = Product.objects.values('name')
>>> p[1]['name']
'HUAWEI nova 2s'
values_list方法,以列表形式表示返回数据,列表元素以元组格式表示
>>> p = Product.objects.values_list('name')[:3]
>>> p
<QuerySet [('荣耀V10',), ('HUAWEI nova 2s',), ('荣耀Waterplay',)]>
使用get方法查询数据,等同于Select * from index_product where id=2
>>> p = Product.objects.get(id=2)
>>> p.name
'HUAWEI nova 2s'
使用filter方法查询数据,注意区分get和filter的差异:filter返回的是列表
>>> p = Product.objects.filter(id=2)
>>> p[0].name
'HUAWEI nova 2s'
SQL的 and 查询主要在filter里面添加多个查询条件
>>> p = Product.objects.filter(id=4,name='荣耀畅玩平板')
>>> p
<QuerySet [<Product: Product object (4)>]>
SQL的 or 查询,需要引入Q,编写格式:Q(field=value) | Q(field=value)
等同于Select * from index_product where name='华为荣耀V9' or id=6
>>> from django.db.models import Q
>>> p = Product.objects.filter(Q(name='华为荣耀V9') | Q(id=6))
>>> p
<QuerySet [<Product: Product object (6)>, <Product: Product object (9)>, <Product: Product object (10)>, <Product: Product object (11)>]>
>>>
使用count方法统计查询数据的数据量
>>> p = Product.objects.filter(name='华为荣耀V9').count()
>>> p
3
去重查询, distinct方法无须设置参数,去重方法根据values设置的字段执行
等同于 Select DISTINCT name from index_product where name='华为荣耀V9'
>>> p = Product.objects.values('name').filter(name='华为荣耀V9').distinct()
>>> p
<QuerySet [{'name': '华为荣耀V9'}]>
>>>
根据id降序排列,降序只要在order_by里面的字段前面加“ - ”即可
order_by可设置多字段排列,如Product.objects.order_by('-id', name)
>>> p = Product.objects.order_by('-id')
>>> p
<QuerySet [<Product: Product object (11)>, <Product: Product object (10)>, <Product: Product object (9)>,
...
聚合查询,实现对数据值求和、求平均值等。Django提供annotate和aggregate方法实现
annotate类似于SQL的group by方法,如果不设置values,就会默认对主键进行group by 分组
等同于SQL语句 Select name,SUM(id) AS 'id_sum' from index_product GROUP BY name ORDER BY NULL
>>> from django.db.models import Sum, Count
>>> p = Product.objects.values('name').annotate(Sum('id'))
>>> p
<QuerySet [{'name': '荣耀V10', 'id__sum': Decimal('1')}, {'name': 'HUAWEI nova 2s', 'id__sum': Decimal('2')}, {'name': '荣耀Waterplay', 'id__sum': Decimal('3')}, {'name': '荣耀畅玩平板', 'id__sum': Decimal('4')}, {'name': 'PORSCHE DESIGN', 'id__sum': Decimal('5')}, {'name': '华为运动手环', 'id__sum': Decimal('6')}, {'name': '荣耀移动电源10000mAh', 'id__sum': Decimal('7')}, {'name': '荣耀体脂秤
', 'id__sum': Decimal('8')}, {'name': '华为荣耀V9', 'id__sum': Decimal('30')}]>
aggregate是将某个字段的值进行计算并只返回计算结果
等同于Select COUNT(id) as 'id_count' from index_product
>>> from django.db.models import Count
>>> p = Product.objects.aggregate(id_count=Count('id'))
>>> p
{'id_count': 11}
其他匹配符
上述代码在查询时主要使用等值的方法来匹配结果,如果想使用大于、不等于和模糊查询的匹配方法,则可以使用以下匹配符实现。
匹配符 | 使用 | 说明 |
---|---|---|
__exact | filter(name__exact='荣耀') | 精确等于,如同SQL的 like '荣耀' |
__iexact | filter(name__iexact='荣耀') | 精确等于并忽略大小写 |
__contains | filter(name__contains='荣耀') | 模糊匹配,同SQL的 like '%荣耀%' |
__icontains | filter(name__icontains='荣耀') | 模糊匹配,忽略大小写 |
__gt | filter(id__gt=5) | 大于 |
__gte | filter(id__gte=5) | 大于等于 |
__lt | filter(id__lt=5) | 小于 |
__lte | filter(id__lte=5) | 小于等于 |
__in | filter(id__in=[1,2,3]) | 判断是否在列表内 |
__startswith | filter(name__startswith='荣耀') | 以...开头 |
__istartswith | filter(name__istartswith='荣耀') | 以...开头,并忽略大小写 |
__endswith | filter(name__endswith='荣耀') | 以...结尾 |
__iendswith | filter(name__iendswith='荣耀') | 以...结尾,并忽略大小写 |
__range | fitler(name__range='荣耀') | 在...范围内 |
__year | filter(date__year=2018) | 日期字段的年份 |
__month | filter(date__month=12) | 日期字段的月份 |
__day | filter(date__day=30) | 日期字段的天数 |
__isnull | filter(name__isnull=True/False) | 判断是否为空 |
举个例子:
查询 id>9 的数据
>>> p = Product.objects.filter(id__gt=9)
>>> p
<QuerySet [<Product: Product object (10)>, <Product: Product object (11)>]>
>>>
多表查询
以上仅仅局限在单个数据表的操作,在日常开发中,常常需要对多个数据表同时进行数据查询。多个数据表查询需要数据表之间建立了表关系才得以实现。
一对一或一对多的表关系是通过外键实现关联的,而多表查询分为正向查询和反向查询。
以模型Type和Product为例:
- 如果查询对象的主体是模型Type,要查询模型Type的数据,那么该查询称为正向查询
- 如果查询的主体是模型Type,要通过模型Type查询模型Product的数据,那么该查询称为反向查询。
>>> from index.models import *
>>> t = Type.objects.filter(product__id=11)
正向查询
>>> t
<QuerySet [<Type: Type object (1)>]>
>>> t[0].type_name
'手机'
反向查询
>>> t[0].product_set.values('name')
<QuerySet [{'name': '荣耀V10'}, {'name': 'HUAWEI nova 2s'}, {'name': '华为荣耀V9'}, {'name': '华为荣耀V9'}, {'name': '华为荣耀V9'}]>
正向查询的查询对象主体和查询的数据都来自于模型Type,因此正向查询在数据库中只执行一次SQL查询。而反向查询先查询模型Type的数据,然后根据第一次查询的结果再查询与模型Product相互关联的数据。
为了减少反向查询的查询次数,可以使用select_related
方法实现。
select_related的使用说明如下:
- 模型A或B均可作为查询对象主体,只要两者之间有外键关联即可。
- select_related的参数值是查询主体模型定义的字段,如Product.objects.select_related('type'),type是模型Product的字段。
- 如果在查询中需要使用另一个数据表的字段,可以使用“外键__字段名”来指向该表的字段。如type__type_name代表由模型Product的外键type指向模型Type的字段type_name。
查询模型Product的字段name和模型Type的字段type_name
相当于SQL的 SELECT `name`, `type_name` FROM `index_product` INNER JOIN `index_type` ON (`type_id` = `id`)
>>> p = Product.objects.select_related('type').values('name', 'type__type_name')
>>> p
<QuerySet [{'name': '荣耀V10', 'type__type_name': '手机'}, {'name': 'HUAWEI nova 2s', 'type__type_name': '手机'}, {'name': '华为荣耀V9', 'type__type_name': '手机'}, {'name': '华为荣耀V9', 'type__type_name': '手机'}, {'name': '华为荣耀V9', 'type__type_name': '手机'}, {'name': '荣耀Waterplay', 'type__type_name': '平板电脑'}, {'name': '荣耀畅玩平板', 'type__type_name': '平板电脑'}, {'name': 'PORSCHE DESIGN', 'type__type_name': '智能穿戴'}, {'name': '华为运动手环', 'type__type_name': '智能穿戴'}, {'name': '荣耀移动电源10000mAh', 'type__type_name': '通用配件'}, {'name': '荣耀体脂秤', 'type__type_name': '通用配件'}]>
查询两个模型的全部数据
相当于SQL的 SELECT * FROM `index_product` INNER JOIN `index_type` ON (`type_id` = `id`)
>>> p = Product.objects.select_related('type').all()
>>> p
<QuerySet [<Product: Product object (1)>, <Product: Product object (2)>, <Product: Product object (9)>, <Product: Product object (10)>, <Product: Product object (11)>, <Product: Product object (3)>, <Product: Product object (4)>, <Product: Product object (5)>, <Product: Product object (6)>, <Product: Product object (7)>, <Product: Product object (8)>]>
获取两个模型的数据,以模型Product的id大于8为查询条件
相当于SQL的 SELECT * FROM `index_product` INNER JOIN `index_type` ON (`type_id` = `id`) WHERE `index_product`.`id` > 8
>>> p = Product.objects.select_related('type').filter(id__gt=8)
>>> p
<QuerySet [<Product: Product object (9)>, <Product: Product object (10)>, <Product: Product object (11)>]>
获取两个模型的数据,以模型Type的type_name字段等于手机为查询条件
相当于 SQL 的 SELECT * FROM `index_product` INNER JOIN `index_type` ON (`type_id` = `id`) WHERE `index_type`.`type_name` = 手机
>>> p =Product.objects.select_related('type').filter(type__type_name='手机').all()
>>> p
<QuerySet [<Product: Product object (1)>, <Product: Product object (2)>, <Product: Product object (9)>, <Product: Product object (10)>, <Product: Product object (11)>]>
>>> p[0]
<Product: Product object (1)>
>>> p[0].type
<Type: Type object (1)>
>>> p[0].type.type_name
'手机'
除此之外,select_related还可以支持三个或三个以上的数据表同时查询,以常见的学校-班级-学生为例。
from django.db import models
#学校
class Schools(models.Model):
sid = models.AutoField('序号',primary_key=True)
sname = models.CharField('校名', max_length=50)
#班级
class Classes(models.Model):
cid = models.AutoField('序号', primary_key=True)
cname = models.CharField('班名', max_length=10)
sid = models.ForeignKey(Schools, on_delete=models.CASCADE)
#学生
class Students(models.Model):
pid = models.AutoField('序号', primary_key=True)
pname = models.CharField('姓名', max_length=10)
cid = models.ForeignKey(Classes, on_delete=models.CASCADE)
上面的模型中,学生表通过外键cid关联到班级表,班级表通过外键sid关联到学校表。执行完迁移建好表后,我们向数据表中添加如下数据:
多表查询主要情况有两种,一种是从大往小查,即从学校查到学生;一种是从小往大查,即从学生查到学校。
第一种情况:如,我们要查苏州健雄学校下的所有学生。
>>> from school.models import Schools,Classes,Students
>>> p = Students.objects.select_related('cid__sid').filter(cid__sid__sname='苏州健雄')
>>> p
<QuerySet [<Students: Students object (1)>, <Students: Students object (2)>, <Students: Students object (3)>]>
>>> for u in p:
... u.pname
...
'小刚'
'小强'
'小明'
我们查到了苏州健雄学校下的3个学生,大家可以对照一下上表,是正确的。
第二种情况:我们要查小芳所在的学校。
>>> p = Students.objects.select_related('cid__sid').get(pname='小芳')
>>> p.cid.sid.sname
'苏州大学'
或者用filter查询,注意与get取值的差别,filter查询结果是列表
>>> p = Students.objects.select_related('cid__sid').filter(pname='小芳')
>>> p[0].cid.sid.sname
'苏州大学'
通过上述例子可以发现,