本文是《Python ORM 三部曲的第二部 - Django ORM 的用法 / 原理 / 优化》
上一部的地址为《Python ORM 三部曲的第一部 - Python ORM 的三种实现模式》
本文基于最新 Django 版本
本文是《Python ORM 三部曲的第一部 - Python 的三种数据源架构模式》
本文适用于:
本文将解决你以下的疑惑:
新工作的技术栈是以 Flask 为主,SQLAlchemy 是 许多玩 Flask 的人的标配。好,文档读起来,笔记搞起来。
所以,本文记录的是 Django ORM
逃。
这篇文章也是我对比 SqlAlchemy 以及 DjangoORM 的产物
Django 世界里面,Django 的文档每次刷都会有新的发现。
from django.db import models
class Musician(models.Model):
first_name = models.CharField(max_length=50) # Field
last_name = models.CharField(max_length=50)
instrument = models.CharField(max_length=100)
class Album(models.Model):
artist = models.ForeignKey(Musician, on_delete=models.CASCADE)
name = models.CharField(max_length=100)
release_date = models.DateField()
num_stars = models.IntegerField()
class Meta: # Model Meta
order_with_respect_to = 'question'
可以看出,包含如下的部分:
DjangoORM 是 ActivityRecord 模式的一种实现,在该模式下,Model 与 session 耦合。
虽然有这么多东东,其实常用的如下
有的字段属于那种,有也可以,没有也可以的。
FileField 之类的 往往现在都被 CDN 取代
如此可见,Django 的 ORM 比起 SQLAlchemy 做了不少应用层的校验,一些 help_text
表和表之间的关系
from django.db import models
class Manufacturer(models.Model):
# ...
pass
class Car(models.Model):
manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)
# ...
from django.db import models
class Topping(models.Model):
# ...
pass
class Pizza(models.Model):
# ...
toppings = models.ManyToManyField(Topping)
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=128)
def __str__(self):
return self.name
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
def __str__(self):
return self.name
class Membership(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)
Models across files
objects
Models 重写方法
Models 继承
https://docs.djangoproject.com/en/2.0/topics/db/models/#model-inheritance
组织代码 - 以及应对循环引用
c = Child(name="苏轼")
c.save()
p = Parent(name="苏辙")
p.best_child = c
p.children.add([c,c2,c3,c4])
p.save()
过滤
filter(**kwargs)
exclude(**kwargs)
.all()
Blog.objects.filter(entryheadlinecontains=‘Lennon’)
https://docs.djangoproject.com/en/2.0/topics/db/queries/#lookups-that-span-relationships
https://docs.djangoproject.com/en/2.0/topics/db/queries/#spanning-multi-valued-relationships
select_related
生成 join 的 SQL, 可以用来减少 N+1 , 不过仅仅支持一对多,和一对一
prefetch_related
Blog.objects.filter(entryheadlinecontains=‘Lennon’)[30:20]
query = query.filter(**kwargs)
query = query.exclude(**kwargs)
Django 里面最强大的就是其 Q 表达式了
Q(questionstartswith=‘Who’) | Q(questionstartswith=‘What’)
Poll.objects.get(
Q(question__startswith=‘Who’),
Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))
)
这个表达式甚至可以嵌套超级深从而完成一个比较深的跨表查询。
并且,这种 API 查询反而在写前端 API 的时候,可以传入 question__startswith 这类参数,从而直接完成一组搜索。
Q / F
需要注意的的是,SQLAlchemy 必须显式执行查询,而 Django 不一定。
https://docs.djangoproject.com/en/2.0/ref/models/querysets/#when-querysets-are-evaluated
在 Django 内部实现的时候,一个 queryset 创建 / 过滤 / 切片 / 传送,除非这个 queryset 被 evaluated 了, 否则不会做数据库的操作。
all()
first()
last()
exist()
blog = Blog(name='My blog', tagline='Blogging is easy')
blog.save() # blog.pk == 1
blog.pk = None
blog.save() # blog.pk == 2
# 但这个并不拷贝外键????
https://docs.djangoproject.com/en/2.0/topics/db/queries/#copying-model-instances
# 默认查询的是所有字段,但我希望查询部分字段
# TODO: 阿萨德
# Distinct
# OrderBy
https://docs.djangoproject.com/en/2.0/topics/db/queries/#caching-and-querysets
单个 object 更新
blog.title = "大宝天天见"
blog.save()
批量更新
query.update(headline=F('blog__name'))
一对多的更新(类似于 Set 操作)
add(obj1, obj2, ...)
create(**kwargs)
remove(obj1, obj2, ...)
clear()
set(objs)
class Car(models.Model):
manufacturer = models.ForeignKey(
'production.Manufacturer', # 用来解决循环 circular import
on_delete=models.CASCADE,
)
on_delete 的情况
https://docs.djangoproject.com/en/2.0/topics/db/aggregation/
Book.objects.all().aggregate(Max('price'))
Book.objects.aggregate(price_diff=Max('price', output_field=FloatField()) - Avg('price'))
Book.objects.annotate(num_authors=Count('authors'))
Book.objects.annotate(num_authors=Count('authors')).aggregate(Avg('num_authors')) # {'num_authors__avg': 1.66}
https://docs.djangoproject.com/en/2.0/topics/db/search/
Option.objects.annotate(
rank_num=Window(
expression=Rank(),
partition_by=F("vote_id"),
order_by=[F("current_vote_count").desc(), F("id").desc()],
),
lag_vote_num=Window(
expression=Lag("current_vote_count"),
partition_by=F("vote_id"),
order_by=[F("current_vote_count").desc(), F("id").desc()],
),
)
.filter(vote=obj.vote)
.order_by("-current_vote_count", "id")
.values("id", "rank_num", "current_vote_count", "lag_vote_num")
https://docs.djangoproject.com/en/2.0/ref/models/instances/
https://docs.djangoproject.com/en/2.0/topics/db/managers/
https://docs.djangoproject.com/en/2.0/topics/db/sql/
https://docs.djangoproject.com/en/2.0/ref/models/database-functions/
连接池是一种永远在线模型的实现
连接池:驱动程序类型
连接池:代理类型
select count(*) from pg_stat_activity where pid <> pg_backend_pid() and usename = current_user;
select count(*) from pg_stat_activity where pid <> pg_backend_pid() and usename = current_user;
手动
queryset.explain
from django.db import connection
connection.queries
from django.db import reset_queries
reset_queries()
obj == nobj # obj.id == nobj.id
>>> entry = Entry.objects.get(id=1)
>>> entry.blog # Blog object is retrieved at this point
>>> entry.blog # cached version, no DB access
>>> entry = Entry.objects.get(id=1)
>>> entry.authors.all() # query performed
>>> entry.authors.all() # query performed again
ChangeLog: