Search code examples
djangodjango-testingdjango-2.1django-select-related

Select related executes queries


I can't make select_related work with the following configuration:

Models:

class Text(models.Model):
    author = models.ForeignKey('authors.Author', 
                               on_delete=models.SET_NULL,
                               blank=True,
                               null=True)

Author model:

class Author(models.Model):
    name = models.CharField(max_length=200)
    def get_absolute_url(self):
        kwargs = {'pk': self.pk}
        return reverse('author-detail', kwargs=kwargs)

View

In a view, I use the select_related function to avoid hitting the db when querying for text's authors e.g.:mytext.author:

class TextsViewTest(TestCase):
    def text_view(request,
                 pk,                            
                 template_name='texts/detail.html'):

        source_text = Text.objects.select_related('author').get(pk=pk)
        return render(request, template_name,
                 {
                     'source': source_text,
                 })

Test

According to select_related it shouldn't hit the database when accessing the Text.author relationship, but when testing it with:

def test_layout_content_header__uses_prefetched_relationships(self):
    author = Author.objects.create(name="foobar")
    source_text = Text.objects.create(author=author)
    context = {'source': source_text}
    with self.assertNumQueries(0):
        from django.template.loader import render_to_string
        rendered = render_to_string("text/_content_header.html", context)

template

text/content_header.html:

{% if source.author %} by <em><a href="{{source.author.get_absolute_url}}">{{source.author.name}}</a></em>{% endif %}

Output

./manage test texts.test_views shows a hit:

Creating test database for alias 'default'...
System check identified no issues (0 silenced).
F
======================================================================
FAIL: test_layout_content_header__uses_prefetched_relationships (author.tests.test_views.TextsViewTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/.../text/tests/test_views.py", line 1035, in test_layout_content_header__uses_prefetched_relationships
    source_text.author
  File "/.../lib/python3.6/site-packages/django/test/testcases.py", line 80, in __exit__
    '%d. %s' % (i, query['sql']) for i, query in enumerate(self.captured_queries, start=1)
AssertionError: 1 != 0 : 1 queries executed, 0 expected
Captured queries were:
1. SELECT "authors_author"."id", "authors_author"."name", FROM "authors_author" WHERE "authors_author"."id" = 1

----------------------------------------------------------------------
Ran 1 test in 0.489s

FAILED (failures=1)
Destroying test database for alias 'default'...

Any ideas?


Solution

  • It looks like you are not using your view's code inside the test. Try either to copy same query into your test, e.g.:

    context = {'source': Text.objects.select_related('author').get(pk=source_text.pk)}
    with self.assertNumQueries(0):
        from django.template.loader import render_to_string
        rendered = render_to_string("text/_content_header.html", context)
    

    Or reuse the view code (it seems to be declared in the Test Case, right?)

    with self.assertNumQueries(1):
        self.text_view(MagicMock(), source_text.pk)
    

    Although you might need to specify bit more advanced request mock, e.g. using the RequestFactory