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


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.

Sponsor the project on GitHub: