Django ORM 模型基本增删改查操作

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 查询。接下来还介绍了如何自定义管理器,对经常使用的查询进一步封装,进一步释放重复代码。