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."""
fromdependency_injectorimport containers, providers
fromgithubimport Github
from.import services
classContainer(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.containersimport 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."""
fromtypingimport List
fromdjango.httpimport HttpRequest, HttpResponse
fromdjango.shortcutsimport render
fromdependency_injector.wiringimport inject, Provide
fromgithubnavigator.containersimport Container
fromgithubnavigator.servicesimport SearchService
@inject
defindex(
 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."""
fromdjango.appsimport AppConfig
fromgithubnavigatorimport container
classWebConfig(AppConfig):
 name = "web"
 defready(self):
 container.wire(modules=[".views"])

Tests

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

"""Tests module."""
fromunittestimport mock
fromdjango.urlsimport reverse
fromdjango.testimport TestCase
fromgithubimport Github
fromgithubnavigatorimport container
classIndexTests(TestCase):
 deftest_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")
 deftest_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:

[フレーム]