Django ORM模型:想说爱你不容易

做者:Vamei 出处:http://www.cnblogs.com/vamei 严禁转载。git

 

使用Python的Django模型的话,通常都会用它自带的ORM(Object-relational mapping)模型。这个ORM模型的设计比较简单,学起来不会特别花时间。不过,Django的ORM模型有本身的一套语法,有时候会以为别扭。这里聊一下我本身的体会。数据库

 

模型设计

这一部分算处理得比较好的部分。Django的数据模型的创建过程很简单,就是继承django.db.models中的Model类,而后给它增长属性。每个属性能够对应关系数据库中的一个字段。好比在一个叫myapp的Django App下,建立models.py文件:django

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对应了限制条件:app

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参数为真时,对应字段能够为留为空白。)spa

 

在基本的模型设计上,Django ORM没有留什么坑。设计

 

关系

Django中的一对1、多对1、多对多关系能够经过下面方式表达:code

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的定义中,用到一对1、多对1、多对多关系。它们分别经过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语句。这个是我最强烈的一个感觉。固然,Django ORM仍是可用的工具。我写这篇文章的目的,是提醒你们不要误把糟糕的设计当作精巧的语法。

相关文章
相关标签/搜索