Creating Cascading Selects in Django with HTMX
Reference https://www.djangoproject.com/ and https://htmx.org/
Note: Django Admin used for add category and subcategory data.
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 Category(models.Model):
name = models.CharField(db_column='name', max_length=100)
class Meta:
verbose_name = 'Category'
verbose_name_plural = 'Categories'
def __unicode__(self):
return self.name
def __str__(self):
return self.name
class Subcategory(models.Model):
category = models.ForeignKey(Category, on_delete=models.CASCADE)
name = models.CharField(db_column='name', max_length=100)
class Meta:
verbose_name = 'Subcategory'
verbose_name_plural = 'Subcategories'
def __unicode__(self):
return self.name
def __str__(self):
return self.name
Step 5: Register Model in Django Admin
from django.contrib import admin
# Register your models here.
from django.contrib import admin
from .models import Category, Subcategory
class CategoryAdmin(admin.ModelAdmin):
list_display = ('name',)
admin.site.register(Category, CategoryAdmin)
class SubcategoryAdmin(admin.ModelAdmin):
list_display = ('name','category')
admin.site.register(Subcategory, SubcategoryAdmin)
Step 6: Makemigrations and Migrate
Make migrations: python manage.py makemigrations
Migrate: python manage.py migrate
Step 7: Create HTML Files in Templates Folder
Create templates folder in main app
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>Document</title>
</head>
<body>
<h1>Cascading Select</h1>
<form method="POST">
{% csrf_token %}
<label>Category</label>
<select id="category" name="category" hx-get="{% url 'subcategory_choice' %}" hx-target="#subcategory" hx-indicator=".htmx-indicator">
<option value="" selected>Select category</option>
{% for category in categories %}
<option value={{category.id}}>{{category.name}}</option>
{% endfor %}
</select>
<label>Subcategory</label>
<select id="subcategory" name="subcategory">
</select>
<span class="htmx-indicator">
Loading...
</span>
<br>
<br>
<button type="submit">Submit</button>
</form>
<script src="https://unpkg.com/htmx.org@2.0.3"></script>
</body>
</html>
Create templates/subcategory_choice.html
{% if subcategories %}
{% for subcategory in subcategories %}
<option value={{subcategory.id}}>{{subcategory.name}}</option>
{% endfor %}
{% endif %}
Step 8: Create Function Views myapp/views.py
from django.shortcuts import render, redirect
from .models import Category, Subcategory
def index(request):
categories = Category.objects.all()
if request.method == "GET" :
return render(request, 'index.html', {'categories': categories})
elif request.method == "POST":
selected_category_id = request.POST.get("category")
selected_subcategory_id = request.POST.get("subcategory")
print("Category ID", selected_category_id)
print("Subcategory ID", selected_subcategory_id)
return redirect("index")
def subcategory_choice(request):
category = request.GET.get("category")
if category:
subcategories = Subcategory.objects.filter(category__id = category)
return render(request, 'subcategory_choice.html', {'subcategories': subcategories})
else:
return render(request, 'subcategory_choice.html', {})
Step 9: Setup URLS
Create myapp/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name="index"),
path('subcategory_choice', views.subcategory_choice, name="subcategory_choice"),
]
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: Create Superuser
Create superuser: python manage.py createsuperuser
Type username, email password and retype password
Step 11: Run Server and Testing
Run Server: python manage.py runserver
Testing:
a. Login in Django Admin, add categories and subcategories http://127.0.0.1:8000/admin
b. Test Cascading Select
http://127.0.0.1:8000/