Django example

This example shows how to use Dependency Injector with Django.

The example application helps to search for repositories on the Github.

../_images/django.png

The source code is available on the Github.

Application structure

Application has standard Django project structure. It consists of githubnavigator project package and web application package:

./
├── githubnavigator/
│   ├── __init__.py
│   ├── asgi.py
│   ├── containers.py
│   ├── services.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── web/
│   ├── templates/
│   │   ├── base.html
│   │   └── index.html
│   ├── __init__.py
│   ├── apps.py
│   ├── tests.py
│   ├── urls.py
│   └── views.py
├── manage.py
└── requirements.txt

Container

Declarative container is defined in githubnavigator/containers.py:

"""Containers module."""

from dependency_injector import containers, providers
from github import Github

from . import services


class Container(containers.DeclarativeContainer):

    config = providers.Configuration()

    github_client = providers.Factory(
        Github,
        login_or_token=config.GITHUB_TOKEN,
        timeout=config.GITHUB_REQUEST_TIMEOUT,
    )

    search_service = providers.Factory(
        services.SearchService,
        github_client=github_client,
    )

Container instance is created in githubnavigator/__init__.py:

"""Project package."""

from .containers import Container
from . import settings


container = Container()
container.config.from_dict(settings.__dict__)

Views

View has dependencies on search service and some config options. The dependencies are injected using Wiring feature.

Listing of web/views.py:

"""Views module."""

from typing import List

from django.http import HttpRequest, HttpResponse
from django.shortcuts import render
from dependency_injector.wiring import inject, Provide

from githubnavigator.containers import Container
from githubnavigator.services import SearchService


@inject
def index(
        request: HttpRequest,
        search_service: SearchService = Provide[Container.search_service],
        default_query: str = Provide[Container.config.DEFAULT_QUERY],
        default_limit: int = Provide[Container.config.DEFAULT_LIMIT.as_int()],
        limit_options: List[int] = Provide[Container.config.LIMIT_OPTIONS],
) -> HttpResponse:
    query = request.GET.get('query', default_query)
    limit = int(request.GET.get('limit', default_limit))

    repositories = search_service.search_repositories(query, limit)

    return render(
        request,
        template_name='index.html',
        context={
            'query': query,
            'limit': limit,
            'limit_options': limit_options,
            'repositories': repositories,
        }
    )

App config

Container is wired to the views module in the app config web/apps.py:

"""Application config module."""

from django.apps import AppConfig

from githubnavigator import container
from . import views


class WebConfig(AppConfig):
    name = 'web'

    def ready(self):
        container.wire(modules=[views])

Tests

Tests use Provider overriding feature to replace github client with a mock web/tests.py:

"""Tests module."""

from unittest import mock

from django.urls import reverse
from django.test import TestCase
from github import Github

from githubnavigator import container


class IndexTests(TestCase):

    def test_index(self):
        github_client_mock = mock.Mock(spec=Github)
        github_client_mock.search_repositories.return_value = [
            mock.Mock(
                html_url='repo1-url',
                name='repo1-name',
                owner=mock.Mock(
                    login='owner1-login',
                    html_url='owner1-url',
                    avatar_url='owner1-avatar-url',
                ),
                get_commits=mock.Mock(return_value=[mock.Mock()]),
            ),
            mock.Mock(
                html_url='repo2-url',
                name='repo2-name',
                owner=mock.Mock(
                    login='owner2-login',
                    html_url='owner2-url',
                    avatar_url='owner2-avatar-url',
                ),
                get_commits=mock.Mock(return_value=[mock.Mock()]),
            ),
        ]

        with container.github_client.override(github_client_mock):
            response = self.client.get(reverse('index'))

        self.assertContains(response, 'Results found: 2')

        self.assertContains(response, 'repo1-url')
        self.assertContains(response, 'repo1-name')
        self.assertContains(response, 'owner1-login')
        self.assertContains(response, 'owner1-url')
        self.assertContains(response, 'owner1-avatar-url')

        self.assertContains(response, 'repo2-url')
        self.assertContains(response, 'repo2-name')
        self.assertContains(response, 'owner2-login')
        self.assertContains(response, 'owner2-url')
        self.assertContains(response, 'owner2-avatar-url')

    def test_index_no_results(self):
        github_client_mock = mock.Mock(spec=Github)
        github_client_mock.search_repositories.return_value = []

        with container.github_client.override(github_client_mock):
            response = self.client.get(reverse('index'))

        self.assertContains(response, 'Results found: 0')

Sources

Explore the sources on the Github.