Ticket #17003: prefetch_singly_related_objs.diff
File prefetch_singly_related_objs.diff, 29.7 KB (added by , 13 years ago) |
---|
-
django/contrib/contenttypes/generic.py
diff -r 554e071b5c4a django/contrib/contenttypes/generic.py
a b 1 1 """ 2 2 Classes allowing "generic" relations through ContentType and object-id fields. 3 3 """ 4 from collections import defaultdict 5 from functools import partial 4 6 5 from functools import partial6 7 from django.core.exceptions import ObjectDoesNotExist 7 8 from django.db import connection 8 9 from django.db.models import signals … … 59 60 # This should never happen. I love comments like this, don't you? 60 61 raise Exception("Impossible arguments to GFK.get_content_type!") 61 62 63 def get_prefetch_query_set(self, instances): 64 # For efficiency, group the instances by content type and then do one 65 # query per model 66 fk_dict = defaultdict(list) 67 # We need one instance for each group in order to get the right db: 68 instance_dict = {} 69 ct_attname = self.model._meta.get_field(self.ct_field).get_attname() 70 for instance in instances: 71 # We avoid looking for values if either ct_id or fkey value is None 72 ct_id = getattr(instance, ct_attname) 73 if ct_id is not None: 74 fk_val = getattr(instance, self.fk_field) 75 if fk_val is not None: 76 fk_dict[ct_id].append(fk_val) 77 instance_dict[ct_id] = instance 78 79 ret_val = [] 80 for ct_id, fkeys in fk_dict.items(): 81 instance = instance_dict[ct_id] 82 ct = self.get_content_type(id=ct_id, using=instance._state.db) 83 ret_val.extend(ct.get_all_objects_for_this_type(pk__in=fkeys)) 84 85 # For doing the join in Python, we have to match both the FK val and the 86 # content type, so the 'attr' vals we return need to be callables that 87 # will return a (fk, class) pair. 88 return (ret_val, 89 lambda obj: (obj.pk, obj.__class__), 90 lambda obj: (getattr(obj, self.fk_field), 91 self.get_content_type(id=getattr(obj, ct_attname), 92 using=obj._state.db).model_class()), 93 True, 94 self.cache_attr) 95 96 def is_cached(self, instance): 97 return hasattr(instance, self.cache_attr) 98 62 99 def __get__(self, instance, instance_type=None): 63 100 if instance is None: 64 101 return self … … 282 319 [obj._get_pk_val() for obj in instances] 283 320 } 284 321 qs = super(GenericRelatedObjectManager, self).get_query_set().using(db).filter(**query) 285 return (qs, self.object_id_field_name, 'pk' )322 return (qs, self.object_id_field_name, 'pk', False, self.prefetch_cache_name) 286 323 287 324 def add(self, *objs): 288 325 for obj in objs: -
django/contrib/contenttypes/models.py
diff -r 554e071b5c4a django/contrib/contenttypes/models.py
a b 113 113 """ 114 114 return self.model_class()._base_manager.using(self._state.db).get(**kwargs) 115 115 116 def get_all_objects_for_this_type(self, **kwargs): 117 """ 118 Returns all objects of this type for the keyword arguments given. 119 """ 120 return self.model_class()._base_manager.using(self._state.db).filter(**kwargs) 121 116 122 def natural_key(self): 117 123 return (self.app_label, self.model) -
django/db/models/fields/related.py
diff -r 554e071b5c4a django/db/models/fields/related.py
a b 227 227 self.related = related 228 228 self.cache_name = related.get_cache_name() 229 229 230 def is_cached(self, instance): 231 return hasattr(instance, self.cache_name) 232 230 233 def __get__(self, instance, instance_type=None): 231 234 if instance is None: 232 235 return self … … 283 286 # ReverseSingleRelatedObjectDescriptor instance. 284 287 def __init__(self, field_with_rel): 285 288 self.field = field_with_rel 289 self.cache_name = self.field.get_cache_name() 290 291 def is_cached(self, instance): 292 return hasattr(instance, self.cache_name) 293 294 def get_query_set(self, instance=None): 295 hints = {} 296 if instance is not None: 297 hints['instance'] = instance 298 db = router.db_for_read(self.field.rel.to, **hints) 299 rel_mgr = self.field.rel.to._default_manager 300 # If the related manager indicates that it should be used for 301 # related fields, respect that. 302 if getattr(rel_mgr, 'use_for_related_fields', False): 303 return rel_mgr.using(db) 304 else: 305 return QuerySet(self.field.rel.to).using(db) 306 307 def get_prefetch_query_set(self, instances): 308 """ 309 Creates a prefetch query for a list of instances. 310 311 Returns a tuple: 312 (queryset of instances of self.model that are related to passed in instances, 313 attr of returned instances needed for matching, 314 attr of passed in instances needed for matching, 315 boolean that is True for singly related objects, 316 cache name to assign to). 317 318 The 'attr' return values can also be callables which return the values 319 needed, if a simple 'getattr(obj, attr)' will not suffice. 320 """ 321 322 vals = [getattr(instance, self.field.attname) for instance in instances] 323 other_field = self.field.rel.get_related_field() 324 if other_field.rel: 325 params = {'%s__pk__in' % self.field.rel.field_name: vals} 326 else: 327 params = {'%s__in' % self.field.rel.field_name: vals} 328 #from IPython.Shell import IPShellEmbed; IPShellEmbed([])() 329 return (self.get_query_set().filter(**params), 330 self.field.rel.field_name, 331 self.field.attname, 332 True, 333 self.cache_name) 286 334 287 335 def __get__(self, instance, instance_type=None): 288 336 if instance is None: 289 337 return self 290 338 291 cache_name = self.field.get_cache_name()292 339 try: 293 return getattr(instance, cache_name)340 return getattr(instance, self.cache_name) 294 341 except AttributeError: 295 342 val = getattr(instance, self.field.attname) 296 343 if val is None: … … 303 350 params = {'%s__pk' % self.field.rel.field_name: val} 304 351 else: 305 352 params = {'%s__exact' % self.field.rel.field_name: val} 306 307 # If the related manager indicates that it should be used for 308 # related fields, respect that. 309 rel_mgr = self.field.rel.to._default_manager 310 db = router.db_for_read(self.field.rel.to, instance=instance) 311 if getattr(rel_mgr, 'use_for_related_fields', False): 312 rel_obj = rel_mgr.using(db).get(**params) 313 else: 314 rel_obj = QuerySet(self.field.rel.to).using(db).get(**params) 315 setattr(instance, cache_name, rel_obj) 353 qs = self.get_query_set(instance=instance) 354 rel_obj = qs.get(**params) 355 setattr(instance, self.cache_name, rel_obj) 316 356 return rel_obj 317 357 318 358 def __set__(self, instance, value): … … 433 473 query = {'%s__%s__in' % (rel_field.name, attname): 434 474 [getattr(obj, attname) for obj in instances]} 435 475 qs = super(RelatedManager, self).get_query_set().using(db).filter(**query) 436 return (qs, rel_field.get_attname(), attname) 476 return (qs, rel_field.get_attname(), attname, 477 False, rel_field.related_query_name()) 437 478 438 479 def add(self, *objs): 439 480 for obj in objs: … … 507 548 return super(ManyRelatedManager, self).get_query_set().using(db)._next_is_sticky().filter(**self.core_filters) 508 549 509 550 def get_prefetch_query_set(self, instances): 510 """511 Returns a tuple:512 (queryset of instances of self.model that are related to passed in instances513 attr of returned instances needed for matching514 attr of passed in instances needed for matching)515 """516 551 from django.db import connections 517 552 db = self._db or router.db_for_read(self.model) 518 553 query = {'%s__pk__in' % self.query_field_name: … … 534 569 qs = qs.extra(select={'_prefetch_related_val': 535 570 '%s.%s' % (qn(join_table), qn(source_col))}) 536 571 select_attname = fk.rel.get_related_field().get_attname() 537 return (qs, '_prefetch_related_val', select_attname) 572 return (qs, '_prefetch_related_val', select_attname, 573 False, self.prefetch_cache_name) 538 574 539 575 # If the ManyToMany relation has an intermediary model, 540 576 # the add and remove methods do not exist. -
django/db/models/query.py
diff -r 554e071b5c4a django/db/models/query.py
a b 1612 1612 break 1613 1613 1614 1614 # Descend down tree 1615 try: 1616 rel_obj = getattr(obj_list[0], attr) 1617 except AttributeError: 1615 1616 # We assume that objects retrieved are homogenous (which is the premise 1617 # of prefetch_related), so what applies to first object applies to all. 1618 first_obj = obj_list[0] 1619 prefetcher, attr_found, is_fetched = get_prefetcher(first_obj, attr) 1620 1621 if not attr_found: 1618 1622 raise AttributeError("Cannot find '%s' on %s object, '%s' is an invalid " 1619 1623 "parameter to prefetch_related()" % 1620 (attr, obj_list[0].__class__.__name__, lookup))1624 (attr, first_obj.__class__.__name__, lookup)) 1621 1625 1622 can_prefetch = hasattr(rel_obj, 'get_prefetch_query_set')1623 if level == len(attrs) - 1 and not can_prefetch:1624 # Last one, this *must* resolve to a related manager.1625 raise ValueError("'%s' does not resolve to a supported 'many related"1626 " manager' for model %s - this is an invalid"1627 " parameter to prefetch_related()."1628 % (lookup, model.__name__))1626 if level == len(attrs) - 1 and prefetcher is None: 1627 # Last one, this *must* resolve to something that supports 1628 # prefetching, otherwise there is no point adding it and the 1629 # developer asking for it has made a mistake. 1630 raise ValueError("'%s' does not resolve to a item that supports " 1631 "prefetching - this is an invalid parameter to " 1632 "prefetch_related()." % lookup) 1629 1633 1630 if can_prefetch:1634 if prefetcher is not None and not is_fetched: 1631 1635 # Check we didn't do this already 1632 1636 current_lookup = LOOKUP_SEP.join(attrs[0:level+1]) 1633 1637 if current_lookup in done_queries: 1634 1638 obj_list = done_queries[current_lookup] 1635 1639 else: 1636 relmanager = rel_obj 1637 obj_list, additional_prl = prefetch_one_level(obj_list, relmanager, attr) 1640 obj_list, additional_prl = prefetch_one_level(obj_list, prefetcher, attr) 1638 1641 for f in additional_prl: 1639 1642 new_prl = LOOKUP_SEP.join([current_lookup, f]) 1640 1643 related_lookups.append(new_prl) 1641 1644 done_queries[current_lookup] = obj_list 1642 1645 else: 1643 # Assume we've got some singly related object. We replace 1644 # the current list of parent objects with that list. 1646 # Either a singly related object that has already been fetched 1647 # (e.g. via select_related), or hopefully some other property 1648 # that doesn't support prefetching but needs to be traversed. 1649 1650 # We replace the current list of parent objects with that list. 1645 1651 obj_list = [getattr(obj, attr) for obj in obj_list] 1646 1652 1647 1653 # Filter out 'None' so that we can continue with nullable … … 1649 1655 obj_list = [obj for obj in obj_list if obj is not None] 1650 1656 1651 1657 1658 def get_prefetcher(instance, attr): 1659 """ 1660 For the attribute 'attr' on the given instance, finds 1661 an object that has a get_prefetch_query_set(). 1662 Return a 3 tuple containing: 1663 (the object with get_prefetch_query_set (or None), 1664 a boolean that is False if the attribute was not found at all, 1665 a boolean that is True if the attribute has already been fetched) 1666 """ 1667 prefetcher = None 1668 attr_found = False 1669 is_fetched = False 1670 1671 # For singly related objects, we have to avoid getting the attribute 1672 # from the object, as this will trigger the query. So we first try 1673 # on the class, in order to get the descriptor object. 1674 rel_obj_descriptor = getattr(instance.__class__, attr, None) 1675 if rel_obj_descriptor is None: 1676 try: 1677 rel_obj = getattr(instance, attr) 1678 attr_found = True 1679 except AttributeError: 1680 pass 1681 else: 1682 attr_found = True 1683 if rel_obj_descriptor: 1684 # singly related object, descriptor object has the 1685 # get_prefetch_query_set() method. 1686 if hasattr(rel_obj_descriptor, 'get_prefetch_query_set'): 1687 prefetcher = rel_obj_descriptor 1688 if rel_obj_descriptor.is_cached(instance): 1689 is_fetched = True 1690 else: 1691 # descriptor doesn't support prefetching, so we go ahead and get 1692 # the attribute on the instance rather than the class to 1693 # support many related managers 1694 rel_obj = getattr(instance, attr) 1695 if hasattr(rel_obj, 'get_prefetch_query_set'): 1696 prefetcher = rel_obj 1697 return prefetcher, attr_found, is_fetched 1698 1699 1652 1700 def prefetch_one_level(instances, relmanager, attname): 1653 1701 """ 1654 1702 Helper function for prefetch_related_objects … … 1660 1708 prefetches that must be done due to prefetch_related lookups 1661 1709 found from default managers. 1662 1710 """ 1663 rel_qs, rel_obj_attr, instance_attr = relmanager.get_prefetch_query_set(instances) 1711 rel_qs, rel_obj_attr, instance_attr, single, cache_name =\ 1712 relmanager.get_prefetch_query_set(instances) 1664 1713 # We have to handle the possibility that the default manager itself added 1665 1714 # prefetch_related lookups to the QuerySet we just got back. We don't want to 1666 1715 # trigger the prefetch_related functionality by evaluating the query. … … 1676 1725 1677 1726 rel_obj_cache = {} 1678 1727 for rel_obj in all_related_objects: 1679 rel_attr_val = getattr(rel_obj, rel_obj_attr) 1728 if callable(rel_obj_attr): 1729 rel_attr_val = rel_obj_attr(rel_obj) 1730 else: 1731 rel_attr_val = getattr(rel_obj, rel_obj_attr) 1680 1732 if rel_attr_val not in rel_obj_cache: 1681 1733 rel_obj_cache[rel_attr_val] = [] 1682 1734 rel_obj_cache[rel_attr_val].append(rel_obj) 1683 1735 1684 1736 for obj in instances: 1685 qs = getattr(obj, attname).all() 1686 instance_attr_val = getattr(obj, instance_attr) 1687 qs._result_cache = rel_obj_cache.get(instance_attr_val, []) 1688 # We don't want the individual qs doing prefetch_related now, since we 1689 # have merged this into the current work. 1690 qs._prefetch_done = True 1691 obj._prefetched_objects_cache[attname] = qs 1737 if callable(instance_attr): 1738 instance_attr_val = instance_attr(obj) 1739 else: 1740 instance_attr_val = getattr(obj, instance_attr) 1741 vals = rel_obj_cache.get(instance_attr_val, []) 1742 if single: 1743 # Need to assign to single cache on instance 1744 if vals: 1745 setattr(obj, cache_name, vals[0]) 1746 else: 1747 # Multi, attribute represents a manager with an .all() method that 1748 # returns a QuerySet 1749 qs = getattr(obj, attname).all() 1750 qs._result_cache = vals 1751 # We don't want the individual qs doing prefetch_related now, since we 1752 # have merged this into the current work. 1753 qs._prefetch_done = True 1754 obj._prefetched_objects_cache[cache_name] = qs 1692 1755 return all_related_objects, additional_prl -
docs/ref/models/querysets.txt
diff -r 554e071b5c4a docs/ref/models/querysets.txt
a b 696 696 .. versionadded:: 1.4 697 697 698 698 Returns a ``QuerySet`` that will automatically retrieve, in a single batch, 699 related many-to-many and many-to-one objects for each of the specified lookups. 700 701 This is similar to ``select_related`` for the 'many related objects' case, but 702 note that ``prefetch_related`` causes a separate query to be issued for each set 703 of related objects that you request, unlike ``select_related`` which modifies 704 the original query with joins in order to get the related objects. With 705 ``prefetch_related``, the additional queries are done as soon as the QuerySet 706 begins to be evaluated. 699 related objects for each of the specified lookups. 700 701 This has a similar purpose to ``select_related``, in that both are designed to 702 stop accessing related objects causing a deluge of database queries, but the 703 strategy is quite different. 704 705 ``select_related`` works by creating a SQL join and including the fields of the 706 related object in the SELECT statement. For this reason, ``select_related`` gets 707 the related object in the same database query. However, to avoid the much larger 708 result set that would result from joining across a 'many' relationship, 709 ``select_related`` is limited to the single-valued foreign key and one-to-one 710 relationships. 711 712 ``prefetch_related``, on the other hand, does a separate lookup for each 713 relationship, and does the 'joining' in Python. This allows it to prefetch 714 many-to-many and many-to-one objects, which cannot be done using 715 ``select_related``, in addition to the foreign key and one-to-one relationships 716 that are supported by ``select_related``. It also supports prefetching of 717 ``GenericRelations`` and ``GenericForeignKey``. 707 718 708 719 For example, suppose you have these models:: 709 720 … … 733 744 ``QuerySets`` that have a pre-filled cache of the relevant results. These 734 745 ``QuerySets`` are then used in the ``self.toppings.all()`` calls. 735 746 736 Please note that use of ``prefetch_related`` will mean that the additional 737 queries run will **always** be executed - even if you never use the related 738 objects - and it always fully populates the result cache on the primary 739 ``QuerySet`` (which can sometimes be avoided in other cases). 747 The additional queries are executed after the QuerySet has begun to be evaluated 748 and the primary query has been executed. Note that the result cache of the 749 primary QuerySet and all specified related objects will then be fully loaded 750 into memory, which is often avoided in other cases - even after a query has been 751 executed in the database, QuerySet normally tries to make uses of chunking 752 between the database to avoid loading all objects into memory before you need 753 them. 740 754 741 755 Also remember that, as always with QuerySets, any subsequent chained methods 742 w ill ignore previously cached results, and retrieve data using a fresh database743 query. So, if you write the following:756 which imply a different database query will ignore previously cached results, 757 and retrieve data using a fresh database query. So, if you write the following: 744 758 745 759 >>> pizzas = Pizza.objects.prefetch_related('toppings') 746 760 >>> [list(pizza.toppings.filter(spicy=True)) for pizza in pizzas] … … 749 763 you - in fact it hurts performance, since you have done a database query that 750 764 you haven't used. So use this feature with caution! 751 765 752 The lookups that must be supplied to this method can be any attributes on the753 model instances which represent related queries that return multiple754 objects. This includes attributes representing the 'many' side of ``ForeignKey``755 relationships, forward and reverse ``ManyToManyField`` attributes, and also any756 ``GenericRelations``.757 758 766 You can also use the normal join syntax to do related fields of related 759 767 fields. Suppose we have an additional model to the example above:: 760 768 … … 770 778 belonging to those pizzas. This will result in a total of 3 database queries - 771 779 one for the restaurants, one for the pizzas, and one for the toppings. 772 780 781 >>> Restaurant.objects.prefetch_related('best_pizza__toppings') 782 783 This will fetch the best pizza and all the toppings for the best pizza for each 784 restaurant. This will be done in 3 database queries - one for the restaurants, 785 one for the 'best pizzas', and one for one for the toppings. 786 787 Of course, the ``best_pizza`` relationship could also be fetched using 788 ``select_related`` to reduce the query count to 2: 789 773 790 >>> Restaurant.objects.select_related('best_pizza').prefetch_related('best_pizza__toppings') 774 791 775 This will fetch the best pizza and all the toppings for the best pizza for each 776 restaurant. This will be done in 2 database queries - one for the restaurants 777 and 'best pizzas' combined (achieved through use of ``select_related``), and one 778 for the toppings. 792 Since the prefetch is executed after the main query (which includes the joins 793 needed by ``select_related``), it is able to detect that the ``best_pizza`` 794 objects have already been fetched, and it will skip fetching them again. 779 795 780 796 Chaining ``prefetch_related`` calls will accumulate the fields that should have 781 797 this behavior applied. To clear any ``prefetch_related`` behavior, pass `None` … … 783 799 784 800 >>> non_prefetched = qs.prefetch_related(None) 785 801 786 One difference when using ``prefetch_related`` is that, in some circumstances, 787 objects created by a query can be shared between the different objects that they 788 are related to i.e. a single Python model instance can appear at more than one 789 point in the tree of objects that are returned. Normally this behavior will not 790 be a problem, and will in fact save both memory and CPU time. 802 One difference to note when using ``prefetch_related`` is that objects created 803 by a query can be shared between the different objects that they are related to 804 i.e. a single Python model instance can appear at more than one point in the 805 tree of objects that are returned. This will be normally happens with foreign 806 key relationships. Normally this behavior will not be a problem, and will in 807 fact save both memory and CPU time. 808 809 While ``prefetch_related`` supports prefetching ``GenericForeignKey`` 810 relationships, the number of queries will depend on the data. Since a 811 ``GenericForeignKey`` can reference data in multiple tables, one query per table 812 referenced is needed, rather than one query for all the items. 791 813 792 814 extra 793 815 ~~~~~ -
tests/modeltests/prefetch_related/tests.py
diff -r 554e071b5c4a tests/modeltests/prefetch_related/tests.py
a b 54 54 normal_lists = [list(a.books.all()) for a in Author.objects.all()] 55 55 self.assertEqual(lists, normal_lists) 56 56 57 def test_foreignkey_forward(self): 58 with self.assertNumQueries(2): 59 books = [a.first_book for a in Author.objects.prefetch_related('first_book')] 60 61 normal_books = [a.first_book for a in Author.objects.all()] 62 self.assertEqual(books, normal_books) 63 57 64 def test_foreignkey_reverse(self): 58 65 with self.assertNumQueries(2): 59 66 lists = [list(b.first_time_authors.all()) … … 175 182 self.assertTrue('prefetch_related' in str(cm.exception)) 176 183 177 184 def test_invalid_final_lookup(self): 178 qs = Book.objects.prefetch_related('authors__ first_book')185 qs = Book.objects.prefetch_related('authors__name') 179 186 with self.assertRaises(ValueError) as cm: 180 187 list(qs) 181 188 182 189 self.assertTrue('prefetch_related' in str(cm.exception)) 183 self.assertTrue(" first_book" in str(cm.exception))190 self.assertTrue("name" in str(cm.exception)) 184 191 185 192 186 193 class DefaultManagerTests(TestCase): … … 222 229 223 230 class GenericRelationTests(TestCase): 224 231 225 def test_traverse_GFK(self): 226 """ 227 Test that we can traverse a 'content_object' with prefetch_related() 228 """ 229 # In fact, there is no special support for this in prefetch_related code 230 # - we can traverse any object that will lead us to objects that have 231 # related managers. 232 232 def setUp(self): 233 233 book1 = Book.objects.create(title="Winnie the Pooh") 234 234 book2 = Book.objects.create(title="Do you like green eggs and spam?") 235 book3 = Book.objects.create(title="Three Men In A Boat") 235 236 236 237 reader1 = Reader.objects.create(name="me") 237 238 reader2 = Reader.objects.create(name="you") 239 reader3 = Reader.objects.create(name="someone") 238 240 239 book1.read_by.add(reader1 )241 book1.read_by.add(reader1, reader2) 240 242 book2.read_by.add(reader2) 243 book3.read_by.add(reader3) 241 244 242 TaggedItem.objects.create(tag="awesome", content_object=book1) 243 TaggedItem.objects.create(tag="awesome", content_object=book2) 245 self.book1, self.book2, self.book3 = book1, book2, book3 246 self.reader1, self.reader2, self.reader3 = reader1, reader2, reader3 247 248 def test_prefetch_GFK(self): 249 TaggedItem.objects.create(tag="awesome", content_object=self.book1) 250 TaggedItem.objects.create(tag="great", content_object=self.reader1) 251 TaggedItem.objects.create(tag="stupid", content_object=self.book2) 252 TaggedItem.objects.create(tag="amazing", content_object=self.reader3) 253 254 # 1 for TaggedItem table, 1 for Book table, 1 for Reader table 255 with self.assertNumQueries(3): 256 qs = TaggedItem.objects.prefetch_related('content_object') 257 list(qs) 258 259 def test_traverse_GFK(self): 260 """ 261 Test that we can traverse a 'content_object' with prefetch_related() and 262 get to related objects on the other side (assuming it is suitably 263 filtered) 264 """ 265 TaggedItem.objects.create(tag="awesome", content_object=self.book1) 266 TaggedItem.objects.create(tag="awesome", content_object=self.book2) 267 TaggedItem.objects.create(tag="awesome", content_object=self.book3) 268 TaggedItem.objects.create(tag="awesome", content_object=self.reader1) 269 TaggedItem.objects.create(tag="awesome", content_object=self.reader2) 244 270 245 271 ct = ContentType.objects.get_for_model(Book) 246 272 247 # We get 4 queries - 1 for main query, 2 for each access to 248 # 'content_object' because these can't be handled by select_related, and 249 # 1 for the 'read_by' relation. 250 with self.assertNumQueries(4): 273 # We get 3 queries - 1 for main query, 1 for content_objects since they 274 # all use the same table, and 1 for the 'read_by' relation. 275 with self.assertNumQueries(3): 251 276 # If we limit to books, we know that they will have 'read_by' 252 277 # attributes, so the following makes sense: 253 qs = TaggedItem.objects.select_related('content_type').prefetch_related('content_object__read_by').filter(tag='awesome').filter(content_type=ct, tag='awesome') 254 readers_of_awesome_books = [r.name for tag in qs 255 for r in tag.content_object.read_by.all()] 256 self.assertEqual(readers_of_awesome_books, ["me", "you"]) 257 278 qs = TaggedItem.objects.filter(content_type=ct, tag='awesome').prefetch_related('content_object__read_by') 279 readers_of_awesome_books = set([r.name for tag in qs 280 for r in tag.content_object.read_by.all()]) 281 self.assertEqual(readers_of_awesome_books, set(["me", "you", "someone"])) 258 282 259 283 def test_generic_relation(self): 260 284 b = Bookmark.objects.create(url='http://www.djangoproject.com/') … … 311 335 self.assertEquals(lst, lst2) 312 336 313 337 def test_parent_link_prefetch(self): 314 with self.assertRaises(ValueError) as cm: 315 qs = list(AuthorWithAge.objects.prefetch_related('author')) 316 self.assertTrue('prefetch_related' in str(cm.exception)) 338 with self.assertNumQueries(2): 339 [a.author for a in AuthorWithAge.objects.prefetch_related('author')] 317 340 318 341 319 342 class ForeignKeyToFieldTest(TestCase): … … 406 429 worker2 = Employee.objects.create(name="Angela", boss=boss) 407 430 408 431 def test_traverse_nullable(self): 432 # Because we use select_related() for 'boss', it doesn't need to be 433 # prefetched, but we can still traverse it although it contains some nulls 409 434 with self.assertNumQueries(2): 410 435 qs = Employee.objects.select_related('boss').prefetch_related('boss__serfs') 411 436 co_serfs = [list(e.boss.serfs.all()) if e.boss is not None else [] … … 416 441 for e in qs2] 417 442 418 443 self.assertEqual(co_serfs, co_serfs2) 444 445 def test_prefetch_nullable(self): 446 # One for main employee, one for boss, one for serfs 447 with self.assertNumQueries(3): 448 qs = Employee.objects.prefetch_related('boss__serfs') 449 co_serfs = [list(e.boss.serfs.all()) if e.boss is not None else [] 450 for e in qs] 451 452 qs2 = Employee.objects.all() 453 co_serfs2 = [list(e.boss.serfs.all()) if e.boss is not None else [] 454 for e in qs2] 455 456 self.assertEqual(co_serfs, co_serfs2)