Django ORM Cookbook

Django ORM Cookbook 是一本关于如何使用Django ORM和模型的手册。Django是一个”MTV”(模型-模板-视图)框架,本书提供 M 模型部分的深入探究。

本书由50个”如何使用Django ORM/查询集/模型实现X”形式的问题组成。

原书链接:http://books.agiliq.com/projects/django-orm-cookbook/en/latest/

_images/BookCover.jpg

前言

Django ORM 是Django的重要支柱之一。它提供了一种数据库不可知的的抽象方式来操作数据库。

Django ORM将易用性和强大的抽象性结合到了一起。它保持”让简单事变得容易,让困难事变得可能”的原则。

在这本书中,我们将会通过使用DJango ORM 来学习它。我们会提出50个关于Django ORM的问题,以便我们深入了解Django ORM。

如何阅读这本书

书中的每一章都由问题组成。问题会被分组在相关的主题。你可以通过以下两种方式阅读。

1.如果你正在查找某个特定问题的答案,读那一章和那个主题下的其他章节。

2.如果你想要更深入的了解Django ORM 和模型层,请从头到尾阅读各章。

查询和筛选

如何查看Django ORM查询集的原生SQL?

有时你想要知道Django ORM是怎样执行我们的查询的或是我们所写的orm语句对应的SQL语句是什么。这很容易得到,你可以在queryset.query对象上使用 str 方法得到SQL语句。

有一个模型类Event,你可以通过Event.objects.all()来获取所有的记录,然后再执行 str(queryset.query)

>>> queryset = Event.objects.all()
>>> str(queryset.query)
SELECT "events_event"."id", "events_event"."epic_id",
    "events_event"."details", "events_event"."years_ago"
    FROM "events_event"
_images/sql_query.png

示例 2

>>> queryset = Event.objects.filter(years_ago__gt=5)
>>> str(queryset.query)
SELECT "events_event"."id", "events_event"."epic_id", "events_event"."details",
"events_event"."years_ago" FROM "events_event"
WHERE "events_event"."years_ago" > 5

如何在Django ORM中使用 OR 查询?

_images/usertable.png

如果你正在使用 django.contrib.auth,会发现有一个叫 auth_user 的表。它包含 usernamefirst_namelast_name 等等字段。

常见的需求是在两个或多个条件下执行or筛选查询。比如说你想所有的用户的firstname以’R’开始或last name以’D’开始。

