Building a (flat) website in Django

Why Django and why a flat website?

Dynamic websites, such as those built on WordPress, offer significant advantages in terms of ease of content management and personalization. Content Management Systems (CMS) like WordPress allow non-technical users to update content, manage media, and install plugins to extend functionality. This makes dynamic websites ideal for blogs, e-commerce sites, and news portals where content frequently changes. However, this dynamic nature introduces several vulnerabilities:

Static websites, composed of hand-cranked HTML files, are inherently more secure due to their simplicity. With no server-side processing or database queries, there are fewer attack vectors for hackers to exploit. This makes static sites immune to SQL injection and significantly reduces the risk of XSS and CSRF attacks. They load faster, are easier to deploy, and require minimal maintenance, making them ideal for smaller sites with infrequent updates. However, updating content can be cumbersome as it requires manual coding changes.

By building a flat, non-database-driven, website in Django, developers can enjoy the best of both worlds. Django is an all-encompassing framework that includes everything needed for web development, from URL routing and view functions to templating and form handling. This means developers can build robust and scalable websites without having to piece together various tools and libraries. For non-database-driven websites, Django’s URL dispatcher and templating engine are particularly useful, allowing for clean URL management and consistent HTML rendering.vThe security features built into Django ensure that any interaction points are safeguarded against common web vulnerabilities.

In this article I'm going to walk through creating a flat, non-dynamic website writen in pure Python and leveraging the Django framework. Markdown makes adding content quick and easy with more focus on writing the text and less on presenting it while Django provides consistent templating and URL handling.

Creating the Django project structure

Views

Start by defining a class to represent a markdown view. This will be sub-classed later.

class MarkdownView(View):
    content = None
    title = None

    def get(self, request, *args, **kwargs):
        template_name = "website/markdown_template.html"
        md = markdown.Markdown(extensions=["fenced_code", TocExtension(baselevel=2)])
        return render(
            request,
            template_name,
            {"title": self.title, "content": md.convert(self.content)},
        )

Then define a subclass to implement the view:

import markdown
from markdown.extensions.toc import TocExtension

from django.shortcuts import render
from django.views import View

class IndexView(MarkdownView):
    title = "Welcome!"
    content = """
        Welcome to my small corner of the Interwebs. A place where I dump random thoughts
        on infrastructure engineering, home automation and other geeky stuff.

        Things currently taking space in my head are:

        - [Building a flat website in Django](/building-a-flat-django-website),
        - Standing up a multi-node Kubernetes cluster on a bunch of Raspberry Pis, with 
        k3s and longhorn.
        - Building a CI/CD pipeline to deploy my website to my k3s cluster.
    """

URLs

Now we need to link the view classes to a real URL via the urls.py file:

from django.urls import path

from .views import IndexView

urlpatterns = [
    path("", IndexView.as_view(), name="index"),
]

Templates and template tags

The MarkdownView class refers to a Django template which bootstraps the web rendered webpage content.

Templates are defined in the templates/ directory. In our case, the MarkdownView class renders the website/markdown_template.html template:

{% extends 'website/base.html' %}
{% load django_bootstrap5 %}

{% block title %}{{ title }}{% endblock %}

{% block content %}
{{ content | safe }}
{% endblock %}

Templates can inherit other templates allowing the final page content to be built up in managable parts. In this case, markdown_template.html extends base.html:

{% extends 'website/bootstrap.html' %}

{% load django_bootstrap5 %}
{% load website_extras %}

{% block bootstrap5_content %}

<header class="d-flex flex-wrap border-bottom">
  <!-- Fixed navbar -->
  <nav class="navbar navbar-expand-md fixed-top border-bottom bg-light">
    <div class="container-fluid">
      <a class="navbar-brand fs-3" href="/">cpmills.com</a>
    </div>
  </nav>
</header>

<main class="container">
  <div class="p-2 p-md-4 mb-3"></div>

    <!-- Heading -->
    <div class="row py-lg-3">
      <h1 class="display-4">{% block title %}(no title){% endblock %}</h1>
    </div>

    <!-- Body -->
    <div class="mb-3">
        {% block content %}(no content){% endblock %}
    </div>
</main>

<footer class="footer mt-auto py-3 border-top bg-light">
  <div class="text-end">
    <span class="me-5 text-body-tertiary" style="font-size: 0.75rem !important">v{% version %}</span>
  </div>
</footer>

{% endblock %}

and in turn base.html extends bootstrap.html:

{% extends 'django_bootstrap5/bootstrap5.html' %}

{% block bootstrap5_title %}{% block title %}{% endblock %}{% endblock %}

django_bootstrap5/bootstrap5.html is installed with the django-bootstrap5 python package.

Unit testing a website

Running the server in development mode

Setting up a production environment

Deployment

A deployment creates a replicaset of n replica copies of our website pod.

A deployment keeps revisionHistoryLimit number of replicasets in order to provide a means to roll back and forward between versions of our app.

Upgrading a deployment

kubectl set image deployments/website-deployment website=cpmills1975/website:v1.0.2

Rolling back

kubectl rollout undo deployments/website-deployment