Frontend

Django Templates


Templates Folder Layout

project/
├── .envs   
├── apps 
├── .....
├── templates    ---> templates folder
│   ├── account  ---> account app templates
│   ├── audit_logs 
│   ├── pages.   ----> landing pages/ shared components
│           ├── components
│               ├── nav          ---> component for the main navbar
│               ├── menu   --->  ---> menu components
│           ├── ...             ---> other example components
│           └── sidenav
│   ├── dashboard        ---> dashboard app components
│   ├── layouts          ---> base layout for the dashboard.
│   ├── ......
│   ├── 403.html
│   ├── 404.html
│   ├── 500.html
│   ├── base.html       ---> base template for the website.
│   ├── robots.txt

Template Structure

The main template is base.html which is extended by all the layout templates including layouts/responsive_flat.html ( used for the dashboard pages ) and layouts/landing.html ( used for the marketing pages ).

base.html

{% load static i18n %}
<!DOCTYPE html>
{% get_current_language as language_code %}

<!--See layouts/dashboard for the theme switching -->
<html lang="{{ language_code }}"{% block theme %}{% endblock %}{% block html_class %}{% endblock %}>

<head>
  <title>{% spaceless %}{% block title %}Home{% endblock %}{% endspaceless %} </title>
   .....

  {# Main TailwindCSS Stylesheet #}    
  {% compress css %}
    <link rel="stylesheet" type="text/css" href="{% static 'css/app.css' %}"/>
  {% endcompress %}

  {# Alpine JS & HTMX scripts #}
    {% block head_js %}
        <script defer src="https://unpkg.com/@alpinejs/intersect@3.x.x/dist/cdn.min.js"></script>
        <script defer src="https://unpkg.com/@alpinejs/collapse@3.x.x/dist/cdn.min.js"></script>
        <script defer src="https://unpkg.com/@alpinejs/persist@3.x.x/dist/cdn.min.js"></script>
        <script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
        <script src="https://unpkg.com/htmx.org@1.7.0"></script>
   {% endblock %}

</head>

<body id="id-body" class="{{theme}}">
<div x-data="app"> {# global app component alpinejs #}
  <div class="flex h-screen antialiased overflow-x-hidden bg-base-200 text-base-content"
          {% block alpine_x_data %}{% endblock %}>

    {# page wrapper class also include alpine js page state #}
    <div class="{% block page_wrapper_class %}h-full w-full overflow-hidden fixed{% endblock %}">
      {# content goes here #}
      {% block content %}{% endblock %}

      {# app notifications handler #}
      {% include "components/tailwind/notifications/notifications_component.html" %}

    </div>
  </div>
  <div class="fixed bottom-left" id="consent_section"></div>
</div>
{% block footer_js %}

  {# This block should be on every page. It has the following global scripts/utilities. Override with caution  #}
  {# Global scripts - main alpinejs 'app' component, swaggerjs client for the api, user notifications, utils #}
  {% compress js %}
    <script src="{% static 'js/project.js' %}"></script>
    ........
    ........
  {% endcompress %}

  <script>
    // the application uses the swagger js client to interact with the api
    // if you are building a separate frontend, consult the docs on how to generate a client in typescript
    const client = SwaggerClient('/api/schema/', {
     .....
    })

    // initialize a global store for the app
    document.addEventListener('alpine:initializing', () => {
      Alpine.store('app',{
        darkMode: '{{theme}}' === 'dark',
        allowNotifications: false,
        magicLink:"",
        init(){
        ........    
      })
    })
  </script>

{% endblock %}
.....
</body>
</html>
  • includes reference to required AlpineJS library <script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
  • The footer block {% block footer_js %} includes a section for the globaljs scripts required on every page.
  • If inheriting the page and overriding this block don’t forget to call {{block.super}} to ensure all the scripts are loaded.
  • A swagger-api js client is defined in this section. It is configured with the appropriate headers to communicate with the backend. See the section on below on Communicating with the backend API for more info.
  • the section also includes an AlpineJS global store called app which stores global variables for site theme and other user settings.

layouts/dashboard.html

<!--Theme is cached on the backend to allow for syncing across backends. See context_processors.py for 'theme' -->
{% block theme %}
  {% if request.user.is_authenticated %} data-theme="{{ theme }}"
      {% else %}data-theme="light"{% endif %}
{% endblock %}
...
{% block content %}
  {% include "components/tailwind/navbar/main_navbar.html" %}
    ...
{% endblock %}
  • All dashboard or authenticated user pages should inherit from this page.
  • {% block theme %} includes the attributes required to set up the theme

Sidenav

The sidenav is loaded via the {% display_sidenav %} template tag.

The menu is dynamic and can be configured using a JSON object. This means you can swap out menu style, dashboard layouts with minimal effort. You can display different menu tabs for users with different roles, authenticated users, etc.

For example, the control panel tab will only be visible to superusers.

Example of a menu tab that is only visible to admins.

   {
            "name": "Admin",
            "admin_only": True,
            "tabs": [
                {
                    "name": "Control Panel",
                    "path": "dashboard:admin",
                    "icon": "document-report",
                    "roles": ["is_superuser"],
                },
            ],
        },

To configure, see the DashboardNavModel pydantic model in common/constants.py

Heroicons

The app uses the excellent django heroicons lib to easily embed icons in templates.

# load the template tag
{% load heroicons }
{% heroicon_solid 'bell' class="h-4 w-4" %}

Communicating with the backend API

For interacting with backend API endpoints from within the template, the app uses Swagger API javascript client . In most cases, this will not be necessary since most of this can be achieved with just HTMX.

However, if you are embedding SPA framework components, this may come in handy. A brief look at the setup:

  • the base.html template defines a swagger client for communicating with the backend endpoints.

     const client = SwaggerClient('/api/schema/', {   ..... })
    
  • each endpoint includes an operationId which can be used for interacting with the api.

  • for example for interacting with the stripe endpoint /payments/api/stripe/activate_license , you can call the endpoint directly or use the client as show below.
    let postData = {id:'xx'}
    
    client.then(
      client => client.apis.payments.activateLicense({}, {requestBody: postData})
       ).then(data => console.log(data)
       ).catch((error) => console.log(error)
    );
    

If you need a primer on Django templates checkout the Intro to Django templates post on our blog.