object relation mapping: 对象关系映射,在ORM框架中,它帮我们把类和数据表进行了一个映射,可以让我们通过类和类对象就能够操作它所对应的表格中的数据.ORM框架还有一个功能,它可以根据我们设计的类自动帮我们生成数据库中的表格,省去了自己建表的过程.
django中内嵌了ORM框架,不需要直接面向数据库编程,而是定义模型类,通过模型类和对象完成数据表的增删改查操作.
使用django进行数据库开发的步骤如下:
在django项目创建成功之后,setting.py文件中自动配置了数据的内容, 配置的是sqlite3数据库:
DATABASES = {
'default': {
# 默认的配置, 使用的是sqlite3数据库.
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
这里使用的是MySQL数据库,所以在进行以下修改修改:
第一步: 安装MySQl数据库驱动
pip install PyMySQL
第二步:进行转换,让pymysql的内容转换为mysqldb,以便于ORM能够匹配
# 复制下面的内容,拷贝到django工程同名子目录的init.py文件中
from pymysql import install_as_MySQLdb
install_as_MySQLdb()
作用:让Django的Orm能以mysqldb的方式来调用PyMySQL
第三步:对数据库进行配置:修改数据库的ip地址,端口号,用户名等内容
# 将以下内容覆盖到原来setting.py文件中
DATEBASES = {
'default': {
# 我们这里需要把sqlite3修改为mysql
'ENGINE': 'django.db.backends.mysql',
'HOST': '127.0.0.1', # 数据库主机
'PORT': 3306, # 数据库端口
'USER': 'root', # 数据库用户名
'PASSWORD': 'mysql', # 数据库用户密码
'NAME': 'django_demo' # 数据库名字
}
}
第四步:修改完成后,就可以创建数据库定义模型类了
mysql -u root -p
create database django_demo charset=utf8;
show databases;
这里定义模型类和Flask类似,但是需要注意:
from django.db import models
接下来首先以"图书-英雄"管理为例进行演示
- 创建子应用koobset
- 在子应用的models.py文件中定义模型类
第一张表:
# 从django,db中导入models
from django.db import models
# 定义图书模型BookInfo
# 注意:一定要继承自model.Model
class BookInfo(model.Models):
"""
定义图书模型类
这里定义的属性对应的都是数据库中的字段
"""
btitle = models.CharField(max_length=20, verbose_name='名称')
bpub_date = models.DateField(verbose_name='发布日期')
bread = models.IntegerField(default=0, verbose_name='阅读量')
bcomment = models.IntegerField(default=0, verbose_name='评论量')
is_delete = models.BooleanField(default=False, verbose_name='逻辑删除')
# 该类用于指定数据库中的表名.
class Meta:
db_table = 'tb_books' # 指明数据库表名
verbose_name = '图书' # 在admin站点中显示的名称
verbose_name_plural = verbose_name # 显示的复数名称
def __str__(self):
"""定义每个数据对象的显示信息"""
return self.btitle
说明:
第二张表:
from django.db import models
#定义英雄模型类HeroInfo
class HeroInfo(models.Model):
# 这个是元组是一个可选项,元组中也是元组, 其中:
# 0, 1: 是在数据库中真实存在的值
# 意味着hgender这个字段在数据库中存储的要么是0,要么是1
# male, female: 用于显示在admin站点中
# 类似于: verbose_name
GENDER_CHOICES = (
(0, 'male'),
(1, 'female')
)
hname = models.CharField(max_length=20, verbose_name='名称')
# 性别我们使用的是类似于枚举类型来做的:
# choices: 可选范围,传入的是个元组类型,在上面定义的.
hgender = models.SmallIntegerField(choices=GENDER_CHOICES, default=0, verbose_name='性别')
hcomment = models.CharField(max_length=200, null=True, verbose_name='描述信息')
# 利用ForeignKey这个属性指定hbook这个字段为外键.
# 第一个参数: 表示外键关联到书籍表
# 注意: 如果调用外键hbook的话: 返回的是book这个模型对象.
hbook = models.ForeignKey(BookInfo, on_delete=models.CASCADE, verbose_name='图书') # 外键
is_delete = models.BooleanField(default=False, verbose_name='逻辑删除')
class Meta:
db_table = 'tb_heros'
verbose_name = '英雄'
verbose_name_plural = verbose_name
def __str__(self):
return self.hname
说明:
模型类如果未指明表名,Django默认以 小写app应用名_小写模型类名 为数据库表名。
可通过db_table 指明数据库表名。
django会为表创建自动增长的主键列,每个模型只能有一个主键列,如果使用选项设置某属性为主键列后django不会再创建自动增长的主键列。
默认创建的主键列属性为id,可以使用pk代替,pk全拼为primary key。
不能是python的保留关键字。
不允许使用连续的下划线,这是由django的查询方式决定的。
定义属性时需要指定字段类型,通过字段类型的参数指定选项,语法如下:
属性名=models.字段类型(选项)
类型 | 说明 |
---|---|
AutoField | 自动增长的IntegerField,通常不用指定,不指定时Django会自动创建属性名为id的自动增长属性 |
BooleanField | 布尔字段,值为True或False |
NullBooleanField | 支持Null、True、False三种值 |
CharField | 字符串,参数max_length表示最大字符个数 |
TextField | 大文本字段,一般超过4000个字符时使用 |
IntegerField | 整数 |
DecimalField | 十进制浮点数, 参数max_digits表示总位数, 参数decimal_places表示小数位数 |
FloatField | 浮点数 |
DateField | 日期, 参数auto_now表示每次保存对象时,自动设置该字段为当前时间,用于"最后一次修改"的时间戳,它总是使用当前日期,默认为False; 参数auto_now_add表示当对象第一次被创建时自动设置当前时间,用于创建的时间戳,它总是使用当前日期,默认为False; 参数auto_now_add和auto_now是相互排斥的,组合将会发生错误 |
TimeField | 时间,参数同DateField |
DateTimeField | 日期时间,参数同DateField |
FileField | 上传文件字段 |
ImageField | 继承于FileField,对上传的内容进行校验,确保是有效的图片 |
选项 | 说明 |
---|---|
null | 如果为True,表示允许为空,默认值是False (判断是否允许在数据库系统中字段为空) |
blank | 如果为True,则该字段允许为空白,默认值是False, 这个和django中的表无关,和表单有关,django自带有表单系统,有时候用户提交的 |
db_column | 字段的名称,如果未指定,则使用属性的名称 |
db_index | 若值为True, 则在表中会为此字段创建索引,默认值是False |
default | 默认 |
primary_key | 若为True,则该字段会成为模型的主键字段,默认值是False,一般作为AutoField的选项使用 |
unique | 如果为True, 这个字段在表中必须有唯一值,默认值是False |
null是数据库范畴的概念,blank是表单验证范畴的
在设置外键时,需要通过on_delete选项指明主表删除数据时,对于外键引用表数据如何处理,在django.db.models中包含了可选项:
选项 | 说明 |
---|---|
CASCADE | 级联,删除主表数据时同时一起删除外键表中数据, 级联删除。 |
PROTECT | 保护模式,如果采用该选项,删除的时候,会抛出ProtectedError错误。阻止用户的删除. |
SET_NULL | 置空模式,删除的时候,外键字段被设置为空,仅在该字段null=True: 允许为null时可用. |
SET_DEFAULT | 置默认值,删除的时候,外键字段设置为默认值,所以定义外键的时候注意加上一个默认值。 |
DO_NOTHING | 不做任何操作,如果数据库前置指明级联性,此选项会抛出IntegrityError异常 |
SET() | 自定义一个值,该值只能是对应的实体 |
例如:SET()选项
# 当删除mymodel对应的user的时候,mymodel不会删除掉,而是找到一个名叫 deleted的user,与之重建关联
from django.conf import settings
from django.contrib.auth import get_user_model
from django.db import models
def get_sentinel_user():
return get_user_model().objects.get_or_create(username='deleted')[0]
class MyModel(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.SET(get_sentinel_user),
)
将模型类同步到数据库中.
python manage.py makemigrations
python manage.py migrate
insert into tb_books(btitle,bpub_date,bread,bcomment,is_delete) values
('射雕英雄传','1980-5-1',12,34,0),
('天龙八部','1986-7-24',36,40,0),
('笑傲江湖','1995-12-24',20,80,0),
('雪山飞狐','1987-11-11',58,24,0);
insert into tb_heros(hname,hgender,hbook_id,hcomment,is_delete) values
('郭靖',1,1,'降龙十八掌',0),
('黄蓉',0,1,'打狗棍法',0),
('黄药师',1,1,'弹指神通',0),
('欧阳锋',1,1,'蛤蟆功',0),
('梅超风',0,1,'九阴白骨爪',0),
('乔峰',1,2,'降龙十八掌',0),
('段誉',1,2,'六脉神剑',0),
('虚竹',1,2,'天山六阳掌',0),
('王语嫣',0,2,'神仙姐姐',0),
('令狐冲',1,3,'独孤九剑',0),
('任盈盈',0,3,'弹琴',0),
('岳不群',1,3,'华山剑法',0),
('东方不败',0,3,'葵花宝典',0),
('胡斐',1,4,'胡家刀法',0),
('苗若兰',0,4,'黄衣',0),
('程灵素',0,4,'医术',0),
('袁紫衣',0,4,'六合拳',0);
Django的manage提供了sheel命令工具,帮助我们配置好当前工程的运行环境(如连接数据库等),以便可以直接在终端中执行测试python语句。
通过如下命令进入shell:
python manage.py shell
from booktest.models import BookInfo, HeroInfo
查看mysql数据库日志可以查看对数据库的操作记录。 mysql日志文件默认没有产生,需要做如下配置:
sudo vi /etc/mysql/mysql.conf.d/mysqld.cnf
把68,69行前面的#去除,然后保存并使用如下命令重启mysql服务。
sudo service mysql restart
使用如下命令打开mysql日志文件。
# 可以实时查看数据库的日志
tail -f /var/log/mysql/mysql.log
日志文件路径的查看可以参考:
链接
增加数据有两种方法:
1.创建对象添加数据,然后嗲用save()保存
2.通过模型类的create()函数保存
通过创建模型类对象,执行对象的save()方法保存到数据中.
> from datetime import date
> book = BookInfo(
btitle="西游记",
bpub_date = date(1988,1,1),
bread = 10,
bcomment=10
)
> book.save()
> hero = HeroInfo(
hname = "孙悟空",
hgender = 0,
hbook = book
)
> hero.save()
> hero2 = HeroInfo(
hname='猪八戒',
hgender=0,
hbook_id=book.id
)
> hero2.save()
通过模型类.objects.create()保存
> HeroInfo.objects.create(
hname = "沙悟净",
hgender = 0,
hbook = book
)
<HeroInfo:沙悟净>
基本查询有三种形式:
- get: 查询单一结果,如果不存在会抛出模型类.DoesNotExist异常。
- all: 查询多个结果。
- count: 查询结果数量。
例如:
> BookInfo.objects.all()
<QuerySet [<BookInfo: 射雕英雄传>, <BookInfo: 天龙八部>, <BookInfo: 笑傲江湖>, <BookInfo: 雪山飞狐>, <BookInfo: 西游记>]>
> book = BookInfo.objects.get(btitle='西游记')
> book.id
5
> BookInfo.objects.get(id=3)
<BookInfo: 笑傲江湖>
> BookInfo.objects.get(pk=3)
<BookInfo: 雪山飞狐>
> BookInfo.objects.get(id=100)
Traceback (most recent call last):
File "" , line 1, in <module>
File "/Users/delron/.virtualenv/dj/lib/python3.6/site-packages/django/db/models/manager.py", line 85, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
File "/Users/delron/.virtualenv/dj/lib/python3.6/site-packages/django/db/models/query.py", line 380, in get
self.model._meta.object_name
db.models.DoesNotExist: BookInfo matching query does not exist.
> BookInfo.objects.count()
实现SQL中的where功能,包括:
属性名称和比较运算符间使用两个下划线,所以属性名不能包括多个下划线
属性名__比较运算符=值
exact:表示判等
例: 查询编号为1的图书
BookInfo.objects.filter(id__exact=1)
# 也可以简写为:
BookInfi.objects.filter(id=1)
contains:是否包含
说明:如果要包含%无需转义, 直接写即可
例: 查询书名中包含"传"的图书:
BookInfo.objects.filter(btitle__contains = "传")
startswith、endswith: 以指定值开头或结尾
例: 查询书名以"部"结尾的图书
BookInfo.objects.filter(btitle__endswith = "部")
以上运算符都区分大小写,在这些运算符前加上i表示不区分大小写,如iexact、icontains、istartswith、iendswith.
isnull: 是否为null
例: 查询书名不为空的图书
BookInfo.objects.filter(btitle__isnull = False)
in: 是否包含在范围内.
例:查询编号为1或3或5的图书
BookInfo.objects.filter(id__in = [1,3,5])
运算符 | 说明 |
---|---|
gt | 大于 (greater then) |
gte | 大于等于 (greater then equal) |
lt | 小于 (less then) |
lte | 小于等于 (less then equal) |
例: 查询编号大于3的图书
BookInfo.objectsfilter(id__gt=3)
不等于的运算符,使用exclude()过滤器
例:查询编号不等于3的图书
BookInfo.objects.exclude(id=3)
year、month、day、week_day、hour、minute、second:对日期时间类型的属性进行运算。
例:查询1980年发表的图书。
BookInfo.objects.filter(bpub_date__year=1980)
例:查询1980年1月1日后发表的图书。
BookInfo.objects.filter(bpub_date__gt=date(1990, 1, 1))
比较两个属性的值使用F对象。F对象被定义在django.db.models中
语法如下:
F(属性名)
例如: 查询阅读量大于等于评论量的图书
from django.db.models import F
BookInfo.objects.filter(bread__gte = F('bcomment'))
``
可以在F对象上使用算术运算.
例: 查询阅读量大于2倍的评论量的图书
```python
BookInfo.objects.filter(bread__gt=F('bcomment')*2)
Q对象:
一般我们在Django程序中查询数据库操作都是在QuerySe中进行的, 例如下代码:
>>> q1 = BookInfo.objects.filter(headline__startswith="What")
>>> q2 = q1.exclude(pub_date__gte=datetime.date.today())
>>> q3 = q1.filter(pub_date__gte=datetime.date.today())
或者将其组合起来 ,如下:
>>>q1 = BookInfo.objects.filter(headline_startswith="What").exclude(pub_date_gte=datetime.date.today())
随着我们的程序越来越复杂,查询的条件也跟着复杂起来,这样简单的通过一个filter()来进行查询的条件将导致我们的查询越来越长。
Q( )对象就是为了将这些条件组合起来。
我们在查询的条件中需要组合条件时(例如两个条件“且”或者“或”)时。我们可以使用Q( )查询对象。
例如下面的代码:
# 导入Q对象
from django.db.models imports Q
# 使用Q对象查询:
q=Q(question_startswith="What")
这样就生成了一个Q()对象
我们可以使用符号 & 或者 | 将多个Q( )对象组合起来传递给filter(),exclude(),get()等函数。
当多个Q( )对象组合起来时,Django会自动生成一个新的Q( )。
例如下面代码就将两个条件组合成了一个
Q(question__startswith = 'Who') | Q(question__startswith = 'What')
使用上述代码可以使用SQL语句这么理解:
WHERE question LIKE 'Who%' OR question LIKE 'What%'
另外:
我们可以在Q()对象的前边使用字符"~“来代表"非”, 例如:
Q(question__startswith="Who") | ~Q(pub_date__year=2005)
对应的SQL语句理解为:
WHERE question like "Who%" OR year(pub_date) !=2005
总结
Q对象被义在django.db.models中
使用时,需要导入:
from django.db.models import Q
语法如下:
Q(属性名__运算符=值)
Q对象可以使用 &、| 连接
& 表示逻辑与
| 表示逻辑或。
例1:查询阅读量大于20,或编号小于3的图书:
BookInfo.objects.filter(Q(bread__gt=20) | Q(pk__lt=3))
例2:
BookInfo.objects.get(
Q(question__startswith='Who'),
Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))
)
上面的代码可以理解为:
SELECT * from BookInfo WHERE question LIKE 'Who%' AND (pub_date = '2005-05-02' OR pub_date = '2005-05-06')
Q对象前可以使用~操作符,表示非not。
例:查询编号不等于3的图书。
BookInfo.objects.filter(~Q(pk=3))
聚合函数
使用 aggregate( ) 过滤器调用聚合函数。
函数 | 说明 |
---|---|
Avg | 平均 |
Count | 数量 |
Max | 最大 |
Min | 最小 |
Sum | 求和 |
被定义在django.db.models中,引入:
from django.db.models import Sum, Avg, Count, Max, Min
例:
查询图书的总阅读量:
# 导入:
from django.db.models import Sum
# 查询图书的阅读总量
BookInfo.objects.aggregate(Sum('bread'))
注意:aggregate的返回值是一个字典类型
格式如下:
{'属性名':值}
# 如:
{'bread__sum':30}
使用count时一般不适用aggregate()过滤器
例: 查询图书的行书
# 使用count时直接调用即可
BookInfo.objects.count()
注意count函数的返回值是一个数字。
使用order_by对结果进行排序
# 依照阅读量的个数: 从少到多依次排序
# 升序
BookInfo.objects.all().order_by('bread')
# 降序
BookInfo.objects.all().order_by('-bread')
一对多的访问方式:
一对应的是: 模型类对象.
多对应的是: 模型类名小写_set
使用方式:
变量 = 模型类.objects.get(属性=某一个)
变量.关联类_set.条件
例如:
# 获取id=1的书(射雕英雄传)所对应的英雄人物:
b = BookInfo.objects.get(id=1)
b.heroinfo_set.all()
结果:
<QuerySet [<HeroInfo: 郭靖>, <HeroInfo: 黄蓉>, <HeroInfo: 黄药师>, <HeroInfo: 欧阳锋>, <HeroInfo: 梅超风>]>
由多到一的访问方式:
多对应的是: 模型类对象
一对应的是: 模型类中的关系类属性名
# 获取id=1的英雄们(射雕英雄传中的), 所在的书籍名称:
h = HeroInfo.objects.get(id=1)
h.hbook
结果:
<BookInfo: 射雕英雄传>
获取: 一个对象对应的模型类所关联对象的id语法:
多对应的模型类对象.关联类属性_id
例:
# 获取id=1的英雄人物(郭靖), 所对应书籍的id值(主键):
h = HeroInfo.objects.get(id=1)
h.hbook_id
结果:
1
关联过滤查询
第一种:
由多模型类条件查询一模型类数据:
多模型 =====> 一模型
语法如下:
关联模型类名小写__属性名__条件运算符=值
注意:
其中: __条件运算符 部分,可以没有.
如果: __条件运算符 部分没有,则表示等于( = )。
这里的都是双下划线, 而不是单下划线, 否则报错.
例如 :
查询图书,要求图书英雄为"孙悟空"
# 查询图书,要求图书英雄为"孙悟空"
BookInfo.objects.filter(heroinfo__hname='孙悟空')
结果:
<QuerySet [<BookInfo: 西游记>]>
查询图书,要求图书中英雄的描述包含"八"
# 查询图书,要求图书中英雄的描述包含"八"
BookInfo.objects.filter(heroinfo__hcomment__contains='八')
结果:
<QuerySet [<BookInfo: 射雕英雄传>, <BookInfo: 天龙八部>]>
第二种:
由一模型类条件查询多模型类数据:
语法如下:
一模型类关联属性名__一模型类属性名__条件运算符=值
注意:如果没有"__运算符"部分,表示等于。
例如 :
查询书名为“天龙八部”的所有英雄。
# 查询书名为“天龙八部”的所有英雄
HeroInfo.objects.filter(hbook__btitle='天龙八部')
结果:
<QuerySet [<HeroInfo: 乔峰>, <HeroInfo: 段誉>, <HeroInfo: 虚竹>, <HeroInfo: 王语嫣>]>
查询图书阅读量大于30的所有英雄
# 查询图书阅读量大于30的所有英雄
HeroInfo.objects.filter(hbook__bread__gt=30)
结果:
<QuerySet [<HeroInfo: 乔峰>, <HeroInfo: 段誉>, <HeroInfo: 虚竹>, <HeroInfo: 王语嫣>, <HeroInfo: 胡斐>, <HeroInfo: 苗若兰>, <HeroInfo: 程灵素>, <HeroInfo: 袁紫衣>]>
修改更新有两种方法:
save
update
修改模型类对象的属性,然后执行save()方法
hero = HeroInfo.objects.get(hname='猪八戒')
hero.hname = '猪悟能'
hero.save()
使用模型类.objects.filter().update(),会返回受影响的行数
HeroInfo.objects.filter(hname='沙悟净').update(hname='沙僧')
删除有两种方法
hero = HeroInfo.objects.get(id=13)
hero.delete()
HeroInfo.objects.filter(id=14).delete()
Django的ORM中存在查询集的概念。
查询集,也称查询结果集、QuerySet,表示从数据库中获取的对象集合。
当调用如下过滤器方法时,Django会返回查询集(而不是简单的列表):
BookInfo.objects.filter(bread__gt=30).order_by('bpub_date')
也就意味着查询集可以含有零个、一个或多个过滤器。过滤器基于所给的参数限制查询的结果。
从SQL的角度讲,查询集与select语句等价,过滤器像where、limit、order by子句。
判断某一个查询集中是否有数据:
创建查询集不会访问数据库,直到调用数据时,才会访问数据库,调用数据的情况包括迭代、序列化、与if合用
例如,当执行如下语句时,并未进行数据库查询,只是创建了一个查询集qs
qs = BookInfo.objects.all()
继续执行遍历迭代操作后,才真正的进行了数据库的查询
for book in qs:
print(book.btitle)
```
### 2.2 缓存
使用同一个查询集,第一次使用时会发生数据库的查询,然后Django会把结果缓存下来,再次使用这个查询集时会使用缓存的数据,减少了数据库的查询次数。
**情况一**:如下是两个查询集,无法重用缓存,每次查询都会与数据库进行一次交互,增加了数据库的负载。
```python
from booktest.models import BookInfo
[book.id for book in BookInfo.objects.all()]
[book.id for book in BookInfo.objects.all()]
情况二:经过存储后,可以重用查询集,第二次使用缓存中的数据。
qs=BookInfo.objects.all()
[book.id for book in qs]
[book.id for book in qs]
可以对查询集进行取下标或切片操作,等同于sql中的limit和offset子句。
注意:不支持负数索引。
对查询集进行切片后返回一个新的查询集,不会立即执行查询。
如果获取一个对象,直接使用[0],等同于[0:1].get(),但是如果没有数据,[0]引发IndexError异常,[0:1].get()如果没有数据引发DoesNotExist异常。
示例:获取第1、2项,运行查看。
qs = BookInfo.objects.all()[0:2]