18 Django ORM 模型的基本增删改查操作
本小节将介绍 Django 的 ORM 模型中对表的增删改查操作,主要针对的是 MySQL 数据库,且操作的表是前面创建的 Member 表。所有的操作将在 Django 的 shell 模式下进行,只需要在 settings.py
中配置好对应的数据库信息即可。
1. Django ORM 模型的增删改查操作
话不多说,直接进入 django 的交互命令模式:
[root@server ~]# pyenv activate django-manual
pyenv-virtualenv: prompt changing will be removed from future release. configure `export PYENV_VIRTUALENV_DISABLE_PROMPT=1' to simulate the behavior.
(django-manual) [root@server ~]# cd django-manual/first_django_app/
(django-manual) [root@server first_django_app]# python manage.py shell
Python 3.8.1 (default, Dec 24 2019, 17:04:00)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>>
前面在 hello_app 应用目录下的 models.py 中定义了 Member 模型类,导入进来。然后我们实例化 Member 类,并给实例的属性赋值,最后调用模型类的 save() 方法,将该实例保存到表中:
>>> from hello_app.models import Member
>>> from hello_app.models import Member
>>> m1 = Member()
>>> m1.name = 'spyinx'
>>> m1.age = 29
>>> m1.sex = 0
>>> m1.occupation = "程序员"
>>> m1.phone_num = '18054293763'
>>> m1.city = 'guangzhou'
>>> m1.save()
通过 mysql 客户端可以查看该保存的记录,如下:
[root@server first_django_app]# mysql -u store -pstore.123@ -h 180.76.152.113 -P 9002
Welcome to the MariaDB monitor. Commands end with ; or g.
Your MySQL connection id is 73555
Server version: 5.7.26 MySQL Community Server (GPL)
Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or 'h' for help. Type 'c' to clear the current input statement.
MySQL [(none)]> use django_manual
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
MySQL [django_manual]> select * from member where 1=1G;
*************************** 1. row ***************************
id: 1
name: spyinx
age: 29
sex: 0
occupation: 程序员
phone_num: 18054293763
email:
city: guangzhou
register_date: 2020-04-05 07:30:45.043377
1 row in set (0.00 sec)
ERROR: No query specified
MySQL [django_manual]>
接下来是查询的操作介绍,为了能更好的演示查询操作,我们通过如下代码在 member 中添加100条记录:
from datetime import datetime
import random
import MySQLdb
occupations = ['web', 'server', 'ops', 'security', 'teacher', 'ui', 'product', 'leader']
cities = ['beijing', 'guangzhou', 'shenzhen', 'shanghai', 'wuhan']
def gen_phone_num():
phone_num = "18"
for i in range(9):
phone_num += str(random.randint(0, 9))
return phone_num
conn = MySQLdb.connect(host='180.76.152.113', port=9002, user='store', passwd='store.123@', db='django_manual')
conn.autocommit(True)
data = (('spyinx-%d' % i,
random.randint(20, 40),
random.randint(0, 1),
occupations[random.randint(0, len(occupations) - 1)],
gen_phone_num(),
'22%d@qq.com' % i,
cities[random.randint(0, len(cities) - 1)],
datetime.now().strftime("%Y-%m-%d %H:%M:%S")) for i in range(100))
try:
cursor = conn.cursor()
cursor.executemany('insert into member(`name`, `age`, `sex`, `occupation`, `phone_num`, `email`, `city`, `register_date`) VALUES (NULL, %s, %s, %s, %s, %s, %s, %s);', data)
print('批量插入完成')
except Exception as e:
print('插入异常,执行回滚动作: {}'.format(str(e)))
conn.rollback()
finally:
if conn:
conn.close()
执行 python 代码后,我们通过 mysql 客户端确认100条数据已经成功插入到数据库中:
MySQL [django_manual]> select count(*) from member where 1=1G;
*************************** 1. row ***************************
count(*): 101
1 row in set (0.00 sec)
我们执行如下操作:
>>> type(Member.objects)
<class 'django.db.models.manager.Manager'>
>>> Member.objects.get(name='spyinx')
<Member: <spyinx, 18054293763>>
>>> Member.objects.all().count()
101
>>> type(Member.objects.all())
<class 'django.db.models.query.QuerySet'>
上面的语句中 Member.objects.get(name='spyinx')
中,objects 是一个特殊的属性,通过它来查询数据库,它是模型的一个 Manager。首先来看看这个 Manager 类提供的常用方法:
- all():查询所有结果,返回的类型为 QuerySet 实例;
- filter(**kwargs):根据条件过滤查询结果,返回的类型为 QuerySet 实例;
- get(**kwargs):返回与所给筛选条件相匹配的记录,只返回一个结果。如果符合筛选条件的记录超过一个或者没有都会抛出错误,返回的类型为模型对象实例;
- exclude(**kwargs):和 filter() 方法正好相反,筛选出不匹配的结果,返回的类型为 QuerySet 实例;
- values(*args):返回一个ValueQuerySet,一个特殊的QuerySet,运行后得到的并不是一系列 model 的实例化对象,而是一个可迭代的字典序列;
- values_list(*args):它与 values() 类似,只不过 values_list() 返回的是一个元组序列,而 values() 返回的是一个字典序列;
- order_by(*args):对结果按照传入的字段进行排序,返回的类型为 QuerySet 实例;
- reverse():对查询结果反向排序,返回的类型为 QuerySet 实例;
- distinct():去掉查询结果中重复的部分,返回的类型为 QuerySet 实例;
- count():返回数据库中匹配查询的记录数,返回类型为 int;
- first():返回第一条记录,结果为模型对象实例;;
- last():返回最后一条记录,结果为模型对象实例;
- exists():如果 QuerySet 包含数据,就返回 True,否则返回 False。
如果上述这些方法的返回结果是一个 QuerySet 实例,那么它也同样具有上面这些方法,因此可以继续调用,形成链式调用,示例如下:
>>> Member.objects.all().count()
101
>>> Member.objects.all().reverse().first()
<Member: <spyinx-99, 18022422977>>
此外,在 filter() 方法中还有一些比较神奇的双下划线辅助我们进一步过滤结果:
MySQL [django_manual]> select id, name, phone_num from member where name like 'spyinx-2%';
+----+-----------+-------------+
| id | name | phone_num |
+----+-----------+-------------+
| 24 | spyinx-2 | 18627420378 |
| 42 | spyinx-20 | 18687483216 |
| 43 | spyinx-21 | 18338528387 |
| 44 | spyinx-22 | 18702966393 |
| 45 | spyinx-23 | 18386787195 |
| 46 | spyinx-24 | 18003292724 |
| 47 | spyinx-25 | 18160946579 |
| 48 | spyinx-26 | 18517339819 |
| 49 | spyinx-27 | 18575613014 |
| 50 | spyinx-28 | 18869175798 |
| 51 | spyinx-29 | 18603950130 |
+----+-----------+-------------+
11 rows in set (0.00 sec)
>>> Member.objects.all().filter(name__contains='spyinx-2')
<QuerySet [<Member: <spyinx-2, 18627420378>>, <Member: <spyinx-20, 18687483216>>, <Member: <spyinx-21, 18338528387>>, <Member: <spyinx-22, 18702966393>>, <Member: <spyinx-23, 18386787195>>, <Member: <spyinx-24, 18003292724>>, <Member: <spyinx-25, 18160946579>>, <Member: <spyinx-26, 18517339819>>, <Member: <spyinx-27, 18575613014>>, <Member: <spyinx-28, 18869175798>>, <Member: <spyinx-29, 18603950130>>]>
>>> Member.objects.all().filter(name__contains='spyinx-2').filter(id__lt=47, id__gt=42)
<QuerySet [<Member: <spyinx-21, 18338528387>>, <Member: <spyinx-22, 18702966393>>, <Member: <spyinx-23, 18386787195>>, <Member: <spyinx-24, 18003292724>>]>
这种双下划线的过滤字段有:
contains/icontains:过滤字段的值包含某个字符串的结果;
in:和 SQL 语句中的 in 类似,过滤字段的值在某个列表内的结果,比如:
>>> Member.objects.all().filter(name__contains='spyinx-2').filter(id__in=[42, 43]) <QuerySet [<Member: <spyinx-20, 18687483216>>, <Member: <spyinx-21, 18338528387>>]>
lt/gt:过滤字段值小于或者大于某个值的结果;
range:过滤字段值在某个范围内的结果;
>>> Member.objects.all().filter(name__contains='spyinx-2').filter(id__range=[48, 50]) <QuerySet [<Member: <spyinx-26, 18517339819>>, <Member: <spyinx-27, 18575613014>>, <Member: <spyinx-28, 18869175798>>]>
startswith/istartswith:匹配字段的值以某个字符串开始,前面的 i 标识是否区分大小写;
endswith/iendswiths:匹配字段的值以某个字符串结束;
>>> Member.objects.all().filter(id__gt=90).filter(name__endswith='2') <QuerySet [<Member: <spyinx-72, 18749521006>>, <Member: <spyinx-82, 18970386795>>, <Member: <spyinx-92, 18324708274>>]>
F查询和Q查询
前面我们构造的过滤器都只是将字段值与某个常量做比较。如果我们要对两个字段的值做比较,就需要使用 Django 提供 F() 来做这样的比较。F() 的实例可以在查询中引用字段,来比较同一个 model 实例中两个不同字段的值:
>>> from django.db.models import Q
# 找出id值大于age*4的记录
>>> Member.objects.all().filter(id__gt=F('age')*4)
<QuerySet [<Member: <spyinx-70, 18918359267>>, <Member: <spyinx-77, 18393464230>>, <Member: <spyinx-90, 18272147421>>, <Member: <spyinx-91, 18756265752>>, <Member: <spyinx-92, 18324708274>>, <Member: <spyinx-97, 18154031313>>]>
# 将所有记录中age字段的值加1
>>> Member.objects.all().update(age=F('age')+1)
101
此外,前面的多个 filter() 方法实现的是过滤条件的 “AND” 操作,如果想实现过滤条件 “OR” 操作呢,就需要使用到 Django 为我们提供的 Q() 方法:
>>> from django.db.models import Q
# 过滤条件的 OR 操作
>>> Member.objects.all().filter(Q(name='spyinx-22') | Q(name='spyinx-11'))
<QuerySet [<Member: <spyinx-11, 18919885274>>, <Member: <spyinx-22, 18702966393>>]>
# 过滤条件的 AND 操作
>>> Member.objects.all().filter(Q(name__contains='spyinx-2') & Q(name__endswith='2'))
<QuerySet [<Member: <spyinx-2, 18627420378>>, <Member: <spyinx-22, 18702966393>>]>
对于记录的更新和删除操作,我们同样有对应的 update() 方法以及 delete() 方法:
# 删除name=spyinx的记录
>>> Member.objects.all().filter(Q(name='spyinx')).delete()
(1, {'hello_app.Member': 1})
>>> Member.objects.all().count()
100
# 所有记录的年龄字段加1
>>> Member.objects.all().update(age=F('age')+1)
101
2. Django 自定义管理器
前面我们提到 objects 是一个特殊的属性, 它是模型的一个 Manager。接下来我们操作下如何自定义 Manager 以及自定义查询方法。我们在 hello_app 应用目录下的 models.py 文件中添加一个 MemberManager 类:
# hello_app/models.py
from django.db import models
# Create your models here.
class MemberManager(models.Manager):
def middle_age(self, age=30):
return self.filter(age__gt=age)
class Member(models.Model):
sex_choices = (
(0, '男'),
(1, '女'),
)
name = models.CharField('姓名', max_length=30)
age = models.CharField('年龄', max_length=30)
sex = models.SmallIntegerField('申请状态', choices=sex_choices, default=0)
...
# 使用新的 Manager
objects = MemberManager()
...
这样子,我们来使用下这个新增的方法,如下:
(django-manual) [root@server first_django_app]# python manage.py shell
Python 3.8.1 (default, Dec 24 2019, 17:04:00)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linux
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from hello_app.models import Member
# 筛选出年龄大于40的记录
>>> Member.objects.middle_age(40)
<QuerySet [<Member: <spyinx-0, 18103841793>>, <Member: <spyinx-3, 18586383292>>, <Member: <spyinx-4, 18492437671>>, <Member: <spyinx-17, 18681960581>>, <Member: <spyinx-19, 18211435798>>, <Member: <spyinx-25, 18160946579>>, <Member: <spyinx-31, 18880660482>>, <Member: <spyinx-47, 18337991495>>, <Member: <spyinx-48, 18191766331>>, <Member: <spyinx-51, 18213698092>>, <Member: <spyinx-55, 18093331199>>, <Member: <spyinx-67, 18100571566>>, <Member: <spyinx-87, 18053563269>>]>
此外,我们也可以对这个管理器进行命名和重写 Manager 中查询的 QuerySet:
from django.db import models
class MemberManager(models.Manager):
def get_queryset(self):
return super(MemberManager, self).get_queryset().filter(name__contains='spyinx-2')
def middle_age(self, age=30):
return self.filter(age__gt=age)
class Member(models.Model):
sex_choices = (
(0, '男'),
(1, '女'),
)
name = models.CharField('姓名', max_length=30)
age = models.CharField('年龄', max_length=30)
...
objects = models.Manager()
# 自定义Manager
custom_objects = MemberManager()
...
此时模型有两个 Manager, 一个是 objects,另一个是我们自定义的 custom_objects。使用如下:
>>> from hello_app.models import Member
>>> Member.objects.all().count()
100
>>> Member.custom_objects.all().count()
11
>>>
这里也可以看到,我们自定义的 get_queryset()
方法也生效了。
3. 小结
本节中我们介绍了 Django 内嵌 ORM 模型的增删改查操作,主要是针对查询操作介绍了各种过滤方式,包括 F 查询和 Q 查询。接下来还介绍了如何自定义管理器,对经常使用的查询进一步封装,进一步释放重复代码。