django-div¶
Build and parse HTML in Python with Pydantic models.
from django_div import A, Div, P
print(Div(P("Hello, World!"), A("Click", href="/x"), class_="card"))
Children are positional, attributes are keyword arguments. Text is escaped, void elements self-close, and Python attribute spellings map onto HTML ones.
Why this exists¶
Most HTML-in-Python libraries build markup and stop there. Here the tree is a Pydantic model, which means the same objects can go in four directions:
-
Build
Compose elements as Python values, with escaping handled for you.
-
Parse
Read existing markup back into the same tree, then search and edit it.
-
Serialize
Round-trip a page through JSON with its element classes intact.
-
Render in Django
Components as templates, with escaping that works both ways.
Install¶
With uv, create a virtual environment if needed and install the package:
With pip, install into your active virtual environment:
For uv-managed projects, uv add records the dependency in pyproject.toml.
The same extras below also work with uv pip install and python -m pip install:
uv add django-div # building only
uv add 'django-div[parse]' # plus from_html()/parse(), via bs4 + lxml
uv add 'django-div[html5]' # spec-exact parsing, ~3x slower than lxml
uv add 'django-div[markdown]' # plus from_markdown(), via markdown-it-py
django-div needs Python 3.12 or later.
Pydantic is the only hard dependency. Parsers are optional and their imports
are guarded, so building HTML pulls in nothing else. Django is optional too:
only django_div.django imports it.
A tour in one page¶
from django_div import Div, H1, Li, P, Span, Ul, from_html
# Nesting, attributes, escaping
Div(H1("Title"), P("a < b"), class_="page")
# <div class="page"><h1>Title</h1><p>a < b</p></div>
# None and False children drop out, so inline conditionals work
Div("Hello", user and Span(user.name))
# Collections flatten, so comprehensions splat in
Ul(Li(item) for item in items)
# Parse markup into the same kind of tree
page = from_html('<div><a href="/x" target="_blank">Click</a></div>')
page.find("a").attrs["href"] # '/x'
page.text # 'Click'
# Edit it and render it back out
for link in page.find_all("a", target="_blank"):
link.attrs["rel"] = "noopener"
print(page)
Where to next¶
The four cards above cover building, parsing, serializing, and Django. Beyond those:
- Markdown: render the tree as Markdown, read Markdown in
- Cookbooks: HTML · Django · Markdown
- API reference: every function, class, and constant
- Contributing: setup, conventions, tests
Reusable elements and browser data¶
Derive element variants with with_attrs(), render explicit ARIA states
from Python booleans, and pass browser data with JsonScript.
The cookbook includes button variants, disclosures,
HTML templates, and JSON data recipes.
Search by class tokens or custom conditions with predicates, and use
transform() to replace, remove, or unwrap nodes on a copied tree.
See searching and
transforming a copy for traversal and
copy semantics.
llms.txt¶
This documentation is available in the llms.txt format, a Markdown convention suited to LLMs and AI coding assistants.
Two files are published:
llms.txt: a short description of the project plus links to each section. The structure is described here.llms-full.txt: the same index with the content of every page inlined.
Every page is also published as Markdown alongside its HTML, so you can point
an assistant at a single section rather than the whole corpus. Append .md to
the page name:
https://django-div.readthedocs.io/en/latest/building.md
https://django-div.readthedocs.io/en/latest/django.md
Where it came from¶
django-div started as five throwaway scripts trying to answer one question:
how do you make Div("hello", class_="x") work when Pydantic wants keyword
fields? Those experiments are still in the git history at the
Baseline: original django-div demo experiments commit.
Prior art¶
htpy, dominate, and django-components cover adjacent ground, and htpy in particular landed on a very similar constructor shape. django-div's angle is the Pydantic model underneath: the same objects parse, validate, and serialize.