(7)Django-模型与数据库

Django对各种数据库都提供了很好的支持,而且为这些数据库提供了统一的调用API,这些API统称为ORM框架。通过使用Django内置的ORM框架可以实现数据库连接和读写操作。


构建模型

ORM框架是一种程序技术,用于实现面向对象编程语言中不同类型系统的数据之间的转换。从效果上说,其实是创建了一个可在编程语言中使用的“虚拟对象数据库”,通过对虚拟对象数据库操作,从而实现对目标数据库的操作,虚拟对象数据库与目标数据库是相互对应的。在Django中,虚拟对象数据库也称为模型,通过模型实现对目标数据库的读写操作。实现方法如下:

  1. 需要在项目的settings.py中设置数据库信息,具体参考前面的章节
  2. 构建虚拟对象数据库,在App的models.py文件中以类的形式定义模型。
  3. 通过模型在目标数据库中创建相应的数据表。
  4. 在视图函数中通过对模型操作实现目标数据库的读写操作。
    以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
'苏州大学'

通过上述例子可以发现,

我们通过设置select_related的参数值即可实现三个或三个以上的多表查询。上面例子中的参数值为‘cid__sid’,注意引号不能少。cid是模型Students的外键,指向模型Classes,sid是模型Classes的外键,指向模型School。两个外键之间通过双下划线连接且两个字段都指向另一个模型。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352

推荐阅读更多精彩内容