1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
|
"""
41. Tests for select_related()
``select_related()`` follows all relationships and pre-caches any foreign key
values so that complex trees can be fetched in a single query. However, this
isn't always a good idea, so the ``depth`` argument control how many "levels"
the select-related behavior will traverse.
"""
from django.db import models
# Who remembers high school biology?
class Domain(models.Model):
name = models.CharField(max_length=50)
def __unicode__(self):
return self.name
class Kingdom(models.Model):
name = models.CharField(max_length=50)
domain = models.ForeignKey(Domain)
def __unicode__(self):
return self.name
class Phylum(models.Model):
name = models.CharField(max_length=50)
kingdom = models.ForeignKey(Kingdom)
def __unicode__(self):
return self.name
class Klass(models.Model):
name = models.CharField(max_length=50)
phylum = models.ForeignKey(Phylum)
def __unicode__(self):
return self.name
class Order(models.Model):
name = models.CharField(max_length=50)
klass = models.ForeignKey(Klass)
def __unicode__(self):
return self.name
class Family(models.Model):
name = models.CharField(max_length=50)
order = models.ForeignKey(Order)
def __unicode__(self):
return self.name
class Genus(models.Model):
name = models.CharField(max_length=50)
family = models.ForeignKey(Family)
def __unicode__(self):
return self.name
class Species(models.Model):
name = models.CharField(max_length=50)
genus = models.ForeignKey(Genus)
def __unicode__(self):
return self.name
def create_tree(stringtree):
"""Helper to create a complete tree"""
names = stringtree.split()
models = [Domain, Kingdom, Phylum, Klass, Order, Family, Genus, Species]
assert len(names) == len(models), (names, models)
parent = None
for name, model in zip(names, models):
try:
obj = model.objects.get(name=name)
except model.DoesNotExist:
obj = model(name=name)
if parent:
setattr(obj, parent.__class__.__name__.lower(), parent)
obj.save()
parent = obj
__test__ = {'API_TESTS':"""
# Set up.
# The test runner sets settings.DEBUG to False, but we want to gather queries
# so we'll set it to True here and reset it at the end of the test suite.
>>> from django.conf import settings
>>> settings.DEBUG = True
>>> create_tree("Eukaryota Animalia Anthropoda Insecta Diptera Drosophilidae Drosophila melanogaster")
>>> create_tree("Eukaryota Animalia Chordata Mammalia Primates Hominidae Homo sapiens")
>>> create_tree("Eukaryota Plantae Magnoliophyta Magnoliopsida Fabales Fabaceae Pisum sativum")
>>> create_tree("Eukaryota Fungi Basidiomycota Homobasidiomycatae Agaricales Amanitacae Amanita muscaria")
>>> from django import db
# Normally, accessing FKs doesn't fill in related objects:
>>> db.reset_queries()
>>> fly = Species.objects.get(name="melanogaster")
>>> fly.genus.family.order.klass.phylum.kingdom.domain
<Domain: Eukaryota>
>>> len(db.connection.queries)
8
# However, a select_related() call will fill in those related objects without any extra queries:
>>> db.reset_queries()
>>> person = Species.objects.select_related(depth=10).get(name="sapiens")
>>> person.genus.family.order.klass.phylum.kingdom.domain
<Domain: Eukaryota>
>>> len(db.connection.queries)
1
# select_related() also of course applies to entire lists, not just items.
# Without select_related()
>>> db.reset_queries()
>>> world = Species.objects.all()
>>> [o.genus.family for o in world]
[<Family: Drosophilidae>, <Family: Hominidae>, <Family: Fabaceae>, <Family: Amanitacae>]
>>> len(db.connection.queries)
9
# With select_related():
>>> db.reset_queries()
>>> world = Species.objects.all().select_related()
>>> [o.genus.family for o in world]
[<Family: Drosophilidae>, <Family: Hominidae>, <Family: Fabaceae>, <Family: Amanitacae>]
>>> len(db.connection.queries)
1
# The "depth" argument to select_related() will stop the descent at a particular level:
>>> db.reset_queries()
>>> pea = Species.objects.select_related(depth=1).get(name="sativum")
>>> pea.genus.family.order.klass.phylum.kingdom.domain
<Domain: Eukaryota>
# Notice: one fewer queries than above because of depth=1
>>> len(db.connection.queries)
7
>>> db.reset_queries()
>>> pea = Species.objects.select_related(depth=5).get(name="sativum")
>>> pea.genus.family.order.klass.phylum.kingdom.domain
<Domain: Eukaryota>
>>> len(db.connection.queries)
3
>>> db.reset_queries()
>>> world = Species.objects.all().select_related(depth=2)
>>> [o.genus.family.order for o in world]
[<Order: Diptera>, <Order: Primates>, <Order: Fabales>, <Order: Agaricales>]
>>> len(db.connection.queries)
5
>>> s = Species.objects.all().select_related(depth=1).extra(select={'a': 'select_related_species.id + 10'})[0]
>>> s.id + 10 == s.a
True
# The optional fields passed to select_related() control which related models
# we pull in. This allows for smaller queries and can act as an alternative
# (or, in addition to) the depth parameter.
# In the next two cases, we explicitly say to select the 'genus' and
# 'genus.family' models, leading to the same number of queries as before.
>>> db.reset_queries()
>>> world = Species.objects.select_related('genus__family')
>>> [o.genus.family for o in world]
[<Family: Drosophilidae>, <Family: Hominidae>, <Family: Fabaceae>, <Family: Amanitacae>]
>>> len(db.connection.queries)
1
>>> db.reset_queries()
>>> world = Species.objects.filter(genus__name='Amanita').select_related('genus__family')
>>> [o.genus.family.order for o in world]
[<Order: Agaricales>]
>>> len(db.connection.queries)
2
>>> db.reset_queries()
>>> Species.objects.all().select_related('genus__family__order').order_by('id')[0:1].get().genus.family.order.name
u'Diptera'
>>> len(db.connection.queries)
1
# Specifying both "depth" and fields is an error.
>>> Species.objects.select_related('genus__family__order', depth=4)
Traceback (most recent call last):
...
TypeError: Cannot pass both "depth" and fields to select_related()
# Reset DEBUG to where we found it.
>>> settings.DEBUG = False
"""}
|