Ticket #22014: prefetch_recursion_protection_fail.diff

File prefetch_recursion_protection_fail.diff, 4.9 KB (added by StillNewb, 13 months ago)
  • tests/prefetch_related/models.py

    diff --git a/tests/prefetch_related/models.py b/tests/prefetch_related/models.py
    index 35be3d6..9418ee7 100644
    a b class AuthorAddress(models.Model): 
    5151class Book(models.Model):
    5252    title = models.CharField(max_length=255)
    5353    authors = models.ManyToManyField(Author, related_name='books')
     54    publishers = models.ManyToManyField('Publisher', related_name='books')
    5455
    5556    def __str__(self):
    5657        return self.title
    class Book(models.Model): 
    5859    class Meta:
    5960        ordering = ['id']
    6061
     62class Publisher(models.Model):
     63    authors = models.ManyToManyField('Author', related_name='publishers')
     64
    6165
    6266class BookWithYear(Book):
    6367    book = models.OneToOneField(Book, parent_link=True)
  • tests/prefetch_related/tests.py

    diff --git a/tests/prefetch_related/tests.py b/tests/prefetch_related/tests.py
    index 5dfca0f..ccbdcf0 100644
    a b  
    11from __future__ import unicode_literals
     2from collections import deque
    23
    34from django.contrib.contenttypes.models import ContentType
    45from django.db import connection
    from django.utils import six 
    910from .models import (Author, Book, Reader, Qualification, Teacher, Department,
    1011    TaggedItem, Bookmark, AuthorAddress, FavoriteAuthors, AuthorWithAge,
    1112    BookWithYear, BookReview, Person, House, Room, Employee, Comment,
    12     LessonEntry, WordEntry, Author2)
     13    LessonEntry, WordEntry, Author2, Publisher)
    1314
    1415
    1516class PrefetchRelatedTests(TestCase):
    class Ticket21410Tests(TestCase): 
    996997
    997998    def test_bug(self):
    998999        list(Author2.objects.prefetch_related('first_book', 'favorite_books'))
     1000
     1001
     1002class RecursionProtectionTests(TestCase):
     1003    def setUp(self):
     1004        self.book1 = Book.objects.create()
     1005        self.book2 = Book.objects.create()
     1006        self.author1 = Author.objects.create(first_book=self.book1, name='Author #1')
     1007        self.author2 = Author.objects.create(first_book=self.book1, name='Author #2')
     1008        self.publisher = Publisher.objects.create()
     1009        self.author1.books.add(self.book1)
     1010        self.author1.save()
     1011        self.author2.books.add(self.book2)
     1012        self.author2.save()
     1013        self.book1.publishers.add(self.publisher)
     1014        self.book1.save()
     1015        self.publisher.authors.add(self.author1, self.author2)
     1016        self.publisher.save()
     1017
     1018    def walk(self, qs):
     1019        get_publishers = lambda b: b.publishers.all()
     1020        get_authors = lambda p: p.authors.all()
     1021        get_books = lambda a: a.books.all()
     1022
     1023        fetched_books = []
     1024        for e in qs:
     1025            for b in get_books(e):
     1026                for p in get_publishers(b):
     1027                    for a in get_authors(p):
     1028                        for fetched_book in get_books(a):
     1029                            fetched_books.append(fetched_book)
     1030        return fetched_books
     1031
     1032    @override_settings(DEBUG=True)
     1033    def test_descriptors_storing_protection(self):
     1034        """
     1035        What happens here:
     1036        ------------------
     1037        There is the check in `prefetch_related_objects`:
     1038        ::
     1039
     1040            if not (lookup in auto_lookups and descriptor in followed_descriptors):
     1041                done_queries[prefetch_to] = obj_list
     1042                auto_lookups.extend(normalize_prefetch_lookups(additional_lookups, prefetch_to))
     1043
     1044        if lookup is not autolookup and descriptor for it not been seen
     1045        lookup being added to `done_queries`.
     1046
     1047        This tests creates situation where:
     1048        lookup is autolookup and descriptor for it was added while
     1049        traversing same field but for different lookup.
     1050        So it never would be added to `done_lookups`.
     1051        """
     1052        qs1 = Author.objects.filter(pk=self.author1.pk).prefetch_related(Prefetch('books', Book.objects.prefetch_related('publishers__authors__books')), 'books__publishers__authors__books')
     1053        qs2 = Author.objects.filter(pk=self.author1.pk).prefetch_related(Prefetch('books', Book.objects.prefetch_related('publishers__authors__books')), 'books__publishers__authors__books', 'books__publishers__authors__books')
     1054
     1055        def check_num_queries(qs):
     1056            old_num_queries = len(connection.queries)
     1057            self.assertEqual(len(self.walk(qs)), 2)
     1058            return connection.queries[old_num_queries:]
     1059
     1060        queries_qs1 = check_num_queries(qs1)
     1061        queries_qs2 = check_num_queries(qs2)
     1062        self.assertEqual(len(queries_qs1), len(queries_qs2))
     1063
     1064    @override_settings(DEBUG=True)
     1065    def test_only_unique_queries(self):
     1066        qs = Author.objects.filter(pk=self.author1.pk).prefetch_related(Prefetch('books', Book.objects.prefetch_related('publishers__authors__books')), 'books__publishers__authors__books')
     1067        connection.queries = []
     1068        self.walk(qs)
     1069        queries = map(lambda e: e['sql'], connection.queries)
     1070        self.assertEqual(len(queries), len(set(queries)))
Back to Top