Using Views
Views with any sort of UI on HQ are mostly class-based, while functional views are largely used for data.
On this page
Overview
In Django there are two approaches to creating a view: class-based views and functional views.
Class-based views (CBV) are the most commonly chosen view for rendering a user-facing UIs on HQ. This is due to the ease of setting up the template context in the view inheritance, as well as the application of permissions-related decorators that cover an entire section or subclass of pages. Very rarely are class-based views chosen for returning data.
Functional views are used mainly for data views (for instance returning a JsonResponse
),
POSTing data, or as redirect views. Sometimes functional views are used to render templates,
but this is not very common on CommCare HQ and is not recommended unless the use case requires it.
Templates
Regardless of whether a view is class-based or functional, if the view returns a template it must extend one of the two types of templates below.
Centered Page
A Centered Page is generally a very simple single-page UI. This is usually reserved for login and registration, error pages, or a user's list of projects. These are one-off pages that don't fit into a collection of views or navigational area.
All templates that form a centered page extend the hqwebapp/bootstrap5/base_page.html
template,
or extends a template that eventually extends the base_page.html
template.
{% extends "hqwebapp/bootstrap5/base_page.html" %} {% load i18n %} {% block page_content %} <p> This is an example page extending the <code>hqwebapp/bootstrap5/base_page.html</code> template. </p> {% endblock %}
Section (Two-Column) Page
A Section Page is a page that has left-hand side navigation and breadcrumbs. There is an overall "section" that ties all the pages shown in the left-hand navigation together. Examples are Reports and Project Settings sections of a project space.
{% extends "hqwebapp/bootstrap5/base_section.html" %} {% load i18n %} {% block page_content %} <p> This is an example page extending the <code>hqwebapp/bootstrap5/base_section.html</code> template. </p> {% endblock %}
Template Context
The template context is a dictionary passed to the template that defines all the variables the template needs to render that page. In class-based views, there is a lot of guidance built into the CBV structure that ensures no important pieces of the template context are missed.
If you see views where the breadcrumbs are partially rendered or the page header looks a bit different from the other pages, it's likely that view did not have its template context properly set up.
With functional views, it is very easy to return a poorly formatted template context that causes the resulting
page to look a bit strange. The developer might be tempted to override page_header
or navigation
blocks from the extending templates. Please do not rush to do this! First, explore whether the template context is
missing any information. We've created some helper methods corehq.apps.hqwebapp.context
to help
structure the template context if you are working with a functional view. This usage is shown in the functional
view examples below.
Class-Based Views
In comparison with functional views, class-based views help keep the UI standardized. As you will see in the
examples below, the template context is conveniently taken care of by the class structure. This template context
helps make sure that all the necessary pieces of information are provided to render breadcrumbs, navigation,
page titles, and title
tags properly in the base_page.html
and
base_section.html
templates.
Below is an example of a centered page and a section page. The templates are covered in the Templates section above.
from django.urls import reverse from django.utils.decorators import method_decorator from django.utils.translation import gettext_lazy from corehq.apps.domain.decorators import login_required from corehq.apps.hqwebapp.context import ParentPage from corehq.apps.hqwebapp.decorators import use_bootstrap5 from corehq.apps.hqwebapp.views import BasePageView, BaseSectionPageView @method_decorator(login_required, name='dispatch') @method_decorator(use_bootstrap5, name='dispatch') class ExampleCenteredPageView(BasePageView): urlname = "example_centered_page_b5" page_title = gettext_lazy("Centered Page Example") template_name = "styleguide/bootstrap5/examples/simple_centered_page.html" @property def page_url(self): return reverse(self.urlname) # Typically, section pages will all inherit from one "base section view" that # defines the general page_url format, section_url, section_name, and the minimum # required permissions decorators for that page. @method_decorator(login_required, name='dispatch') @method_decorator(use_bootstrap5, name='dispatch') class BaseExampleView(BaseSectionPageView): section_name = gettext_lazy("Example Views") @property def page_url(self): return reverse(self.urlname) @property def section_url(self): # This can be another page relevant to the section. # Since there are only two pages in this example section, # `ExampleParentSectionPageView.urlname` is used. return reverse(ExampleParentSectionPageView.urlname) class ExampleParentSectionPageView(BaseExampleView): urlname = "example_section_parent_b5" page_title = gettext_lazy("Section Page Example") template_name = "styleguide/bootstrap5/examples/simple_section_parent.html" @property def page_context(self): return { "child_page_url": reverse(ExampleChildSectionPageView.urlname), } class ExampleChildSectionPageView(BaseExampleView): urlname = "example_section_b5" page_title = gettext_lazy("A Detail Page (Section Example)") template_name = "styleguide/bootstrap5/examples/simple_section.html" @property def parent_pages(self): return [ ParentPage(ExampleParentSectionPageView.page_title, reverse(ExampleParentSectionPageView.urlname)), ]
We won't dive into great detail about the implementation here, as there is a lot of information about using Class-based views on CommCare HQ in our Read-the-docs.
This page serves as a quick reference and a way to see a working example. It's also a good place to start to inspect any front end changes to templates in the event of another Bootstrap upgrade.
Functional Views
Below are several examples of functional views. The views rendering templates make use of the context helper
get_page_context
found in corehq.apps.hqwebapp.context
to help structure the template
context needed to correctly render templates extending the base_page.html
and
base_section.html
templates.
You can also see working examples of a Function-Based Centered Page and a Function-Based Section Page.
from django.http import HttpResponseRedirect, JsonResponse from django.shortcuts import render from django.views.decorators.http import require_POST from django.urls import reverse from django.utils.translation import gettext as _ from corehq.apps.domain.decorators import login_required from corehq.apps.hqwebapp.context import get_page_context, Section, ParentPage from corehq.apps.hqwebapp.decorators import use_bootstrap5 @require_POST # often, other decorators will go here too def example_data_view(request): # any additional arguments from the URL go here # do something with the request data return JsonResponse({ "success": True, }) def simple_redirect_view(request): # do something with the request return HttpResponseRedirect(reverse("example_centered_page_functional_b5")) # redirect to a different page @login_required @use_bootstrap5 def example_centered_page_view(request): context = get_page_context( _("Centered Page Example"), reverse("example_centered_page_functional_b5"), ) return render(request, 'styleguide/bootstrap5/examples/simple_centered_page.html', context) @login_required # often this is login_and_domain_required @use_bootstrap5 def example_section_page_view(request): context = get_page_context( _("A Detail Page (Section Example)"), reverse("example_section_functional_b5"), section=Section( _("Example Views"), reverse("example_section_functional_b5"), ), parent_pages=[ ParentPage( _("Section Page Example"), reverse("example_parent_page_functional_b5") ), ] ) return render(request, 'styleguide/bootstrap5/examples/simple_section.html', context) @login_required @use_bootstrap5 def example_parent_page_view(request): context = get_page_context( _("Section Page Example"), reverse("example_parent_page_functional_b5"), section=Section( _("Example Views"), reverse("example_section_functional_b5"), ), ) # this is not a standard template context variable context['child_page_url'] = reverse("example_section_functional_b5") return render(request, 'styleguide/bootstrap5/examples/simple_section_parent.html', context)
Mapping Urls to Views
Below is an example of how the class-based and functional-view examples above are specified in a
urlpattern
.
from django.urls import re_path as url from corehq.apps.styleguide.examples.bootstrap5.class_view import ( ExampleCenteredPageView, ExampleChildSectionPageView, ExampleParentSectionPageView, ) from corehq.apps.styleguide.examples.bootstrap5.functional_view import ( simple_redirect_view, example_centered_page_view, example_section_page_view, example_parent_page_view, ) urlpatterns = [ url(r'^$', simple_redirect_view, name="example_views_b5"), url(r'^centered_page/$', ExampleCenteredPageView.as_view(), name=ExampleCenteredPageView.urlname), url(r'^centered_page_functional/$', example_centered_page_view, name="example_centered_page_functional_b5"), url(r'^example_section_functional/$', example_section_page_view, name="example_section_functional_b5"), url(r'^example_section/$', ExampleChildSectionPageView.as_view(), name=ExampleChildSectionPageView.urlname), url(r'^example_parent_functional/$', example_parent_page_view, name="example_parent_page_functional_b5"), url(r'^example_parent/$', ExampleParentSectionPageView.as_view(), name=ExampleParentSectionPageView.urlname), ]