My favorite Django 6 Setup
My favorite Django 6 Setup
Requirements is just uv installed before:
https://docs.astral.sh/uv/getting-started/installation/#pypi
create a project folder and enter that.
Install Django
uv add django
uv run django-admin startproject core .
uv run manage.py startapp app
uv run manage.py migrate
uv run manage.py createsuperuser
core/settings.py
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
"app", # Added this
]
mkdir templates
in core/settings.py
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
# 'DIRS': [], originally
'DIRS': [BASE_DIR / 'templates'], # Changed to this
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
in core/urls.py
from django.contrib import admin
from django.urls import include, path # Add include
urlpatterns = [
path("admin/", admin.site.urls),
path("", include("app.urls")), # Added this
]
create app/urls.py
from django.urls import path
from . import views
urlpatterns = [
path("", views.index, name="home"),
]
add this to core/settings.py
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/6.0/howto/static-files/
STATIC_URL = "static/"
STATICFILES_DIRS = [BASE_DIR / "static"] # Added this
Install Daisy UI including Tailwind css
https://daisyui.com/docs/install/django/
mkdir static
mkdir static/css
cd static/css
curl -sL daisyui.com/fast | bash
cd back to project root
Install Alpinejs
mkdir static/src
curl -o static/src/alpine.min.js https://cdn.jsdelivr.net/npm/alpinejs@3.15.11/dist/cdn.min.js
Install htmx
uv add django_htmx
in core/settings.py
# Application definition
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"app",
"django_htmx", # Added this
]
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"django_htmx.middleware.HtmxMiddleware", # Added this
]
Install font awasome
https://fontawesome.com/download
mkdir static/webfonts
wget https://use.fontawesome.com/releases/v7.2.0/fontawesome-free-7.2.0-web.zip
unzip fontawesome-free-7.2.0-web.zip
cp fontawesome-free-7.2.0-web/css/all.min.css static/src/fa.min.css
cp -r fontawesome-free-7.2.0-web/webfonts/* static/webfonts/
rm -rf fontawesome-free-7.2.0-web fontawesome-free-7.2.0-web.zip
Install django-cotton
uv add django-cotton
“Add ‘django_cotton’, to your INSTALLED_APPS:”
Install django-tasks
uv add django-tasks-db
add “django_tasks_db”, to “INSTALLED_APPS”
add to core/settings.py
TASKS = {
"default": {
"BACKEND": "django_tasks_db.DatabaseBackend",
}
}
Create view
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.urls import reverse
from django.views.decorators.http import require_POST
from django_tasks_db.models import DBTaskResult
from . import tasks
def _recent_tasks():
return DBTaskResult.objects.all().order_by("-enqueued_at")[:8]
def index(request):
return render(request, "index.html", {"tasks": _recent_tasks()})
@require_POST
def enqueue_report(request):
label = (request.POST.get("label") or "Untitled report").strip()
tasks.generate_report.enqueue(label)
if request.htmx:
return render(request, "index.html#task-list", {"tasks": _recent_tasks()})
return HttpResponseRedirect(reverse("home"))
def task_list(request):
return render(request, "index.html#task-list", {"tasks": _recent_tasks()})
add urls in addp/urls.py
from django.urls import path
from . import views
urlpatterns = [
path("", views.index, name="home"),
path("tasks/enqueue/", views.enqueue_report, name="enqueue_report"), # Added this
path("tasks/", views.task_list, name="task_list"), # Added this
]
add app/tasks.py
import random
import time
from django.tasks import task
@task()
def generate_report(label: str) -> dict:
duration = random.uniform(2, 5)
time.sleep(duration)
return {"label": label, "duration_seconds": round(duration, 2)}
add templates/base.html
{% load static django_htmx %}
<!doctype html>
<html lang="en" data-theme="dim">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}blogpost · Django 6 dashboard{% endblock %}</title>
<link rel="stylesheet" href="{% static 'css/output.css' %}">
<link rel="stylesheet" href="{% static 'src/fa.min.css' %}">
</head>
<body class="min-h-screen bg-base-200 text-base-content">
<div class="navbar bg-base-100 shadow-sm sticky top-0 z-30"
x-data="{ theme: document.documentElement.dataset.theme }">
<div class="flex-1">
<a href="{% url 'home' %}" class="btn btn-ghost text-lg gap-2">
<i class="fa-solid fa-feather-pointed text-primary"></i>
blogpost
</a>
</div>
<div class="flex-none gap-2">
<span class="badge badge-outline hidden sm:inline-flex" x-text="`theme: ${theme}`"></span>
<label class="swap swap-rotate btn btn-ghost btn-circle" title="Toggle theme">
<input type="checkbox"
:checked="theme === 'emerald'"
@change="theme = theme === 'dim' ? 'emerald' : 'dim'; document.documentElement.dataset.theme = theme">
<i class="swap-on fa-solid fa-sun"></i>
<i class="swap-off fa-solid fa-moon"></i>
</label>
</div>
</div>
<main class="max-w-6xl mx-auto px-4 py-8 space-y-8">
{% block content %}{% endblock %}
</main>
<script src="{% static 'src/alpine.min.js' %}" defer></script>
{% htmx_script %}
{% django_htmx_script %}
</body>
</html>
add templates/index.html
{% extends "base.html" %}
{% load static %}
{% block content %}
<section class="hero bg-base-100 rounded-box shadow">
<div class="hero-content flex-col lg:flex-row-reverse gap-10 py-10">
<div class="text-6xl opacity-80">
<i class="fa-brands fa-python text-primary"></i>
<i class="fa-solid fa-arrow-right mx-2 text-base-content/30"></i>
<i class="fa-solid fa-gauge-high text-secondary"></i>
</div>
<div class="max-w-xl">
<div class="badge badge-primary badge-soft mb-3">Django 6.0 · Python 3.13</div>
<h1 class="text-4xl font-bold leading-tight">What's new, live.</h1>
<p class="py-4 opacity-80">
A working tour of two headline features from Django 6 —
<strong>native background Tasks</strong> and <strong>template partials</strong> —
wired up to the frontend stack this project is built on.
</p>
<div class="flex gap-2 flex-wrap">
<span class="badge badge-outline">uv</span>
<span class="badge badge-outline">Tailwind v4</span>
<span class="badge badge-outline">daisyUI</span>
<span class="badge badge-outline">HTMX</span>
<span class="badge badge-outline">Alpine.js</span>
<span class="badge badge-outline">django-cotton</span>
<span class="badge badge-outline">Font Awesome</span>
</div>
</div>
</div>
</section>
<section class="card bg-base-100 shadow">
<div class="card-body">
<div class="flex items-start justify-between flex-wrap gap-4">
<div class="max-w-2xl">
<h2 class="card-title">
<i class="fa-solid fa-list-check text-primary"></i>
Background Tasks
<span class="badge badge-success badge-soft">new in 6.0</span>
</h2>
<p class="opacity-70 mt-1">
Enqueue a job from the browser — it lands in SQLite via
<code class="kbd kbd-sm">django-tasks-db</code> and a worker picks it up.
Watch the row flip from <span class="badge badge-ghost badge-sm">READY</span>
to <span class="badge badge-warning badge-sm">RUNNING</span>
to <span class="badge badge-success badge-sm">SUCCESSFUL</span>.
</p>
<p class="text-xs opacity-60 mt-2">
<i class="fa-solid fa-circle-info"></i>
Nothing will actually run until you start a worker:
<code class="kbd kbd-sm">uv run python manage.py db_worker</code>
</p>
</div>
<form hx-post="{% url 'enqueue_report' %}"
hx-target="#task-list"
hx-swap="outerHTML"
class="join">
{% csrf_token %}
<input name="label"
placeholder="Report name"
class="input input-bordered join-item"
required>
<button class="btn btn-primary join-item">
<i class="fa-solid fa-paper-plane"></i> Enqueue
</button>
</form>
</div>
<div hx-get="{% url 'task_list' %}"
hx-trigger="every 2s"
hx-target="#task-list"
hx-swap="outerHTML"
class="text-xs opacity-60 mt-4 flex items-center gap-2">
<span class="loading loading-dots loading-xs"></span>
polling every 2s via HTMX — the server returns
<code>index.html#task-list</code>, a template partial
</div>
{% partialdef task-list inline %}
<div id="task-list" class="overflow-x-auto mt-2">
<table class="table table-sm table-zebra">
<thead>
<tr>
<th>Status</th>
<th>Task</th>
<th>Args</th>
<th>Enqueued</th>
<th>Finished</th>
<th>Result</th>
</tr>
</thead>
<tbody>
{% for t in tasks %}
<tr>
<td>
{% if t.status == "SUCCESSFUL" %}
<span class="badge badge-success badge-sm">{{ t.status }}</span>
{% elif t.status == "FAILED" %}
<span class="badge badge-error badge-sm">{{ t.status }}</span>
{% elif t.status == "RUNNING" %}
<span class="badge badge-warning badge-sm">{{ t.status }}</span>
{% else %}
<span class="badge badge-ghost badge-sm">{{ t.status }}</span>
{% endif %}
</td>
<td class="font-mono text-xs">{{ t.task_name }}</td>
<td class="font-mono text-xs opacity-70">{{ t.args_kwargs.args|join:", " }}</td>
<td class="text-xs opacity-70">{{ t.enqueued_at|date:"H:i:s" }}</td>
<td class="text-xs opacity-70">{{ t.finished_at|date:"H:i:s"|default:"—" }}</td>
<td class="text-xs opacity-70">{{ t.return_value|default:"—" }}</td>
</tr>
{% empty %}
<tr><td colspan="6" class="text-center opacity-60 py-6">No tasks yet — enqueue one above.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
{% endpartialdef %}
</div>
</section>
<section class="card bg-base-100 shadow">
<div class="card-body">
<h2 class="card-title">
<i class="fa-solid fa-puzzle-piece text-secondary"></i>
Template partials
<span class="badge badge-success badge-soft">new in 6.0</span>
</h2>
<p class="opacity-70">
Reusable template fragments defined inline. Declare with
<code class="kbd kbd-sm">{% verbatim %}{% partialdef name %}{% endverbatim %}</code>,
then render from a view as <code class="kbd kbd-sm">"index.html#name"</code>.
The task table above is the very partial being re-rendered on every HTMX poll —
no second template file needed.
</p>
<div class="mockup-code text-xs">
<pre data-prefix="1"><code>{% verbatim %}{% partialdef task-list inline %}{% endverbatim %}</code></pre>
<pre data-prefix="2"><code> <table>...</table></code></pre>
<pre data-prefix="3"><code>{% verbatim %}{% endpartialdef %}{% endverbatim %}</code></pre>
<pre data-prefix="·"><code></code></pre>
<pre data-prefix="$" class="text-success"><code># in the view</code></pre>
<pre data-prefix=">"><code>return render(request, "index.html#task-list", ctx)</code></pre>
</div>
</div>
</section>
<section>
<h2 class="text-2xl font-bold mb-4 px-2 flex items-center gap-2">
<i class="fa-solid fa-layer-group text-accent"></i> Frontend stack
</h2>
<div class="grid md:grid-cols-2 gap-4">
<c-feature icon="fa-palette" icon-bg="bg-primary/15 text-primary" title="daisyUI" badge="Tailwind v4">
Pre-built semantic components —
<span class="kbd kbd-xs">card</span>,
<span class="kbd kbd-xs">badge</span>,
<span class="kbd kbd-xs">btn</span>,
<span class="kbd kbd-xs">stats</span> —
and themeable via <code>data-theme</code>. The navbar toggle flips between
<code>dim</code> and <code>emerald</code> at runtime.
</c-feature>
<c-feature icon="fa-bolt" icon-bg="bg-secondary/15 text-secondary" title="HTMX" badge="hx-*">
The form posts and the poller both live entirely in HTML attributes. The
<code>django-htmx</code> middleware exposes <code>request.htmx</code> so views
can return a partial on HTMX requests and a full page otherwise.
</c-feature>
<c-feature icon="fa-mountain" icon-bg="bg-accent/15 text-accent" title="Alpine.js" badge="x-data">
Tiny client-side state for interactions that don't need the server. The theme
toggle uses <code>x-data</code> + <code>@change</code> to rewrite
<code>document.documentElement.dataset.theme</code>. Quick demo:
<div class="mt-3" x-data="{ n: 0 }">
<button class="btn btn-xs btn-accent" @click="n++">
Clicked <span class="font-mono ml-1" x-text="n"></span> times
</button>
</div>
</c-feature>
<c-feature icon="fa-cubes" icon-bg="bg-info/15 text-info" title="django-cotton" badge="<c-*>">
Every card in this grid is rendered by a single
<code class="kbd kbd-sm"><c-feature></code> component at
<code>templates/cotton/feature.html</code>. Attributes become template variables
(<code>icon</code>, <code>title</code>, <code>badge</code>) and children flow
into <code>{% verbatim %}{{ slot }}{% endverbatim %}</code>.
</c-feature>
<c-feature icon="fa-icons" icon-bg="bg-warning/15 text-warning" title="Font Awesome" badge="self-hosted">
Every icon on this page — the navbar feather, the task checkmark, the stack tiles
themselves — is Font Awesome, served locally from
<code>static/src/fa.min.css</code> + <code>static/webfonts/</code>, no CDN.
All three families are available:
<div class="flex items-center gap-4 mt-3 text-2xl">
<span title="solid"><i class="fa-solid fa-rocket text-primary"></i></span>
<span title="regular"><i class="fa-regular fa-heart text-secondary"></i></span>
<span title="brands"><i class="fa-brands fa-github"></i></span>
<span title="brands"><i class="fa-brands fa-python text-info"></i></span>
<span title="brands"><i class="fa-brands fa-js text-warning"></i></span>
<span title="solid"><i class="fa-solid fa-database text-success"></i></span>
</div>
</c-feature>
</div>
</section>
{% endblock %}
create template/cotton
create template/cotton/feature.html
<div class="card bg-base-100 shadow">
<div class="card-body">
<h3 class="card-title gap-3">
<span class="{{ icon_bg|default:'bg-primary/15 text-primary' }} rounded-lg w-10 h-10 grid place-items-center shrink-0">
<i class="fa-solid {{ icon }}"></i>
</span>
<span>{{ title }}</span>
{% if badge %}<span class="badge badge-soft badge-sm">{{ badge }}</span>{% endif %}
</h3>
<div class="opacity-80 space-y-2 text-sm">{{ slot }}</div>
</div>
</div>
uv run manage.py migrate
rebuild CSS on template edits
static/css/tailwindcss -i static/css/input.css -o static/css/output.css
start server
uv run manage.py runserver
in another terminal start task worker
uv run python manage.py db_worker