1
1
import datetime
2
- from django .db .models import Count
2
+ import re
3
+ from django .db .models import Count , Prefetch
3
4
4
5
import pytest
5
6
6
7
from graphene import List , NonNull , ObjectType , Schema , String
7
8
8
9
from ..fields import DjangoListField
9
10
from ..types import DjangoObjectType
10
- from .models import Article as ArticleModel
11
- from .models import Reporter as ReporterModel
11
+ from .models import (
12
+ Article as ArticleModel ,
13
+ Film as FilmModel ,
14
+ FilmDetails as FilmDetailsModel ,
15
+ Reporter as ReporterModel ,
16
+ )
12
17
13
18
14
19
class TestDjangoListField :
@@ -500,3 +505,142 @@ class Query(ObjectType):
500
505
501
506
assert not result .errors
502
507
assert result .data == {"reporters" : [{"firstName" : "Tara" }]}
508
+
509
+ def test_select_related_and_prefetch_related_are_respected (self , django_assert_num_queries ):
510
+ class Article (DjangoObjectType ):
511
+ class Meta :
512
+ model = ArticleModel
513
+ fields = ("headline" , "editor" , "reporter" )
514
+
515
+ class Film (DjangoObjectType ):
516
+ class Meta :
517
+ model = FilmModel
518
+ fields = ("genre" , "details" )
519
+
520
+ class FilmDetail (DjangoObjectType ):
521
+ class Meta :
522
+ model = FilmDetailsModel
523
+ fields = ("location" ,)
524
+
525
+ class Reporter (DjangoObjectType ):
526
+ class Meta :
527
+ model = ReporterModel
528
+ fields = ("first_name" , "articles" , "films" )
529
+
530
+ class Query (ObjectType ):
531
+ articles = DjangoListField (Article )
532
+
533
+ @staticmethod
534
+ def resolve_articles (root , info ):
535
+ # Optimize for querying associated editors and reporters, and the films and film
536
+ # details of those reporters. This is similar to what would happen using a library
537
+ # like https://github.com/tfoxy/graphene-django-optimizer for a query like the one
538
+ # below (albeit simplified and hardcoded here).
539
+ return ArticleModel .objects .select_related ("editor" , "reporter" ).prefetch_related (
540
+ Prefetch ("reporter__films" , queryset = FilmModel .objects .select_related ("details" )),
541
+ )
542
+
543
+ schema = Schema (query = Query )
544
+
545
+ query = """
546
+ query {
547
+ articles {
548
+ headline
549
+
550
+ editor {
551
+ firstName
552
+ }
553
+
554
+ reporter {
555
+ firstName
556
+
557
+ films {
558
+ genre
559
+
560
+ details {
561
+ location
562
+ }
563
+ }
564
+ }
565
+ }
566
+ }
567
+ """
568
+
569
+ r1 = ReporterModel .objects .create (first_name = "Tara" , last_name = "West" )
570
+ r2 = ReporterModel .objects .create (first_name = "Debra" , last_name = "Payne" )
571
+
572
+ ArticleModel .objects .create (
573
+ headline = "Amazing news" ,
574
+ reporter = r1 ,
575
+ pub_date = datetime .date .today (),
576
+ pub_date_time = datetime .datetime .now (),
577
+ editor = r2 ,
578
+ )
579
+ ArticleModel .objects .create (
580
+ headline = "Not so good news" ,
581
+ reporter = r2 ,
582
+ pub_date = datetime .date .today (),
583
+ pub_date_time = datetime .datetime .now (),
584
+ editor = r1 ,
585
+ )
586
+
587
+ film1 = FilmModel .objects .create (genre = "ac" )
588
+ film2 = FilmModel .objects .create (genre = "ot" )
589
+ film3 = FilmModel .objects .create (genre = "do" )
590
+ FilmDetailsModel .objects .create (location = "Hollywood" , film = film1 )
591
+ FilmDetailsModel .objects .create (location = "Antarctica" , film = film3 )
592
+ r1 .films .add (film1 , film2 )
593
+ r2 .films .add (film3 )
594
+
595
+ # We expect 2 queries to be performed based on the above resolver definition: one for all
596
+ # articles joined with the reporters model (for associated editors and reporters), and one
597
+ # for the films prefetch (which includes its `select_related` JOIN logic in its queryset)
598
+ with django_assert_num_queries (2 ) as captured :
599
+ result = schema .execute (query )
600
+
601
+ assert not result .errors
602
+ assert result .data == {
603
+ "articles" : [
604
+ {
605
+ "headline" : "Amazing news" ,
606
+ "editor" : {
607
+ "firstName" : "Debra"
608
+ },
609
+ "reporter" : {
610
+ "firstName" : "Tara" ,
611
+ "films" : [
612
+ {"genre" : "AC" , "details" : {"location" : "Hollywood" }},
613
+ {"genre" : "OT" , "details" : None },
614
+ ]
615
+ },
616
+ },
617
+ {
618
+ "headline" : "Not so good news" ,
619
+ "editor" : {
620
+ "firstName" : "Tara"
621
+ },
622
+ "reporter" : {
623
+ "firstName" : "Debra" ,
624
+ "films" : [
625
+ {"genre" : "DO" , "details" : {"location" : "Antarctica" }},
626
+ ]
627
+ },
628
+ },
629
+ ]
630
+ }
631
+
632
+ assert len (captured .captured_queries ) == 2 # Sanity-check
633
+
634
+ # First we should have queried for all articles in a single query, joining on the reporters
635
+ # model (for the editors and reporters ForeignKeys)
636
+ assert re .match (
637
+ r'SELECT .* "tests_article" INNER JOIN "tests_reporter"' ,
638
+ captured .captured_queries [0 ]["sql" ],
639
+ )
640
+
641
+ # Then we should have queried for all of the films of all reporters, joined with the film
642
+ # details for each film, using a single query
643
+ assert re .match (
644
+ r'SELECT .* FROM "tests_film" INNER JOIN "tests_film_reporters" .* LEFT OUTER JOIN "tests_filmdetails"' ,
645
+ captured .captured_queries [1 ]["sql" ],
646
+ )
0 commit comments