Django 提供了两个选项。

  • queryset_1 | queryset_2
  • filter(Q(<condition_1>)|Q(<condition_2>)

详细查询语句

以上条件下的SQL查询语句如下所示:

SELECT username, first_name, last_name, email FROM auth_user WHERE first_name LIKE 'R%' OR last_name LIKE 'D%';
_images/sqluser_result1.png

相似地我们的orm查询语句会像这样:

queryset = User.objects.filter(
        first_name__startswith='R'
    ) | User.objects.filter(
    last_name__startswith='D'
)
queryset
<QuerySet [<User: Ricky>, <User: Ritesh>, <User: Radha>, <User: Raghu>, <User: rishab>]>

查看下生成的SQL查询语句:

In [5]: str(queryset.query)
Out[5]: 'SELECT "auth_user"."id", "auth_user"."password", "auth_user"."last_login",
"auth_user"."is_superuser", "auth_user"."username", "auth_user"."first_name",
"auth_user"."last_name", "auth_user"."email", "auth_user"."is_staff",
"auth_user"."is_active", "auth_user"."date_joined" FROM "auth_user"
WHERE ("auth_user"."first_name"::text LIKE R% OR "auth_user"."last_name"::text LIKE D%)'

或者,你也可以使用Q对象。

from django.db.models import Q
qs = User.objects.filter(Q(first_name__startswith='R')|Q(last_name__startswith='D'))

检查下生成查询语句,你会发现结果是一致的。

In [9]: str(qs.query)
Out[9]: 'SELECT "auth_user"."id", "auth_user"."password", "auth_user"."last_login",
 "auth_user"."is_superuser", "auth_user"."username", "auth_user"."first_name",
  "auth_user"."last_name", "auth_user"."email", "auth_user"."is_staff",
  "auth_user"."is_active", "auth_user"."date_joined" FROM "auth_user"
  WHERE ("auth_user"."first_name"::text LIKE R% OR "auth_user"."last_name"::text LIKE D%)'

如何在Django ORM中使用 AND 查询?

_images/usertable.png

如果你正在使用 django.contrib.auth,会发现有一个叫 auth_user 的表。它包含 usernamefirst_namelast_name 等等字段。

如果你经常需要执行and操作,以寻找匹配多个条件的查询集

比如说你想所有的用户的firstname以字母’R’开始并且last name是以字母’D’开始。

Django 提供了三个选项

  • filter(<condition_1>, <condition_2>)
  • queryset_1 & queryset_2
  • filter(Q(<condition_1>) & Q(<condition_2>))

详细查询语句

以上条件下的SQL查询语句如下所示:

SELECT username, first_name, last_name, email FROM auth_user WHERE first_name LIKE 'R%' AND last_name LIKE 'D%';
_images/sqluser_result2.png

filter方法中多个条件联合查询的默认方式是AND,所以你可以直接使用。

queryset_1 = User.objects.filter(
    first_name__startswith='R',
    last_name__startswith='D'
)

另外,你可以明确地在查询集上使用`&`操作符。

queryset_2 = User.objects.filter(
    first_name__startswith='R'
) & User.objects.filter(
    last_name__startswith='D'
)

为了完全的定制化,你可以使用:code:`Q`对象。

queryset_3 = User.objects.filter(
    Q(first_name__startswith='R') &
    Q(last_name__startswith='D')
)


queryset_1
<QuerySet [<User: Ricky>, <User: Ritesh>, <User: rishab>]>

你可以查看生成的查询语句并验证他们是否完全一致。

In [10]: str(queryset_2.query)
Out[10]: 'SELECT "auth_user"."id", "auth_user"."password", "auth_user"."last_login", "auth_user"."is_superuser", "auth_user"."username", "auth_user"."first_name", "auth_user"."last_name", "auth_user"."email", "auth_user"."is_staff", "auth_user"."is_active", "auth_user"."date_joined" FROM "auth_user" WHERE ("auth_user"."first_name"::text LIKE R% AND "auth_user"."last_name"::text LIKE D%)'

In [11]: str(queryset_1.query) == str(queryset_2.query) == str(queryset_3.query)
Out[11]: True

如何在Django ORM中使用 NOT 查询?

_images/usertable.png

如果你正在使用 django.contrib.auth,会发现有一个叫 auth_user 的表。它包含 usernamefirst_namelast_name 等等字段。

假如你想要获取所有id不小于5的用户,你需一个NOT操作。

Django 提供了两种选择。

  • exclude(<condition>)
  • filter(~Q(<condition>))

详细查询语句

以上条件下的SQL查询语句如下所示:

SELECT id, username, first_name, last_name, email FROM auth_user WHERE NOT id < 5;
_images/sqluser_notquery.png

方法一:使用exclude()方法

>>> queryset = User.objects.exclude(id__lt=5)
>>> queryset
<QuerySet [<User: Ritesh>, <User: Billy>, <User: Radha>, <User: sohan>, <User: Raghu>, <User: rishab>]>

方法二:使用Q()方法

>>> from django.db.models import Q
>>> queryset = User.objects.filter(~Q(id__lt=5))
>>> queryst
<QuerySet [<User: Ritesh>, <User: Billy>, <User: Radha>, <User: sohan>, <User: Raghu>, <User: rishab>]>

如何从相同或不同的模型类中联合两个查询集结果?

UNION 运算符被用于联合两个或多个查询的结果集。 查询集可以来自相同或不同的模型类。如果查询集来自不同的模型,需要保证字段和数据类型相匹配。

让我们继续使用 auth_user 模型生成两组查询集:

>>> q1 = User.objects.filter(id__gte=5)
>>> q1
<QuerySet [<User: Ritesh>, <User: Billy>, <User: Radha>, <User: sohan>, <User: Raghu>, <User: rishab>]>
>>> q2 = User.objects.filter(id__lte=9)
>>> q2
<QuerySet [<User: yash>, <User: John>, <User: Ricky>, <User: sharukh>, <User: Ritesh>, <User: Billy>, <User: Radha>, <User: sohan>, <User: Raghu>]>
>>> q1.union(q2)
<QuerySet [<User: yash>, <User: John>, <User: Ricky>, <User: sharukh>, <User: Ritesh>, <User: Billy>, <User: Radha>, <User: sohan>, <User: Raghu>, <User: rishab>]>
>>> q2.union(q1)
<QuerySet [<User: yash>, <User: John>, <User: Ricky>, <User: sharukh>, <User: Ritesh>, <User: Billy>, <User: Radha>, <User: sohan>, <User: Raghu>, <User: rishab>]>

现在试试这个:

>>> q3 = EventVillain.objects.all()
>>> q3
<QuerySet [<EventVillain: EventVillain object (1)>]>
>>> q1.union(q3)
django.db.utils.OperationalError: SELECTs to the left and right of UNION do not have the same number of result columns

union操作只能在查询集拥有相同的字段和数据类型前提下执行。因此我们最后一次union操作出现错误。你可以两个models上执行union,只要他们有相同字段或相同字段子集。

由于 HeroVillain 两个都有 namename,我们可以使用 values_list 来限制所选字段,然后执行union操作。

Hero.objects.all().values_list(
    "name", "gender"
).union(
Villain.objects.all().values_list(
    "name", "gender"
))

这会返回给你所有的 HeroVillain 对象的名字和性别。

如何选择同一查询集中的某些字段?

_images/usertable.png

auth_user 模型中有一些字段,但有时,你不需要使用所有的字段,这样的话,我们可以只请求期望的字段。

Django提供了两种方法实现

  • 查询集的 valuesvalues_list 方法
  • only 方法

比如我们想要获取姓名以R开头的所有用户的 first_namelast_name 。为减少数据的工作量,你并不想要获取其他字段。

>>> User.objects.filter(
    first_name__startswith='R'
).values('first_name', 'last_name')
<QuerySet [{'first_name': 'Ricky', 'last_name': 'Dayal'}, {'first_name': 'Ritesh', 'last_name': 'Deshmukh'}, {'first_name': 'Radha', 'last_name': 'George'}, {'first_name': 'Raghu', 'last_name': 'Khan'}, {'first_name': 'Rishabh', 'last_name': 'Deol'}]

你可以使用 str(queryset.query) 验证生成的SQL语句:

SELECT "auth_user"."first_name", "auth_user"."last_name"
FROM "auth_user" WHERE "auth_user"."first_name"::text LIKE R%

输出会是字典元素的列表。

另外,你也可以使用:

>> queryset = User.objects.filter(
    first_name__startswith='R'
).only("first_name", "last_name")

str(queryset.query) 生成的是:

SELECT "auth_user"."id", "auth_user"."first_name", "auth_user"."last_name"
FROM "auth_user" WHERE "auth_user"."first_name"::text LIKE R%

onlyvalues 唯一的不同是 only 还会获取相应的 id

如何在Django中使用子查询(内连接)?

Django 允许使用SQL子查询,我们有一个 UserParent 模型,其中和auth user有一对一的关系。我们要找到所有具有 UserParent 字段的 UserParent

>>> from django.db.models import Subquery
>>> users = User.objects.all()
>>> UserParent.objects.filter(user_id__in=Subquery(users.values('id')))
<QuerySet [<UserParent: UserParent object (2)>, <UserParent: UserParent object (5)>, <UserParent: UserParent object (8)>]>

现在换点更复杂的,我们想要在每一个 Category 找到最勇敢的 Hero

模型类可能如下所示:

class Category(models.Model):
    name = models.CharField(max_length=100)


class Hero(models.Model):
    # ...
    name = models.CharField(max_length=100)
    category = models.ForeignKey(Category, on_delete=models.CASCADE)

    benevolence_factor = models.PositiveSmallIntegerField(
        help_text="How benevolent this hero is?",
        default=50
    )

你用以下方式找到最勇敢的英雄:

hero_qs = Hero.objects.filter(
    category=OuterRef("pk")
).order_by("-benevolence_factor")
Category.objects.all().annotate(
    most_benevolent_hero=Subquery(
        hero_qs.values('name')[:1]
    )
)

如果你查看生成的sql,你会看到:

SELECT "entities_category"."id",
       "entities_category"."name",

  (SELECT U0."name"
   FROM "entities_hero" U0
   WHERE U0."category_id" = ("entities_category"."id")
   ORDER BY U0."benevolence_factor" DESC
   LIMIT 1) AS "most_benevolent_hero"
FROM "entities_category"

我们来分解下请求集的逻辑。第一步是:

hero_qs = Hero.objects.filter(
    category=OuterRef("pk")
).order_by("-benevolence_factor")

我们正在给 Hero 以倒序方式排序,并且使用 category=OuterRef("pk") 来声明我们会在子查询中用到它。

然后我们使用 most_benevolent_hero=Subquery(hero_qs.values('name')[:1]) 以获得 Category 查询集的子查询。 hero_qs.values('name')[:1] 选取了子查询中的一个名称。

如何基于字段值比较标准来筛选字符集?

Django orm 让基于固定值的筛选查询更容易。 要获取所有的 first_name 以字母 'R' 开头的 User 对象, 你可以使用 User.objects.filter(first_name__startswith='R')

那如果你想比较first_name和last name怎么办呢?你可以使用F表达式。首先创建一些users:

In [27]: User.objects.create_user(email="shabda@example.com", username="shabda", first_name="Shabda", last_name="Raaj")
Out[27]: <User: shabda>

In [28]: User.objects.create_user(email="guido@example.com", username="Guido", first_name="Guido", last_name="Guido")
Out[28]: <User: Guido>

现在你可以找到 first_name==last_name 的用户了。

In [29]: User.objects.filter(last_name=F("first_name"))
Out[29]: <QuerySet [<User: Guido>]>

F 表达式也可以使用聚合函数来计算某些字段。如果我们想要first name 和last name字母都相同的用户怎么办?

你可以通过使用 Substr("first_name", 1, 1) 设置首字母:

In [41]: User.objects.create_user(email="guido@example.com", username="Tim", first_name="Tim", last_name="Teters")
Out[41]: <User: Tim>
#...
In [46]: User.objects.annotate(first=Substr("first_name", 1, 1), last=Substr("last_name", 1, 1)).filter(first=F("last"))
Out[46]: <QuerySet [<User: Guido>, <User: Tim>]>

F 表达式也可以和 __gt , __lt 以及其他表达式一起使用。

如何筛选没有任何文件的FileField字段?

FileFieldImageField 存着文件或图片的路径。在数据库层面他们和 CharField 是一样的。

所以我们可以按如下方式查找没有文件的FileField。

no_files_objects = MyModel.objects.filter(
    Q(file='')|Q(file=None)
)

如何在Django ORM中执行JOIN操作?

SQL中的JOIN语句是用来连接两个或多个表中相同字段的数据或行。JOIN可以以多种方式执行。如下所示。

>>> a1 = Article.objects.select_related('reporter') // Using select_related
>>> a1
<QuerySet [<Article: International News>, <Article: Local News>, <Article: Morning news>, <Article: Prime time>, <Article: Test Article>, <Article: Weather Report>]>
>>> print(a1.query)
SELECT "events_article"."id", "events_article"."headline", "events_article"."pub_date", "events_article"."reporter_id", "events_article"."slug", "auth_user"."id", "auth_user"."password", "auth_user"."last_login", "auth_user"."is_superuser", "auth_user"."username", "auth_user"."first_name", "auth_user"."last_name", "auth_user"."email", "auth_user"."is_staff", "auth_user"."is_active", "auth_user"."date_joined" FROM "events_article" INNER JOIN "auth_user" ON ("events_article"."reporter_id" = "auth_user"."id") ORDER BY "events_article"."headline" ASC
>>> a2 = Article.objects.filter(reporter__username='John')
>>> a2
<QuerySet [<Article: International News>, <Article: Local News>, <Article: Prime time>, <Article: Test Article>, <Article: Weather Report>]>
>>> print(a2.query)
SELECT "events_article"."id", "events_article"."headline", "events_article"."pub_date", "events_article"."reporter_id", "events_article"."slug" FROM "events_article" INNER JOIN "auth_user" ON ("events_article"."reporter_id" = "auth_user"."id") WHERE "auth_user"."username" = John ORDER BY "events_article"."headline" ASC

如何使用Django ORM找到排行第二的记录?

你可能会遇到这种情况–你想要根据年龄或薪资找到其中排在第二的用户(从大到小)。

虽然ORM提供了灵活的 first(), last() 查询集接口,但并没有任意第几项的查询接口。你可以使用切片来实现。

_images/usertable.png

我们可以使用切片操作来找到查询中第几个记录。

>>> user = User.objects.order_by('-last_login')[1] // Second Highest record w.r.t 'last_login'
>>> user.first_name
'Raghu'
>>> user = User.objects.order_by('-last_login')[2] // Third Highest record w.r.t 'last_login'
>>> user.first_name
'Sohan'

User.objects.order_by('-last_login')[2] 只是使用 LIMIT ... OFFSET 从数据库中获取所需的对象。如果你查看生成的sql语句,你可以看到类似这样的代码:

SELECT "auth_user"."id",
       "auth_user"."password",
       "auth_user"."last_login",
       "auth_user"."is_superuser",
       "auth_user"."username",
       "auth_user"."first_name",
       "auth_user"."last_name",
       "auth_user"."email",
       "auth_user"."is_staff",
       "auth_user"."is_active",
       "auth_user"."date_joined"
FROM "auth_user"
ORDER BY "auth_user"."last_login" DESC
LIMIT 1
OFFSET 2

如何查找具有重复字段值的列?

_images/usertable2.png

假如也想要所有的用户的 first_name 匹配其他用户。

你可以使用以下技巧来找到重复的记录。

>>> duplicates = User.objects.values(
    'first_name'
    ).annotate(name_count=Count('first_name')).filter(name_count__gt=1)
>>> duplicates
<QuerySet [{'first_name': 'John', 'name_count': 3}]>

如果你想找到所有的记录,你也可以这样:

>>> records = User.objects.filter(first_name__in=[item['first_name'] for item in duplicates])
>>> print([item.id for item in records])
[2, 11, 13]

如何从查询集中找到独一无二的字段值?

_images/usertable2.png

你想要查找名字没有重复的用户,你可以这样做

distinct = User.objects.values(
    'first_name'
).annotate(
    name_count=Count('first_name')
).filter(name_count=1)
records = User.objects.filter(first_name__in=[item['first_name'] for item in distinct])

这个和 User.objects.distinct("first_name").all() 不同, User.objects.distinct("first_name").all() 会获取遇到的不同的first_name时的第一条记录。(注意部分数据库如MySQL,distinct用法是 User.objects.value("first_name").distinct())

如何使用Q对象进行复杂查询?

在之前的章节里我们使用了 Q 对象来做 ORANDNOT 运算。Q 对象给你在where查询上绝对的控制权。

如果你想要对查询条件使用 :code:OR 。

>>> from django.db.models import Q
>>> queryset = User.objects.filter(
    Q(first_name__startswith='R') | Q(last_name__startswith='D')
)
>>> queryset
<QuerySet [<User: Ricky>, <User: Ritesh>, <User: Radha>, <User: Raghu>, <User: rishab>]>

如果你想对你的查询条件使用 AND

>>> queryset = User.objects.filter(
    Q(first_name__startswith='R') & Q(last_name__startswith='D')
)
>>> queryset
<QuerySet [<User: Ricky>, <User: Ritesh>, <User: rishab>]>

如果你想要找到所有 first_name 以R开始且 last_name 不是以Z开始的用户。

>>> queryset = User.objects.filter(
    Q(first_name__startswith='R') & ~Q(last_name__startswith='Z')
)

查看生成的查询语句,你会看到:

SELECT "auth_user"."id",
       "auth_user"."password",
       "auth_user"."last_login",
       "auth_user"."is_superuser",
       "auth_user"."username",
       "auth_user"."first_name",
       "auth_user"."last_name",
       "auth_user"."email",
       "auth_user"."is_staff",
       "auth_user"."is_active",
       "auth_user"."date_joined"
FROM "auth_user"
WHERE ("auth_user"."first_name"::text LIKE R%
       AND NOT ("auth_user"."last_name"::text LIKE Z%))

你可以以更复杂的方式组合Q对象来生成更复杂的查询。

如何在Django ORM中使用聚合查询?

Django ORM中的聚合查询可以通过使用聚合函数完成,如 Max , Min , Avg , Sum 等。Django 查询能帮我们增删改查对象。但有时我们需要获取对象的一些汇总值。我们可以通过以下例子得到:

>>> from django.db.models import Avg, Max, Min, Sum, Count
>>> User.objects.all().aggregate(Avg('id'))
{'id__avg': 7.571428571428571}
>>> User.objects.all().aggregate(Max('id'))
{'id__max': 15}
>>> User.objects.all().aggregate(Min('id'))
{'id__min': 1}
>>> User.objects.all().aggregate(Sum('id'))
{'id__sum': 106}

如何从模型中有效地选取一个随机对象?

category 模型类如下。

class Category(models.Model):
    name = models.CharField(max_length=100)

    class Meta:
        verbose_name_plural = "Categories"

    def __str__(self):
        return self.name

你想要获取一个随机的分类。我们来看下几种替代方法。

最直接的方法是你可以用随机的 order_by 然后获取第一条记录。类似这样:

def get_random():
    return Category.objects.order_by("?").first()

注意:order_by('?') 查询可能会性能很差,这取决于你在使用的数据库后端。

INSERT INTO entities_category
            (name)
(SELECT Md5(Random() :: text) AS descr
 FROM   generate_series(1, 1000000));

你不需要理解以上SQL的所有细节,它创建了一百万个数字并使用 md5-s 来生成名称,然后将其插入到数据库中。

现在,你可以得到最大的id,生成一个范围在[1, max_id]中间的随机数,然后获取到数据而不需要给整张表排序(假设表中没有被删除的记录)。

In [1]: from django.db.models import Max

In [2]: from entities.models import Category

In [3]: import random

In [4]: def get_random2():
   ...:     max_id = Category.objects.all().aggregate(max_id=Max("id"))['max_id']
   ...:     pk = random.randint(1, max_id)
   ...:     return Category.objects.get(pk=pk)
   ...:

In [5]: get_random2()
Out[5]: <Category: e2c3a10d3e9c46788833c4ece2a418e2>

In [6]: get_random2()
Out[6]: <Category: f164ad0c5bc8300b469d1c428a514cc1>

如果你的模型中有删除的纪录,你可稍微调整下函数,循环直到你获取到有效的 Category

In [8]: def get_random3():
   ...:     max_id = Category.objects.all().aggregate(max_id=Max("id"))['max_id']
   ...:     while True:
   ...:         pk = random.randint(1, max_id)
   ...:         category = Category.objects.filter(pk=pk).first()
   ...:         if category:
   ...:             return category
   ...:

In [9]: get_random3()
Out[9]: <Category: 334aa9926bd65dc0f9dd4fc86ce42e75>

In [10]: get_random3()
Out[10]: <Category: 4092762909c2c034e90c3d2eb5a73447>

while True: 循环会迅速返回,除非你的模型有大量的删除。我们使用 timeit 来看看差别。

In [14]: timeit.timeit(get_random3, number=100)
Out[14]: 0.20055226399563253

In [15]: timeit.timeit(get_random, number=100)
Out[15]: 56.92513192095794

get_random3 大概比 get_random 快了283倍。 get_random 是最通用的方式,除非你更改了Django生成id的默认方式–自增整数,或者有过太多的删除记录, get_random3 将会生效。

如何在查询集中使用任意的数据库函数?

Django 有 Lower , CoalesceMax 等函数,但它并不支持所有的数据库函数,尤其是数据库特有的函数。

Django 提供了 Func 函数,它可以允许你任意使用甚至Django没有提供的数据库函数。

Postgres 有个 fuzzystrmatch 模块,它提供了几种确定相似性的函数。使用 :code:`create extension fuzzystrmatch`在你的postgres数据库上安装这个插件。

我们将使用 levenshtein 函数。先让我们创建一些Hero对象吧。

Hero.objects.create(name="Zeus", description="A greek God", benevolence_factor=80, category_id=12, origin_id=1)
Hero.objects.create(name="ZeuX", description="A greek God", benevolence_factor=80, category_id=12, origin_id=1)
Hero.objects.create(name="Xeus", description="A greek God", benevolence_factor=80, category_id=12, origin_id=1)
Hero.objects.create(name="Poseidon", description="A greek God", benevolence_factor=80, category_id=12, origin_id=1)

我们想要找出名字和Zeus相似的 Hero 对象。可以这样做:

from django.db.models import Func, F
Hero.objects.annotate(like_zeus=Func(F('name'), function='levenshtein', template="%(function)s(%(expressions)s, 'Zeus')"))

like_zeus=Func(F('name'), function='levenshtein', template="%(function)s(%(expressions)s, 'Zeus')") 接收两个允许数据库展示的参数,即 functiontemplate。如果你需要重复使用这个函数,你可以和下面一样定义一个类方法。

class LevenshteinLikeZeus(Func):
    function='levenshtein'
    template="%(function)s(%(expressions)s, 'Zeus')"

使用 Hero.objects.annotate(like_zeus=LevenshteinLikeZeus(F("name"))), 然后你就可以像下面这样筛选这个标注字段。

In [16]: Hero.objects.annotate(
    ...:         like_zeus=LevenshteinLikeZeus(F("name"))
    ...:     ).filter(
    ...:         like_zeus__lt=2
    ...:     )
    ...:
Out[16]: <QuerySet [<Hero: Zeus>, <Hero: ZeuX>, <Hero: Xeus>]>

增删改

如何一次创建多个对象?

如果我们想要一次保存多个对象这里有些条件。假如我们想一次添加多个类别并且我们不想过多的请求数据库。我们可以使用 bulk_create 来一次创建多个对象。

示例如下。

>>> Category.objects.all().count()
2
>>> Category.objects.bulk_create(
    [Category(name="God"),
     Category(name="Demi God"),
     Category(name="Mortal")]
)
[<Category: God>, <Category: Demi God>, <Category: Mortal>]
>>> Category.objects.all().count()
5

bulk_create 接收一个未保存对象的列表。

如何复制一个现有的模型对象?

Django中没有内建的方法用于复制模型实例,用所有字段的复制值来创建新的模型类是可行的。

如果将一个实例的 pk 集设置为None保存实例,那这个实例用于在数据库中创建新记录。这意味着除 pk 外的所有字段都被复制了。

In [2]: Hero.objects.all().count()
Out[2]: 4

In [3]: hero = Hero.objects.first()

In [4]: hero.pk = None

In [5]: hero.save()

In [6]: Hero.objects.all().count()
Out[6]: 5

如何确保只有一个对象被创建?

有时你想要确保每个模型只有一条记录被创建。这常被用来做应用程序配置存储,或作为访问共享资源的锁定机制。

让我们转换我们的 Origin 为单例模式。

class Origin(models.Model):
    name = models.CharField(max_length=100)

    def save(self, *args, **kwargs):
        if self.__class__.objects.count():
            self.pk = self.__class__.objects.first().pk
        super().save(*args, **kwargs)

我们做了什么呢?我们重写了 save 方法,并设置 pk 为一个存在的值。这确保了当 create 方法被调用的同时,如果有其他对象存在,就会抛出一个 IntegrityError

如何在保存时更新其他模型中的非规范化字段?

模型类如下所示:

class Category(models.Model):
    name = models.CharField(max_length=100)
    hero_count = models.PositiveIntegerField()
    villain_count = models.PositiveIntegerField()

    class Meta:
        verbose_name_plural = "Categories"


class Hero(models.Model):
    name = models.CharField(max_length=100)
    category = models.ForeignKey(Category, on_delete=models.CASCADE)
    # ...


class Villain(models.Model):
    name = models.CharField(max_length=100)
    category = models.ForeignKey(Category, on_delete=models.CASCADE)
    # ...

当新对象被创建,你需要 hero_countvillain_count 字段被更新。

你可以做如下操作

class Hero(models.Model):
    # ...

    def save(self, *args, **kwargs):
        if not self.pk:
            Category.objects.filter(pk=self.category_id).update(hero_count=F('hero_count')+1)
        super().save(*args, **kwargs)


class Villain(models.Model):
    # ...

    def save(self, *args, **kwargs):
        if not self.pk:
            Category.objects.filter(pk=self.category_id).update(villain_count=F('villain_count')+1)
        super().save(*args, **kwargs)

注意到我们并没有使用 self.category.hero_count += 1 ,因为 update 会做一次数据库更新。

另一个方法是使用`signals`,你可以这样做:

from django.db.models.signals import pre_save
from django.dispatch import receiver

@receiver(pre_save, sender=Hero, dispatch_uid="update_hero_count")
def update_hero_count(sender, **kwargs):
    hero = kwargs['instance']
    if hero.pk:
        Category.objects.filter(pk=hero.category_id).update(hero_count=F('hero_count')+1)

@receiver(pre_save, sender=Villain, dispatch_uid="update_villain_count")
def update_villain_count(sender, **kwargs):
    villain = kwargs['instance']
    if villain.pk:
        Category.objects.filter(pk=villain.category_id).update(villain_count=F('villain_count')+1)

信号 vs 重写 .save

既然 signals.save 都可以被用保存行为,那什么时候你该用哪一个?

  • 如果你的字段依赖模型是由你控制的,使用重写 .save
  • 如果你的字段依赖模型来自第三方应用,你不能控制,使用 signal

如何使用Django ORM执行类似truncate操作?

SQL中truncate语句用于清空一张表为以后使用。 虽然Django并没有提供内建函数来清空一张表,但还是可以使用 delete() 得到类似的结果。

例如:

>>> Category.objects.all().count()
7
>>> Category.objects.all().delete()
(7, {'entity.Category': 7})
>>> Category.objects.all().count()
0

这个语句能起作用,但它使用SQL中 DELETE FROM ... 语句。如果你有大量的数据,这将会非常慢。如果你想要激活 truncate ,你可以给 Category 添加一个 classmethod

class Category(models.Model):
    # ...

    @classmethod
    def truncate(cls):
        with connection.cursor() as cursor:
            cursor.execute('TRUNCATE TABLE "{0}" CASCADE'.format(cls._meta.db_table))

然后你可以调用 Category.truncate() 来实现真正的数据库truncate。

Django在对象创建和更新时,会引发哪些信号?

Django 提供了用于模型对象创建和删除周期的信号钩子。这些Django提供的信号是:

  • pre_init
  • post_init
  • pre_save
  • post_save
  • pre_delete
  • post_delete

在这其中最常用的信号是 pre_savepost_save 。我们来仔细看看他们。

信号 vs 重写.save()

既然信号和重写 .save 有类似的作用,用哪一个是一个时常困惑的问题。这里给出了何时需要用哪一个。

  • 如果你想其他人,如第三方应用,重写或定制对象的 save 行为,你应该抛出你自己singnals。
  • 如果你想要介入一个你无权控制更改的应用中的 save 行为,你应该使用 post_savepre_save 钩子信号。

如果你想定制化你自己的应用的 save 行为,你可以重写 save 方法。

让我们看一个 UserToken 模型的例子。这是一个用于提供身份验证的类,并且应该随 User 的创建而创建。

class UserToken(models.Model):
    token = models.CharField(max_length=64)

    # ...

如何将字符串转化为datetime并存入数据库?

我们可以使用django中的很多方法来转化一个日期字符串并将其存入数据库。部分方法如下。

假设我们有一个日期字符串如“2018-03-11”,我们不能直接将其存入到我们的date字段,因此我们需要使用日期解析器或是python标准库。

>>> user = User.objects.get(id=1)
>>> date_str = "2018-03-11"
>>> from django.utils.dateparse import parse_date // Way 1
>>> temp_date = parse_date(date_str)
>>> a1 = Article(headline="String converted to date", pub_date=temp_date, reporter=user)
>>> a1.save()
>>> a1.pub_date
datetime.date(2018, 3, 11)
>>> from datetime import datetime // Way 2
>>> temp_date = datetime.strptime(date_str, "%Y-%m-%d").date()
>>> a2 = Article(headline="String converted to date way 2", pub_date=temp_date, reporter=user)
>>> a2.save()
>>> a2.pub_date
datetime.date(2018, 3, 11)

排序

如果以升序或降序方式给查询集排序?

给查询集排序可以通过 order_by 方法来实现。我们需要传入待排序(升序或降序)的字段。查询语句如下:

>>> User.objects.all().order_by('date_joined') # For ascending
<QuerySet [<User: yash>, <User: John>, <User: Ricky>, <User: sharukh>, <User: Ritesh>, <User: Billy>, <User: Radha>, <User: Raghu>, <User: rishab>, <User: johny>, <User: paul>, <User: johny1>, <User: alien>]>
>>> User.objects.all().order_by('-date_joined') # For descending; Not '-' sign in order_by method
<QuerySet [<User: alien>, <User: johny1>, <User: paul>, <User: johny>, <User: rishab>, <User: Raghu>, <User: Radha>, <User: Billy>, <User: Ritesh>, <User: sharukh>, <User: Ricky>, <User: John>, <User: yash>]>

你可以传入多个字段给 order_by 方法。

User.objects.all().order_by('date_joined', '-last_login')

查看一下SQL语句:

SELECT "auth_user"."id",
       -- More fields
       "auth_user"."date_joined"
FROM "auth_user"
ORDER BY "auth_user"."date_joined" ASC,
         "auth_user"."last_login" DESC

如何以不区分大小写的方式排序查询集?

_images/usertable2.png

无论何时我们想要尝试通过一些字符串来排序,排序总是通过字母顺序和基于大小写的。例如:

>>> User.objects.all().order_by('username').values_list('username', flat=True)
<QuerySet ['Billy', 'John', 'Radha', 'Raghu', 'Ricky', 'Ritesh', 'johny', 'johny1', 'paul', 'rishab', 'sharukh', 'sohan', 'yash']>

如果我们想要不区分大小写的方式排序。我们可以这样做。

>>> from django.db.models.functions import Lower
>>> User.objects.all().order_by(Lower('username')).values_list('username', flat=True)
<QuerySet ['Billy', 'John', 'johny', 'johny1', 'paul', 'Radha', 'Raghu', 'Ricky', 'rishab', 'Ritesh', 'sharukh', 'sohan', 'yash']>

另外,我们可以用 Lower 注释然后将其排序。

User.objects.annotate(
    uname=Lower('username')
).order_by('uname').values_list('username', flat=True)

如何给两个字段排序?

查询集的 order_by 方法可以接收一个或多个属性名,也就允许你可以给两个或多个字段排序。

In [5]: from django.contrib.auth.models import User

In [6]: User.objects.all().order_by("is_active", "-last_login", "first_name")
Out[6]: <QuerySet [<User: Guido>, <User: shabda>, <User: Tim>]>

如何给关联模型(使用外键)的一个字段排序?

有两个模型类,CategoryHero

class Category(models.Model):
    name = models.CharField(max_length=100)


class Hero(models.Model):
    # ...
    name = models.CharField(max_length=100)
    category = models.ForeignKey(Category, on_delete=models.CASCADE)

你如果想要通过类别和英雄名称来给每个类别下的英雄排序。你可以这样做:

Hero.objects.all().order_by(
    'category__name', 'name'
)

注意 'category__name' 中的双下划线。使用双下划线你可以给关联模型的一个字段排序。

查看SQL如下:

SELECT "entities_hero"."id",
       "entities_hero"."name",
       -- more fields
FROM "entities_hero"
INNER JOIN "entities_category" ON ("entities_hero"."category_id" = "entities_category"."id")
ORDER BY "entities_category"."name" ASC,
         "entities_hero"."name" ASC

如何给注解字段排序?

有两个模型 CategoryHero

class Category(models.Model):
    name = models.CharField(max_length=100)


class Hero(models.Model):
    # ...
    name = models.CharField(max_length=100)
    category = models.ForeignKey(Category, on_delete=models.CASCADE)

如果想要获取按 Hero 数量排序的 Category 。你可以这样做:

Category.objects.annotate(
    hero_count=Count("hero")
).order_by(
    "-hero_count"
)

数据库模型

如何建立一对一的关系模型?

当第一个表中的只有一条记录和关联表中的一条记录想对应时就会产生一对一的关系。

这里我们有一个例子,我们都知道每个人都只能有一个亲生父母,即父亲和母亲。

我们已有了auth用户模型,我们将添加一个新的UserParent模型,如下所示。

from django.contrib.auth.models import User

class UserParent(models.Model):
    user = models.OneToOneField(
        User,
        on_delete=models.CASCADE,
        primary_key=True,
    )
    father_name = models.CharField(max_length=100)
    mother_name = models.CharField(max_length=100)

>>> u1 = User.objects.get(first_name='Ritesh', last_name='Deshmukh')
>>> u2 = User.objects.get(first_name='Sohan', last_name='Upadhyay')
>>> p1 = UserParent(user=u1, father_name='Vilasrao Deshmukh', mother_name='Vaishali Deshmukh')
>>> p1.save()
>>> p1.user.first_name
'Ritesh'
>>> p2 = UserParent(user=u2, father_name='Mr R S Upadhyay', mother_name='Mrs S K Upadhyay')
>>> p2.save()
>>> p2.user.last_name
'Upadhyay'

on_delete 方法被用于告诉Django如何处理依赖你删除模型实例的模型实例(如外键关系)。on_delete=models.CASCADE 告诉Django执行级联删除效果,即,继续也把依赖模型删除。

>>> u2.delete()

并将删除 UserParent 的关联记录。

如何建立一对多的关系模型?

在关系型数据库,当一个表的父级记录引用了多个其他的子记录时, 会产生一对多关系。在这种关系中,父级记录不要求有子记录。因此一对多关系允许零个,单个或多个子记录。

定义一对多的关系,使用 ForeignKey

class Article(models.Model):
    headline = models.CharField(max_length=100)
    pub_date = models.DateField()
    reporter = models.ForeignKey(User, on_delete=models.CASCADE, related_name='reporter')

    def __str__(self):
        return self.headline

    class Meta:
        ordering = ('headline',)

>>> u1 = User(username='johny1', first_name='Johny', last_name='Smith', email='johny@example.com')
>>> u1.save()
>>> u2 = User(username='alien', first_name='Alien', last_name='Mars', email='alien@example.com')
>>> u2.save()
>>> from datetime import date
>>> a1 = Article(headline="This is a test", pub_date=date(2018, 3, 6), reporter=u1)
>>> a1.save()
>>> a1.reporter.id
13
>>> a1.reporter
<User: johny1>

如果你想要在保存对象前使用它,你会得到ValueError错误:

>>> u3 = User(username='someuser', first_name='Some', last_name='User', email='some@example.com')
>>> Article.objects.create(headline="This is a test", pub_date=date(2018, 3, 7), reporter=u1)
Traceback (most recent call last):
...
ValueError: save() prohibited to prevent data loss due to unsaved related object 'reporter'.
>>> Article.objects.create(headline="This is a test", pub_date=date(2018, 3, 7), reporter=u1)
>>> Article.objects.filter(reporter=u1)
<QuerySet [<Article: This is a test>, <Article: This is a test>]>

以上请求集展示了有多篇 Articles 的用户U1,也就是一对多。

如何建立多对多的模型关系?

多对多的模型关系指当一个数据表中的父级行包含第二表中的多个子行且反之亦然的表与表之间的关系。

为了更好的互动性,我们将聊聊twitter应用。仅仅使用一些字段和多对多关系,我们可以生成一个简单的twitter应用。

基本上我们的推特由三个基础构成,推文,粉丝,喜欢/不喜欢。

我们只要两个模型类就能让这一切工作。我们继承了Django的auth_user。

class User(AbstractUser):
    tweet = models.ManyToManyField(Tweet, blank=True)
    follower = models.ManyToManyField(settings.AUTH_USER_MODEL, blank=True)
    pass

class Tweet(models.Model):
    tweet = models.TextField()
    favourite = models.ManyToManyField(settings.AUTH_USER_MODEL, blank=True, related_name='user_favourite')

    def __unicode__(self):
        return self.tweet

以上的模型能做些什么呢?

1)用户可以关注或取关其他用户。

