diff options
author | django-bot <ops@djangoproject.com> | 2022-02-03 20:24:19 +0100 |
---|---|---|
committer | Mariusz Felisiak <felisiak.mariusz@gmail.com> | 2022-02-07 20:37:05 +0100 |
commit | 9c19aff7c7561e3a82978a272ecdaad40dda5c00 (patch) | |
tree | f0506b668a013d0063e5fba3dbf4863b466713ba /tests/filtered_relation | |
parent | f68fa8b45dfac545cfc4111d4e52804c86db68d3 (diff) | |
download | django-9c19aff7c7561e3a82978a272ecdaad40dda5c00.tar.gz |
Refs #33476 -- Reformatted code with Black.
Diffstat (limited to 'tests/filtered_relation')
-rw-r--r-- | tests/filtered_relation/models.py | 64 | ||||
-rw-r--r-- | tests/filtered_relation/tests.py | 911 |
2 files changed, 601 insertions, 374 deletions
diff --git a/tests/filtered_relation/models.py b/tests/filtered_relation/models.py index c7efa4cd04..d34a86305f 100644 --- a/tests/filtered_relation/models.py +++ b/tests/filtered_relation/models.py @@ -1,6 +1,4 @@ -from django.contrib.contenttypes.fields import ( - GenericForeignKey, GenericRelation, -) +from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation from django.contrib.contenttypes.models import ContentType from django.db import models @@ -8,9 +6,9 @@ from django.db import models class Author(models.Model): name = models.CharField(max_length=50, unique=True) favorite_books = models.ManyToManyField( - 'Book', - related_name='preferred_by_authors', - related_query_name='preferred_by_authors', + "Book", + related_name="preferred_by_authors", + related_query_name="preferred_by_authors", ) content_type = models.ForeignKey(ContentType, models.CASCADE, null=True) object_id = models.PositiveIntegerField(null=True) @@ -22,20 +20,20 @@ class Editor(models.Model): class Book(models.Model): - AVAILABLE = 'available' - RESERVED = 'reserved' - RENTED = 'rented' + AVAILABLE = "available" + RESERVED = "reserved" + RENTED = "rented" STATES = ( - (AVAILABLE, 'Available'), - (RESERVED, 'reserved'), - (RENTED, 'Rented'), + (AVAILABLE, "Available"), + (RESERVED, "reserved"), + (RENTED, "Rented"), ) title = models.CharField(max_length=255) author = models.ForeignKey( Author, models.CASCADE, - related_name='books', - related_query_name='book', + related_name="books", + related_query_name="book", ) editor = models.ForeignKey(Editor, models.CASCADE) generic_author = GenericRelation(Author) @@ -47,45 +45,45 @@ class Borrower(models.Model): class Reservation(models.Model): - NEW = 'new' - STOPPED = 'stopped' + NEW = "new" + STOPPED = "stopped" STATES = ( - (NEW, 'New'), - (STOPPED, 'Stopped'), + (NEW, "New"), + (STOPPED, "Stopped"), ) borrower = models.ForeignKey( Borrower, models.CASCADE, - related_name='reservations', - related_query_name='reservation', + related_name="reservations", + related_query_name="reservation", ) book = models.ForeignKey( Book, models.CASCADE, - related_name='reservations', - related_query_name='reservation', + related_name="reservations", + related_query_name="reservation", ) state = models.CharField(max_length=7, choices=STATES, default=NEW) class RentalSession(models.Model): - NEW = 'new' - STOPPED = 'stopped' + NEW = "new" + STOPPED = "stopped" STATES = ( - (NEW, 'New'), - (STOPPED, 'Stopped'), + (NEW, "New"), + (STOPPED, "Stopped"), ) borrower = models.ForeignKey( Borrower, models.CASCADE, - related_name='rental_sessions', - related_query_name='rental_session', + related_name="rental_sessions", + related_query_name="rental_session", ) book = models.ForeignKey( Book, models.CASCADE, - related_name='rental_sessions', - related_query_name='rental_session', + related_name="rental_sessions", + related_query_name="rental_session", ) state = models.CharField(max_length=7, choices=STATES, default=NEW) @@ -103,18 +101,18 @@ class ExchangeRate(models.Model): from_currency = models.ForeignKey( Currency, models.CASCADE, - related_name='rates_from', + related_name="rates_from", ) to_currency = models.ForeignKey( Currency, models.CASCADE, - related_name='rates_to', + related_name="rates_to", ) rate = models.DecimalField(max_digits=6, decimal_places=4) class BookDailySales(models.Model): - book = models.ForeignKey(Book, models.CASCADE, related_name='daily_sales') + book = models.ForeignKey(Book, models.CASCADE, related_name="daily_sales") sale_date = models.DateField() currency = models.ForeignKey(Currency, models.CASCADE) seller = models.ForeignKey(Seller, models.CASCADE) diff --git a/tests/filtered_relation/tests.py b/tests/filtered_relation/tests.py index 5ab273aeb5..bf099f763a 100644 --- a/tests/filtered_relation/tests.py +++ b/tests/filtered_relation/tests.py @@ -4,43 +4,57 @@ from unittest import mock from django.db import connection, transaction from django.db.models import ( - Case, Count, DecimalField, F, FilteredRelation, Q, Sum, When, + Case, + Count, + DecimalField, + F, + FilteredRelation, + Q, + Sum, + When, ) from django.test import TestCase from django.test.testcases import skipUnlessDBFeature from .models import ( - Author, Book, BookDailySales, Borrower, Currency, Editor, ExchangeRate, - RentalSession, Reservation, Seller, + Author, + Book, + BookDailySales, + Borrower, + Currency, + Editor, + ExchangeRate, + RentalSession, + Reservation, + Seller, ) class FilteredRelationTests(TestCase): - @classmethod def setUpTestData(cls): - cls.author1 = Author.objects.create(name='Alice') - cls.author2 = Author.objects.create(name='Jane') - cls.editor_a = Editor.objects.create(name='a') - cls.editor_b = Editor.objects.create(name='b') + cls.author1 = Author.objects.create(name="Alice") + cls.author2 = Author.objects.create(name="Jane") + cls.editor_a = Editor.objects.create(name="a") + cls.editor_b = Editor.objects.create(name="b") cls.book1 = Book.objects.create( - title='Poem by Alice', + title="Poem by Alice", editor=cls.editor_a, author=cls.author1, ) cls.book1.generic_author.set([cls.author2]) cls.book2 = Book.objects.create( - title='The book by Jane A', + title="The book by Jane A", editor=cls.editor_b, author=cls.author2, ) cls.book3 = Book.objects.create( - title='The book by Jane B', + title="The book by Jane B", editor=cls.editor_b, author=cls.author2, ) cls.book4 = Book.objects.create( - title='The book by Alice', + title="The book by Alice", editor=cls.editor_a, author=cls.author1, ) @@ -48,81 +62,124 @@ class FilteredRelationTests(TestCase): cls.author1.favorite_books.add(cls.book3) def test_select_related(self): - qs = Author.objects.annotate( - book_join=FilteredRelation('book'), - ).select_related('book_join__editor').order_by('pk', 'book_join__pk') + qs = ( + Author.objects.annotate( + book_join=FilteredRelation("book"), + ) + .select_related("book_join__editor") + .order_by("pk", "book_join__pk") + ) with self.assertNumQueries(1): - self.assertQuerysetEqual(qs, [ - (self.author1, self.book1, self.editor_a, self.author1), - (self.author1, self.book4, self.editor_a, self.author1), - (self.author2, self.book2, self.editor_b, self.author2), - (self.author2, self.book3, self.editor_b, self.author2), - ], lambda x: (x, x.book_join, x.book_join.editor, x.book_join.author)) + self.assertQuerysetEqual( + qs, + [ + (self.author1, self.book1, self.editor_a, self.author1), + (self.author1, self.book4, self.editor_a, self.author1), + (self.author2, self.book2, self.editor_b, self.author2), + (self.author2, self.book3, self.editor_b, self.author2), + ], + lambda x: (x, x.book_join, x.book_join.editor, x.book_join.author), + ) def test_select_related_multiple(self): - qs = Book.objects.annotate( - author_join=FilteredRelation('author'), - editor_join=FilteredRelation('editor'), - ).select_related('author_join', 'editor_join').order_by('pk') - self.assertQuerysetEqual(qs, [ - (self.book1, self.author1, self.editor_a), - (self.book2, self.author2, self.editor_b), - (self.book3, self.author2, self.editor_b), - (self.book4, self.author1, self.editor_a), - ], lambda x: (x, x.author_join, x.editor_join)) + qs = ( + Book.objects.annotate( + author_join=FilteredRelation("author"), + editor_join=FilteredRelation("editor"), + ) + .select_related("author_join", "editor_join") + .order_by("pk") + ) + self.assertQuerysetEqual( + qs, + [ + (self.book1, self.author1, self.editor_a), + (self.book2, self.author2, self.editor_b), + (self.book3, self.author2, self.editor_b), + (self.book4, self.author1, self.editor_a), + ], + lambda x: (x, x.author_join, x.editor_join), + ) def test_select_related_with_empty_relation(self): - qs = Author.objects.annotate( - book_join=FilteredRelation('book', condition=Q(pk=-1)), - ).select_related('book_join').order_by('pk') + qs = ( + Author.objects.annotate( + book_join=FilteredRelation("book", condition=Q(pk=-1)), + ) + .select_related("book_join") + .order_by("pk") + ) self.assertSequenceEqual(qs, [self.author1, self.author2]) def test_select_related_foreign_key(self): - qs = Book.objects.annotate( - author_join=FilteredRelation('author'), - ).select_related('author_join').order_by('pk') + qs = ( + Book.objects.annotate( + author_join=FilteredRelation("author"), + ) + .select_related("author_join") + .order_by("pk") + ) with self.assertNumQueries(1): - self.assertQuerysetEqual(qs, [ - (self.book1, self.author1), - (self.book2, self.author2), - (self.book3, self.author2), - (self.book4, self.author1), - ], lambda x: (x, x.author_join)) - - @skipUnlessDBFeature('has_select_for_update', 'has_select_for_update_of') - def test_select_related_foreign_key_for_update_of(self): - with transaction.atomic(): - qs = Book.objects.annotate( - author_join=FilteredRelation('author'), - ).select_related('author_join').select_for_update(of=('self',)).order_by('pk') - with self.assertNumQueries(1): - self.assertQuerysetEqual(qs, [ + self.assertQuerysetEqual( + qs, + [ (self.book1, self.author1), (self.book2, self.author2), (self.book3, self.author2), (self.book4, self.author1), - ], lambda x: (x, x.author_join)) + ], + lambda x: (x, x.author_join), + ) + + @skipUnlessDBFeature("has_select_for_update", "has_select_for_update_of") + def test_select_related_foreign_key_for_update_of(self): + with transaction.atomic(): + qs = ( + Book.objects.annotate( + author_join=FilteredRelation("author"), + ) + .select_related("author_join") + .select_for_update(of=("self",)) + .order_by("pk") + ) + with self.assertNumQueries(1): + self.assertQuerysetEqual( + qs, + [ + (self.book1, self.author1), + (self.book2, self.author2), + (self.book3, self.author2), + (self.book4, self.author1), + ], + lambda x: (x, x.author_join), + ) def test_without_join(self): self.assertSequenceEqual( Author.objects.annotate( - book_alice=FilteredRelation('book', condition=Q(book__title__iexact='poem by alice')), + book_alice=FilteredRelation( + "book", condition=Q(book__title__iexact="poem by alice") + ), ), - [self.author1, self.author2] + [self.author1, self.author2], ) def test_with_join(self): self.assertSequenceEqual( Author.objects.annotate( - book_alice=FilteredRelation('book', condition=Q(book__title__iexact='poem by alice')), + book_alice=FilteredRelation( + "book", condition=Q(book__title__iexact="poem by alice") + ), ).filter(book_alice__isnull=False), - [self.author1] + [self.author1], ) def test_with_exclude(self): self.assertSequenceEqual( Author.objects.annotate( - book_alice=FilteredRelation('book', condition=Q(book__title__iexact='poem by alice')), + book_alice=FilteredRelation( + "book", condition=Q(book__title__iexact="poem by alice") + ), ).exclude(book_alice__isnull=False), [self.author2], ) @@ -131,55 +188,72 @@ class FilteredRelationTests(TestCase): self.assertSequenceEqual( Author.objects.annotate( book_alice=FilteredRelation( - 'book', condition=Q( - Q(book__title__iexact='poem by alice') | - Q(book__state=Book.RENTED) + "book", + condition=Q( + Q(book__title__iexact="poem by alice") + | Q(book__state=Book.RENTED) ), ), ).filter(book_alice__isnull=False), - [self.author1] + [self.author1], ) def test_internal_queryset_alias_mapping(self): queryset = Author.objects.annotate( - book_alice=FilteredRelation('book', condition=Q(book__title__iexact='poem by alice')), + book_alice=FilteredRelation( + "book", condition=Q(book__title__iexact="poem by alice") + ), ).filter(book_alice__isnull=False) self.assertIn( - 'INNER JOIN {} book_alice ON'.format(connection.ops.quote_name('filtered_relation_book')), - str(queryset.query) + "INNER JOIN {} book_alice ON".format( + connection.ops.quote_name("filtered_relation_book") + ), + str(queryset.query), ) def test_with_multiple_filter(self): self.assertSequenceEqual( Author.objects.annotate( book_editor_a=FilteredRelation( - 'book', - condition=Q(book__title__icontains='book', book__editor_id=self.editor_a.pk), + "book", + condition=Q( + book__title__icontains="book", book__editor_id=self.editor_a.pk + ), ), ).filter(book_editor_a__isnull=False), - [self.author1] + [self.author1], ) def test_multiple_times(self): self.assertSequenceEqual( Author.objects.annotate( - book_title_alice=FilteredRelation('book', condition=Q(book__title__icontains='alice')), - ).filter(book_title_alice__isnull=False).filter(book_title_alice__isnull=False).distinct(), - [self.author1] + book_title_alice=FilteredRelation( + "book", condition=Q(book__title__icontains="alice") + ), + ) + .filter(book_title_alice__isnull=False) + .filter(book_title_alice__isnull=False) + .distinct(), + [self.author1], ) def test_exclude_relation_with_join(self): self.assertSequenceEqual( Author.objects.annotate( - book_alice=FilteredRelation('book', condition=~Q(book__title__icontains='alice')), - ).filter(book_alice__isnull=False).distinct(), - [self.author2] + book_alice=FilteredRelation( + "book", condition=~Q(book__title__icontains="alice") + ), + ) + .filter(book_alice__isnull=False) + .distinct(), + [self.author2], ) def test_with_m2m(self): qs = Author.objects.annotate( favorite_books_written_by_jane=FilteredRelation( - 'favorite_books', condition=Q(favorite_books__in=[self.book2]), + "favorite_books", + condition=Q(favorite_books__in=[self.book2]), ), ).filter(favorite_books_written_by_jane__isnull=False) self.assertSequenceEqual(qs, [self.author1]) @@ -187,79 +261,120 @@ class FilteredRelationTests(TestCase): def test_with_m2m_deep(self): qs = Author.objects.annotate( favorite_books_written_by_jane=FilteredRelation( - 'favorite_books', condition=Q(favorite_books__author=self.author2), + "favorite_books", + condition=Q(favorite_books__author=self.author2), ), - ).filter(favorite_books_written_by_jane__title='The book by Jane B') + ).filter(favorite_books_written_by_jane__title="The book by Jane B") self.assertSequenceEqual(qs, [self.author1]) def test_with_m2m_multijoin(self): - qs = Author.objects.annotate( - favorite_books_written_by_jane=FilteredRelation( - 'favorite_books', condition=Q(favorite_books__author=self.author2), + qs = ( + Author.objects.annotate( + favorite_books_written_by_jane=FilteredRelation( + "favorite_books", + condition=Q(favorite_books__author=self.author2), + ) ) - ).filter(favorite_books_written_by_jane__editor__name='b').distinct() + .filter(favorite_books_written_by_jane__editor__name="b") + .distinct() + ) self.assertSequenceEqual(qs, [self.author1]) def test_values_list(self): self.assertSequenceEqual( Author.objects.annotate( - book_alice=FilteredRelation('book', condition=Q(book__title__iexact='poem by alice')), - ).filter(book_alice__isnull=False).values_list('book_alice__title', flat=True), - ['Poem by Alice'] + book_alice=FilteredRelation( + "book", condition=Q(book__title__iexact="poem by alice") + ), + ) + .filter(book_alice__isnull=False) + .values_list("book_alice__title", flat=True), + ["Poem by Alice"], ) def test_values(self): self.assertSequenceEqual( Author.objects.annotate( - book_alice=FilteredRelation('book', condition=Q(book__title__iexact='poem by alice')), - ).filter(book_alice__isnull=False).values(), - [{'id': self.author1.pk, 'name': 'Alice', 'content_type_id': None, 'object_id': None}] + book_alice=FilteredRelation( + "book", condition=Q(book__title__iexact="poem by alice") + ), + ) + .filter(book_alice__isnull=False) + .values(), + [ + { + "id": self.author1.pk, + "name": "Alice", + "content_type_id": None, + "object_id": None, + } + ], ) def test_extra(self): self.assertSequenceEqual( Author.objects.annotate( - book_alice=FilteredRelation('book', condition=Q(book__title__iexact='poem by alice')), - ).filter(book_alice__isnull=False).extra(where=['1 = 1']), - [self.author1] + book_alice=FilteredRelation( + "book", condition=Q(book__title__iexact="poem by alice") + ), + ) + .filter(book_alice__isnull=False) + .extra(where=["1 = 1"]), + [self.author1], ) - @skipUnlessDBFeature('supports_select_union') + @skipUnlessDBFeature("supports_select_union") def test_union(self): qs1 = Author.objects.annotate( - book_alice=FilteredRelation('book', condition=Q(book__title__iexact='poem by alice')), + book_alice=FilteredRelation( + "book", condition=Q(book__title__iexact="poem by alice") + ), ).filter(book_alice__isnull=False) qs2 = Author.objects.annotate( - book_jane=FilteredRelation('book', condition=Q(book__title__iexact='the book by jane a')), + book_jane=FilteredRelation( + "book", condition=Q(book__title__iexact="the book by jane a") + ), ).filter(book_jane__isnull=False) self.assertSequenceEqual(qs1.union(qs2), [self.author1, self.author2]) - @skipUnlessDBFeature('supports_select_intersection') + @skipUnlessDBFeature("supports_select_intersection") def test_intersection(self): qs1 = Author.objects.annotate( - book_alice=FilteredRelation('book', condition=Q(book__title__iexact='poem by alice')), + book_alice=FilteredRelation( + "book", condition=Q(book__title__iexact="poem by alice") + ), ).filter(book_alice__isnull=False) qs2 = Author.objects.annotate( - book_jane=FilteredRelation('book', condition=Q(book__title__iexact='the book by jane a')), + book_jane=FilteredRelation( + "book", condition=Q(book__title__iexact="the book by jane a") + ), ).filter(book_jane__isnull=False) self.assertSequenceEqual(qs1.intersection(qs2), []) - @skipUnlessDBFeature('supports_select_difference') + @skipUnlessDBFeature("supports_select_difference") def test_difference(self): qs1 = Author.objects.annotate( - book_alice=FilteredRelation('book', condition=Q(book__title__iexact='poem by alice')), + book_alice=FilteredRelation( + "book", condition=Q(book__title__iexact="poem by alice") + ), ).filter(book_alice__isnull=False) qs2 = Author.objects.annotate( - book_jane=FilteredRelation('book', condition=Q(book__title__iexact='the book by jane a')), + book_jane=FilteredRelation( + "book", condition=Q(book__title__iexact="the book by jane a") + ), ).filter(book_jane__isnull=False) self.assertSequenceEqual(qs1.difference(qs2), [self.author1]) def test_select_for_update(self): self.assertSequenceEqual( Author.objects.annotate( - book_jane=FilteredRelation('book', condition=Q(book__title__iexact='the book by jane a')), - ).filter(book_jane__isnull=False).select_for_update(), - [self.author2] + book_jane=FilteredRelation( + "book", condition=Q(book__title__iexact="the book by jane a") + ), + ) + .filter(book_jane__isnull=False) + .select_for_update(), + [self.author2], ) def test_defer(self): @@ -267,127 +382,198 @@ class FilteredRelationTests(TestCase): with self.assertNumQueries(2): self.assertQuerysetEqual( Author.objects.annotate( - book_alice=FilteredRelation('book', condition=Q(book__title__iexact='poem by alice')), - ).filter(book_alice__isnull=False).select_related('book_alice').defer('book_alice__title'), - ['Poem by Alice'], lambda author: author.book_alice.title + book_alice=FilteredRelation( + "book", condition=Q(book__title__iexact="poem by alice") + ), + ) + .filter(book_alice__isnull=False) + .select_related("book_alice") + .defer("book_alice__title"), + ["Poem by Alice"], + lambda author: author.book_alice.title, ) def test_only_not_supported(self): - msg = 'only() is not supported with FilteredRelation.' + msg = "only() is not supported with FilteredRelation." with self.assertRaisesMessage(ValueError, msg): Author.objects.annotate( - book_alice=FilteredRelation('book', condition=Q(book__title__iexact='poem by alice')), - ).filter(book_alice__isnull=False).select_related('book_alice').only('book_alice__state') + book_alice=FilteredRelation( + "book", condition=Q(book__title__iexact="poem by alice") + ), + ).filter(book_alice__isnull=False).select_related("book_alice").only( + "book_alice__state" + ) def test_as_subquery(self): inner_qs = Author.objects.annotate( - book_alice=FilteredRelation('book', condition=Q(book__title__iexact='poem by alice')), + book_alice=FilteredRelation( + "book", condition=Q(book__title__iexact="poem by alice") + ), ).filter(book_alice__isnull=False) qs = Author.objects.filter(id__in=inner_qs) self.assertSequenceEqual(qs, [self.author1]) def test_nested_foreign_key(self): - qs = Author.objects.annotate( - book_editor_worked_with=FilteredRelation( - 'book__editor', - condition=Q(book__title__icontains='book by'), - ), - ).filter( - book_editor_worked_with__isnull=False, - ).select_related( - 'book_editor_worked_with', - ).order_by('pk', 'book_editor_worked_with__pk') + qs = ( + Author.objects.annotate( + book_editor_worked_with=FilteredRelation( + "book__editor", + condition=Q(book__title__icontains="book by"), + ), + ) + .filter( + book_editor_worked_with__isnull=False, + ) + .select_related( + "book_editor_worked_with", + ) + .order_by("pk", "book_editor_worked_with__pk") + ) with self.assertNumQueries(1): - self.assertQuerysetEqual(qs, [ - (self.author1, self.editor_a), - (self.author2, self.editor_b), - (self.author2, self.editor_b), - ], lambda x: (x, x.book_editor_worked_with)) + self.assertQuerysetEqual( + qs, + [ + (self.author1, self.editor_a), + (self.author2, self.editor_b), + (self.author2, self.editor_b), + ], + lambda x: (x, x.book_editor_worked_with), + ) def test_nested_foreign_key_nested_field(self): - qs = Author.objects.annotate( - book_editor_worked_with=FilteredRelation( - 'book__editor', - condition=Q(book__title__icontains='book by') - ), - ).filter( - book_editor_worked_with__isnull=False, - ).values( - 'name', 'book_editor_worked_with__name', - ).order_by('name', 'book_editor_worked_with__name').distinct() - self.assertSequenceEqual(qs, [ - {'name': self.author1.name, 'book_editor_worked_with__name': self.editor_a.name}, - {'name': self.author2.name, 'book_editor_worked_with__name': self.editor_b.name}, - ]) + qs = ( + Author.objects.annotate( + book_editor_worked_with=FilteredRelation( + "book__editor", condition=Q(book__title__icontains="book by") + ), + ) + .filter( + book_editor_worked_with__isnull=False, + ) + .values( + "name", + "book_editor_worked_with__name", + ) + .order_by("name", "book_editor_worked_with__name") + .distinct() + ) + self.assertSequenceEqual( + qs, + [ + { + "name": self.author1.name, + "book_editor_worked_with__name": self.editor_a.name, + }, + { + "name": self.author2.name, + "book_editor_worked_with__name": self.editor_b.name, + }, + ], + ) def test_nested_foreign_key_filtered_base_object(self): - qs = Author.objects.annotate( - alice_editors=FilteredRelation( - 'book__editor', - condition=Q(name='Alice'), - ), - ).values( - 'name', 'alice_editors__pk', - ).order_by('name', 'alice_editors__name').distinct() - self.assertSequenceEqual(qs, [ - {'name': self.author1.name, 'alice_editors__pk': self.editor_a.pk}, - {'name': self.author2.name, 'alice_editors__pk': None}, - ]) + qs = ( + Author.objects.annotate( + alice_editors=FilteredRelation( + "book__editor", + condition=Q(name="Alice"), + ), + ) + .values( + "name", + "alice_editors__pk", + ) + .order_by("name", "alice_editors__name") + .distinct() + ) + self.assertSequenceEqual( + qs, + [ + {"name": self.author1.name, "alice_editors__pk": self.editor_a.pk}, + {"name": self.author2.name, "alice_editors__pk": None}, + ], + ) def test_nested_m2m_filtered(self): - qs = Book.objects.annotate( - favorite_book=FilteredRelation( - 'author__favorite_books', - condition=Q(author__favorite_books__title__icontains='book by') - ), - ).values( - 'title', 'favorite_book__pk', - ).order_by('title', 'favorite_book__title') - self.assertSequenceEqual(qs, [ - {'title': self.book1.title, 'favorite_book__pk': self.book2.pk}, - {'title': self.book1.title, 'favorite_book__pk': self.book3.pk}, - {'title': self.book4.title, 'favorite_book__pk': self.book2.pk}, - {'title': self.book4.title, 'favorite_book__pk': self.book3.pk}, - {'title': self.book2.title, 'favorite_book__pk': None}, - {'title': self.book3.title, 'favorite_book__pk': None}, - ]) + qs = ( + Book.objects.annotate( + favorite_book=FilteredRelation( + "author__favorite_books", + condition=Q(author__favorite_books__title__icontains="book by"), + ), + ) + .values( + "title", + "favorite_book__pk", + ) + .order_by("title", "favorite_book__title") + ) + self.assertSequenceEqual( + qs, + [ + {"title": self.book1.title, "favorite_book__pk": self.book2.pk}, + {"title": self.book1.title, "favorite_book__pk": self.book3.pk}, + {"title": self.book4.title, "favorite_book__pk": self.book2.pk}, + {"title": self.book4.title, "favorite_book__pk": self.book3.pk}, + {"title": self.book2.title, "favorite_book__pk": None}, + {"title": self.book3.title, "favorite_book__pk": None}, + ], + ) def test_nested_chained_relations(self): - qs = Author.objects.annotate( - my_books=FilteredRelation( - 'book', condition=Q(book__title__icontains='book by'), - ), - preferred_by_authors=FilteredRelation( - 'my_books__preferred_by_authors', - condition=Q(my_books__preferred_by_authors__name='Alice'), - ), - ).annotate( - author=F('name'), - book_title=F('my_books__title'), - preferred_by_author_pk=F('preferred_by_authors'), - ).order_by('author', 'book_title', 'preferred_by_author_pk') - self.assertQuerysetEqual(qs, [ - ('Alice', 'The book by Alice', None), - ('Jane', 'The book by Jane A', self.author1.pk), - ('Jane', 'The book by Jane B', self.author1.pk), - ], lambda x: (x.author, x.book_title, x.preferred_by_author_pk)) + qs = ( + Author.objects.annotate( + my_books=FilteredRelation( + "book", + condition=Q(book__title__icontains="book by"), + ), + preferred_by_authors=FilteredRelation( + "my_books__preferred_by_authors", + condition=Q(my_books__preferred_by_authors__name="Alice"), + ), + ) + .annotate( + author=F("name"), + book_title=F("my_books__title"), + preferred_by_author_pk=F("preferred_by_authors"), + ) + .order_by("author", "book_title", "preferred_by_author_pk") + ) + self.assertQuerysetEqual( + qs, + [ + ("Alice", "The book by Alice", None), + ("Jane", "The book by Jane A", self.author1.pk), + ("Jane", "The book by Jane B", self.author1.pk), + ], + lambda x: (x.author, x.book_title, x.preferred_by_author_pk), + ) def test_deep_nested_foreign_key(self): - qs = Book.objects.annotate( - author_favorite_book_editor=FilteredRelation( - 'author__favorite_books__editor', - condition=Q(author__favorite_books__title__icontains='Jane A'), - ), - ).filter( - author_favorite_book_editor__isnull=False, - ).select_related( - 'author_favorite_book_editor', - ).order_by('pk', 'author_favorite_book_editor__pk') + qs = ( + Book.objects.annotate( + author_favorite_book_editor=FilteredRelation( + "author__favorite_books__editor", + condition=Q(author__favorite_books__title__icontains="Jane A"), + ), + ) + .filter( + author_favorite_book_editor__isnull=False, + ) + .select_related( + "author_favorite_book_editor", + ) + .order_by("pk", "author_favorite_book_editor__pk") + ) with self.assertNumQueries(1): - self.assertQuerysetEqual(qs, [ - (self.book1, self.editor_b), - (self.book4, self.editor_b), - ], lambda x: (x, x.author_favorite_book_editor)) + self.assertQuerysetEqual( + qs, + [ + (self.book1, self.editor_b), + (self.book4, self.editor_b), + ], + lambda x: (x, x.author_favorite_book_editor), + ) def test_relation_name_lookup(self): msg = ( @@ -397,8 +583,8 @@ class FilteredRelationTests(TestCase): with self.assertRaisesMessage(ValueError, msg): Author.objects.annotate( book_title=FilteredRelation( - 'book__title__icontains', - condition=Q(book__title='Poem by Alice'), + "book__title__icontains", + condition=Q(book__title="Poem by Alice"), ), ) @@ -410,8 +596,8 @@ class FilteredRelationTests(TestCase): with self.assertRaisesMessage(ValueError, msg): Author.objects.annotate( book_editor=FilteredRelation( - 'book__editor', - condition=Q(book__author__name__icontains='book'), + "book__editor", + condition=Q(book__author__name__icontains="book"), ), ) @@ -424,63 +610,66 @@ class FilteredRelationTests(TestCase): with self.assertRaisesMessage(ValueError, msg): Author.objects.annotate( book_editor=FilteredRelation( - 'book', - condition=Q(book__editor__name__icontains='b'), + "book", + condition=Q(book__editor__name__icontains="b"), ), ) def test_with_empty_relation_name_error(self): - with self.assertRaisesMessage(ValueError, 'relation_name cannot be empty.'): - FilteredRelation('', condition=Q(blank='')) + with self.assertRaisesMessage(ValueError, "relation_name cannot be empty."): + FilteredRelation("", condition=Q(blank="")) def test_with_condition_as_expression_error(self): - msg = 'condition argument must be a Q() instance.' + msg = "condition argument must be a Q() instance." expression = Case( - When(book__title__iexact='poem by alice', then=True), default=False, + When(book__title__iexact="poem by alice", then=True), + default=False, ) with self.assertRaisesMessage(ValueError, msg): - FilteredRelation('book', condition=expression) + FilteredRelation("book", condition=expression) def test_with_prefetch_related(self): - msg = 'prefetch_related() is not supported with FilteredRelation.' + msg = "prefetch_related() is not supported with FilteredRelation." qs = Author.objects.annotate( - book_title_contains_b=FilteredRelation('book', condition=Q(book__title__icontains='b')), + book_title_contains_b=FilteredRelation( + "book", condition=Q(book__title__icontains="b") + ), ).filter( book_title_contains_b__isnull=False, ) with self.assertRaisesMessage(ValueError, msg): - qs.prefetch_related('book_title_contains_b') + qs.prefetch_related("book_title_contains_b") with self.assertRaisesMessage(ValueError, msg): - qs.prefetch_related('book_title_contains_b__editor') + qs.prefetch_related("book_title_contains_b__editor") def test_with_generic_foreign_key(self): self.assertSequenceEqual( Book.objects.annotate( generic_authored_book=FilteredRelation( - 'generic_author', - condition=Q(generic_author__isnull=False) + "generic_author", condition=Q(generic_author__isnull=False) ), ).filter(generic_authored_book__isnull=False), - [self.book1] + [self.book1], ) def test_eq(self): - self.assertEqual(FilteredRelation('book', condition=Q(book__title='b')), mock.ANY) + self.assertEqual( + FilteredRelation("book", condition=Q(book__title="b")), mock.ANY + ) class FilteredRelationAggregationTests(TestCase): - @classmethod def setUpTestData(cls): - cls.author1 = Author.objects.create(name='Alice') - cls.editor_a = Editor.objects.create(name='a') + cls.author1 = Author.objects.create(name="Alice") + cls.editor_a = Editor.objects.create(name="a") cls.book1 = Book.objects.create( - title='Poem by Alice', + title="Poem by Alice", editor=cls.editor_a, author=cls.author1, ) - cls.borrower1 = Borrower.objects.create(name='Jenny') - cls.borrower2 = Borrower.objects.create(name='Kevin') + cls.borrower1 = Borrower.objects.create(name="Jenny") + cls.borrower2 = Borrower.objects.create(name="Kevin") # borrower 1 reserves, rents, and returns book1. Reservation.objects.create( borrower=cls.borrower1, @@ -517,130 +706,158 @@ class FilteredRelationAggregationTests(TestCase): Goal: Count number of books that are either currently reserved or rented by borrower1 or available. """ - qs = Book.objects.annotate( - is_reserved_or_rented_by=Case( - When(reservation__state=Reservation.NEW, then=F('reservation__borrower__pk')), - When(rental_session__state=RentalSession.NEW, then=F('rental_session__borrower__pk')), - default=None, + qs = ( + Book.objects.annotate( + is_reserved_or_rented_by=Case( + When( + reservation__state=Reservation.NEW, + then=F("reservation__borrower__pk"), + ), + When( + rental_session__state=RentalSession.NEW, + then=F("rental_session__borrower__pk"), + ), + default=None, + ) ) - ).filter( - Q(is_reserved_or_rented_by=self.borrower1.pk) | Q(state=Book.AVAILABLE) - ).distinct() + .filter( + Q(is_reserved_or_rented_by=self.borrower1.pk) | Q(state=Book.AVAILABLE) + ) + .distinct() + ) self.assertEqual(qs.count(), 1) # If count is equal to 1, the same aggregation should return in the # same result but it returns 4. - self.assertSequenceEqual(qs.annotate(total=Count('pk')).values('total'), [{'total': 4}]) + self.assertSequenceEqual( + qs.annotate(total=Count("pk")).values("total"), [{"total": 4}] + ) # With FilteredRelation, the result is as expected (1). - qs = Book.objects.annotate( - active_reservations=FilteredRelation( - 'reservation', condition=Q( - reservation__state=Reservation.NEW, - reservation__borrower=self.borrower1, - ) - ), - ).annotate( - active_rental_sessions=FilteredRelation( - 'rental_session', condition=Q( - rental_session__state=RentalSession.NEW, - rental_session__borrower=self.borrower1, + qs = ( + Book.objects.annotate( + active_reservations=FilteredRelation( + "reservation", + condition=Q( + reservation__state=Reservation.NEW, + reservation__borrower=self.borrower1, + ), + ), + ) + .annotate( + active_rental_sessions=FilteredRelation( + "rental_session", + condition=Q( + rental_session__state=RentalSession.NEW, + rental_session__borrower=self.borrower1, + ), + ), + ) + .filter( + ( + Q(active_reservations__isnull=False) + | Q(active_rental_sessions__isnull=False) ) - ), - ).filter( - (Q(active_reservations__isnull=False) | Q(active_rental_sessions__isnull=False)) | - Q(state=Book.AVAILABLE) - ).distinct() + | Q(state=Book.AVAILABLE) + ) + .distinct() + ) self.assertEqual(qs.count(), 1) - self.assertSequenceEqual(qs.annotate(total=Count('pk')).values('total'), [{'total': 1}]) + self.assertSequenceEqual( + qs.annotate(total=Count("pk")).values("total"), [{"total": 1}] + ) class FilteredRelationAnalyticalAggregationTests(TestCase): @classmethod def setUpTestData(cls): - author = Author.objects.create(name='Author') - editor = Editor.objects.create(name='Editor') + author = Author.objects.create(name="Author") + editor = Editor.objects.create(name="Editor") cls.book1 = Book.objects.create( - title='Poem by Alice', + title="Poem by Alice", editor=editor, author=author, ) cls.book2 = Book.objects.create( - title='The book by Jane A', + title="The book by Jane A", editor=editor, author=author, ) cls.book3 = Book.objects.create( - title='The book by Jane B', + title="The book by Jane B", editor=editor, author=author, ) - cls.seller1 = Seller.objects.create(name='Seller 1') - cls.seller2 = Seller.objects.create(name='Seller 2') - cls.usd = Currency.objects.create(currency='USD') - cls.eur = Currency.objects.create(currency='EUR') + cls.seller1 = Seller.objects.create(name="Seller 1") + cls.seller2 = Seller.objects.create(name="Seller 2") + cls.usd = Currency.objects.create(currency="USD") + cls.eur = Currency.objects.create(currency="EUR") cls.sales_date1 = date(2020, 7, 6) cls.sales_date2 = date(2020, 7, 7) - ExchangeRate.objects.bulk_create([ - ExchangeRate( - rate_date=cls.sales_date1, - from_currency=cls.usd, - to_currency=cls.eur, - rate=0.40, - ), - ExchangeRate( - rate_date=cls.sales_date1, - from_currency=cls.eur, - to_currency=cls.usd, - rate=1.60, - ), - ExchangeRate( - rate_date=cls.sales_date2, - from_currency=cls.usd, - to_currency=cls.eur, - rate=0.50, - ), - ExchangeRate( - rate_date=cls.sales_date2, - from_currency=cls.eur, - to_currency=cls.usd, - rate=1.50, - ), - ExchangeRate( - rate_date=cls.sales_date2, - from_currency=cls.usd, - to_currency=cls.usd, - rate=1.00, - ), - ]) - BookDailySales.objects.bulk_create([ - BookDailySales( - book=cls.book1, - sale_date=cls.sales_date1, - currency=cls.usd, - sales=100.00, - seller=cls.seller1, - ), - BookDailySales( - book=cls.book2, - sale_date=cls.sales_date1, - currency=cls.eur, - sales=200.00, - seller=cls.seller1, - ), - BookDailySales( - book=cls.book1, - sale_date=cls.sales_date2, - currency=cls.usd, - sales=50.00, - seller=cls.seller2, - ), - BookDailySales( - book=cls.book2, - sale_date=cls.sales_date2, - currency=cls.eur, - sales=100.00, - seller=cls.seller2, - ), - ]) + ExchangeRate.objects.bulk_create( + [ + ExchangeRate( + rate_date=cls.sales_date1, + from_currency=cls.usd, + to_currency=cls.eur, + rate=0.40, + ), + ExchangeRate( + rate_date=cls.sales_date1, + from_currency=cls.eur, + to_currency=cls.usd, + rate=1.60, + ), + ExchangeRate( + rate_date=cls.sales_date2, + from_currency=cls.usd, + to_currency=cls.eur, + rate=0.50, + ), + ExchangeRate( + rate_date=cls.sales_date2, + from_currency=cls.eur, + to_currency=cls.usd, + rate=1.50, + ), + ExchangeRate( + rate_date=cls.sales_date2, + from_currency=cls.usd, + to_currency=cls.usd, + rate=1.00, + ), + ] + ) + BookDailySales.objects.bulk_create( + [ + BookDailySales( + book=cls.book1, + sale_date=cls.sales_date1, + currency=cls.usd, + sales=100.00, + seller=cls.seller1, + ), + BookDailySales( + book=cls.book2, + sale_date=cls.sales_date1, + currency=cls.eur, + sales=200.00, + seller=cls.seller1, + ), + BookDailySales( + book=cls.book1, + sale_date=cls.sales_date2, + currency=cls.usd, + sales=50.00, + seller=cls.seller2, + ), + BookDailySales( + book=cls.book2, + sale_date=cls.sales_date2, + currency=cls.eur, + sales=100.00, + seller=cls.seller2, + ), + ] + ) def test_aggregate(self): tests = [ @@ -649,25 +866,37 @@ class FilteredRelationAnalyticalAggregationTests(TestCase): ] for condition in tests: with self.subTest(condition=condition): - qs = Book.objects.annotate( - recent_sales=FilteredRelation('daily_sales', condition=condition), - recent_sales_rates=FilteredRelation( - 'recent_sales__currency__rates_from', - condition=Q( - recent_sales__currency__rates_from__rate_date=F('recent_sales__sale_date'), - recent_sales__currency__rates_from__to_currency=self.usd, + qs = ( + Book.objects.annotate( + recent_sales=FilteredRelation( + "daily_sales", condition=condition ), - ), - ).annotate( - sales_sum=Sum( - F('recent_sales__sales') * F('recent_sales_rates__rate'), - output_field=DecimalField(), - ), - ).values('title', 'sales_sum').order_by( - F('sales_sum').desc(nulls_last=True), + recent_sales_rates=FilteredRelation( + "recent_sales__currency__rates_from", + condition=Q( + recent_sales__currency__rates_from__rate_date=F( + "recent_sales__sale_date" + ), + recent_sales__currency__rates_from__to_currency=self.usd, + ), + ), + ) + .annotate( + sales_sum=Sum( + F("recent_sales__sales") * F("recent_sales_rates__rate"), + output_field=DecimalField(), + ), + ) + .values("title", "sales_sum") + .order_by( + F("sales_sum").desc(nulls_last=True), + ) + ) + self.assertSequenceEqual( + qs, + [ + {"title": self.book2.title, "sales_sum": Decimal(150.00)}, + {"title": self.book1.title, "sales_sum": Decimal(50.00)}, + {"title": self.book3.title, "sales_sum": None}, + ], ) - self.assertSequenceEqual(qs, [ - {'title': self.book2.title, 'sales_sum': Decimal(150.00)}, - {'title': self.book1.title, 'sales_sum': Decimal(50.00)}, - {'title': self.book3.title, 'sales_sum': None}, - ]) |