使用Python
的
Django
模型的话,一般都会用它自带的
ORM
(
Object-relational mapping
)模型。这个
ORM
模型的设计比较简单,学起来不会特别花时间。不过,
Django
的
ORM
模型有自己的一套语法,有时候会觉得别扭。这里聊一下我自己的体会,希望对大家
学习django有所帮助
。
模型设计
这一部分算处理得比较好的部分。Django
的数据模型的建立过程很简单,就是继承
django.db.models
中的
Model
类,然后给它增加属性。每一个属性可以对应关系数据库中的一个字段。比如在一个叫
myapp
的
Django App
下,创建
models.py
文件:
from django.db
import models
class Person(
models.Model):
name = models.CharField(
max_length=10)
通过manage.py
的
makemigrations
和
migrate
命令,就可以执行数据库的迁移。上面的
name
属性,就对应了生成的
myapp_person
表中名为
"name"
的一列。这里的
max_length=10
对应了限制条件:
VARCHAR(10)
(在MySQL V4
中,代表了
10
个字节;在
MySQL V5
中,代表了
10
个字符。)
除了上面的字符类型,其他常见的字段类型,在Django
都有对应的
*Field
来表达,比如
TextField
、
DateField
、
DateTimeField
、
IntegerField
、
DecimalField
。此外,还有一些常见的限制条件,除了上面的
max_length
,还有
default
、
unique
、
null
、
primary_key
等等。数字类型的限制条件有
max
、
min
、
max_digits
、
decimal_places
。这些限制条件都通过参数的形式传给属性。有一些限制条件是
Django
提供的,并没有数据库层面的对应物,比如
blank
。
(
当
blank
参数为真时,对应字段可以为留为空白。
)
在基本的模型设计上,Django ORM
没有留什么坑。
关系
Django
中的一对一、多对一、多对多关系可以通过下面方式表达:
from django.db
import models
class Company(
models.Model):
name = models.CharField(
max_length=10)
class Group(
models.Model):
name = models.CharField(
max_length=10)
class Person(
models.Model):
name = models.CharField(
max_length=10)
class Customer(
models.Model):
name = models.CharField(
max_length=10)
person = models.OneToOneField(Person)
company = models.ForeignKey(Company,
on_delete=
models.CASCADE)
groups = models.ManyToManyField(Group)
Customer
的定义中,用到一对一、多对一、多对多关系。它们分别通过
OneToOneField
、
ForeignKey
和
ManyToManyField
来实现。
需要注意的是,在Django ORM
中,只能通过
ForeignKey
来定义多对一关系,不能显示地定义一对多关系。但你可以使用模型对象的
*_set
语法来反向调用多对一关系。比如说:
company.customer_set #company
是一个
Company
的实例
就可以根据一对多关系,调到该公司下的所有客户。此外,多对多关系也可以用类似的方式反向调用,比如:
group.customer_set
此外,你还可以在模型中加入related_name
参数,从而在反省调用时,改用
"*_set"
之外的其他名称,比如:
class
Customer(models.Model):
person = models.OneToOneField(Person)
address = models.CharField(max_length=100)
company = models.ForeignKey(Company, on_delete=models.CASCADE, related_name="customers")
如果两个模型之间有多个关系时,related_name
可以防止
*_set
重名。
总的来说,上面的解决方案可以实现功能,并不影响使用。但我总是觉得这个解决方案有些丑陋。由于不能显式地表达两个模型之间的关系,模型之间的关系看起来不够明了。特别是读代码时,第一个类定义完全没法提示一对多的关系。我必须要看到了第二个类定义,才能搞明白两个模型之间的关系。真希望有一种显式说明关系的办法,降低读代码时的认知负担。
查询
Django ORM
可以通过一些方法来实现。其中的很多方法返回的是
Django
自定义的
QuerySet
类的迭代器。
Python
看到迭代器时会懒惰求值,所以这些方法返回时并不会真正进行数据库操作。这样,多个方法串联操作时,就避免了重复操作数据库。返回
QuerySet
的常见方法包括:
all()
filter()
exclude()
annotate()
order_by()
reverse()
distinct()
...
对于依赖具体数据的操作,QuerySet
会求值。比如遍历
QuerySet
时,就会先执行数据库操作。用
len()
获得
QuerySet
长度时,也会造成
QuerySet
估值。此外
QuerySet
一些方法,比
get()
、
count()
、
earlist()
、
exists()
等,都会对
QuerySet
进行求值。因此,在写程序时,要注意
QuerySet
求值的时间点,避免重复的数据库操作。
SQL
的
WHERE
条件可以通过参数的形式来传给方法。这些参数一般是
"[
字段
]__[
运算符
]"
的命名方式,比如:
Customer.objects.filter(name__contains="abc")
除了contains
,还有
in
、
gt
、
lt
、
startswith
、
date
、
range
等等操作符,能实现的
WHERE
条件确实够全的了。
不过,这又是一个有点别扭的地方,即通过命名方式来控制查询行为。我看过有的ORM
是用
lambda
的形式来表达
WHERE
条件,还有的会做一个类似于
contains()
的方法,都要比
Django ORM
的方式好看。如果是跨表查询,
Django
的方式就更丑了:
Customer.objects.filter(company__name__contains="xxx")
无限的双下划线啊……
聚合
Django
实现聚合的方式简直是噩梦。貌似
ORM
对表达
GROUP BY
很无力,源代码里的注释就认输了:
聚合的aggregate()
和
annotate()
方法可以实现基本的功能,但稍微复杂一点,代码就变得魔幻了:
看到一大串values()
、
annotate()
变来变去,有没有觉得头晕?我觉得这种情况下,可以直接上原始的
SQL
查询语句了,没必要再自己折腾自己。
F表达式和Q表达式
F
表达式指代了一列,对于
update
操作时引用列的值有用。
Q
表达式代表了
WHERE
的一个条件,可以用于多个
WHERE
条件的连接。这些都是
Django ORM
用来弥补缺陷的。就拿
Q
表达式来说。查询方法中跟多个参数的话,相当于多个
WHERE
条件。这些条件会默认为
AND
关系。为了表达
OR
和
NOT
关系,
Django ORM
就造了个
Q
表达式,比如:
filter(
Q(
name__contains="abc")|Q(
name__startswith("xxx")))
为了弥补缺陷,Django ORM
又增加了一种语法风格。于是,学习路上又多了一个坑
……
总结
总的来说,Django ORM
在实现基础的数据库操作方面没问题。但如果需要构建复杂的
SQL
语句,与其在
Django ORM
里绕来绕去,还不如直接用原始的
SQL
语句。这个是我最强烈的一个感受。
来源:
博客园