diff --git a/bin/lint b/bin/lint
index f199d21bf023..d20e5cf1a5f2 100755
--- a/bin/lint
+++ b/bin/lint
@@ -7,6 +7,6 @@ python -m flake8 .
python -m black --check --diff *.py warehouse/ tests/
python -m isort --check *.py warehouse/ tests/
sphinx-lint --enable=all --disable=line-too-long README.rst CONTRIBUTING.rst docs/dev/ --ignore=docs/dev/_build/
-python -m curlylint ./warehouse/templates ./docs/blog
+python -m djlint --check --lint ./warehouse/templates ./docs/blog
python -m mypy -p warehouse
./bin/flushes
diff --git a/bin/reformat b/bin/reformat
index 7975086b3bdf..e2420b8b49c3 100755
--- a/bin/reformat
+++ b/bin/reformat
@@ -4,3 +4,4 @@ set -ex
find . -name '*.py' -exec python -m pyupgrade --py313-plus {} +
python -m isort *.py warehouse/ tests/
python -m black *.py warehouse/ tests/
+python -m djlint --reformat ./warehouse/templates ./docs/blog
diff --git a/docs/blog/overrides/main.html b/docs/blog/overrides/main.html
index 35ae6c0ddc4d..de9302e9c20c 100644
--- a/docs/blog/overrides/main.html
+++ b/docs/blog/overrides/main.html
@@ -1,17 +1,14 @@
{% extends "base.html" %}
-
{% block extrahead %}
-{# JSON Feed #}
-{% if "rss" in config.plugins %}
-
-
-{% endif %}
+ {# JSON Feed #}
+ {% if "rss" in config.plugins %}
+
+
+ {% endif %}
{% endblock %}
diff --git a/pyproject.toml b/pyproject.toml
index bc030d39192b..995ef587fea9 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -28,10 +28,19 @@ exclude_lines = [
"if (typing\\.)?TYPE_CHECKING:",
]
-[tool.curlylint]
-include = '\.(html|jinja|txt)$'
-# For jinja's i18n extension:
-template_tags = [['trans', 'pluralize', 'endtrans']]
+[tool.djlint]
+# https://www.djlint.com/docs/configuration/
+indent = 2
+profile = "jinja"
+use_gitignore = true
+# TODO: Evaluate which `img` tags can get height/width attributes and remove H006.
+# H006: tag should have `height` and `width` attributes.
+# TODO: Convert single quotes to double quotes and remove T002 from ignore.
+# T002: Double quotes should be used in tags. Ex {% extends "this.html" %}
+# TODO: Add endblock names everywhere and remove T003 from ignore.
+# T003: Endblock should have name. Ex: {% endblock body %}.
+# T028: Handled by warehouse config. See: https://github.com/pypi/warehouse/pull/14709
+ignore = "H006,T002,T003,T028"
[tool.isort]
profile = 'black'
diff --git a/requirements/lint.in b/requirements/lint.in
index 3332314f3a77..d7e5eaca28f5 100644
--- a/requirements/lint.in
+++ b/requirements/lint.in
@@ -1,6 +1,6 @@
+djlint
flake8
flake8-pytest-style
-curlylint
pep8-naming
black==25.1.0
isort>=5.13.1
diff --git a/requirements/lint.txt b/requirements/lint.txt
index a0db64a0ebc5..8b19909e17f4 100644
--- a/requirements/lint.txt
+++ b/requirements/lint.txt
@@ -4,10 +4,6 @@
#
# pip-compile --allow-unsafe --generate-hashes --output-file=requirements/lint.txt requirements/lint.in
#
-attrs==25.3.0 \
- --hash=sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3 \
- --hash=sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b
- # via curlylint
black==25.1.0 \
--hash=sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171 \
--hash=sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7 \
@@ -118,7 +114,11 @@ click==8.1.8 \
--hash=sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a
# via
# black
- # curlylint
+ # djlint
+colorama==0.4.6 \
+ --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \
+ --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
+ # via djlint
cryptography==44.0.3 \
--hash=sha256:02f55fb4f8b79c1221b0961488eaae21015b69b210e18c386b69de182ebb1259 \
--hash=sha256:157f1f3b8d941c2bd8f3ffee0af9b049c9665c39d3da9db2dc338feca5e98a43 \
@@ -160,10 +160,40 @@ cryptography==44.0.3 \
# via
# types-pyopenssl
# types-redis
-curlylint==0.13.1 \
- --hash=sha256:008b9d160f3920404ac12efb05c0a39e209cb972f9aafd956b79c5f4e2162752 \
- --hash=sha256:9546ea82cdfc9292fd6fe49dca28587164bd315782a209c0a46e013d7f38d2fa
+cssbeautifier==1.15.4 \
+ --hash=sha256:78c84d5e5378df7d08622bbd0477a1abdbd209680e95480bf22f12d5701efc98 \
+ --hash=sha256:9bb08dc3f64c101a01677f128acf01905914cf406baf87434dcde05b74c0acf5
+ # via djlint
+djlint==1.36.4 \
+ --hash=sha256:16ce37e085afe5a30953b2bd87cbe34c37843d94c701fc68a2dda06c1e428ff4 \
+ --hash=sha256:17254f218b46fe5a714b224c85074c099bcb74e3b2e1f15c2ddc2cf415a408a1 \
+ --hash=sha256:3164a048c7bb0baf042387b1e33f9bbbf99d90d1337bb4c3d66eb0f96f5400a1 \
+ --hash=sha256:3196d5277da5934962d67ad6c33a948ba77a7b6eadf064648bef6ee5f216b03c \
+ --hash=sha256:3d68da0ed10ee9ca1e32e225cbb8e9b98bf7e6f8b48a8e4836117b6605b88cc7 \
+ --hash=sha256:4bc6a1320c0030244b530ac200642f883d3daa451a115920ef3d56d08b644292 \
+ --hash=sha256:53cbc450aa425c832f09bc453b8a94a039d147b096740df54a3547fada77ed08 \
+ --hash=sha256:5b01a98df3e1ab89a552793590875bc6e954cad661a9304057db75363d519fa0 \
+ --hash=sha256:6c601dfa68ea253311deb4a29a7362b7a64933bdfcfb5a06618f3e70ad1fa835 \
+ --hash=sha256:79489e262b5ac23a8dfb7ca37f1eea979674cfc2d2644f7061d95bea12c38f7e \
+ --hash=sha256:7a483390d17e44df5bc23dcea29bdf6b63f3ed8b4731d844773a4829af4f5e0b \
+ --hash=sha256:89678661888c03d7bc6cadd75af69db29962b5ecbf93a81518262f5c48329f04 \
+ --hash=sha256:962f7b83aee166e499eff916d631c6dde7f1447d7610785a60ed2a75a5763483 \
+ --hash=sha256:a2dfb60883ceb92465201bfd392291a7597c6752baede6fbb6f1980cac8d6c5c \
+ --hash=sha256:bb6903777bf3124f5efedcddf1f4716aef097a7ec4223fc0fa54b865829a6e08 \
+ --hash=sha256:bda5014f295002363381969864addeb2db13955f1b26e772657c3b273ed7809f \
+ --hash=sha256:c0478d5392247f1e6ee29220bbdbf7fb4e1bc0e7e83d291fda6fb926c1787ba7 \
+ --hash=sha256:dabbb4f7b93223d471d09ae34ed515fef98b2233cbca2449ad117416c44b1351 \
+ --hash=sha256:e58c5fa8c6477144a0be0a87273706a059e6dd0d6efae01146ae8c29cdfca675 \
+ --hash=sha256:e9699b8ac3057a6ed04fb90835b89bee954ed1959c01541ce4f8f729c938afdd \
+ --hash=sha256:ead475013bcac46095b1bbc8cf97ed2f06e83422335734363f8a76b4ba7e47c2 \
+ --hash=sha256:ff9faffd7d43ac20467493fa71d5355b5b330a00ade1c4d1e859022f4195223b
# via -r requirements/lint.in
+editorconfig==0.17.0 \
+ --hash=sha256:8739052279699840065d3a9f5c125d7d5a98daeefe53b0e5274261d77cb49aa2 \
+ --hash=sha256:fe491719c5f65959ec00b167d07740e7ffec9a3f362038c72b289330b9991dfc
+ # via
+ # cssbeautifier
+ # jsbeautifier
flake8==7.2.0 \
--hash=sha256:93b92ba5bdb60754a6da14fa3b93a9361fd00a59632ada61fd7b130436c40343 \
--hash=sha256:fa558ae3f6f7dbf2b4f22663e5343b6b6023620461f8d4ff2019ef4b5ee70426
@@ -182,6 +212,16 @@ isort==6.0.1 \
--hash=sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450 \
--hash=sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615
# via -r requirements/lint.in
+jsbeautifier==1.15.4 \
+ --hash=sha256:5bb18d9efb9331d825735fbc5360ee8f1aac5e52780042803943aa7f854f7592 \
+ --hash=sha256:72f65de312a3f10900d7685557f84cb61a9733c50dcc27271a39f5b0051bf528
+ # via
+ # cssbeautifier
+ # djlint
+json5==0.12.0 \
+ --hash=sha256:0b4b6ff56801a1c7dc817b0241bca4ce474a0e6a163bfef3fc594d3fd263ff3a \
+ --hash=sha256:6d37aa6c08b0609f16e1ec5ff94697e2cbbfbad5ac112afa05794da9ab7810db
+ # via djlint
markupsafe==3.0.2 \
--hash=sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4 \
--hash=sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30 \
@@ -369,16 +409,12 @@ packaging==25.0 \
--hash=sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 \
--hash=sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f
# via black
-parsy==1.1.0 \
- --hash=sha256:25bd5cea2954950ebbfdf71f8bdaf7fd45a5df5325fd36a1064be2204d9d4c94 \
- --hash=sha256:36173ba01a5372c7a1b32352cc73a279a49198f52252adf1c8c1ed41d1f94e8d
- # via curlylint
pathspec==0.12.1 \
--hash=sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08 \
--hash=sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712
# via
# black
- # curlylint
+ # djlint
pep8-naming==0.15.1 \
--hash=sha256:eb63925e7fd9e028c7f7ee7b1e413ec03d1ee5de0e627012102ee0222c273c86 \
--hash=sha256:f6f4a499aba2deeda93c1f26ccc02f3da32b035c8b2db9696b730ef2c9639d29
@@ -407,6 +443,61 @@ pyupgrade==3.19.1 \
--hash=sha256:8c5b0bfacae5ff30fa136a53eb7f22c34ba007450d4099e9da8089dabb9e67c9 \
--hash=sha256:d10e8c5f54b8327211828769e98d95d95e4715de632a3414f1eef3f51357b9e2
# via -r requirements/lint.in
+pyyaml==6.0.2 \
+ --hash=sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff \
+ --hash=sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48 \
+ --hash=sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086 \
+ --hash=sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e \
+ --hash=sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133 \
+ --hash=sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5 \
+ --hash=sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484 \
+ --hash=sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee \
+ --hash=sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5 \
+ --hash=sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68 \
+ --hash=sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a \
+ --hash=sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf \
+ --hash=sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99 \
+ --hash=sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8 \
+ --hash=sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85 \
+ --hash=sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19 \
+ --hash=sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc \
+ --hash=sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a \
+ --hash=sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1 \
+ --hash=sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317 \
+ --hash=sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c \
+ --hash=sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631 \
+ --hash=sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d \
+ --hash=sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652 \
+ --hash=sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5 \
+ --hash=sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e \
+ --hash=sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b \
+ --hash=sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8 \
+ --hash=sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476 \
+ --hash=sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706 \
+ --hash=sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563 \
+ --hash=sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237 \
+ --hash=sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b \
+ --hash=sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083 \
+ --hash=sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180 \
+ --hash=sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425 \
+ --hash=sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e \
+ --hash=sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f \
+ --hash=sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725 \
+ --hash=sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183 \
+ --hash=sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab \
+ --hash=sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774 \
+ --hash=sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725 \
+ --hash=sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e \
+ --hash=sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5 \
+ --hash=sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d \
+ --hash=sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290 \
+ --hash=sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44 \
+ --hash=sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed \
+ --hash=sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4 \
+ --hash=sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba \
+ --hash=sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12 \
+ --hash=sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4
+ # via djlint
regex==2024.11.6 \
--hash=sha256:02a02d2bb04fec86ad61f3ea7f49c015a0681bf76abb9857f945d26159d2968c \
--hash=sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60 \
@@ -502,7 +593,15 @@ regex==2024.11.6 \
--hash=sha256:fdabbfc59f2c6edba2a6622c647b716e34e8e3867e0ab975412c5c2f79b82da2 \
--hash=sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9 \
--hash=sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91
- # via sphinx-lint
+ # via
+ # djlint
+ # sphinx-lint
+six==1.17.0 \
+ --hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \
+ --hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81
+ # via
+ # cssbeautifier
+ # jsbeautifier
sphinx-lint==1.0.0 \
--hash=sha256:6117a0f340b2dc73eadfc57db7531d4477e0929f92a0c1a2f61e6edbc272f0bc \
--hash=sha256:6eafdb44172ce526f405bf36c713eb246f1340ec2d667e7298e2487ed76decd2
@@ -511,10 +610,10 @@ tokenize-rt==6.1.0 \
--hash=sha256:d706141cdec4aa5f358945abe36b911b8cbdc844545da99e811250c0cee9b6fc \
--hash=sha256:e8ee836616c0877ab7c7b54776d2fefcc3bde714449a206762425ae114b53c86
# via pyupgrade
-toml==0.10.2 \
- --hash=sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b \
- --hash=sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f
- # via curlylint
+tqdm==4.67.1 \
+ --hash=sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2 \
+ --hash=sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2
+ # via djlint
types-awscrt==0.26.1 \
--hash=sha256:176d320a26990efc057d4bf71396e05be027c142252ac48cc0d87aaea0704280 \
--hash=sha256:aca96f889b3745c0e74f42f08f277fed3bf6e9baa2cf9b06a36f78d77720e504
diff --git a/warehouse/admin/templates/admin/banners/edit.html b/warehouse/admin/templates/admin/banners/edit.html
index 08654f407ef5..3aed991e72dc 100644
--- a/warehouse/admin/templates/admin/banners/edit.html
+++ b/warehouse/admin/templates/admin/banners/edit.html
@@ -100,7 +100,7 @@