2)用户可以他关注的用户的推文。

3)用户可以喜欢/不喜欢其他人的推文。

使用ManyToManyfield可以完成的部分操作:

我们新建了一些推文和用户,到目前为止还没有涉及任何多对多关系的使用。我们继续在下一步将他们联系起来。

工作实例可以在这个仓库找到: https://github.com/yashrastogi16/simpletwitter

如何在模型中包含自引用外键?

自引用外键被用于模型嵌套关系和递归关系的建立。他们的工作方式和一对多关系类似。但顾名思义,这个模型引用了自身。

自引用外键可以有两种方法实现。

class Employee(models.Model):
    manager = models.ForeignKey('self', on_delete=models.CASCADE)

# 或者

class Employee(models.Model):
    manager = models.ForeignKey("app.Employee", on_delete=models.CASCADE)

如何转化已有的数据表到Django模型?

Django有一个实用工具 inspectdb 可以已存在数据库的内省来生成模型。你可以运行这条命令查看输出:

$ python manage.py inspectdb

在运行这条命令前你需要在 :code:`settings.py`文件中配置好你的数据库连接。每个表的结果会是一个包含表模板的文件。你可能想保存这个文件:

$ python manage.py inspectdb > models.py

输出文件会存入到你当前目录。移动文件至正确应用,你就可以开始更进一步的定制化了。

