23. February 2020
Django | Build a Dashboard with Django and Bootstrap: Part 3
This is Part 3 of Building a Dashboard with Django and Bootstrap:
- Part 1: Building a base Django project
- Part 2: Prepare for dynamic content
- Part 3: Handling navigation and the side menu
- Part 4: Deploy Django App to Azure
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 -->