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
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/[email protected]/dist/cdn.min.js"></script>
<script defer src="https://unpkg.com/@alpinejs/[email protected]/dist/cdn.min.js"></script>
<script defer src="https://unpkg.com/@alpinejs/[email protected]/dist/cdn.min.js"></script>
<script defer src="https://unpkg.com/[email protected]/dist/cdn.min.js"></script>
<script src="https://unpkg.com/[email protected]"></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>
<script defer src="https://unpkg.com/[email protected]/dist/cdn.min.js"></script>
{% block footer_js %}
includes a section for the globaljs scripts required on every page. {{block.super}}
to ensure all the scripts are loaded.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.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 %}
{% block theme %}
includes the attributes required to set up the themeThe 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
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" %}
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.
/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.
Success
Error
Warning
Info