如何为数据库视图添加模型?

数据库视图是一种由查询定义一种数据库的可搜索对象。虽然视图并不存储数据,有些人将视图视图当做”虚拟表”,你可以和查询表一样查询视图。视图可以使用join联结两个或多个表的数据,而且只包含一部分的信息。这让他们很方便提取或隐藏复杂的查询。

在我们的SqliteStuio我们可以看到26张表并没有视图。

_images/before_view.png

让我们创建一个简单的视图。

create view temp_user as select id, first_name from auth_user;

在视图被创建之后,我们可以看到有26张表和一个视图。

_images/after_view.png

我们可以使用 managed = Falsedb_table="temp_user" 在我们的app中创建与之相关的模型:

class TempUser(models.Model):
    first_name = models.CharField(max_length=100)

    class Meta:
        managed = False
        db_table = "temp_user"

// We can query the newly created view similar to what we do for any table.
>>> TempUser.objects.all().values()
<QuerySet [{'first_name': 'Yash', 'id': 1}, {'first_name': 'John', 'id': 2}, {'first_name': 'Ricky', 'id': 3}, {'first_name': 'Sharukh', 'id': 4}, {'first_name': 'Ritesh', 'id': 5}, {'first_name': 'Billy', 'id': 6}, {'first_name': 'Radha', 'id': 7}, {'first_name': 'Raghu', 'id': 9}, {'first_name': 'Rishabh', 'id': 10}, {'first_name': 'John', 'id': 11}, {'first_name': 'Paul', 'id': 12}, {'first_name': 'Johny', 'id': 13}, {'first_name': 'Alien', 'id': 14}]>
// You cannot insert new reord in a view.
>>> TempUser.objects.create(first_name='Radhika', id=15)
Traceback (most recent call last):
...
django.db.utils.OperationalError: cannot modify temp_user because it is a view

