Django | Build a Dashboard with Django and Bootstrap: Part 3

uploads/2020/02/00_header_2.png

This is Part 3 of Building a Dashboard with Django and Bootstrap:

Introduction

If you follow the first part of this blog topic, you have a running Django dashboard.

But, the content ist still static. Lets review the current state:

Right now, the whole content of our Django project is provided by the dashboard template

dashboard/template/site/base.html

Looking at our web site, you will see the different side menu items. So, intentionally, our web site should display different pages. And each page should provide the dynamic content.

The final goal of this part is to change our web app, so that each side item navigates us to a different page. For this, we have to take care about two things:

  • Navigation: how to we get to another page in our app
  • Project Structure: where to place the required components for each page

Basics of Navigation

Navigation usually is the process of getting from one page to another by clicking on a link.

So, we need to things:

  • the source page, containing the link
  • the destination page
  • the link, pointing to the destination page

Let’s take a look into the site template with the side menu:

<pre class="EnlighterJSRAW" data-enlighter-group="" data-enlighter-highlight="4,5" data-enlighter-language="generic" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-theme="" data-enlighter-title=""><div id="collapseTwo" class="collapse" aria-labelledby="headingTwo">
	<div class="bg-white py-2 collapse-inner rounded">
		<h6 class="collapse-header">Custom Components:</h6>
		<a class="collapse-item" href="buttons.html">Buttons</a>
		<a class="collapse-item" href="cards.html">Cards</a>
	</div>
</div>

This is the result:

We must replace the html link tag (<a href="buttons.html">) with an Django-conform code. Read here for more details and the basics.

The idea behind the navigation is

So, change the global url mapping file dashboard/urls.py

<pre class="EnlighterJSRAW" data-enlighter-group="" data-enlighter-highlight="1,2,7,8" data-enlighter-language="python" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-theme="" data-enlighter-title="">import dashboard.apps.components.buttons.views as ButtonsViews
import dashboard.apps.components.cards.views as CardsViews

urlpatterns = [
    path('', include('dashboard.apps.urls')),

    path('buttons', 	ButtonsViews.IndexView.as_view(),   name='buttons'),
    path('cards',       CardsViews.IndexView.as_view(),     name='cards'),

    path('admin/', admin.site.urls),
]

dashboard/apps/components/buttons/views.py

<pre class="EnlighterJSRAW" data-enlighter-group="" data-enlighter-highlight="4,5" data-enlighter-language="python" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-theme="" data-enlighter-title="">from django.shortcuts import render
from django.views import generic

class IndexView(generic.TemplateView):
    template_name = 'buttons/base.html'

dashboard/apps/components/cards/views.py

<pre class="EnlighterJSRAW" data-enlighter-group="" data-enlighter-highlight="4,5" data-enlighter-language="python" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-theme="" data-enlighter-title="">from django.shortcuts import render
from django.views import generic

class IndexView(generic.TemplateView):
    template_name = 'cards/base.html'

dashboard/apps/components/cards/templates/cards/base.html

<pre class="EnlighterJSRAW" data-enlighter-group="" data-enlighter-highlight="" data-enlighter-language="generic" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-theme="" data-enlighter-title="">{% extends 'site/base.html' %}
{% load static %}

{% block content %}
<h1 style="text-align: center">CARDS</h1>
{% endblock content %}

dashboard/apps/components/cards/templates/buttons/base.html

<pre class="EnlighterJSRAW" data-enlighter-group="" data-enlighter-highlight="" data-enlighter-language="python" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-theme="" data-enlighter-title="">{% extends 'site/base.html' %}
{% load static %}

{% block content %}
<h1 style="text-align: center">BUTTONS</h1>
{% endblock content %}

Save everything and view at the resulting page

  • Django create the side menu item Cards
  • with the corresponding link localhost:9000/cards
  • which will be the destination page, if you click on the link
  • and finally, Django creates the desired page (through view.py)

Now, think about the names, e. g. buttons and cards in our Components.

Your project is getting bigger and you plan to add an additional page info with general information for both Components and Utilities menu.

So, you will have to additional files, for example

  • dashboard/apps/components/info/views.py
  • dashboard/apps/utilities/info/views.py

