Ticket #17003: prefetch_singly_related_objs.3.diff
File prefetch_singly_related_objs.3.diff, 33.3 KB (added by , 13 years ago) |
---|
-
django/contrib/contenttypes/generic.py
diff -r 554e071b5c4a django/contrib/contenttypes/generic.py
a b 2 2 Classes allowing "generic" relations through ContentType and object-id fields. 3 3 """ 4 4 5 from collections import defaultdict 5 6 from functools import partial 7 6 8 from django.core.exceptions import ObjectDoesNotExist 7 9 from django.db import connection 8 10 from django.db.models import signals … … 59 61 # This should never happen. I love comments like this, don't you? 60 62 raise Exception("Impossible arguments to GFK.get_content_type!") 61 63 64 def get_prefetch_query_set(self, instances): 65 # For efficiency, group the instances by content type and then do one 66 # query per model 67 fk_dict = defaultdict(list) 68 # We need one instance for each group in order to get the right db: 69 instance_dict = {} 70 ct_attname = self.model._meta.get_field(self.ct_field).get_attname() 71 for instance in instances: 72 # We avoid looking for values if either ct_id or fkey value is None 73 ct_id = getattr(instance, ct_attname) 74 if ct_id is not None: 75 fk_val = getattr(instance, self.fk_field) 76 if fk_val is not None: 77 fk_dict[ct_id].append(fk_val) 78 instance_dict[ct_id] = instance 79 80 ret_val = [] 81 for ct_id, fkeys in fk_dict.items(): 82 instance = instance_dict[ct_id] 83 ct = self.get_content_type(id=ct_id, using=instance._state.db) 84 ret_val.extend(ct.get_all_objects_for_this_type(pk__in=fkeys)) 85 86 # For doing the join in Python, we have to match both the FK val and the 87 # content type, so the 'attr' vals we return need to be callables that 88 # will return a (fk, class) pair. 89 def gfk_key(obj): 90 ct_id = getattr(obj, ct_attname) 91 if ct_id is None: 92 return None 93 else: 94 return (getattr(obj, self.fk_field), 95 self.get_content_type(id=ct_id, 96 using=obj._state.db).model_class()) 97 98 return (ret_val, 99 lambda obj: (obj.pk, obj.__class__), 100 gfk_key, 101 True, 102 self.cache_attr) 103 104 def is_cached(self, instance): 105 return hasattr(instance, self.cache_attr) 106 62 107 def __get__(self, instance, instance_type=None): 63 108 if instance is None: 64 109 return self … … 282 327 [obj._get_pk_val() for obj in instances] 283 328 } 284 329 qs = super(GenericRelatedObjectManager, self).get_query_set().using(db).filter(**query) 285 return (qs, self.object_id_field_name, 'pk') 330 return (qs, 331 self.object_id_field_name, 332 lambda obj: obj._get_pk_val(), 333 False, 334 self.prefetch_cache_name) 286 335 287 336 def add(self, *objs): 288 337 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 233 def get_query_set(self, **db_hints): 234 db = router.db_for_read(self.related.model, **db_hints) 235 return self.related.model._base_manager.using(db) 236 237 def get_prefetch_query_set(self, instances): 238 vals = [instance._get_pk_val() for instance in instances] 239 params = {'%s__pk__in' % self.related.field.name: vals} 240 return (self.get_query_set(), 241 self.related.field.attname, 242 lambda obj: obj._get_pk_val(), 243 True, 244 self.cache_name) 245 230 246 def __get__(self, instance, instance_type=None): 231 247 if instance is None: 232 248 return self … … 234 250 return getattr(instance, self.cache_name) 235 251 except AttributeError: 236 252 params = {'%s__pk' % self.related.field.name: instance._get_pk_val()} 237 db = router.db_for_read(self.related.model, instance=instance) 238 rel_obj = self.related.model._base_manager.using(db).get(**params) 253 rel_obj = self.get_query_set(instance=instance).get(**params) 239 254 setattr(instance, self.cache_name, rel_obj) 240 255 return rel_obj 241 256 … … 283 298 # ReverseSingleRelatedObjectDescriptor instance. 284 299 def __init__(self, field_with_rel): 285 300 self.field = field_with_rel 301 self.cache_name = self.field.get_cache_name() 302 303 def is_cached(self, instance): 304 return hasattr(instance, self.cache_name) 305 306 def get_query_set(self, **db_hints): 307 db = router.db_for_read(self.field.rel.to, **db_hints) 308 rel_mgr = self.field.rel.to._default_manager 309 # If the related manager indicates that it should be used for 310 # related fields, respect that. 311 if getattr(rel_mgr, 'use_for_related_fields', False): 312 return rel_mgr.using(db) 313 else: 314 return QuerySet(self.field.rel.to).using(db) 315 316 def get_prefetch_query_set(self, instances): 317 vals = [getattr(instance, self.field.attname) for instance in instances] 318 other_field = self.field.rel.get_related_field() 319 if other_field.rel: 320 params = {'%s__pk__in' % self.field.rel.field_name: vals} 321 else: 322 params = {'%s__in' % self.field.rel.field_name: vals} 323 return (self.get_query_set().filter(**params), 324 self.field.rel.field_name, 325 self.field.attname, 326 True, 327 self.cache_name) 286 328 287 329 def __get__(self, instance, instance_type=None): 288 330 if instance is None: 289 331 return self 290 332 291 cache_name = self.field.get_cache_name()292 333 try: 293 return getattr(instance, cache_name)334 return getattr(instance, self.cache_name) 294 335 except AttributeError: 295 336 val = getattr(instance, self.field.attname) 296 337 if val is None: … … 303 344 params = {'%s__pk' % self.field.rel.field_name: val} 304 345 else: 305 346 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) 347 qs = self.get_query_set(instance=instance) 348 rel_obj = qs.get(**params) 349 setattr(instance, self.cache_name, rel_obj) 316 350 return rel_obj 317 351 318 352 def __set__(self, instance, value): … … 425 459 return super(RelatedManager, self).get_query_set().using(db).filter(**self.core_filters) 426 460 427 461 def get_prefetch_query_set(self, instances): 428 """429 Return a queryset that does the bulk lookup needed430 by prefetch_related functionality.431 """432 462 db = self._db or router.db_for_read(self.model) 433 463 query = {'%s__%s__in' % (rel_field.name, attname): 434 464 [getattr(obj, attname) for obj in instances]} 435 465 qs = super(RelatedManager, self).get_query_set().using(db).filter(**query) 436 return (qs, rel_field.get_attname(), attname) 466 return (qs, rel_field.get_attname(), attname, 467 False, rel_field.related_query_name()) 437 468 438 469 def add(self, *objs): 439 470 for obj in objs: … … 507 538 return super(ManyRelatedManager, self).get_query_set().using(db)._next_is_sticky().filter(**self.core_filters) 508 539 509 540 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 541 from django.db import connections 517 542 db = self._db or router.db_for_read(self.model) 518 543 query = {'%s__pk__in' % self.query_field_name: … … 534 559 qs = qs.extra(select={'_prefetch_related_val': 535 560 '%s.%s' % (qn(join_table), qn(source_col))}) 536 561 select_attname = fk.rel.get_related_field().get_attname() 537 return (qs, '_prefetch_related_val', select_attname) 562 return (qs, '_prefetch_related_val', select_attname, 563 False, self.prefetch_cache_name) 538 564 539 565 # If the ManyToMany relation has an intermediary model, 540 566 # 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 1652 def prefetch_one_level(instances, relmanager, attname): 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 1700 def prefetch_one_level(instances, prefetcher, attname): 1653 1701 """ 1654 1702 Helper function for prefetch_related_objects 1655 1703 1656 Runs prefetches on all instances using the manager relmanager,1657 assigning results to queryset against instance.attname.1704 Runs prefetches on all instances using the prefetcher object, 1705 assigning results to relevant caches in instance. 1658 1706 1659 1707 The prefetched objects are returned, along with any additional 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 # prefetcher must have a method get_prefetch_query_set() which takes a list 1712 # of instances, and returns a tuple: 1713 1714 # (queryset of instances of self.model that are related to passed in instances, 1715 # attr of returned instances needed for matching, 1716 # attr of passed in instances needed for matching, 1717 # boolean that is True for singly related objects, 1718 # cache name to assign to). 1719 1720 # The 'attr' return values can also be callables which return the values 1721 # needed, if a simple 'getattr(obj, attr)' will not suffice, and such 1722 # callables could return a compound value, as long as it can be used as a 1723 # dictionary key. 1724 1725 rel_qs, rel_obj_attr, instance_attr, single, cache_name =\ 1726 prefetcher.get_prefetch_query_set(instances) 1664 1727 # We have to handle the possibility that the default manager itself added 1665 1728 # prefetch_related lookups to the QuerySet we just got back. We don't want to 1666 1729 # trigger the prefetch_related functionality by evaluating the query. … … 1676 1739 1677 1740 rel_obj_cache = {} 1678 1741 for rel_obj in all_related_objects: 1679 rel_attr_val = getattr(rel_obj, rel_obj_attr) 1742 if callable(rel_obj_attr): 1743 rel_attr_val = rel_obj_attr(rel_obj) 1744 else: 1745 rel_attr_val = getattr(rel_obj, rel_obj_attr) 1680 1746 if rel_attr_val not in rel_obj_cache: 1681 1747 rel_obj_cache[rel_attr_val] = [] 1682 1748 rel_obj_cache[rel_attr_val].append(rel_obj) 1683 1749 1684 1750 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 1751 if callable(instance_attr): 1752 instance_attr_val = instance_attr(obj) 1753 else: 1754 instance_attr_val = getattr(obj, instance_attr) 1755 vals = rel_obj_cache.get(instance_attr_val, []) 1756 if single: 1757 # Need to assign to single cache on instance 1758 if vals: 1759 setattr(obj, cache_name, vals[0]) 1760 else: 1761 # Multi, attribute represents a manager with an .all() method that 1762 # returns a QuerySet 1763 qs = getattr(obj, attname).all() 1764 qs._result_cache = vals 1765 # We don't want the individual qs doing prefetch_related now, since we 1766 # have merged this into the current work. 1767 qs._prefetch_done = True 1768 obj._prefetched_objects_cache[cache_name] = qs 1692 1769 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/models.py
diff -r 554e071b5c4a tests/modeltests/prefetch_related/models.py
a b 104 104 ordering = ['id'] 105 105 106 106 107 ## Generic relationtests107 ## GenericRelation/GenericForeignKey tests 108 108 109 109 class TaggedItem(models.Model): 110 110 tag = models.SlugField() 111 111 content_type = models.ForeignKey(ContentType, related_name="taggeditem_set2") 112 112 object_id = models.PositiveIntegerField() 113 113 content_object = generic.GenericForeignKey('content_type', 'object_id') 114 created_by_ct = models.ForeignKey(ContentType, null=True) 115 created_by_fkey = models.PositiveIntegerField(null=True) 116 created_by = generic.GenericForeignKey('created_by_ct', 'created_by_fkey') 114 117 115 118 def __unicode__(self): 116 119 return self.tag -
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 qs255 for r in tag.content_object.read_by.all()]256 self.assertEqual(readers_of_awesome_books, ["me", "you"])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"])) 257 282 283 def test_nullable_GFK(self): 284 TaggedItem.objects.create(tag="awesome", content_object=self.book1, 285 created_by=self.reader1) 286 TaggedItem.objects.create(tag="great", content_object=self.book2) 287 TaggedItem.objects.create(tag="rubbish", content_object=self.book3) 288 289 with self.assertNumQueries(2): 290 result = [t.created_by for t in TaggedItem.objects.prefetch_related('created_by')] 291 292 self.assertEqual(result, 293 [t.created_by for t in TaggedItem.objects.all()]) 258 294 259 295 def test_generic_relation(self): 260 296 b = Bookmark.objects.create(url='http://www.djangoproject.com/') … … 311 347 self.assertEquals(lst, lst2) 312 348 313 349 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)) 350 with self.assertNumQueries(2): 351 [a.author for a in AuthorWithAge.objects.prefetch_related('author')] 352 353 def test_child_link_prefetch(self): 354 with self.assertNumQueries(2): 355 l = [a.authorwithage for a in Author.objects.prefetch_related('authorwithage')] 356 357 self.assertEqual(l, [a.authorwithage for a in Author.objects.all()]) 317 358 318 359 319 360 class ForeignKeyToFieldTest(TestCase): … … 406 447 worker2 = Employee.objects.create(name="Angela", boss=boss) 407 448 408 449 def test_traverse_nullable(self): 450 # Because we use select_related() for 'boss', it doesn't need to be 451 # prefetched, but we can still traverse it although it contains some nulls 409 452 with self.assertNumQueries(2): 410 453 qs = Employee.objects.select_related('boss').prefetch_related('boss__serfs') 411 454 co_serfs = [list(e.boss.serfs.all()) if e.boss is not None else [] … … 416 459 for e in qs2] 417 460 418 461 self.assertEqual(co_serfs, co_serfs2) 462 463 def test_prefetch_nullable(self): 464 # One for main employee, one for boss, one for serfs 465 with self.assertNumQueries(3): 466 qs = Employee.objects.prefetch_related('boss__serfs') 467 co_serfs = [list(e.boss.serfs.all()) if e.boss is not None else [] 468 for e in qs] 469 470 qs2 = Employee.objects.all() 471 co_serfs2 = [list(e.boss.serfs.all()) if e.boss is not None else [] 472 for e in qs2] 473 474 self.assertEqual(co_serfs, co_serfs2)