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 = >>> m1.sex = >>> 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=1\G; *************************** 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 datetimeimport randomimport MySQLdb occupations = ['web', 'server', 'ops', 'security', 'teacher', 'ui', 'product', 'leader']cities = ['beijing', 'guangzhou', 'shenzhen', 'shanghai', 'wuhan']def gen_phone_num():phone_num = 18for i in range():phone_num += str(random.randint(, ))return phone_num conn = MySQLdb.connect(host='180.76.152.113', port=, user='store', passwd='store.123@', db='django_manual')conn.autocommit(True)data = (('spyinx-%d' % i, \ random.randint(, ), \ random.randint(, ), \ occupations[random.randint(, len(occupations) - )], \ gen_phone_num(), \ '22%d@qq.com' % i, \ cities[random.randint(, len(cities) - )], \ datetime.now().strftime(%Y-%m-%d %H:%M:%S)) for i in range())try:cursor = conn.cursor()cursor.executemany('insert into member(`name`, `age`, `sex`, `occupation`, `phone_num`, `email`, `city`, `register_date`) values (%s, %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=1\G; *************************** 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, >>>>> Member.objects.all().count()>>> 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()>>> Member.objects.all().reverse().first()<Member: <spyinx, >>
此外,在 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, >>, <Member: <spyinx, >>, <Member: <spyinx, >>, <Member: <spyinx, >>, <Member: <spyinx, >>, <Member: <spyinx, >>, <Member: <spyinx, >>, <Member: <spyinx, >>, <Member: <spyinx, >>, <Member: <spyinx, >>, <Member: <spyinx, >>]>>>> Member.objects.all().filter(name__contains='spyinx-2').filter(id__lt=, id__gt=)<QuerySet [<Member: <spyinx, >>, <Member: <spyinx, >>, <Member: <spyinx, >>, <Member: <spyinx, >>]>
这种双下划线的过滤字段有:
contains/icontains:过滤字段的值包含某个字符串的结果;
in:和 SQL 语句中的 in 类似,过滤字段的值在某个列表内的结果,比如:
>>> Member.objects.all().filter(name__contains='spyinx-2').filter(id__in=[, ])<QuerySet [<Member: <spyinx, >>, <Member: <spyinx, >>]>
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=).filter(name__endswith='2')<QuerySet [<Member: <spyinx, >>, <Member: <spyinx, >>, <Member: <spyinx, >>]>
F查询和Q查询
前面我们构造的过滤器都只是将字段值与某个常量做比较。如果我们要对两个字段的值做比较,就需要使用 Django 提供 F() 来做这样的比较。F() 的实例可以在查询中引用字段,来比较同一个 model 实例中两个不同字段的值:
>>> from django.db.models import Q# 找出id值大于age*4的记录>>> Member.objects.all().filter(id__gt=F('age')*)<QuerySet [<Member: <spyinx, >>, <Member: <spyinx, >>, <Member: <spyinx, >>, <Member: <spyinx, >>, <Member: <spyinx, >>, <Member: <spyinx, >>]># 将所有记录中age字段的值加1>>> Member.objects.all().update(age=F('age')+)
此外,前面的多个 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, >>, <Member: <spyinx, >>]># 过滤条件的 AND 操作>>> Member.objects.all().filter(Q(name__contains='spyinx-2') & Q(name__endswith='2'))<QuerySet [<Member: <spyinx, >>, <Member: <spyinx, >>]>
对于记录的更新和删除操作,我们同样有对应的 update() 方法以及 delete() 方法:
# 删除name=spyinx的记录>>> Member.objects.all().filter(Q(name='spyinx')).delete()(, {'hello_app.Member': })>>> Member.objects.all().count()# 所有记录的年龄字段加1>>> Member.objects.all().update(age=F('age')+)
2. Django 自定义管理器
前面我们提到 objects 是一个特殊的属性, 它是模型的一个 Manager。接下来我们操作下如何自定义 Manager 以及自定义查询方法。我们在 hello_app 应用目录下的 models.py 文件中添加一个 MemberManager 类:
# hello_app/models.pyfrom django.db import models# Create your models here.class MemberManager(models.Manager):def middle_age(self, age=):return self.filter(age__gt=age)class Member(models.Model):sex_choices = ((, '男'),(, '女'),)name = models.CharField('姓名', max_length=)age = models.CharField('年龄', max_length=)sex = models.SmallIntegerField('申请状态', choices=sex_choices, default=)...# 使用新的 Managerobjects = MemberManager()...
这样子,我们来使用下这个新增的方法,如下:
(django-manual) [root@server first_django_app]# python manage.py shellPython . (default, Dec , ::) [GCC . (Red Hat .-)] on linux Type help, copyright, credits or license for more information.(InteractiveConsole)>>> from hello_app.models import Member# 筛选出年龄大于40的记录>>> Member.objects.middle_age()<QuerySet [<Member: <spyinx, >>, <Member: <spyinx, >>, <Member: <spyinx, >>, <Member: <spyinx, >>, <Member: <spyinx, >>, <Member: <spyinx, >>, <Member: <spyinx, >>, <Member: <spyinx, >>, <Member: <spyinx, >>, <Member: <spyinx, >>, <Member: <spyinx, >>, <Member: <spyinx, >>, <Member: <spyinx, >>]>
此外,我们也可以对这个管理器进行命名和重写 Manager 中查询的 QuerySet:
from django.db import modelsclass MemberManager(models.Manager):def get_queryset(self):return super(MemberManager, self).get_queryset().filter(name__contains='spyinx-2')def middle_age(self, age=):return self.filter(age__gt=age)class Member(models.Model):sex_choices = ((, '男'),(, '女'),)name = models.CharField('姓名', max_length=)age = models.CharField('年龄', max_length=)...objects = models.Manager()# 自定义Managercustom_objects = MemberManager()...
此时模型有两个 Manager, 一个是 objects,另一个是我们自定义的 custom_objects。使用如下:
>>> from hello_app.models import Member>>> Member.objects.all().count()>>> Member.custom_objects.all().count()>>>
这里也可以看到,我们自定义的 get_queryset()
方法也生效了。
3. 小结
本节中我们介绍了 Django 内嵌 ORM 模型的增删改查操作,主要是针对查询操作介绍了各种过滤方式,包括 F 查询和 Q 查询。接下来还介绍了如何自定义管理器,对经常使用的查询进一步封装,进一步释放重复代码。