Agents learnt Django from a decade of answers, so they still write url(), ugettext_lazy, force_text, request.is_ajax(), Meta.index_together and {% load staticfiles %}. All of those are gone, and Django 6.0 and 6.1 removed more: CheckConstraint(check=), positional Model.save() arguments, format_html() with no arguments, finders.find(all=) and the Postgres aggregates' ordering=. Django 6.1 also moves mail settings into MAILERS and adds fetch modes.
This loadout pairs a short Cursor rule and AGENTS.md (a never/always table and the Django 6 version facts) with a skill that checks the work in three layers. A standard-library Python scanner reports removed APIs as errors and behaviour changes as warnings. django-upgrade rewrites what it safely can. Then manage.py check, makemigrations --check and the tests run with the next two deprecation warnings raised as errors. The verifier runs all of it on a small Django 6.1 shop project and a Django 3-era sample, with the pinned Django 6.1.2 and django-upgrade 1.33.0.
What's in it
- Never/always table for Django 6.1 in a Cursor rule for .py and templates
- Scanner flags 17 removed Django APIs as errors, read-only
- Runs django-upgrade --check at target 6.1 alongside the scanner
- Tests run with RemovedInDjango70Warning and RemovedInDjango2028Warning as errors
- Covers 6.1's MAILERS setting and FETCH_RAISE for N+1 queries
You'll need
- Tools:
python3,uv
Verification
| Check | Result | What we found |
|---|---|---|
| Listinglisting.yaml matches the schema and declares every file in the folder. | Pass | Schema valid; 8 files declared, none missing or extra
|
| Files parseJSON parses; SKILL.md and .mdc front matter is valid YAML with the fields each agent needs; code fences are closed. | Pass | All 8 files parse; front matter valid |
| AdaptersCLAUDE.md imports @AGENTS.md, GEMINI.md imports @./AGENTS.md, and both match the generator. | Pass | CLAUDE.md and GEMINI.md import AGENTS.md and are up to date
|
| Install pathsEvery file lands where each agent documents it, with no collisions, and the laid-out project is re-discovered correctly. | Pass | Correct paths for all 6 agents; layouts re-discovered
|
| MCP configmcp.json is structurally valid, converts to each agent's format, declares no literal secrets, and referenced packages exist on npm/PyPI. | Not applicable | No MCP servers in this loadout |
| Safety scanThe intake scanner finds no credentials, risky shell, hidden Unicode, instructions hidden in HTML comments or after long blank padding, or prompt-injection and covert wording (such as telling the agent to keep something from you), and the listing's network flag is honest. | Pass | Clean: no credentials, risky shell, hidden text or covert instructions
|
| ReferencesLinks and file paths between the loadout's files resolve, including for each agent's installed layout. | Pass | 3 references between files resolve for every agent |
| Size limitsFile count and sizes are within the submission limits; SKILL.md bodies stay under 500 lines. | Pass | 8 files, 18.4 KB total; largest skills/django-6-guard/scripts/django6_guard.py 7.6 KB
|
| LicenceA recognised SPDX licence, with the licence text available. | Pass | MIT, by SetupBay; text in the repository LICENSE |
| Starter buildA minimal fixture project with the loadout applied installs and builds or type-checks, and the loadout's own code examples compile. | Pass | django6_guard.py passes a small Django 6.1 shop project and flags every old line in the skill's Django 3-era sample: 12 removed APIs as errors and 2 EMAIL_* settings as warnings. django-upgrade 1.33.0 finds nothing to rewrite in the project and would rewrite the sample. The pinned Django 6.1.2 passe
|
What this means: a script checked the files themselves: they parse, they install where each agent looks, any MCP config is sound, the safety scan is clean, references between files resolve, and, where a free and repeatable fixture exists, a small starter project builds with the loadout applied. No AI agent was run. Per-agent results from agent runs are separate and stay untested until an agent actually runs.
Install
Choose your agent. Run the commands from your project root, or download a zip already laid out in the right folders.
Claude Code reads CLAUDE.md (imports AGENTS.md with @AGENTS.md) and skills from .claude/skills/.
curl -fsSL --create-dirs -o AGENTS.md https://setupbay.com/raw/django-6-upgrade-guard/1.0.0/AGENTS.md curl -fsSL --create-dirs -o CLAUDE.md https://setupbay.com/raw/django-6-upgrade-guard/1.0.0/CLAUDE.md curl -fsSL --create-dirs -o .claude/skills/django-6-guard/SKILL.md https://setupbay.com/raw/django-6-upgrade-guard/1.0.0/skills/django-6-guard/SKILL.md curl -fsSL --create-dirs -o .claude/skills/django-6-guard/scripts/django6_guard.py https://setupbay.com/raw/django-6-upgrade-guard/1.0.0/skills/django-6-guard/scripts/django6_guard.py curl -fsSL --create-dirs -o .claude/skills/django-6-guard/references/removals.md https://setupbay.com/raw/django-6-upgrade-guard/1.0.0/skills/django-6-guard/references/removals.md curl -fsSL --create-dirs -o .claude/skills/django-6-guard/references/django3-sample.py.txt https://setupbay.com/raw/django-6-upgrade-guard/1.0.0/skills/django-6-guard/references/django3-sample.py.txt
| File | Goes to |
|---|---|
AGENTS.md | AGENTS.md |
CLAUDE.md | CLAUDE.md |
skills/django-6-guard/SKILL.md | .claude/skills/django-6-guard/SKILL.md |
skills/django-6-guard/scripts/django6_guard.py | .claude/skills/django-6-guard/scripts/django6_guard.py |
skills/django-6-guard/references/removals.md | .claude/skills/django-6-guard/references/removals.md |
skills/django-6-guard/references/django3-sample.py.txt | .claude/skills/django-6-guard/references/django3-sample.py.txt |
Already have an AGENTS.md or CLAUDE.md? The commands above overwrite it — download the file instead and merge the sections you want. Which file wins when several are present: what Claude Code reads.
Codex CLI reads AGENTS.md and skills from .agents/skills/.
curl -fsSL --create-dirs -o AGENTS.md https://setupbay.com/raw/django-6-upgrade-guard/1.0.0/AGENTS.md curl -fsSL --create-dirs -o .agents/skills/django-6-guard/SKILL.md https://setupbay.com/raw/django-6-upgrade-guard/1.0.0/skills/django-6-guard/SKILL.md curl -fsSL --create-dirs -o .agents/skills/django-6-guard/scripts/django6_guard.py https://setupbay.com/raw/django-6-upgrade-guard/1.0.0/skills/django-6-guard/scripts/django6_guard.py curl -fsSL --create-dirs -o .agents/skills/django-6-guard/references/removals.md https://setupbay.com/raw/django-6-upgrade-guard/1.0.0/skills/django-6-guard/references/removals.md curl -fsSL --create-dirs -o .agents/skills/django-6-guard/references/django3-sample.py.txt https://setupbay.com/raw/django-6-upgrade-guard/1.0.0/skills/django-6-guard/references/django3-sample.py.txt
| File | Goes to |
|---|---|
AGENTS.md | AGENTS.md |
skills/django-6-guard/SKILL.md | .agents/skills/django-6-guard/SKILL.md |
skills/django-6-guard/scripts/django6_guard.py | .agents/skills/django-6-guard/scripts/django6_guard.py |
skills/django-6-guard/references/removals.md | .agents/skills/django-6-guard/references/removals.md |
skills/django-6-guard/references/django3-sample.py.txt | .agents/skills/django-6-guard/references/django3-sample.py.txt |
Already have an AGENTS.md or CLAUDE.md? The commands above overwrite it — download the file instead and merge the sections you want. Which file wins when several are present: what Codex CLI reads.
Cursor reads AGENTS.md + .cursor/rules/*.mdc and skills from .agents/skills/.
curl -fsSL --create-dirs -o AGENTS.md https://setupbay.com/raw/django-6-upgrade-guard/1.0.0/AGENTS.md curl -fsSL --create-dirs -o .cursor/rules/django-6-only.mdc https://setupbay.com/raw/django-6-upgrade-guard/1.0.0/.cursor/rules/django-6-only.mdc curl -fsSL --create-dirs -o .agents/skills/django-6-guard/SKILL.md https://setupbay.com/raw/django-6-upgrade-guard/1.0.0/skills/django-6-guard/SKILL.md curl -fsSL --create-dirs -o .agents/skills/django-6-guard/scripts/django6_guard.py https://setupbay.com/raw/django-6-upgrade-guard/1.0.0/skills/django-6-guard/scripts/django6_guard.py curl -fsSL --create-dirs -o .agents/skills/django-6-guard/references/removals.md https://setupbay.com/raw/django-6-upgrade-guard/1.0.0/skills/django-6-guard/references/removals.md curl -fsSL --create-dirs -o .agents/skills/django-6-guard/references/django3-sample.py.txt https://setupbay.com/raw/django-6-upgrade-guard/1.0.0/skills/django-6-guard/references/django3-sample.py.txt
| File | Goes to |
|---|---|
AGENTS.md | AGENTS.md |
.cursor/rules/django-6-only.mdc | .cursor/rules/django-6-only.mdc |
skills/django-6-guard/SKILL.md | .agents/skills/django-6-guard/SKILL.md |
skills/django-6-guard/scripts/django6_guard.py | .agents/skills/django-6-guard/scripts/django6_guard.py |
skills/django-6-guard/references/removals.md | .agents/skills/django-6-guard/references/removals.md |
skills/django-6-guard/references/django3-sample.py.txt | .agents/skills/django-6-guard/references/django3-sample.py.txt |
Already have an AGENTS.md or CLAUDE.md? The commands above overwrite it — download the file instead and merge the sections you want. Which file wins when several are present: what Cursor reads.
Gemini CLI reads GEMINI.md (imports AGENTS.md with @./AGENTS.md) and skills from .agents/skills/.
curl -fsSL --create-dirs -o AGENTS.md https://setupbay.com/raw/django-6-upgrade-guard/1.0.0/AGENTS.md curl -fsSL --create-dirs -o GEMINI.md https://setupbay.com/raw/django-6-upgrade-guard/1.0.0/GEMINI.md curl -fsSL --create-dirs -o .agents/skills/django-6-guard/SKILL.md https://setupbay.com/raw/django-6-upgrade-guard/1.0.0/skills/django-6-guard/SKILL.md curl -fsSL --create-dirs -o .agents/skills/django-6-guard/scripts/django6_guard.py https://setupbay.com/raw/django-6-upgrade-guard/1.0.0/skills/django-6-guard/scripts/django6_guard.py curl -fsSL --create-dirs -o .agents/skills/django-6-guard/references/removals.md https://setupbay.com/raw/django-6-upgrade-guard/1.0.0/skills/django-6-guard/references/removals.md curl -fsSL --create-dirs -o .agents/skills/django-6-guard/references/django3-sample.py.txt https://setupbay.com/raw/django-6-upgrade-guard/1.0.0/skills/django-6-guard/references/django3-sample.py.txt
| File | Goes to |
|---|---|
AGENTS.md | AGENTS.md |
GEMINI.md | GEMINI.md |
skills/django-6-guard/SKILL.md | .agents/skills/django-6-guard/SKILL.md |
skills/django-6-guard/scripts/django6_guard.py | .agents/skills/django-6-guard/scripts/django6_guard.py |
skills/django-6-guard/references/removals.md | .agents/skills/django-6-guard/references/removals.md |
skills/django-6-guard/references/django3-sample.py.txt | .agents/skills/django-6-guard/references/django3-sample.py.txt |
Already have an AGENTS.md or CLAUDE.md? The commands above overwrite it — download the file instead and merge the sections you want. Which file wins when several are present: what Gemini CLI reads.
Antigravity CLI reads AGENTS.md (read natively; GEMINI.md is also read) and skills from .agents/skills/.
curl -fsSL --create-dirs -o AGENTS.md https://setupbay.com/raw/django-6-upgrade-guard/1.0.0/AGENTS.md curl -fsSL --create-dirs -o .agents/skills/django-6-guard/SKILL.md https://setupbay.com/raw/django-6-upgrade-guard/1.0.0/skills/django-6-guard/SKILL.md curl -fsSL --create-dirs -o .agents/skills/django-6-guard/scripts/django6_guard.py https://setupbay.com/raw/django-6-upgrade-guard/1.0.0/skills/django-6-guard/scripts/django6_guard.py curl -fsSL --create-dirs -o .agents/skills/django-6-guard/references/removals.md https://setupbay.com/raw/django-6-upgrade-guard/1.0.0/skills/django-6-guard/references/removals.md curl -fsSL --create-dirs -o .agents/skills/django-6-guard/references/django3-sample.py.txt https://setupbay.com/raw/django-6-upgrade-guard/1.0.0/skills/django-6-guard/references/django3-sample.py.txt
| File | Goes to |
|---|---|
AGENTS.md | AGENTS.md |
skills/django-6-guard/SKILL.md | .agents/skills/django-6-guard/SKILL.md |
skills/django-6-guard/scripts/django6_guard.py | .agents/skills/django-6-guard/scripts/django6_guard.py |
skills/django-6-guard/references/removals.md | .agents/skills/django-6-guard/references/removals.md |
skills/django-6-guard/references/django3-sample.py.txt | .agents/skills/django-6-guard/references/django3-sample.py.txt |
Already have an AGENTS.md or CLAUDE.md? The commands above overwrite it — download the file instead and merge the sections you want. Which file wins when several are present: what Antigravity CLI reads.
GitHub Copilot reads AGENTS.md and skills from .agents/skills/.
curl -fsSL --create-dirs -o AGENTS.md https://setupbay.com/raw/django-6-upgrade-guard/1.0.0/AGENTS.md curl -fsSL --create-dirs -o .agents/skills/django-6-guard/SKILL.md https://setupbay.com/raw/django-6-upgrade-guard/1.0.0/skills/django-6-guard/SKILL.md curl -fsSL --create-dirs -o .agents/skills/django-6-guard/scripts/django6_guard.py https://setupbay.com/raw/django-6-upgrade-guard/1.0.0/skills/django-6-guard/scripts/django6_guard.py curl -fsSL --create-dirs -o .agents/skills/django-6-guard/references/removals.md https://setupbay.com/raw/django-6-upgrade-guard/1.0.0/skills/django-6-guard/references/removals.md curl -fsSL --create-dirs -o .agents/skills/django-6-guard/references/django3-sample.py.txt https://setupbay.com/raw/django-6-upgrade-guard/1.0.0/skills/django-6-guard/references/django3-sample.py.txt
| File | Goes to |
|---|---|
AGENTS.md | AGENTS.md |
skills/django-6-guard/SKILL.md | .agents/skills/django-6-guard/SKILL.md |
skills/django-6-guard/scripts/django6_guard.py | .agents/skills/django-6-guard/scripts/django6_guard.py |
skills/django-6-guard/references/removals.md | .agents/skills/django-6-guard/references/removals.md |
skills/django-6-guard/references/django3-sample.py.txt | .agents/skills/django-6-guard/references/django3-sample.py.txt |
Already have an AGENTS.md or CLAUDE.md? The commands above overwrite it — download the file instead and merge the sections you want. Which file wins when several are present: what GitHub Copilot reads.
Or as a Claude Code plugin
Adds the skill to Claude Code for all your projects, without copying files. Plugins can't carry project rules, so add AGENTS.md and CLAUDE.md with the commands above. About the plugin marketplace.
/plugin marketplace add https://setupbay.com/marketplace.json /plugin install django-6-upgrade-guard@setupbay
Files
Rules AGENTS.md Django 6 version facts, rules and the done checklist. 37 lines
# AGENTS.md: Django 6 project (old Django guard)
This project runs **Django 6.1** on Python 3.12 or later. Much of the Django code you remember is from 2.x–4.x,
and Django has removed a lot since then: `url()`, `ugettext`, `force_text`, `is_ajax()`, `index_together`,
`NullBooleanField`, and in 6.0 and 6.1 `CheckConstraint(check=)`, positional `Model.save()` arguments and more.
## Version facts
- Check the pin first: `uv run python -m django --version`.
- Django 6.0 needs Python 3.12+, defaults `DEFAULT_AUTO_FIELD` to `BigAutoField`, and assumes `https` in
`forms.URLField`. It also adds built-in CSP support, template partials and a background Tasks framework (`django.tasks`).
- Django 6.1 removes the 5.2 deprecations, for example `finders.find(all=)` → `find_all=`, and
`ArrayAgg(ordering=)` → `order_by=`. `login(request, None)` no longer falls back to `request.user`.
- Django 6.1 deprecates the `EMAIL_*` connection settings: configure mail in `MAILERS`
(`{"default": {"BACKEND": ..., "OPTIONS": {...}}}`), in the same style as `DATABASES` and `CACHES`.
- Django 6.1 adds fetch modes: `Order.objects.fetch_mode(models.FETCH_RAISE)` turns a lazy load (an N+1) into an
error, and `FETCH_PEERS` loads the field for every instance from the same query at once.
- Deprecation warnings for the next removals are `RemovedInDjango70Warning`, then `RemovedInDjango2028Warning`
(the class name says which release removes them).
- If you're not sure an API exists, check the docs for this version (https://docs.djangoproject.com/en/6.1/) and
the release notes, not old tutorials.
## Rules
- Write Django 6 code only. The `django-6-guard` skill has the full table. The common ones are: `path()` /
`re_path()`, `gettext_lazy`, `force_str`, `CheckConstraint(condition=)`, `save(update_fields=[...])` with keywords,
`Meta.indexes`, `BooleanField(null=True)`, `{% load static %}`, `datetime.UTC` / `zoneinfo`.
- Run `django-upgrade --target-version 6.1` on files you change. It rewrites many old forms safely.
- Every model change comes with a migration made by `makemigrations`. Never write or edit migrations by hand to "fix" a check.
- For simple background jobs, look at `django.tasks` before adding Celery, and ask the human.
- Don't change the Django version or `DEFAULT_AUTO_FIELD` in an existing project without asking.
## Before you say "done"
1. Run the `django-6-guard` scanner on the apps and templates you touched. It must report `0 error(s)`.
2. Run `django-upgrade --check --target-version 6.1` on the changed `.py` files. It must report nothing to rewrite.
3. Run `python manage.py check` and `python manage.py makemigrations --check --dry-run`.
4. Run the tests with next-version deprecations as errors:
`python -W error::django.utils.deprecation.RemovedInDjango70Warning -W error::django.utils.deprecation.RemovedInDjango2028Warning manage.py test`. Adapter CLAUDE.md Imports AGENTS.md for Claude Code. 11 lines
# CLAUDE.md
@AGENTS.md
## Claude Code notes
- Skills from this loadout live in `.claude/skills/`: `django-6-guard`. Use them when their description matches the task.
- Follow AGENTS.md above; where this file and AGENTS.md disagree, AGENTS.md wins.
<!-- From SetupBay "Django 6 guard for removed APIs and django-upgrade" v1.0.0 — https://setupbay.com/loadouts/django-6-upgrade-guard/ --> Adapter GEMINI.md Imports AGENTS.md for Gemini CLI. 11 lines
# GEMINI.md
@./AGENTS.md
## Gemini CLI notes
- Skills from this loadout live in `.agents/skills/`: `django-6-guard`. Use them when their description matches the task.
- Follow AGENTS.md above; where this file and AGENTS.md disagree, AGENTS.md wins.
<!-- From SetupBay "Django 6 guard for removed APIs and django-upgrade" v1.0.0 — https://setupbay.com/loadouts/django-6-upgrade-guard/ --> Cursor rule .cursor/rules/django-6-only.mdc Never/always table for Python files and templates. 25 lines
---
description: Django 6.1 only; no APIs removed in Django 4.0 to 6.1
globs: "**/*.py,**/templates/**/*.html"
alwaysApply: false
---
This project runs Django 6.1 (Python 3.12+). Never write removed Django APIs.
| Never | Always (Django 6.1) |
|---|---|
| `from django.conf.urls import url`; `url(r"^x/$", v)` | `path("x/", v)` or `re_path(r"^x/$", v)` |
| `ugettext`, `ugettext_lazy` | `gettext`, `gettext_lazy` |
| `force_text`, `smart_text` | `force_str`, `smart_str` |
| `request.is_ajax()` | check the `Accept` or `X-Requested-With` header yourself |
| `Meta.index_together` | `Meta.indexes = [models.Index(fields=[...])]` |
| `models.NullBooleanField()` | `models.BooleanField(null=True)` |
| `CheckConstraint(check=Q(...))` | `CheckConstraint(condition=Q(...))` |
| `obj.save(False, False)` | `obj.save(update_fields=[...])` (keywords only) |
| `format_html("<b>fixed</b>")` | `mark_safe(...)`, or `format_html("<b>{}</b>", value)` |
| `finders.find(p, all=True)` | `finders.find(p, find_all=True)` |
| `timezone.utc`, `pytz` | `datetime.UTC`, `zoneinfo.ZoneInfo` |
| `{% load staticfiles %}` | `{% load static %}` |
- After editing, run the `django-6-guard` skill's scanner, `django-upgrade --check --target-version 6.1`, `manage.py check` and the tests. Skill skills/django-6-guard/SKILL.md Scan for removed Django APIs, run django-upgrade, then Django's checks and the tests with deprecations as errors. 49 lines
---
name: django-6-guard
description: Check a Django project for APIs removed between Django 4.0 and 6.1, run django-upgrade and Django's own checks, before saying a Django change is done. Use after writing or editing models, views, URLs, settings or templates, when Django raises an unexpected keyword or missing attribute error, or when porting older Django snippets or tutorials.
---
# Django 6 guard
Agents trained on years of Django answers still write `url()`, `ugettext_lazy`, `force_text`, `request.is_ajax()`,
`index_together` and `{% load staticfiles %}`. All of those are gone. Django 6.0 and 6.1 removed more, including
`CheckConstraint(check=)`, positional `Model.save()` arguments, `format_html()` without arguments,
`finders.find(all=)` and the Postgres aggregates' `ordering=`. This skill finds them, lets `django-upgrade` rewrite
what it can, then lets Django check the rest.
## 1. Confirm the version
- `uv run python -m django --version` should print `6.1.x`. If it doesn't, ask before changing code for a different version.
## 2. Scan
```bash
python3 .agents/skills/django-6-guard/scripts/django6_guard.py shop templates
```
(Use the path the skill was installed to: `.claude/skills/...` for Claude Code, `.agents/skills/...` for the others.)
- Every `error` line is a removed API. Fix it with the replacement on the line. `references/removals.md` says which
release removed each one.
- `warn` lines are behaviour changes to decide on: `login(request, None)`, an explicit `AutoField` default,
positional mail arguments, `EMAIL_*` settings that now belong in `MAILERS`, or Celery added where `django.tasks` might do.
## 3. Let django-upgrade rewrite what it safely can
```bash
uvx --from django-upgrade==1.33.0 django-upgrade --target-version 6.1 shop/*.py
```
- Then run it again with `--check`. It must print nothing. Review its diff like any other change.
- It doesn't cover everything the scanner does (for example `NullBooleanField` or `CheckConstraint(check=)`),
so run both.
## 4. Let Django check it
```bash
python manage.py check
python manage.py makemigrations --check --dry-run
python -W error::django.utils.deprecation.RemovedInDjango70Warning \
-W error::django.utils.deprecation.RemovedInDjango2028Warning manage.py test
```
- `check` reports removed fields as system-check errors (for example `fields.E903` for `NullBooleanField`).
- If `makemigrations --check` wants a migration, create it with `makemigrations` and include it in the change.
## 5. Report
Say which removed APIs you replaced, what django-upgrade rewrote, any migration you added, and that the scanner,
`check`, `makemigrations --check` and the tests were clean. Skill file skills/django-6-guard/scripts/django6_guard.py Read-only scanner for removed Django APIs and Django 6 behaviour changes. 156 lines
#!/usr/bin/env python3
"""Find Django code removed by 6.0 and 6.1 (and older removals agents still write) in a Django 6 project.
Usage: python3 django6_guard.py [PATH ...] (default: the current folder)
python3 django6_guard.py --list (print every rule)
A "guard: ok" comment on a line silences its warnings (never its errors).
Prints one line per finding: path:line: level [rule] what is wrong -> what to use
Exit 0 when nothing at "error" level is found, 1 when something is, 2 on bad usage.
Standard library only. Read-only: it never edits a file and makes no network calls.
"""
import fnmatch
import os
import re
import sys
# (rule id, level, regex, message, file patterns). Patterns run against each line with comments removed.
RULES = [
('check-constraint-check', 'error', 'CheckConstraint\\([^)]*\\bcheck\\s*=',
'CheckConstraint(check=...) was removed in 6.0 -> CheckConstraint(condition=...)', ['*.py']),
('save-positional', 'error', '\\.save\\(\\s*(True|False)\\s*,',
'Model.save() positional arguments were removed in 6.0 -> save(force_insert=..., force_update=...)', ['*.py']),
('format-html-no-args', 'error', 'format_html\\(\\s*[frbu]*(\\"[^\\"]*\\"|\'[^\']*\')\\s*\\)',
'format_html() with no args or kwargs raises in 6.0 -> mark_safe() for fixed HTML, or pass the values as arguments', ['*.py']),
('staticfiles-find-all', 'error', 'finders\\.find\\([^)]*\\ball\\s*=',
'finders.find(all=...) was removed in 6.1 -> find(..., find_all=True)', ['*.py']),
('pg-agg-ordering', 'error', '\\b(ArrayAgg|JSONBAgg|StringAgg)\\([^)]*\\bordering\\s*=',
'ArrayAgg/JSONBAgg/StringAgg(ordering=...) was removed in 6.1 -> order_by=...', ['*.py']),
('choices-meta', 'error', '\\bChoicesMeta\\b',
'ChoicesMeta was removed in 6.0 -> ChoicesType', ['*.py']),
('urlfield-assume', 'error', 'FORMS_URLFIELD_ASSUME_HTTPS',
'FORMS_URLFIELD_ASSUME_HTTPS was removed; URLField now assumes https', ['*.py']),
('log-action', 'error', '\\.log_action\\(|\\.log_deletion\\(',
'LogEntryManager.log_action() / ModelAdmin.log_deletion() were removed in 6.0 -> log_actions() / log_deletions()', ['*.py']),
('index-together', 'error', '\\bindex_together\\s*=',
'Meta.index_together was removed in 5.1 -> indexes = [models.Index(fields=[...])]', ['*.py']),
('url-function', 'error', 'from\\s+django\\.conf\\.urls\\s+import\\s+[^#]*\\burl\\b|\\burl\\(\\s*r?[\\"\']\\^',
'django.conf.urls.url() was removed in 4.0 -> path() or re_path()', ['*.py']),
('ugettext', 'error', '\\bu(gettext|gettext_lazy|ngettext|ngettext_lazy|pgettext)\\b',
'ugettext* was removed in 4.0 -> gettext / gettext_lazy', ['*.py']),
('force-text', 'error', '\\b(force_text|smart_text)\\b',
'force_text / smart_text were removed in 4.0 -> force_str / smart_str', ['*.py']),
('is-ajax', 'error', '\\.is_ajax\\(\\)',
"request.is_ajax() was removed in 4.0 -> check request.headers.get('x-requested-with') or the Accept header", ['*.py']),
('timezone-utc', 'error', 'timezone\\.utc\\b(?!c)|import\\s+pytz|pytz\\.',
'django.utils.timezone.utc and pytz support were removed -> datetime.UTC / zoneinfo.ZoneInfo', ['*.py']),
('use-l10n', 'error', '^\\s*USE_L10N\\s*=',
'USE_L10N was removed in 5.0 (localisation is always on); delete the setting', ['*.py']),
('null-boolean', 'error', '\\bNullBooleanField\\(',
'NullBooleanField is gone (system check fields.E903) -> BooleanField(null=True)', ['*.py']),
('staticfiles-tag', 'error', '\\{%\\s*load\\s+(staticfiles|admin_static)\\s*%\\}',
'{% load staticfiles %} was removed -> {% load static %}', ['*.html', '*.txt']),
('login-user-none', 'warn', '\\b(a?login)\\(\\s*request\\s*,\\s*None\\b',
'login(request, None) no longer falls back to request.user in 6.1 -> pass the user', ['*.py']),
('default-auto-field', 'warn', 'DEFAULT_AUTO_FIELD\\s*=\\s*[\\"\']django\\.db\\.models\\.AutoField[\\"\']',
"Django 6.0 defaults to BigAutoField; keeping AutoField is fine for old apps, but new apps shouldn't set it", ['*.py']),
('mail-positional', 'warn', '\\b(send_mail|send_mass_mail|mail_admins|mail_managers|EmailMessage)\\(\\s*[^)=]+,\\s*[^)=]+,\\s*[^)=]+,\\s*[^)=]+,\\s*[^)=]+[,)]',
'positional arguments past the first four in django.core.mail are deprecated in 6.0 -> keywords', ['*.py']),
('email-settings', 'warn', '^\\s*EMAIL_(BACKEND|HOST|HOST_USER|HOST_PASSWORD|PORT|USE_TLS|USE_SSL|TIMEOUT|SSL_CERTFILE|SSL_KEYFILE|FILE_PATH)\\s*=',
'the EMAIL_* connection settings are deprecated in 6.1 (RemovedInDjango2028Warning) -> MAILERS = {"default": {"BACKEND": ..., "OPTIONS": {...}}}', ['*.py']),
('celery-for-simple-task', 'warn', '@shared_task\\b|from\\s+celery\\s+import',
'Django 6.0 ships a Tasks framework (django.tasks); ask before adding Celery for simple background jobs', ['*.py']),
]
FILES = ['*.py', '*.html', '*.txt']
COMMENT = '#'
SKIP_DIRS = {".git", ".venv", "venv", "node_modules", "__pycache__", ".agents", ".claude", ".cursor", "dist", "build", "target", ".godot"}
COMPILED = [(rid, lvl, re.compile(rx), msg, globs) for rid, lvl, rx, msg, globs in RULES]
def strip_comment(line):
"""Drop a trailing comment, leaving string literals alone."""
q = None
i = 0
while i < len(line):
c = line[i]
if q:
if c == "\\":
i += 2
continue
if c == q:
q = None
elif c in "\"'`":
q = c
elif line.startswith(COMMENT, i):
return line[:i]
i += 1
return line
def wanted(name):
return any(fnmatch.fnmatch(name, g) for g in FILES)
def walk(paths):
for p in paths:
if os.path.isfile(p):
yield p
continue
for root, dirs, files in os.walk(p):
dirs[:] = sorted(d for d in dirs if d not in SKIP_DIRS)
for f in sorted(files):
if wanted(f):
yield os.path.join(root, f)
def scan(path):
out = []
name = os.path.basename(path)
try:
with open(path, encoding="utf-8", errors="replace") as fh:
lines = fh.read().splitlines()
except OSError as e:
return [(path, 0, "error", "unreadable", str(e))]
for n, raw in enumerate(lines, 1):
code = strip_comment(raw)
if not code.strip():
continue
accepted = "guard: ok" in raw # a person accepted the warnings on this line
for rid, lvl, rx, msg, globs in COMPILED:
if accepted and lvl == "warn":
continue
if globs and not any(fnmatch.fnmatch(name, g) for g in globs):
continue
if rx.search(code):
out.append((path, n, lvl, rid, msg))
return out
def main(argv):
if "--list" in argv:
for rid, lvl, _, msg, globs in RULES:
print(f"{lvl:5} [{rid}] {msg}" + (f" (only {', '.join(globs)})" if globs else ""))
return 0
if any(a.startswith("-") for a in argv):
print(__doc__)
return 2
paths = argv or ["."]
for p in paths:
if not os.path.exists(p):
print(f"no such path: {p}", file=sys.stderr)
return 2
files = list(walk(paths))
findings = []
for f in files:
findings.extend(scan(f))
for path, n, lvl, rid, msg in findings:
print(f"{os.path.relpath(path)}:{n}: {lvl} [{rid}] {msg}")
errors = sum(1 for x in findings if x[2] == "error")
warns = sum(1 for x in findings if x[2] == "warn")
print(f"django6_guard: {len(files)} file(s), {errors} error(s), {warns} warning(s)")
return 1 if errors else 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1:])) Skill file skills/django-6-guard/references/removals.md Which release removed each API and what replaces it. 35 lines
# Django removals agents still write
From the Django release notes (https://docs.djangoproject.com/en/6.1/releases/). Checked against Django 6.1.2.
| Old code | Removed in | Use |
|---|---|---|
| `from django.conf.urls import url` | 4.0 | `from django.urls import path, re_path` |
| `ugettext`, `ugettext_lazy`, `ungettext` | 4.0 | `gettext`, `gettext_lazy`, `ngettext` |
| `force_text`, `smart_text` | 4.0 | `force_str`, `smart_str` |
| `request.is_ajax()` | 4.0 | `request.headers.get("x-requested-with") == "XMLHttpRequest"`, or better, the `Accept` header |
| `NullBooleanField()` | 4.0 (model field kept only for old migrations; system check `fields.E903`) | `BooleanField(null=True)` |
| `django.utils.timezone.utc`, pytz support | 5.0 | `datetime.UTC`, `zoneinfo.ZoneInfo` |
| `USE_L10N = True` | 5.0 | delete it; always on |
| `Meta.index_together` | 5.1 | `Meta.indexes = [models.Index(fields=["a", "b"])]` |
| `CheckConstraint(check=...)` | 6.0 | `CheckConstraint(condition=...)` |
| `obj.save(False, False, None, None)` | 6.0 | `obj.save(force_insert=..., update_fields=[...])` |
| `format_html("<b>fixed</b>")` with no args | 6.0 | `mark_safe("<b>fixed</b>")` or `format_html("<b>{}</b>", v)` |
| `ChoicesMeta` | 6.0 | `ChoicesType` |
| `ModelAdmin.log_deletion()`, `LogEntryManager.log_action()` | 6.0 | `log_deletions()`, `log_actions()` |
| `FORMS_URLFIELD_ASSUME_HTTPS` | 6.0 | delete it; `URLField` assumes `https` |
| `finders.find(path, all=True)` | 6.1 | `finders.find(path, find_all=True)` |
| `ArrayAgg("x", ordering=...)` (also JSONBAgg, StringAgg) | 6.1 | `order_by=...` |
| `login(request, None)` falling back to `request.user` | 6.1 | pass the user explicitly |
| `{% load staticfiles %}`, `{% load admin_static %}` | 3.0 | `{% load static %}` |
## New in 6.0 worth knowing
- `DEFAULT_AUTO_FIELD` defaults to `django.db.models.BigAutoField`.
- Python 3.12 or later only.
- Built-in Content Security Policy support (`django.middleware.csp.ContentSecurityPolicyMiddleware`, `SECURE_CSP`).
- Template partials (`{% partialdef %}` / `{% partial %}`).
- A background Tasks framework (`django.tasks`), which needs a backend to actually run tasks.
- Django 6.1: mail is configured in `MAILERS`; the `EMAIL_*` connection settings warn with
`RemovedInDjango2028Warning`.
- Django 6.1: fetch modes (`QuerySet.fetch_mode(models.FETCH_RAISE)`, `FETCH_PEERS`, `FETCH_ONE`). Skill file skills/django-6-guard/references/django3-sample.py.txt A Django 3-era module and settings for checking the guard. 42 lines
# A Django 3.x-era module, used to check the guard. Every marked API is gone in Django 6.1.
from django.conf.urls import url
from django.db import models
from django.utils.encoding import force_text
from django.utils.html import format_html
from django.utils.translation import ugettext_lazy as _
class Order(models.Model):
total = models.IntegerField()
paid = models.NullBooleanField()
code = models.CharField(max_length=8)
class Meta:
index_together = [["code", "total"]]
constraints = [models.CheckConstraint(check=models.Q(total__gte=0), name="total_gte_0")]
def __str__(self):
return force_text(self.code)
def badge(self):
return format_html("<span class='paid'>paid</span>")
def mark_paid(order):
order.paid = True
order.save(False, False)
def view(request):
if request.is_ajax():
return None
urlpatterns = [url(r"^orders/$", view)]
# --- settings.py from the same project ---
USE_L10N = True
EMAIL_HOST = "smtp.example.com"
EMAIL_PORT = 587 All files: source zip · Licence MIT · By SetupBay · First listed
Credits
Public work that inspired this loadout. The wording here is our own; follow the links for the originals.
Installing and checks
How do I install Django 6 guard for removed APIs and django-upgrade in Claude Code?
From your project root, put AGENTS.md, CLAUDE.md, skills/django-6-guard/SKILL.md at .claude/skills/django-6-guard/SKILL.md, skills/django-6-guard/scripts/django6_guard.py at .claude/skills/django-6-guard/scripts/django6_guard.py, skills/django-6-guard/references/removals.md at .claude/skills/django-6-guard/references/removals.md, skills/django-6-guard/references/django3-sample.py.txt at .claude/skills/django-6-guard/references/django3-sample.py.txt. The "Claude Code" tab on this page has the curl commands, or download https://setupbay.com/download/django-6-upgrade-guard/claude-code.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.
How do I install Django 6 guard for removed APIs and django-upgrade in Codex CLI?
From your project root, put AGENTS.md, skills/django-6-guard/SKILL.md at .agents/skills/django-6-guard/SKILL.md, skills/django-6-guard/scripts/django6_guard.py at .agents/skills/django-6-guard/scripts/django6_guard.py, skills/django-6-guard/references/removals.md at .agents/skills/django-6-guard/references/removals.md, skills/django-6-guard/references/django3-sample.py.txt at .agents/skills/django-6-guard/references/django3-sample.py.txt. The "Codex CLI" tab on this page has the curl commands, or download https://setupbay.com/download/django-6-upgrade-guard/codex.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.
How do I install Django 6 guard for removed APIs and django-upgrade in Cursor?
From your project root, put AGENTS.md, .cursor/rules/django-6-only.mdc, skills/django-6-guard/SKILL.md at .agents/skills/django-6-guard/SKILL.md, skills/django-6-guard/scripts/django6_guard.py at .agents/skills/django-6-guard/scripts/django6_guard.py, skills/django-6-guard/references/removals.md at .agents/skills/django-6-guard/references/removals.md, skills/django-6-guard/references/django3-sample.py.txt at .agents/skills/django-6-guard/references/django3-sample.py.txt. The "Cursor" tab on this page has the curl commands, or download https://setupbay.com/download/django-6-upgrade-guard/cursor.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.
How do I install Django 6 guard for removed APIs and django-upgrade in Gemini CLI?
From your project root, put AGENTS.md, GEMINI.md, skills/django-6-guard/SKILL.md at .agents/skills/django-6-guard/SKILL.md, skills/django-6-guard/scripts/django6_guard.py at .agents/skills/django-6-guard/scripts/django6_guard.py, skills/django-6-guard/references/removals.md at .agents/skills/django-6-guard/references/removals.md, skills/django-6-guard/references/django3-sample.py.txt at .agents/skills/django-6-guard/references/django3-sample.py.txt. The "Gemini CLI" tab on this page has the curl commands, or download https://setupbay.com/download/django-6-upgrade-guard/gemini-cli.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.
How do I install Django 6 guard for removed APIs and django-upgrade in Antigravity CLI?
From your project root, put AGENTS.md, skills/django-6-guard/SKILL.md at .agents/skills/django-6-guard/SKILL.md, skills/django-6-guard/scripts/django6_guard.py at .agents/skills/django-6-guard/scripts/django6_guard.py, skills/django-6-guard/references/removals.md at .agents/skills/django-6-guard/references/removals.md, skills/django-6-guard/references/django3-sample.py.txt at .agents/skills/django-6-guard/references/django3-sample.py.txt. The "Antigravity CLI" tab on this page has the curl commands, or download https://setupbay.com/download/django-6-upgrade-guard/antigravity-cli.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.
How do I install Django 6 guard for removed APIs and django-upgrade in GitHub Copilot?
From your project root, put AGENTS.md, skills/django-6-guard/SKILL.md at .agents/skills/django-6-guard/SKILL.md, skills/django-6-guard/scripts/django6_guard.py at .agents/skills/django-6-guard/scripts/django6_guard.py, skills/django-6-guard/references/removals.md at .agents/skills/django-6-guard/references/removals.md, skills/django-6-guard/references/django3-sample.py.txt at .agents/skills/django-6-guard/references/django3-sample.py.txt. The "GitHub Copilot" tab on this page has the curl commands, or download https://setupbay.com/download/django-6-upgrade-guard/copilot.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.
What has been checked in Django 6 guard for removed APIs and django-upgrade?
It passed the Level 1 automated install checks on 2026-10-29: the files parse, install to each agent's documented paths, and pass the safety scan. No AI agent has run it yet, so no agent pass or fail is claimed.
More for Claude Code, Codex, Cursor, Gemini CLI, Antigravity CLI and GitHub Copilot.