|  | 
|  | 1 | +import unittest | 
| 1 | 2 | from datetime import date | 
|  | 3 | +from operator import attrgetter | 
| 2 | 4 | 
 | 
| 3 | 5 | from django.core.exceptions import FieldDoesNotExist | 
| 4 | 6 | from django.db import connection, models | 
|  | 7 | +from django.db.models.expressions import Value | 
| 5 | 8 | from django.test import SimpleTestCase, TestCase | 
| 6 | 9 | from django.test.utils import CaptureQueriesContext, isolate_apps | 
| 7 | 10 | 
 | 
| 8 |  | -from django_mongodb_backend.fields import EmbeddedModelArrayField | 
|  | 11 | +from django_mongodb_backend.fields import ArrayField, EmbeddedModelArrayField | 
| 9 | 12 | from django_mongodb_backend.models import EmbeddedModel | 
| 10 | 13 | 
 | 
| 11 |  | -from .models import Artifact, Exhibit, Movie, Restoration, Review, Section, Tour | 
|  | 14 | +from .models import Artifact, Audit, Exhibit, Movie, Restoration, Review, Section, Tour | 
| 12 | 15 | 
 | 
| 13 | 16 | 
 | 
| 14 | 17 | class MethodTests(SimpleTestCase): | 
| @@ -116,6 +119,7 @@ def setUpTestData(cls): | 
| 116 | 119 |  ], | 
| 117 | 120 |  ) | 
| 118 | 121 |  ], | 
|  | 122 | + main_section=Section(number=2), | 
| 119 | 123 |  ) | 
| 120 | 124 |  cls.lost_empires = Exhibit.objects.create( | 
| 121 | 125 |  name="Lost Empires", | 
| @@ -146,6 +150,9 @@ def setUpTestData(cls): | 
| 146 | 150 |  cls.egypt_tour = Tour.objects.create(guide="Amira", exhibit=cls.egypt) | 
| 147 | 151 |  cls.wonders_tour = Tour.objects.create(guide="Carlos", exhibit=cls.wonders) | 
| 148 | 152 |  cls.lost_tour = Tour.objects.create(guide="Yelena", exhibit=cls.lost_empires) | 
|  | 153 | + cls.audit_1 = Audit.objects.create(section_number=1, reviewed=True) | 
|  | 154 | + cls.audit_2 = Audit.objects.create(section_number=2, reviewed=True) | 
|  | 155 | + cls.audit_3 = Audit.objects.create(section_number=5, reviewed=False) | 
| 149 | 156 | 
 | 
| 150 | 157 |  def test_exact(self): | 
| 151 | 158 |  self.assertCountEqual( | 
| @@ -284,6 +291,71 @@ def test_foreign_field_with_slice(self): | 
| 284 | 291 |  qs = Tour.objects.filter(exhibit__sections__0_2__number__in=[1, 2]) | 
| 285 | 292 |  self.assertCountEqual(qs, [self.wonders_tour, self.egypt_tour]) | 
| 286 | 293 | 
 | 
|  | 294 | + def test_subquery_numeric_lookups(self): | 
|  | 295 | + subquery = Audit.objects.filter( | 
|  | 296 | + section_number__in=models.OuterRef("sections__number") | 
|  | 297 | + ).values("section_number")[:1] | 
|  | 298 | + tests = [ | 
|  | 299 | + ("exact", [self.egypt, self.new_descoveries, self.wonders]), | 
|  | 300 | + ("lt", []), | 
|  | 301 | + ("lte", [self.egypt, self.new_descoveries, self.wonders]), | 
|  | 302 | + ("gt", [self.wonders]), | 
|  | 303 | + ("gte", [self.egypt, self.new_descoveries, self.wonders]), | 
|  | 304 | + ] | 
|  | 305 | + for lookup, expected in tests: | 
|  | 306 | + with self.subTest(lookup=lookup): | 
|  | 307 | + kwargs = {f"sections__number__{lookup}": subquery} | 
|  | 308 | + self.assertCountEqual(Exhibit.objects.filter(**kwargs), expected) | 
|  | 309 | + | 
|  | 310 | + def test_subquery_in_lookup(self): | 
|  | 311 | + subquery = Audit.objects.filter(reviewed=True).values_list("section_number", flat=True) | 
|  | 312 | + result = Exhibit.objects.filter(sections__number__in=subquery) | 
|  | 313 | + self.assertCountEqual(result, [self.wonders, self.new_descoveries, self.egypt]) | 
|  | 314 | + | 
|  | 315 | + def test_array_as_rhs(self): | 
|  | 316 | + result = Exhibit.objects.filter(main_section__number__in=models.F("sections__number")) | 
|  | 317 | + self.assertCountEqual(result, [self.new_descoveries]) | 
|  | 318 | + | 
|  | 319 | + def test_array_annotation_lookup(self): | 
|  | 320 | + result = Exhibit.objects.annotate(section_numbers=models.F("main_section__number")).filter( | 
|  | 321 | + section_numbers__in=models.F("sections__number") | 
|  | 322 | + ) | 
|  | 323 | + self.assertCountEqual(result, [self.new_descoveries]) | 
|  | 324 | + | 
|  | 325 | + def test_array_as_rhs_for_arrayfield_lookups(self): | 
|  | 326 | + tests = [ | 
|  | 327 | + ("exact", [self.wonders]), | 
|  | 328 | + ("lt", [self.new_descoveries]), | 
|  | 329 | + ("lte", [self.wonders, self.new_descoveries]), | 
|  | 330 | + ("gt", [self.egypt, self.lost_empires]), | 
|  | 331 | + ("gte", [self.egypt, self.wonders, self.lost_empires]), | 
|  | 332 | + ("overlap", [self.egypt, self.wonders, self.new_descoveries]), | 
|  | 333 | + ("contained_by", [self.wonders]), | 
|  | 334 | + ("contains", [self.egypt, self.wonders, self.new_descoveries, self.lost_empires]), | 
|  | 335 | + ] | 
|  | 336 | + for lookup, expected in tests: | 
|  | 337 | + with self.subTest(lookup=lookup): | 
|  | 338 | + kwargs = {f"section_numbers__{lookup}": models.F("sections__number")} | 
|  | 339 | + result = Exhibit.objects.annotate( | 
|  | 340 | + section_numbers=Value( | 
|  | 341 | + [1, 2], output_field=ArrayField(base_field=models.IntegerField()) | 
|  | 342 | + ) | 
|  | 343 | + ).filter(**kwargs) | 
|  | 344 | + self.assertCountEqual(result, expected) | 
|  | 345 | + | 
|  | 346 | + @unittest.expectedFailure | 
|  | 347 | + def test_array_annotation_index(self): | 
|  | 348 | + # Slicing and indexing over an annotated EmbeddedModelArrayField would | 
|  | 349 | + # require a refactor of annotation handling. | 
|  | 350 | + result = Exhibit.objects.annotate(section_numbers=models.F("sections__number")).filter( | 
|  | 351 | + section_numbers__0=1 | 
|  | 352 | + ) | 
|  | 353 | + self.assertCountEqual(result, [self.new_descoveries, self.egypt]) | 
|  | 354 | + | 
|  | 355 | + def test_array_annotation(self): | 
|  | 356 | + qs = Exhibit.objects.annotate(section_numbers=models.F("sections__number")).order_by("name") | 
|  | 357 | + self.assertQuerySetEqual(qs, [[1], [], [2], [1, 2]], attrgetter("section_numbers")) | 
|  | 358 | + | 
| 287 | 359 | 
 | 
| 288 | 360 | @isolate_apps("model_fields_") | 
| 289 | 361 | class CheckTests(SimpleTestCase): | 
|  | 
0 commit comments