The corresponding url mapping (urls.py) could look like this:

<pre class="EnlighterJSRAW" data-enlighter-group="" data-enlighter-highlight="" data-enlighter-language="python" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-theme="" data-enlighter-title="">import dashboard.apps.components.info.views as ComponentInfoViews
import dashboard.apps.utilities.info.views as UtilitiesInfoViews

urlpatterns = [
    path('', include('dashboard.apps.urls')),

    path('info', ComponentInfoViews.IndexView.as_view(), name='info'),
    path('info', UtilitiesInfoViews.IndexView.as_view(), name='info'),

Two pages with the same name (info) in different views.py!

Of course, we could choose different names and link (component_info, utilities_info), but this not a good programming style.

We will choose a more modern way of programming

  • Spread the responsibility over separate, independent modules
  • Name these modules with different names

What does this mean? We will have

  • a separate module frontend, only responsible for the start page (frontend)
  • a separate module components, responsible for all components
  • a separate module utilities, responsible for all utilities
  • a separate module pages, responsible for all pages

Resulting folder structure and file content

File dashbard/urls.py

<pre class="EnlighterJSRAW" data-enlighter-group="" data-enlighter-highlight="" data-enlighter-language="generic" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-theme="" data-enlighter-title="">urlpatterns = [
    path('', include('dashboard.apps.urls')),
    path('admin/',	admin.site.urls),
]

File dashbard/apps/urls.py

<pre class="EnlighterJSRAW" data-enlighter-group="" data-enlighter-highlight="" data-enlighter-language="generic" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-theme="" data-enlighter-title="">from django.urls import path
from django.urls.conf import include

from dashboard.apps.frontend.views import IndexView

app_name = 'app'

urlpatterns = [
    path('', IndexView.as_view(), name='index'),
    path('pages/', include('dashboard.apps.pages.urls')),
    path('components/', include('dashboard.apps.components.urls')),
    path('utilities/', include('dashboard.apps.utilities.urls')),
]

File dashbard/apps/components/urls.py

<pre class="EnlighterJSRAW" data-enlighter-group="" data-enlighter-highlight="" data-enlighter-language="generic" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-theme="" data-enlighter-title="">from django.urls import path

import dashboard.apps.components.buttons.views as ButtonsViews
import dashboard.apps.components.cards.views as CardsViews

app_name = 'components'

urlpatterns = [
    path('', ButtonsViews.IndexView.as_view(), name='index'),
    path('buttons/', ButtonsViews.IndexView.as_view(), name='buttons'),
    path('cards/', CardsViews.IndexView.as_view(), name='cards'),
]

File dashbard/apps/utilities/urls.py

<pre class="EnlighterJSRAW" data-enlighter-group="" data-enlighter-highlight="" data-enlighter-language="generic" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-theme="" data-enlighter-title="">from django.urls import path

import dashboard.apps.utilities.colors.views as ColorsViews
import dashboard.apps.utilities.borders.views as BordersViews
import dashboard.apps.utilities.animations.views as AnimationsViews
import dashboard.apps.utilities.others.views as OthersViews

app_name = 'utilities'

urlpatterns = [
    path('', BordersViews.IndexView.as_view(), name='index'),
    path('animations/', AnimationsViews.IndexView.as_view(), name='animations'),
    path('borders/', BordersViews.IndexView.as_view(), name='borders'),
    path('colors/', ColorsViews.IndexView.as_view(), name='colors'),
    path('others/', OthersViews.IndexView.as_view(), name='others'),
]

File dashbard/apps/pages/urls.py

<pre class="EnlighterJSRAW" data-enlighter-group="" data-enlighter-highlight="" data-enlighter-language="generic" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-theme="" data-enlighter-title="">from django.urls import path

import dashboard.apps.pages.blank.views as BlankViews
import dashboard.apps.pages.login.views as LoginViews
import dashboard.apps.pages.pagenotfound.views as PageNotFoundViews
import dashboard.apps.pages.password.views as PasswordViews
import dashboard.apps.pages.register.views as RegisterViews
import dashboard.apps.pages.charts.views as ChartsViews
import dashboard.apps.pages.tables.views as TablesViews

app_name = 'pages'

urlpatterns = [
    path('', ChartsViews.IndexView.as_view(), name='index'),
    path('blank', BlankViews.IndexView.as_view(), name='blank'),
    path('charts', ChartsViews.IndexView.as_view(), name='charts'),
    path('login', LoginViews.IndexView.as_view(), name='login'),
    path('pagenotfound', PageNotFoundViews.IndexView.as_view(), name='pagenotfound'),
    path('password', PasswordViews.IndexView.as_view(), name='password'),
    path('register', RegisterViews.IndexView.as_view(), name='register'),
    path('tables', TablesViews.IndexView.as_view(), name='tables'),
]

Let’s finally check the namespace structure:

<pre class="EnlighterJSRAW" data-enlighter-group="" data-enlighter-highlight="1" data-enlighter-language="shell" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-theme="" data-enlighter-title="">$ find . -name urls.py
./dashboard/urls.py
./dashboard/apps/utilities/urls.py
./dashboard/apps/components/urls.py
./dashboard/apps/urls.py
./dashboard/apps/pages/urls.py

We create three levels for our namespaces:

<pre class="EnlighterJSRAW" data-enlighter-group="" data-enlighter-highlight="" data-enlighter-language="generic" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-theme="" data-enlighter-title=""><a href="{% url 'app:components:buttons' %}" class="collapse-item" >Buttons</a>
<a href="{% url 'app:components:cards' %}" class="collapse-item" >Cards</a>
<pre class="EnlighterJSRAW" data-enlighter-group="" data-enlighter-highlight="" data-enlighter-language="generic" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-theme="" data-enlighter-title=""><a class="collapse-item" href="{% url 'app:utilities:colors' %}">Colors</a>
<a class="collapse-item" href="{% url 'app:utilities:borders' %}">Borders</a>
<a class="collapse-item" href="{% url 'app:utilities:animations' %}">Animations</a>
<a class="collapse-item" href="{% url 'app:utilities:others' %}">Other</a>

Install the Django Extensions for additional commands:

<pre class="EnlighterJSRAW" data-enlighter-group="" data-enlighter-highlight="" data-enlighter-language="shell" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-theme="" data-enlighter-title="">pip install django-extensions

Add Django Extensions to the INSTALLED_APPS

<pre class="EnlighterJSRAW" data-enlighter-group="" data-enlighter-highlight="4" data-enlighter-language="python" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-theme="" data-enlighter-title="">INSTALLED_APPS = [
    ...

    'django_extensions'
]

Show URLs and Namespaces (only for out apps, admin urls are removed)

<pre class="EnlighterJSRAW" data-enlighter-group="" data-enlighter-highlight="" data-enlighter-language="generic" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-theme="" data-enlighter-title="">python3 manage.py show_urls

In summary, these are the steps to create the desired folder structure:

<pre class="EnlighterJSRAW" data-enlighter-group="" data-enlighter-highlight="" data-enlighter-language="generic" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-theme="" data-enlighter-title="">mkdir -p dashboard/apps/components/buttons/templates/buttons
mkdir -p dashboard/apps/components/cards/templates/cards
mkdir -p dashboard/apps/pages/blank/templates/blank
mkdir -p dashboard/apps/pages/charts/templates/charts
mkdir -p dashboard/apps/pages/login/templates/login
mkdir -p dashboard/apps/pages/pagenotfound/templates/pagenotfound
mkdir -p dashboard/apps/pages/password/templates/password
mkdir -p dashboard/apps/pages/register/templates/register
mkdir -p dashboard/apps/pages/tables/templates/tables
mkdir -p dashboard/apps/utilities/animations/templates/animations
mkdir -p dashboard/apps/utilities/borders/templates/borders
mkdir -p dashboard/apps/utilities/colors/templates/colors
mkdir -p dashboard/apps/utilities/others/templates/others
<pre class="EnlighterJSRAW" data-enlighter-group="" data-enlighter-highlight="" data-enlighter-language="generic" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-theme="" data-enlighter-title="">python3 manage.py startapp buttons dashboard/apps/components/buttons
python3 manage.py startapp cards dashboard/apps/components/cards
python3 manage.py startapp blank dashboard/apps/pages/blank
python3 manage.py startapp charts dashboard/apps/pages/charts
python3 manage.py startapp login dashboard/apps/pages/login
python3 manage.py startapp pagenotfound dashboard/apps/pages/pagenotfound
python3 manage.py startapp password dashboard/apps/pages/password
python3 manage.py startapp register dashboard/apps/pages/register
python3 manage.py startapp tables dashboard/apps/pages/tables
python3 manage.py startapp animations dashboard/apps/utilities/animations
python3 manage.py startapp borders dashboard/apps/utilities/borders
python3 manage.py startapp colors dashboard/apps/utilities/colors
python3 manage.py startapp others dashboard/apps/utilities/others
<pre class="EnlighterJSRAW" data-enlighter-group="" data-enlighter-highlight="" data-enlighter-language="generic" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-theme="" data-enlighter-title="">echo "{% extends 'site/base.html' %}

{% block    content %}
{% endblock content %}"> base.html
<pre class="EnlighterJSRAW" data-enlighter-group="" data-enlighter-highlight="" data-enlighter-language="generic" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-theme="" data-enlighter-title="">cp base.html dashboard/apps/components/buttons/templates/buttons
cp base.html dashboard/apps/components/cards/templates/cards
cp base.html dashboard/apps/pages/blank/templates/blank
cp base.html dashboard/apps/pages/charts/templates/charts
cp base.html dashboard/apps/pages/login/templates/login
cp base.html dashboard/apps/pages/pagenotfound/templates/pagenotfound
cp base.html dashboard/apps/pages/password/templates/password
cp base.html dashboard/apps/pages/register/templates/register
cp base.html dashboard/apps/pages/tables/templates/tables
cp base.html dashboard/apps/utilities/animations/templates/animations
cp base.html dashboard/apps/utilities/borders/templates/borders
cp base.html dashboard/apps/utilities/colors/templates/colors
cp base.html dashboard/apps/utilities/others/templates/others
<pre class="EnlighterJSRAW" data-enlighter-group="" data-enlighter-highlight="" data-enlighter-language="generic" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-theme="" data-enlighter-title="">rm base.html

Replacing the static parts with dynamic content could be achieved by the following approach:

  • Identify the dynamic parts
  • Move these parts from the site template into the view template base.html of the component
  • Modify frontend view.py to generate dynamic content from data

The steps are the same for all components (all items of the side menu).

Find the

Identify dynamic parts in template

For every side menu item, their is a corresponding html file in the install folder of the sb-admin-2 template:

Remember the environment variable we create in part 1 for the start of our project

<pre class="EnlighterJSRAW" data-enlighter-group="" data-enlighter-highlight="" data-enlighter-language="generic" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-theme="" data-enlighter-title="">DASHBOARD_ROOT=$(pwd)
<pre class="EnlighterJSRAW" data-enlighter-group="" data-enlighter-highlight="1" data-enlighter-language="generic" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-theme="" data-enlighter-title="">cd $DASHBOARD_ROOT
find dashboard/apps install/sb-admin-2 -name *.html

Each template file base.html has a corresponding html file unter sb-admin-2. Look at the following table to find the mapping:

<pre class="EnlighterJSRAW" data-enlighter-group="" data-enlighter-highlight="" data-enlighter-language="html" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-theme="" data-enlighter-title="">{% extends 'site/base.html' %}

{% block    content %}
{% endblock content %}

And each corresponding view.py file should have the following content, only the template_name should be different (the name of the template base.html file)

<pre class="EnlighterJSRAW" data-enlighter-group="" data-enlighter-highlight="" data-enlighter-language="generic" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-theme="" data-enlighter-title="">from django.views import generic

class IndexView(generic.TemplateView):
    template_name = 'buttons/base.html'

So, for each template file, we have to

  • locate the corresponding html file from the install folder (see table above)
  • copy the content between these tags to the template file:
<pre class="EnlighterJSRAW" data-enlighter-group="" data-enlighter-highlight="" data-enlighter-language="generic" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-theme="" data-enlighter-title="">        <!-- Begin Page Content -->
        <div class="container-fluid">
        ....
        </div>
        <!-- /.container-fluid -->
The Latest