对有联合操作的视图,请参考: http://books.agiliq.com/projects/django-admin-cookbook/en/latest/database_view.html?highlight=view

如何创建可与任何实体相关联的通用模型(如类别或评论)?

有模型如下:

class Category(models.Model):
    name = models.CharField(max_length=100)
    # ...

    class Meta:
        verbose_name_plural = "Categories"


class Hero(models.Model):
    name = models.CharField(max_length=100)
    category = models.ForeignKey(Category, on_delete=models.CASCADE)
    # ...


class Villain(models.Model):
    name = models.CharField(max_length=100)
    category = models.ForeignKey(Category, on_delete=models.CASCADE)
    # ...

可被应用的 Category 是一个通用模型。你可能想要从任何的模型类应用类别到对象。你可以这样做:

from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
# ...

class FlexCategory(models.Model):
    name = models.SlugField()
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')


class Hero(models.Model):
    name = models.CharField(max_length=100)
    flex_category = GenericRelation(FlexCategory, related_query_name='flex_category')
    # ...


class Villain(models.Model):
    name = models.CharField(max_length=100)
    flex_category = GenericRelation(FlexCategory, related_query_name='flex_category')
    # ...

我们使用 ForeignKeyPositiveIntegerFieldFlexCategory 添加了 GenericForeignKey ,然后在你想要类别化的模型上添加了 :code:`GenericRelation 。

在数据库上类似这样:

你可以这样类别化 Hero

FlexCategory.objects.create(content_object=hero, name="mythic")

然后得到名为ghost的类别化后的 Hero.

FlexCategory.objects.create(content_object=hero, name="ghost")

SQL语句如下

SELECT "entities_hero"."name"
FROM "entities_hero"
INNER JOIN "entities_flexcategory" ON ("entities_hero"."id" = "entities_flexcategory"."object_id"
                                       AND ("entities_flexcategory"."content_type_id" = 8))
WHERE "entities_flexcategory"."name" = ghost

如何指定模型的表名?

为了节省你的时间,Django自动从你的模型类和包含它的应用中为你生成了数据表的名字。模型数据表的名字是由模型的应用名(新建应用时的名字)和模型类的名字组成,中间由下划线连接。

我们有两个应用在我们的示例应用里,如 entitiesevents ,所以他们中的所有模型将有如同”app名前缀_模型名”这样的应用名

_images/db_table.png

从新命名我们可以使用 db_table 参数:

class TempUser(models.Model):
    first_name = models.CharField(max_length=100)
    . . .
    class Meta:
        db_table = "temp_user"

如何给模型字段指定列名?

给模型中的列命名可以通过给参数 db_column 传入名字,如果我们没有传入这个参数,Django会通过我们给的字段名来创建行。

class ColumnName(models.Model):
    a = models.CharField(max_length=40,db_column='column1')
    column2 = models.CharField(max_length=50)

    def __str__(self):
        return self.a
_images/db_column.png

如上所示,db_columnfield name 的优先级更高。第一列被命名为collumn1而不是a。

null=Trueblank=True 的区别?

nullblank 的默认值都是 False 。他们两者的值都在字段级别工作,即,我们是否要保持字段null还是blank。

null=True 将会设置字段的值为空,即没有数据。这基本上用于数据库列值。

date = models.DateTimeField(null=True)

blank=True 决定在表格中是否需要该字段。这包含了admin表单和你自己的表单。

title = models.CharField(blank=True) // title can be kept blank. In the database ("") will be stored.

null=True blank=True 这意味着这个字段在所有情况下是可选的。

epic = models.ForeignKey(null=True, blank=True) // The exception is CharFields() and TextFields(), which in Django are never saved as NULL. Blank values are stored in the DB as an empty string (‘’).

但也有个特殊例子,但你需要接受一个 BooleanField 的Null值时,请使用 NullBooleanField

如何使用UUID替代ID作为主键?

不论何时我们创建任何新模型,都会附加着一个ID字段。ID字段的数据类型默认是整型。

为了设置ID字段为uuid,在Django 1.8之后增加了个新字段类型–UUIDField

示例:

import uuid
from django.db import models

class Event(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    details = models.TextField()
    years_ago = models.PositiveIntegerField()

>>> eventobject = Event.objects.all()
>>> eventobject.first().id
'3cd2b4b0c36f43488a93b3bb72029f46'

如何使用Django的slug字段提高代码可读性?

slug 是url的一部分,它以用户可读的形式标识一个网页中的特殊页面。为了能让它工作,Django 为我们提供了slugfield字段。可以和下面一样使用它。我们已经有一个模型 Article ,我们要添加一个slugfield使其具有有用户可读性。

from django.utils.text import slugify
class Article(models.Model):
    headline = models.CharField(max_length=100)
    . . .
    slug = models.SlugField(unique=True)

    def save(self, *args, **kwargs):
        self.slug = slugify(self.headline)
        super(Article, self).save(*args, **kwargs)
    . . .

>>> u1 = User.objects.get(id=1)
>>> from datetime import date
>>> a1 = Article.objects.create(headline="todays market report", pub_date=date(2018, 3, 6), reporter=u1)
>>> a1.save()
// slug here is auto-generated, we haven't created it in the above create method.
>>> a1.slug
'todays-market-report'
Slug字段如此有用是因为:
  • 它是人性化的(如/blog/1 而不是/1/)。
  • 在标题,头部和url中创建一致性是一种不错的SEO。

如果添加多个数据到Django应用?

数据库配置相关的东西基本上在 settings.py 中被完成。所以,要给Django应用添加多个数据库,我们需要把他们添加到 DATABASES 的字典中。

DATABASE_ROUTERS = ['path.to.DemoRouter']
DATABASE_APPS_MAPPING = {'user_data': 'users_db',
                        'customer_data':'customers_db'}

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    },
    'users_db': {
        'NAME': 'user_data',
        'ENGINE': 'django.db.backends.postgresql',
        'USER': 'postgres_user',
        'PASSWORD': 'password'
    },
    'customers_db': {
        'NAME': 'customer_data',
        'ENGINE': 'django.db.backends.mysql',
        'USER': 'mysql_cust',
        'PASSWORD': 'root'
    }
}

对于多个数据库,我们最好聊聊 Database Router 。默认的路由方案如果一个数据库未指明,所有的查询都会返回到默认数据库。 Database Router 默认是 []

class DemoRouter:
    """
    A router to control all database operations on models in the
    user application.
    """
    def db_for_read(self, model, **hints):
        """
        Attempts to read user models go to users_db.
        """
        if model._meta.app_label == 'user_data':
            return 'users_db'
        return None

    def db_for_write(self, model, **hints):
        """
        Attempts to write user models go to users_db.
        """
        if model._meta.app_label == 'user_data':
            return 'users_db'
        return None

    def allow_relation(self, obj1, obj2, **hints):
        """
        Allow relations if a model in the user app is involved.
        """
        if obj1._meta.app_label == 'user_data' or \
           obj2._meta.app_label == 'user_data':
           return True
        return None

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        """
        Make sure the auth app only appears in the 'users_db'
        database.
        """
        if app_label == 'user_data':
            return db == 'users_db'
        return None

各个模型将被修改为:

class User(models.Model):
    username = models.Charfield(ax_length=100)
    . . .
        class Meta:
        app_label = 'user_data'

class Customer(models.Model):
    name = models.TextField(max_length=100)
    . . .
        class Meta:
        app_label = 'customer_data'

使用多个数据库是的有用的命令:

$ ./manage.py migrate --database=users_db

测试

如何断言一个使用了固定查询数量的函数?

我们可以在测试中使用 assertNumQueries() 方法来给查询集计数。

def test_number_of_queries(self):
    User.objects.create(username='testuser1', first_name='Test', last_name='user1')
    # Above ORM create will run only one query.
    self.assertNumQueries(1)
    User.objects.filter(username='testuser').update(username='test1user')
    # One more query added.
    self.assertNumQueries(2)

如何通过在测试运行中重用数据库来加速测试?

当我们执行命令 python manage.py test ,一个新的数据库每次会被创建。如果我们没有太多迁移,这并没有什么问题。 但是当我们有许多迁移时,这将耗费大量的时间在测试运行时来重新创建数据库。为了避免这种清况,我们可以重用久数据库。

你可以通过添加 --keepdb 标识到测试命令后以防止数据库被破坏。这将保存在测试过程中保存测试数据库。如果数据库不存在,它首先会被创建。如果有任何迁移在最后一次测试运行后被添加,为保证同步,这些迁移会被应用。

$ python manage.py test --keepdb

如何从数据库中重载一个模型对象?

通过使用 refresh_from_db() 方法,模型就可以从数据库中被从新加载。这在测试中非常有用。例如:

class TestORM(TestCase):
    def test_update_result(self):
        userobject = User.objects.create(username='testuser', first_name='Test', last_name='user')
        User.objects.filter(username='testuser').update(username='test1user')
        # At this point userobject.val is still testuser, but the value in the database
        # was updated to test1user. The object's updated value needs to be reloaded
        # from the database.
        userobject.refresh_from_db()
        self.assertEqual(userobject.username, 'test1user')

Indices and tables