Htmx is short for high power tools for HTML. It simplifies tedious work for developers. A developer can access modern browser features without writing a single line of Javascript. Yes! that is how powerful htmx is even though it has a size of approximately 10k. According to it’s creator, Carson Gross:
“htmx allows you to access AJAX, CSS Transitions, WebSockets and Server Sent Events directly in HTML, using attributes, so you can build modern user interfaces with the simplicity and power of hypertext”
Source: https://htmx.org/
Now, integrating htmx in Django might get a little confusing if you are just starting out. Luckily, htmx comes with excellent documentation and examples. There are also amazing Github repositories that particularly stands out. One of those repositories ‘guettli / django-htmx-fun‘ has been created by Thomas Güttler. It showcases how we can leverage the power of htmx to build a single page application. The application is a virtual diary where the users can add notes. It supports endless scrolling or otherwise known as lazy loading. Furthermore, it is a perfect and easy example for beginners of htmx. Thus, the remainder of the tutorial will focus on explaining the working principles of the same.
This tutorial assumes that you are already familiar with Django. If you are yet to explore Django you could explore some of my older tutorials here.
The tutorial is divided into four small sections. Each section focuses on one tiny step at a time. This approach reduces the overall complexity of the entire tutorial and promotes ease of learning. The sections are mentioned below :
The installation process of the application is quite straighforward and documented well in the projects readme file. Open your terminal and fire up the following commands:
python3 -m venv django-htmx-fun-env
cd django-htmx-fun-env
. bin/activate
pip install -e git+https://github.com/guettli/django-htmx-fun.git#egg=django-htmx-fun
python3 -m venv django-htmx-fun-env
cd django-htmx-fun-env
. bin/activate
pip install -e git+ssh://git@github.com/guettli/django-htmx-fun.git#egg=django_htmx_fun
Note: To run the last command you need to have ssh setup in your machine. Follow the links provided below to setup SSH in your machine
Next, run database migrations
manage.py migrate
Create a superuser
manage.py createsuperuser
Run the webserver
manage.py runserver
Now you can open up your browser and navigate to http://127.0.0.1:8000/ to view the diary and http://127.0.0.1:8000/admin
Thomas Güttler follows a naming pattern that has been mentioned in his projects readme file. It is imperative that we too understand it meticulously and the same has been provided in Table 1.
Trailing String | FBV | Returns | URL | Notes |
---|---|---|---|---|
_page(): | foo_page(request, …) | HttpResponse with a full-page | /foo | Only HTTP-GET |
_hx(): | foo_hx(request, …) | HttpResponse with a HTML fragment | /foo_hx | Called via HTTP-GET |
_hxpost(): | foo_hxpost(request, …) | HttpResponse with a HTML fragment | /foo_hxpost | Called via HTTP-POST. Use require_POST decorator for additional protection |
_json(): | foo_json(request, …) | JSONResponse | /foo_json | |
_html(): | Python method | HTML SafeString | Usually created via format_html |
Let us now look at its model Note and how it has been defined.
We want to store our inputs into a DB for which we would need to define a model. The model is called Note and its definition is located at diary/models.py:
from django.db import models
from django.utils import timezone
from django.utils.html import format_html
class Note(models.Model):
datetime = models.DateTimeField(default=timezone.now)
title = models.CharField(max_length=1024, default='')
text = models.TextField(default='', blank=True)
def __str__(self):
return 'Note {} {}'.format(self.datetime.strftime('%Y-%m-%d'), self.title)
Once we add the model to our admin panel we can easily perform CRUD operations on it by navigating to 127.0.0.1:8000/admin. The source code is located at diary/admin.py:
from diary.models import Note
from django.contrib import admin
class NoteAdmin(admin.ModelAdmin):
model = Note
list_display = ['id', 'datetime', 'title', 'text']
ordering = ['-datetime', '-id']
admin.site.register(Note, NoteAdmin)
I am not explaining much about the Model and the Admin section. These topics are covered in-depth in one of my favourite courses – the Mozilla Django tutorial. Next, let us understand how the project is structured and get an understanding of its source code.
Before we move forward we need to get some idea of how the project is structured. I will explain the same diagrammatically using Fig. 1. From here onward we will focus on how the project is structured. Our primary goal is to understand how the power of htmx is being leveraged in Django and hence some Django specific explanations might be skipped. Feel free to comment below if you want me to explain any section in-depth. To begin with we go to the homepage of the project – 127.0.0.1:8000
The process might look daunting at the beginning but don’t worry. If you are getting confused with the structure remember that most of the functions can be coupled together into one single function. On the other hand, the structure showcased above promotes uniformity and re-usability of codes.
When we navigate to our homepage, our projects urls.py maps the request to the start_page() method located at views/start.py. The location of each method is displayed on the top left corner of the blocks. The start_page() method returns the output of two more methods note_add_html() and first_note() coupled together as format_html() and output is displayed as page(). The page() method has been defined in views/common.py. It essentially houses the code of our base template for our HTML pages. If we navigate to our source file we would see this in our start_page() method:
def start_page(request):
return page(
format_html(
'''
{note_add}
{first_note}''',
note_add=note_add_html(),
first_note=first_note(),
)
)
and if we check our page() method we would notice how htmx is being loaded:
def page(content):
return HttpResponse(
format_html(
'''<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Diary</title>
<style>
input, textarea {{
display: block;
}}
</style>
</head>
<body>
{content}
</body>
<!---- htmx load starts here --->
<script src="https://unpkg.com/htmx.org@1.4.1/dist/htmx.min.js"
integrity="sha384-1P2DfVFAJH2XsYTakfc6eNhTVtyio74YNA1tc6waJ0bO+IfaexSWXc2x62TgBcXe"
crossorigin="anonymous"></script>
<!--- htmx load ends here-->
</html>
''',
content=content,
)
)
Similarly, if we follow the diagram from top to bottom we would get a fair idea of how each little method is performing its own task. In the diagram, start_page() first refers to note_add_html(). This method is responsible for displaying a form for users to enter new notes into the Note model. Let us check out the source code for the same:
class NoteCreateForm(ModelForm):
class Meta:
fields = ['datetime', 'title', 'text']
model = Note
widgets = {
'text': Textarea(attrs={'rows': 3}),
}
def note_add_html():
form = NoteCreateForm()
return note_form_html(form)
def note_form_html(form):
return format_html(
'''
<form hx-post="{url}" hx-swap="outerHTML">
{form}
<input type="submit">
</form>''',
url=reverse(create_note_hxpost),
form=form,
)
So, node_add_html() is returning note_form_html(). Now our note_form_htm() method is returning HTML with some special htmx attributes. They are:
Once the user clicks the submit button the create create_note_hxpost() is called its code is quite self-explanatory. The code snippet is provided below:
@require_POST
def create_note_hxpost(request):
form = NoteCreateForm(request.POST)
if form.is_valid():
note = form.save()
return HttpResponse(format_html('{} {}', note_add_html(), note_html(note)))
return HttpResponse(note_form_html(form))
Now, the most interesting part about Fig. 1 is the last element; next_html. Our next_html returns a htmx string if the next item exists or return the string The End if no next item exists. Let us look at this special string now and why it is so amazing:
'<div hx-get="note_and_next_hx/1" hx-trigger="revealed" hx-swap="outerHTML">...</div>'
By now you might have noticed how powerful htmx actually is. Our <div> is sending GET requests on-demand and displaying the results. Just one simple line and magic happens. No need to write any additional JavaScript!
Now, you might be wondering what does note_and_next_hx/1 actually do. If we revisit our urls.py we would notice that our GET request gets mapped to the method note_and_next_hx(). Let us see its source code to understand it further:
def note_and_next_hx(request, note_id):
return HttpResponse(note_and_next_html(get_object_or_404(Note, pk=note_id)))
Our method simply returns a HttpResponse against the output of the note_and_next_html() method with the object id as its parameter. And from Fig. 1. we know that the method note_and_next_html() will return the object details and if the next item exists another htmx powered <div> will be created else a plain string ‘The End’ will be showcased.
With over 3 years of versatile experience in IT Specialist, Project Manager, CTO, and Coding Instructor roles, I bring a comprehensive skill set to my current position as a Senior IT Support Analyst at RBC Capital Markets. I am proficient in stakeholder management, envisioning, producing, and delivering well-tested software products, and optimizing business processes. My passion lies in two key areas: technical writing and cloud engineering.
My expertise in technical writing is evidenced by published works on esteemed platforms like Techflow360, FreeCodeCamp, and Elsevier. In the realm of cloud engineering, I am further bolstered by my Google Cloud Associate Cloud Engineer certification.
At She Thinks Code, I actively contribute to offering computer science education to women from Least Developed Countries, harnessing technology to empower individuals. I am eager to explore collaborations and initiatives that capitalize on my expertise in diverse technical environments, including leveraging my cloud engineering skills.
App Engine is a robust platform within Google Cloud that empowers developers to create and…
Django is an open-source web framework that helps developers to create and maintain high-quality, secure…
The problem of converting a string in a zigzag pattern is a classic problem in…
When Neeraj Chopra bagged India's only gold medal in Tokyo 2020 Olympics, the whole nation…
What is Biomechanics? We know, mechanics is the branch of physics dealing with the motion…
As your Django project starts growing; the process of testing and deployment gets cumbersome with…