9 | 10 | from .models import (Author, Book, Reader, Qualification, Teacher, Department, |
10 | 11 | TaggedItem, Bookmark, AuthorAddress, FavoriteAuthors, AuthorWithAge, |
11 | 12 | BookWithYear, BookReview, Person, House, Room, Employee, Comment, |
| 1000 | |
| 1001 | |
| 1002 | class 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))) |