Django HTMX Live Search with Pagination
Live search (or “search-as-you-type”) matters in UI/UX because it significantly reduces user effort and time to discovery, which in turn boosts engagement, satisfaction
Built-in pagination in Django is used to split large datasets (like database querysets) into smaller, manageable pages, improving performance and user experience by not overwhelming users with all data at once.
Reference https://www.djangoproject.com/ and https://htmx.org/
Step 1: Preparation, Create Django Project, Inital Migration
create virtualenv: virtualenv venv
start virtualenv: venv/Scripts/activate
install Django in virtualenv: pip install django==4.2
Create Django: django-admin startproject myproject
Go to myproject folder: cd myproject
Initial Migration: python manage.py migrate
Step 2: Create Django Apps
Create apps: python manage.py startapp myapp
Step 3: Project Setting: Register Apps, Set Templates Folder (myproject/settings.py)
...
INSTALLED_APPS = [
...
'myapp', #updated
]
...
TEMPLATES = [
...
'DIRS': [Path(BASE_DIR, 'templates')], #updated
...
]
...Step 4: Add Model in myapp/models.py
from django.db import models
# Create your models here.
class Person(models.Model):
name = models.CharField(max_length=100)Step 5: Makemigrations and Migrate
Make migrations: (venv) > python manage.py makemigrations
Migrate: (venv) > python manage.py migrate
Step 6: Use Faker to Create Fake 10000 Person Data (with django shell)
(venv) > pip install faker
(venv) > python manage.py shell
>>> from faker import Faker
>>> from myapp.models import Person
>>> fake = Faker()
>>> for i in range(0,10000):
... Person.create.objects(name=fake.name())
...Step 7: Create HTML Files in Templates Folder
Create templates/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Index</title>
</head>
<body>
<h1>Live search with pagination</h1>
<input type="search"
name="keyword"
placeholder="Search"
hx-get="{% url 'person_list' %}"
hx-trigger="load, input changed delay:500ms, keyword"
hx-target="#search-results"
hx-indicator=".htmx-indicator"
>
<br>
<span class="htmx-indicator" >
Searching...
</span>
<div id="search-results">
</div>
<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.8/dist/htmx.min.js"></script>
</body>
</html>Create templates/person_list.html
{% if page_obj %}
<h3>Total record count: {{ page_obj.paginator.count }}</h3>
{% for person in page_obj %}
<p>{{person.name }}</p>
{% endfor %}
<div class="pagination">
<span class="step-links">
{% if page_obj.has_previous %}
<button type="button" hx-get="{% url 'person_list' %}?keyword={{keyword}}&page=1" hx-target="#search-results" >« first</button>
<button type="button" hx-get="{% url 'person_list' %}?keyword={{keyword}}&page={{ page_obj.previous_page_number }}" hx-target="#search-results" >« previous</button>
{% endif %}
<span class="current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
</span>
{% if page_obj.has_next %}
<button type="button" hx-get="{% url 'person_list' %}?keyword={{keyword}}&page={{ page_obj.next_page_number }}" hx-target="#search-results" >next</button>
<button type="button" hx-get="{% url 'person_list' %}?keyword={{keyword}}&page={{ page_obj.paginator.num_pages }}" hx-target="#search-results" >last »</button>
{% endif %}
</span>
</div>
{% else %}
<p>No person</p>
{% endif %}Step 8: Create Function Views myapp/views.py
from django.shortcuts import render
from myapp.models import Person
from django.core.paginator import Paginator
# Create your views here.
def index(request):
template = 'index.html'
extra_context = {
}
return render(request, template, extra_context)
def person_list(request):
template = 'person_list.html'
keyword = request.GET.get("keyword")
persons = []
if keyword:
persons = Person.objects.filter(name__icontains=keyword)
else:
persons = Person.objects.all()
paginator = Paginator(persons, 10) # Show 10 persons per page
page_number = request.GET.get('page')
page_obj = paginator.get_page(page_number)
extra_context = {
'page_obj': page_obj,
'keyword': keyword
}
return render(request, template, extra_context)Step 9: Setup URLS
Create myapp/urls.py
from django.urls import path
from myapp import views
urlpatterns = [
path('', views.index, name='index'),
path('person_list', views.person_list, name='person_list'),
]Update myproject/urls.py
from django.contrib import admin
from django.urls import path, include #updated
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('myapp.urls')), #updated
]Step 10: Run Server and Testing
Run Server: python manage.py runserver
Testing:
