Using Views

Views with any sort of UI on HQ are mostly class-based, while functional views are largely used for data.

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.

HTML
{% 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.

HTML
{% 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.

It's always recommended that you use a class-based view to render a template, unless you have a very good reason for using a functional view.

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.

Python
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.

Python
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.

Python
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),
]