From 397e1d840178489d7724552512824d1cb319e659 Mon Sep 17 00:00:00 2001 From: FROWNINGdev Date: Wed, 2 Sep 2026 10:34:04 +0500 Subject: [PATCH 01/12] docs(i18n): use the reference's dash typography in the Italian index and titles --- docs/i18n/rules/it/DOL011.md | 54 ++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/docs/i18n/rules/it/DOL011.md b/docs/i18n/rules/it/DOL011.md index 8733ec4..80d7fe1 100644 --- a/docs/i18n/rules/it/DOL011.md +++ b/docs/i18n/rules/it/DOL011.md @@ -1,27 +1,27 @@ -# DOL011 - null=True su CharField/TextField - -**Gravità predefinita:** warning · **Applicabilità:** suggestion · **Categoria:** model - -Rileva `null=True` su un `CharField` o un `TextField`. La documentazione di Django stessa lo sconsiglia: una colonna di testo che accetta valori nulli ha due distinti valori per indicare l'assenza di dati, `NULL` e la stringa vuota `''`. Di conseguenza, ogni parte del codice che la utilizza deve controllarli entrambi e le query come `field=''` ignorano silenziosamente le righe contenenti `NULL`. La convenzione di Django prevede una colonna `NOT NULL` con `blank=True` per rendere il campo facoltativo a livello di form, memorizzando `''` quando il valore è assente. La QuickFix ("Replace null=True with blank=True") sostituisce l'argomento sul posto; è un suggerimento perché la modifica richiede una migrazione e, in presenza di dati esistenti, la conversione dei valori `NULL` in `''`. - -## Non corretto - -```python -class Profile(models.Model): - bio = models.TextField(null=True) -``` - -## Corretto - -```python -class Profile(models.Model): - bio = models.TextField(blank=True) -``` - -## Soppressione - -```python -# django-orm-lens-disable-next-line DOL011 -``` - -Un'eccezione legittima è rappresentata dai campi di testo con `unique=True` nei quali più valori assenti non devono entrare in conflitto: in quel caso, sopprimi la regola. In alternativa, disattivala per l'area di lavoro in `.vscode/settings.json`: `{"djangoOrmLens.rules": {"DOL011": "off"}}`. +# DOL011 — null=True su CharField/TextField + +**Gravità predefinita:** warning · **Applicabilità:** suggestion · **Categoria:** model + +Rileva `null=True` su un `CharField` o un `TextField`. La documentazione di Django stessa lo sconsiglia: una colonna di testo che accetta valori nulli ha due distinti valori per indicare l'assenza di dati, `NULL` e la stringa vuota `''`. Di conseguenza, ogni parte del codice che la utilizza deve controllarli entrambi e le query come `field=''` ignorano silenziosamente le righe contenenti `NULL`. La convenzione di Django prevede una colonna `NOT NULL` con `blank=True` per rendere il campo facoltativo a livello di form, memorizzando `''` quando il valore è assente. La QuickFix ("Replace null=True with blank=True") sostituisce l'argomento sul posto; è un suggerimento perché la modifica richiede una migrazione e, in presenza di dati esistenti, la conversione dei valori `NULL` in `''`. + +## Non corretto + +```python +class Profile(models.Model): + bio = models.TextField(null=True) +``` + +## Corretto + +```python +class Profile(models.Model): + bio = models.TextField(blank=True) +``` + +## Soppressione + +```python +# django-orm-lens-disable-next-line DOL011 +``` + +Un'eccezione legittima è rappresentata dai campi di testo con `unique=True` nei quali più valori assenti non devono entrare in conflitto: in quel caso, sopprimi la regola. In alternativa, disattivala per l'area di lavoro in `.vscode/settings.json`: `{"djangoOrmLens.rules": {"DOL011": "off"}}`. From da7ece7fe2c47d27771ec9b34563e2d2c3cb411b Mon Sep 17 00:00:00 2001 From: FROWNINGdev Date: Wed, 2 Sep 2026 10:34:05 +0500 Subject: [PATCH 02/12] docs(i18n): use the reference's dash typography in the Italian index and titles --- docs/i18n/rules/it/DOL012.md | 60 ++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/docs/i18n/rules/it/DOL012.md b/docs/i18n/rules/it/DOL012.md index cc1298e..4646ecd 100644 --- a/docs/i18n/rules/it/DOL012.md +++ b/docs/i18n/rules/it/DOL012.md @@ -1,30 +1,30 @@ -# DOL012 - Modello privo del metodo __str__ - -**Gravità predefinita:** info · **Applicabilità:** suggestion · **Categoria:** model - -Rileva una classe che eredita da `models.Model` e nel cui corpo non è definito alcun metodo `__str__` (i modelli astratti con `abstract = True` in `Meta` vengono ignorati). Senza `__str__`, gli elenchi dell'area di amministrazione, i menu a discesa dei `ForeignKey`, le rappresentazioni nella shell e `{{ obj }}` nei template vengono tutti visualizzati come `ModelName object (1)`, un risultato inutile per le persone e durante il debug. Non è disponibile una QuickFix: generare un corpo significativo richiede di scegliere quale campo mostrare, una decisione che può prendere soltanto chi conosce il modello. - -## Non corretto - -```python -class Article(models.Model): - title = models.CharField(max_length=255) -``` - -## Corretto - -```python -class Article(models.Model): - title = models.CharField(max_length=255) - - def __str__(self) -> str: - return self.title -``` - -## Soppressione - -```python -# django-orm-lens-disable-next-line DOL012 -``` - -In alternativa, disattiva la regola per l'area di lavoro in `.vscode/settings.json`: `{"djangoOrmLens.rules": {"DOL012": "off"}}`. +# DOL012 — Modello privo del metodo __str__ + +**Gravità predefinita:** info · **Applicabilità:** suggestion · **Categoria:** model + +Rileva una classe che eredita da `models.Model` e nel cui corpo non è definito alcun metodo `__str__` (i modelli astratti con `abstract = True` in `Meta` vengono ignorati). Senza `__str__`, gli elenchi dell'area di amministrazione, i menu a discesa dei `ForeignKey`, le rappresentazioni nella shell e `{{ obj }}` nei template vengono tutti visualizzati come `ModelName object (1)`, un risultato inutile per le persone e durante il debug. Non è disponibile una QuickFix: generare un corpo significativo richiede di scegliere quale campo mostrare, una decisione che può prendere soltanto chi conosce il modello. + +## Non corretto + +```python +class Article(models.Model): + title = models.CharField(max_length=255) +``` + +## Corretto + +```python +class Article(models.Model): + title = models.CharField(max_length=255) + + def __str__(self) -> str: + return self.title +``` + +## Soppressione + +```python +# django-orm-lens-disable-next-line DOL012 +``` + +In alternativa, disattiva la regola per l'area di lavoro in `.vscode/settings.json`: `{"djangoOrmLens.rules": {"DOL012": "off"}}`. From 0ea73912dd02e456a003c354a766f17f2a10c671 Mon Sep 17 00:00:00 2001 From: FROWNINGdev Date: Wed, 2 Sep 2026 10:34:07 +0500 Subject: [PATCH 03/12] docs(i18n): use the reference's dash typography in the Italian index and titles --- docs/i18n/rules/it/DOL013.md | 54 ++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/docs/i18n/rules/it/DOL013.md b/docs/i18n/rules/it/DOL013.md index f3071a5..f49b4e7 100644 --- a/docs/i18n/rules/it/DOL013.md +++ b/docs/i18n/rules/it/DOL013.md @@ -1,27 +1,27 @@ -# DOL013 - ForeignKey senza on_delete - -**Gravità predefinita:** error · **Applicabilità:** suggestion · **Categoria:** model - -Rileva una chiamata a `ForeignKey(...)` priva dell'argomento nominato `on_delete=`. `on_delete` è obbligatorio da Django 2.0: ometterlo genera un `TypeError` non appena viene caricato il modulo del modello, quindi la regola rileva il problema durante la scrittura del codice, prima ancora di eseguire l'applicazione. La QuickFix ("Add on_delete=models.CASCADE (edit to your policy)") inserisce `on_delete=models.CASCADE` come modello da adattare. È un suggerimento, non una correzione sicura: la politica di eliminazione è una vera decisione progettuale. `CASCADE` elimina silenziosamente i record dipendenti, mentre `PROTECT`, `SET_NULL`, `SET_DEFAULT` o `DO_NOTHING` potrebbero essere più adatti ai dati effettivi. - -## Non corretto - -```python -class Book(models.Model): - author = models.ForeignKey(Author) -``` - -## Corretto - -```python -class Book(models.Model): - author = models.ForeignKey(Author, on_delete=models.CASCADE) -``` - -## Soppressione - -```python -# django-orm-lens-disable-next-line DOL013 -``` - -In alternativa, disattiva la regola per l'area di lavoro in `.vscode/settings.json`: `{"djangoOrmLens.rules": {"DOL013": "off"}}`. +# DOL013 — ForeignKey senza on_delete + +**Gravità predefinita:** error · **Applicabilità:** suggestion · **Categoria:** model + +Rileva una chiamata a `ForeignKey(...)` priva dell'argomento nominato `on_delete=`. `on_delete` è obbligatorio da Django 2.0: ometterlo genera un `TypeError` non appena viene caricato il modulo del modello, quindi la regola rileva il problema durante la scrittura del codice, prima ancora di eseguire l'applicazione. La QuickFix ("Add on_delete=models.CASCADE (edit to your policy)") inserisce `on_delete=models.CASCADE` come modello da adattare. È un suggerimento, non una correzione sicura: la politica di eliminazione è una vera decisione progettuale. `CASCADE` elimina silenziosamente i record dipendenti, mentre `PROTECT`, `SET_NULL`, `SET_DEFAULT` o `DO_NOTHING` potrebbero essere più adatti ai dati effettivi. + +## Non corretto + +```python +class Book(models.Model): + author = models.ForeignKey(Author) +``` + +## Corretto + +```python +class Book(models.Model): + author = models.ForeignKey(Author, on_delete=models.CASCADE) +``` + +## Soppressione + +```python +# django-orm-lens-disable-next-line DOL013 +``` + +In alternativa, disattiva la regola per l'area di lavoro in `.vscode/settings.json`: `{"djangoOrmLens.rules": {"DOL013": "off"}}`. From 362eb1f54144d0089fdc7c71ba1144ff42749a14 Mon Sep 17 00:00:00 2001 From: FROWNINGdev Date: Wed, 2 Sep 2026 10:34:09 +0500 Subject: [PATCH 04/12] docs(i18n): use the reference's dash typography in the Italian index and titles --- docs/i18n/rules/it/DOL014.md | 54 ++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/docs/i18n/rules/it/DOL014.md b/docs/i18n/rules/it/DOL014.md index b67d54c..90406be 100644 --- a/docs/i18n/rules/it/DOL014.md +++ b/docs/i18n/rules/it/DOL014.md @@ -1,27 +1,27 @@ -# DOL014 - CharField senza max_length - -**Gravità predefinita:** error · **Applicabilità:** suggestion · **Categoria:** model - -Rileva una chiamata a `CharField(...)` priva dell'argomento nominato `max_length=`. Django richiede `max_length` per `CharField`; senza questo argomento, il modello non supera i controlli di Django durante il caricamento. Si tratta quindi di un'altra classe di errori che, altrimenti, emergerebbe soltanto al successivo `runserver` o `makemigrations`. La QuickFix ("Add max_length=255 (edit as needed)") inserisce `max_length=255` come modello da adattare: 255 è una convenzione comune, non una costante speciale, quindi la dimensione della colonna deve essere scelta in base ai dati. È un suggerimento perché il limite corretto dipende dal caso d'uso; se il testo è davvero senza limiti, `TextField` è il campo più adatto. - -## Non corretto - -```python -class Tag(models.Model): - name = models.CharField() -``` - -## Corretto - -```python -class Tag(models.Model): - name = models.CharField(max_length=255) -``` - -## Soppressione - -```python -# django-orm-lens-disable-next-line DOL014 -``` - -In alternativa, disattiva la regola per l'area di lavoro in `.vscode/settings.json`: `{"djangoOrmLens.rules": {"DOL014": "off"}}`. +# DOL014 — CharField senza max_length + +**Gravità predefinita:** error · **Applicabilità:** suggestion · **Categoria:** model + +Rileva una chiamata a `CharField(...)` priva dell'argomento nominato `max_length=`. Django richiede `max_length` per `CharField`; senza questo argomento, il modello non supera i controlli di Django durante il caricamento. Si tratta quindi di un'altra classe di errori che, altrimenti, emergerebbe soltanto al successivo `runserver` o `makemigrations`. La QuickFix ("Add max_length=255 (edit as needed)") inserisce `max_length=255` come modello da adattare: 255 è una convenzione comune, non una costante speciale, quindi la dimensione della colonna deve essere scelta in base ai dati. È un suggerimento perché il limite corretto dipende dal caso d'uso; se il testo è davvero senza limiti, `TextField` è il campo più adatto. + +## Non corretto + +```python +class Tag(models.Model): + name = models.CharField() +``` + +## Corretto + +```python +class Tag(models.Model): + name = models.CharField(max_length=255) +``` + +## Soppressione + +```python +# django-orm-lens-disable-next-line DOL014 +``` + +In alternativa, disattiva la regola per l'area di lavoro in `.vscode/settings.json`: `{"djangoOrmLens.rules": {"DOL014": "off"}}`. From e940c53798489490b85a2fee7e922dd75fb85cbd Mon Sep 17 00:00:00 2001 From: FROWNINGdev Date: Wed, 2 Sep 2026 10:34:11 +0500 Subject: [PATCH 05/12] docs(i18n): use the reference's dash typography in the Italian index and titles --- docs/i18n/rules/it/DOL015.md | 66 ++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/docs/i18n/rules/it/DOL015.md b/docs/i18n/rules/it/DOL015.md index ea8e497..26196d6 100644 --- a/docs/i18n/rules/it/DOL015.md +++ b/docs/i18n/rules/it/DOL015.md @@ -1,33 +1,33 @@ -# DOL015 - max_length su TextField non ha effetto sul database - -**Gravità predefinita:** hint · **Applicabilità:** suggestion · **Categoria:** model - -Rileva `max_length=` su un `TextField(...)`. `TextField` viene mappato a `TEXT`/`CLOB`; Django applica il suo `max_length` soltanto nel widget del form generato automaticamente, mai a livello di database. L'argomento sembra quindi definire un limite rigido, ma non è così: le scritture dirette tramite ORM, le operazioni in blocco e i salvataggi diretti dall'area di amministrazione possono tutti superarlo. Se occorre un limite applicato dal database, usa `CharField(max_length=...)`; se il testo è davvero senza limiti, rimuovi l'argomento. La QuickFix ("Remove max_length from TextField") elimina l'argomento; è un suggerimento perché, in alternativa, potrebbe essere preferibile passare a `CharField`. - -## Non corretto - -```python -class Comment(models.Model): - body = models.TextField(max_length=500) -``` - -## Corretto - -```python -class Comment(models.Model): - body = models.TextField() -``` - -Oppure, quando il limite deve essere applicato a livello di database: - -```python - body = models.CharField(max_length=500) -``` - -## Soppressione - -```python -# django-orm-lens-disable-next-line DOL015 -``` - -Sopprimi la regola se usi intenzionalmente `max_length` soltanto come limite a livello di form. In alternativa, disattivala per l'area di lavoro in `.vscode/settings.json`: `{"djangoOrmLens.rules": {"DOL015": "off"}}`. +# DOL015 — max_length su TextField non ha effetto sul database + +**Gravità predefinita:** hint · **Applicabilità:** suggestion · **Categoria:** model + +Rileva `max_length=` su un `TextField(...)`. `TextField` viene mappato a `TEXT`/`CLOB`; Django applica il suo `max_length` soltanto nel widget del form generato automaticamente, mai a livello di database. L'argomento sembra quindi definire un limite rigido, ma non è così: le scritture dirette tramite ORM, le operazioni in blocco e i salvataggi diretti dall'area di amministrazione possono tutti superarlo. Se occorre un limite applicato dal database, usa `CharField(max_length=...)`; se il testo è davvero senza limiti, rimuovi l'argomento. La QuickFix ("Remove max_length from TextField") elimina l'argomento; è un suggerimento perché, in alternativa, potrebbe essere preferibile passare a `CharField`. + +## Non corretto + +```python +class Comment(models.Model): + body = models.TextField(max_length=500) +``` + +## Corretto + +```python +class Comment(models.Model): + body = models.TextField() +``` + +Oppure, quando il limite deve essere applicato a livello di database: + +```python + body = models.CharField(max_length=500) +``` + +## Soppressione + +```python +# django-orm-lens-disable-next-line DOL015 +``` + +Sopprimi la regola se usi intenzionalmente `max_length` soltanto come limite a livello di form. In alternativa, disattivala per l'area di lavoro in `.vscode/settings.json`: `{"djangoOrmLens.rules": {"DOL015": "off"}}`. From 8f526a3af1991b9bf2611fefaba22168ce207b1b Mon Sep 17 00:00:00 2001 From: FROWNINGdev Date: Wed, 2 Sep 2026 10:34:12 +0500 Subject: [PATCH 06/12] docs(i18n): use the reference's dash typography in the Italian index and titles --- docs/rules/README.md | 120 +++++++++++++++++++++---------------------- 1 file changed, 60 insertions(+), 60 deletions(-) diff --git a/docs/rules/README.md b/docs/rules/README.md index 6927c6a..157bdd4 100644 --- a/docs/rules/README.md +++ b/docs/rules/README.md @@ -1,60 +1,60 @@ -# Rule reference - -🌐 [Italiano](../i18n/rules/it/README.md) - model rules (`DOL011`-`DOL015`) -🌐 [Tiếng Việt](../i18n/rules/vi/README.md) — queryset rules (`DOL001`–`DOL007`) - -Django ORM Lens ships two rule surfaces: - -1. **Editor rules (`DOL###`)** — 16 line-oriented static checks that run inside the VS Code extension on every `.py` file. Findings appear in the Problems panel under the source `Django ORM Lens`, link to these pages from the diagnostic code, and — where a fix is safe to express as a text edit — carry a QuickFix lightbulb. Detection is regex-based with bounded windows; no Python process is involved. -2. **CLI / CI analyzers** — AST-based checks in the Python package (`pip install django-orm-lens`) for terminals and pipelines: [`migration-risk`](migrations.md), [`nplusone`](nplusone.md), [`blast-radius`](blast-radius.md) — which joins migration risks with the code that still references what they change — and [`drift`](drift.md), a `makemigrations --check` that needs no Django boot. - -## Severity and applicability - -Severity mirrors VS Code's four diagnostic levels: `error`, `warning`, `info`, `hint`. Every rule has a default (listed below); override per rule in `.vscode/settings.json` via `djangoOrmLens.rules` — e.g. `{"djangoOrmLens.rules": {"DOL013": "error", "DOL007": "off"}}`. - -Applicability follows Clippy's semantics. It is a property of each individual finding and gates whether an editor may apply the fix unattended: - -| Applicability | Meaning | -|---|---| -| `safe` | Semantically equivalent, always correct to apply. Eligible for auto-apply and "Fix All". | -| `suggestion` | Usually right but may need review — offered as a QuickFix, never included in "Fix All". | -| `unsafe` | Likely-correct but breaks in edge cases — surfaced as a diagnostic only, never auto-applied. | - -## Editor rules - -| Code | Rule | Category | Default severity | Applicability | -|---|---|---|---|---| -| [DOL001](DOL001.md) | Prefer `.exists()` over `.count() > 0` | queryset | info | safe | -| [DOL002](DOL002.md) | Prefer `not .exists()` over `.count() == 0` | queryset | info | safe | -| [DOL003](DOL003.md) | Prefer `not .exists()` over `.first() is None` | queryset | info | safe | -| [DOL004](DOL004.md) | Prefer `.exists()` over `.first() is not None` | queryset | info | safe | -| [DOL005](DOL005.md) | Consider `Q(...)` over `.filter().exclude()` chain | queryset | hint | suggestion | -| [DOL006](DOL006.md) | Drop `list()` around a QuerySet in for-loop | queryset | info | safe | -| [DOL007](DOL007.md) | Possible N+1: attribute access inside for-loop | queryset | warning | unsafe | -| [DOL008](DOL008.md) | Field name in a lookup looks misspelled | correctness | warning | suggestion | -| [DOL011](DOL011.md) | `null=True` on CharField/TextField | model | warning | suggestion | -| [DOL012](DOL012.md) | Model without `__str__` method | model | info | suggestion | -| [DOL013](DOL013.md) | ForeignKey without `on_delete` | model | error | suggestion | -| [DOL014](DOL014.md) | CharField without `max_length` | model | error | suggestion | -| [DOL015](DOL015.md) | TextField with `max_length` has no DB effect | model | hint | suggestion | -| [DOL021](DOL021.md) | `datetime.now()` should be `timezone.now()` | datetime | warning | suggestion | -| [DOL022](DOL022.md) | `datetime.utcnow()` is deprecated | datetime | warning | suggestion | -| [DOL031](DOL031.md) | `render()` with `locals()` as context | forms | warning | suggestion | -| [DOL032](DOL032.md) | `fields = '__all__'` in Meta | forms | warning | unsafe | - -### Suppressing findings inline - -```python -# django-orm-lens-disable-next-line DOL007 (next line; comma-separate for several codes) -qs.count() > 0 # django-orm-lens-disable-line DOL001 -# django-orm-lens-disable DOL011 (own line — disables for the rest of the file) -``` - -Ruff-style bulk selection is also available: `djangoOrmLens.rulesSelect` (e.g. `["DOL0"]`) and `djangoOrmLens.rulesIgnore` (e.g. `["DOL03"]`). - -## CLI / CI analyzers - -These run from the Python CLI, not the editor. Both exit non-zero on findings (see each page for exact semantics) and emit `--format sarif` (SARIF 2.1.0 for GitHub Code Scanning) or `--format github` (workflow commands for zero-setup PR annotations). - -- **[Migration risk rules](migrations.md)** — 16 rules over `/migrations/*.py` flagging operations that are dangerous on production databases. Run with `django-orm-lens migration-risk`. -- **[Static N+1 detector](nplusone.md)** — flags FK / O2O / M2M / reverse-manager access inside for-loops when the source queryset has no matching `select_related` / `prefetch_related`. Run with `django-orm-lens nplusone`. +# Rule reference + +🌐 [Italiano](../i18n/rules/it/README.md) — model rules (`DOL011`–`DOL015`) +🌐 [Tiếng Việt](../i18n/rules/vi/README.md) — queryset rules (`DOL001`–`DOL007`) + +Django ORM Lens ships two rule surfaces: + +1. **Editor rules (`DOL###`)** — 16 line-oriented static checks that run inside the VS Code extension on every `.py` file. Findings appear in the Problems panel under the source `Django ORM Lens`, link to these pages from the diagnostic code, and — where a fix is safe to express as a text edit — carry a QuickFix lightbulb. Detection is regex-based with bounded windows; no Python process is involved. +2. **CLI / CI analyzers** — AST-based checks in the Python package (`pip install django-orm-lens`) for terminals and pipelines: [`migration-risk`](migrations.md), [`nplusone`](nplusone.md), [`blast-radius`](blast-radius.md) — which joins migration risks with the code that still references what they change — and [`drift`](drift.md), a `makemigrations --check` that needs no Django boot. + +## Severity and applicability + +Severity mirrors VS Code's four diagnostic levels: `error`, `warning`, `info`, `hint`. Every rule has a default (listed below); override per rule in `.vscode/settings.json` via `djangoOrmLens.rules` — e.g. `{"djangoOrmLens.rules": {"DOL013": "error", "DOL007": "off"}}`. + +Applicability follows Clippy's semantics. It is a property of each individual finding and gates whether an editor may apply the fix unattended: + +| Applicability | Meaning | +|---|---| +| `safe` | Semantically equivalent, always correct to apply. Eligible for auto-apply and "Fix All". | +| `suggestion` | Usually right but may need review — offered as a QuickFix, never included in "Fix All". | +| `unsafe` | Likely-correct but breaks in edge cases — surfaced as a diagnostic only, never auto-applied. | + +## Editor rules + +| Code | Rule | Category | Default severity | Applicability | +|---|---|---|---|---| +| [DOL001](DOL001.md) | Prefer `.exists()` over `.count() > 0` | queryset | info | safe | +| [DOL002](DOL002.md) | Prefer `not .exists()` over `.count() == 0` | queryset | info | safe | +| [DOL003](DOL003.md) | Prefer `not .exists()` over `.first() is None` | queryset | info | safe | +| [DOL004](DOL004.md) | Prefer `.exists()` over `.first() is not None` | queryset | info | safe | +| [DOL005](DOL005.md) | Consider `Q(...)` over `.filter().exclude()` chain | queryset | hint | suggestion | +| [DOL006](DOL006.md) | Drop `list()` around a QuerySet in for-loop | queryset | info | safe | +| [DOL007](DOL007.md) | Possible N+1: attribute access inside for-loop | queryset | warning | unsafe | +| [DOL008](DOL008.md) | Field name in a lookup looks misspelled | correctness | warning | suggestion | +| [DOL011](DOL011.md) | `null=True` on CharField/TextField | model | warning | suggestion | +| [DOL012](DOL012.md) | Model without `__str__` method | model | info | suggestion | +| [DOL013](DOL013.md) | ForeignKey without `on_delete` | model | error | suggestion | +| [DOL014](DOL014.md) | CharField without `max_length` | model | error | suggestion | +| [DOL015](DOL015.md) | TextField with `max_length` has no DB effect | model | hint | suggestion | +| [DOL021](DOL021.md) | `datetime.now()` should be `timezone.now()` | datetime | warning | suggestion | +| [DOL022](DOL022.md) | `datetime.utcnow()` is deprecated | datetime | warning | suggestion | +| [DOL031](DOL031.md) | `render()` with `locals()` as context | forms | warning | suggestion | +| [DOL032](DOL032.md) | `fields = '__all__'` in Meta | forms | warning | unsafe | + +### Suppressing findings inline + +```python +# django-orm-lens-disable-next-line DOL007 (next line; comma-separate for several codes) +qs.count() > 0 # django-orm-lens-disable-line DOL001 +# django-orm-lens-disable DOL011 (own line — disables for the rest of the file) +``` + +Ruff-style bulk selection is also available: `djangoOrmLens.rulesSelect` (e.g. `["DOL0"]`) and `djangoOrmLens.rulesIgnore` (e.g. `["DOL03"]`). + +## CLI / CI analyzers + +These run from the Python CLI, not the editor. Both exit non-zero on findings (see each page for exact semantics) and emit `--format sarif` (SARIF 2.1.0 for GitHub Code Scanning) or `--format github` (workflow commands for zero-setup PR annotations). + +- **[Migration risk rules](migrations.md)** — 16 rules over `/migrations/*.py` flagging operations that are dangerous on production databases. Run with `django-orm-lens migration-risk`. +- **[Static N+1 detector](nplusone.md)** — flags FK / O2O / M2M / reverse-manager access inside for-loops when the source queryset has no matching `select_related` / `prefetch_related`. Run with `django-orm-lens nplusone`. From dd35e538d3d7a8be0a6a1aa47959a3215453c4b0 Mon Sep 17 00:00:00 2001 From: FROWNINGdev Date: Wed, 2 Sep 2026 10:37:08 +0500 Subject: [PATCH 07/12] docs(i18n): restore LF line endings --- docs/i18n/rules/it/DOL011.md | 54 ++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/docs/i18n/rules/it/DOL011.md b/docs/i18n/rules/it/DOL011.md index 80d7fe1..4f7fbcb 100644 --- a/docs/i18n/rules/it/DOL011.md +++ b/docs/i18n/rules/it/DOL011.md @@ -1,27 +1,27 @@ -# DOL011 — null=True su CharField/TextField - -**Gravità predefinita:** warning · **Applicabilità:** suggestion · **Categoria:** model - -Rileva `null=True` su un `CharField` o un `TextField`. La documentazione di Django stessa lo sconsiglia: una colonna di testo che accetta valori nulli ha due distinti valori per indicare l'assenza di dati, `NULL` e la stringa vuota `''`. Di conseguenza, ogni parte del codice che la utilizza deve controllarli entrambi e le query come `field=''` ignorano silenziosamente le righe contenenti `NULL`. La convenzione di Django prevede una colonna `NOT NULL` con `blank=True` per rendere il campo facoltativo a livello di form, memorizzando `''` quando il valore è assente. La QuickFix ("Replace null=True with blank=True") sostituisce l'argomento sul posto; è un suggerimento perché la modifica richiede una migrazione e, in presenza di dati esistenti, la conversione dei valori `NULL` in `''`. - -## Non corretto - -```python -class Profile(models.Model): - bio = models.TextField(null=True) -``` - -## Corretto - -```python -class Profile(models.Model): - bio = models.TextField(blank=True) -``` - -## Soppressione - -```python -# django-orm-lens-disable-next-line DOL011 -``` - -Un'eccezione legittima è rappresentata dai campi di testo con `unique=True` nei quali più valori assenti non devono entrare in conflitto: in quel caso, sopprimi la regola. In alternativa, disattivala per l'area di lavoro in `.vscode/settings.json`: `{"djangoOrmLens.rules": {"DOL011": "off"}}`. +# DOL011 — null=True su CharField/TextField + +**Gravità predefinita:** warning · **Applicabilità:** suggestion · **Categoria:** model + +Rileva `null=True` su un `CharField` o un `TextField`. La documentazione di Django stessa lo sconsiglia: una colonna di testo che accetta valori nulli ha due distinti valori per indicare l'assenza di dati, `NULL` e la stringa vuota `''`. Di conseguenza, ogni parte del codice che la utilizza deve controllarli entrambi e le query come `field=''` ignorano silenziosamente le righe contenenti `NULL`. La convenzione di Django prevede una colonna `NOT NULL` con `blank=True` per rendere il campo facoltativo a livello di form, memorizzando `''` quando il valore è assente. La QuickFix ("Replace null=True with blank=True") sostituisce l'argomento sul posto; è un suggerimento perché la modifica richiede una migrazione e, in presenza di dati esistenti, la conversione dei valori `NULL` in `''`. + +## Non corretto + +```python +class Profile(models.Model): + bio = models.TextField(null=True) +``` + +## Corretto + +```python +class Profile(models.Model): + bio = models.TextField(blank=True) +``` + +## Soppressione + +```python +# django-orm-lens-disable-next-line DOL011 +``` + +Un'eccezione legittima è rappresentata dai campi di testo con `unique=True` nei quali più valori assenti non devono entrare in conflitto: in quel caso, sopprimi la regola. In alternativa, disattivala per l'area di lavoro in `.vscode/settings.json`: `{"djangoOrmLens.rules": {"DOL011": "off"}}`. From 67bba1dd90636b660796323e81c89de14c00f0dc Mon Sep 17 00:00:00 2001 From: FROWNINGdev Date: Wed, 2 Sep 2026 10:37:10 +0500 Subject: [PATCH 08/12] docs(i18n): restore LF line endings --- docs/i18n/rules/it/DOL012.md | 60 ++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/docs/i18n/rules/it/DOL012.md b/docs/i18n/rules/it/DOL012.md index 4646ecd..9ec17af 100644 --- a/docs/i18n/rules/it/DOL012.md +++ b/docs/i18n/rules/it/DOL012.md @@ -1,30 +1,30 @@ -# DOL012 — Modello privo del metodo __str__ - -**Gravità predefinita:** info · **Applicabilità:** suggestion · **Categoria:** model - -Rileva una classe che eredita da `models.Model` e nel cui corpo non è definito alcun metodo `__str__` (i modelli astratti con `abstract = True` in `Meta` vengono ignorati). Senza `__str__`, gli elenchi dell'area di amministrazione, i menu a discesa dei `ForeignKey`, le rappresentazioni nella shell e `{{ obj }}` nei template vengono tutti visualizzati come `ModelName object (1)`, un risultato inutile per le persone e durante il debug. Non è disponibile una QuickFix: generare un corpo significativo richiede di scegliere quale campo mostrare, una decisione che può prendere soltanto chi conosce il modello. - -## Non corretto - -```python -class Article(models.Model): - title = models.CharField(max_length=255) -``` - -## Corretto - -```python -class Article(models.Model): - title = models.CharField(max_length=255) - - def __str__(self) -> str: - return self.title -``` - -## Soppressione - -```python -# django-orm-lens-disable-next-line DOL012 -``` - -In alternativa, disattiva la regola per l'area di lavoro in `.vscode/settings.json`: `{"djangoOrmLens.rules": {"DOL012": "off"}}`. +# DOL012 — Modello privo del metodo __str__ + +**Gravità predefinita:** info · **Applicabilità:** suggestion · **Categoria:** model + +Rileva una classe che eredita da `models.Model` e nel cui corpo non è definito alcun metodo `__str__` (i modelli astratti con `abstract = True` in `Meta` vengono ignorati). Senza `__str__`, gli elenchi dell'area di amministrazione, i menu a discesa dei `ForeignKey`, le rappresentazioni nella shell e `{{ obj }}` nei template vengono tutti visualizzati come `ModelName object (1)`, un risultato inutile per le persone e durante il debug. Non è disponibile una QuickFix: generare un corpo significativo richiede di scegliere quale campo mostrare, una decisione che può prendere soltanto chi conosce il modello. + +## Non corretto + +```python +class Article(models.Model): + title = models.CharField(max_length=255) +``` + +## Corretto + +```python +class Article(models.Model): + title = models.CharField(max_length=255) + + def __str__(self) -> str: + return self.title +``` + +## Soppressione + +```python +# django-orm-lens-disable-next-line DOL012 +``` + +In alternativa, disattiva la regola per l'area di lavoro in `.vscode/settings.json`: `{"djangoOrmLens.rules": {"DOL012": "off"}}`. From 59ffe64538923674ab2f288a29383730ebca65fc Mon Sep 17 00:00:00 2001 From: FROWNINGdev Date: Wed, 2 Sep 2026 10:37:12 +0500 Subject: [PATCH 09/12] docs(i18n): restore LF line endings --- docs/i18n/rules/it/DOL013.md | 54 ++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/docs/i18n/rules/it/DOL013.md b/docs/i18n/rules/it/DOL013.md index f49b4e7..f80307f 100644 --- a/docs/i18n/rules/it/DOL013.md +++ b/docs/i18n/rules/it/DOL013.md @@ -1,27 +1,27 @@ -# DOL013 — ForeignKey senza on_delete - -**Gravità predefinita:** error · **Applicabilità:** suggestion · **Categoria:** model - -Rileva una chiamata a `ForeignKey(...)` priva dell'argomento nominato `on_delete=`. `on_delete` è obbligatorio da Django 2.0: ometterlo genera un `TypeError` non appena viene caricato il modulo del modello, quindi la regola rileva il problema durante la scrittura del codice, prima ancora di eseguire l'applicazione. La QuickFix ("Add on_delete=models.CASCADE (edit to your policy)") inserisce `on_delete=models.CASCADE` come modello da adattare. È un suggerimento, non una correzione sicura: la politica di eliminazione è una vera decisione progettuale. `CASCADE` elimina silenziosamente i record dipendenti, mentre `PROTECT`, `SET_NULL`, `SET_DEFAULT` o `DO_NOTHING` potrebbero essere più adatti ai dati effettivi. - -## Non corretto - -```python -class Book(models.Model): - author = models.ForeignKey(Author) -``` - -## Corretto - -```python -class Book(models.Model): - author = models.ForeignKey(Author, on_delete=models.CASCADE) -``` - -## Soppressione - -```python -# django-orm-lens-disable-next-line DOL013 -``` - -In alternativa, disattiva la regola per l'area di lavoro in `.vscode/settings.json`: `{"djangoOrmLens.rules": {"DOL013": "off"}}`. +# DOL013 — ForeignKey senza on_delete + +**Gravità predefinita:** error · **Applicabilità:** suggestion · **Categoria:** model + +Rileva una chiamata a `ForeignKey(...)` priva dell'argomento nominato `on_delete=`. `on_delete` è obbligatorio da Django 2.0: ometterlo genera un `TypeError` non appena viene caricato il modulo del modello, quindi la regola rileva il problema durante la scrittura del codice, prima ancora di eseguire l'applicazione. La QuickFix ("Add on_delete=models.CASCADE (edit to your policy)") inserisce `on_delete=models.CASCADE` come modello da adattare. È un suggerimento, non una correzione sicura: la politica di eliminazione è una vera decisione progettuale. `CASCADE` elimina silenziosamente i record dipendenti, mentre `PROTECT`, `SET_NULL`, `SET_DEFAULT` o `DO_NOTHING` potrebbero essere più adatti ai dati effettivi. + +## Non corretto + +```python +class Book(models.Model): + author = models.ForeignKey(Author) +``` + +## Corretto + +```python +class Book(models.Model): + author = models.ForeignKey(Author, on_delete=models.CASCADE) +``` + +## Soppressione + +```python +# django-orm-lens-disable-next-line DOL013 +``` + +In alternativa, disattiva la regola per l'area di lavoro in `.vscode/settings.json`: `{"djangoOrmLens.rules": {"DOL013": "off"}}`. From 3d5d2430535fff1005669bb9f47576141cb43eb6 Mon Sep 17 00:00:00 2001 From: FROWNINGdev Date: Wed, 2 Sep 2026 10:37:13 +0500 Subject: [PATCH 10/12] docs(i18n): restore LF line endings --- docs/i18n/rules/it/DOL014.md | 54 ++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/docs/i18n/rules/it/DOL014.md b/docs/i18n/rules/it/DOL014.md index 90406be..4504eb5 100644 --- a/docs/i18n/rules/it/DOL014.md +++ b/docs/i18n/rules/it/DOL014.md @@ -1,27 +1,27 @@ -# DOL014 — CharField senza max_length - -**Gravità predefinita:** error · **Applicabilità:** suggestion · **Categoria:** model - -Rileva una chiamata a `CharField(...)` priva dell'argomento nominato `max_length=`. Django richiede `max_length` per `CharField`; senza questo argomento, il modello non supera i controlli di Django durante il caricamento. Si tratta quindi di un'altra classe di errori che, altrimenti, emergerebbe soltanto al successivo `runserver` o `makemigrations`. La QuickFix ("Add max_length=255 (edit as needed)") inserisce `max_length=255` come modello da adattare: 255 è una convenzione comune, non una costante speciale, quindi la dimensione della colonna deve essere scelta in base ai dati. È un suggerimento perché il limite corretto dipende dal caso d'uso; se il testo è davvero senza limiti, `TextField` è il campo più adatto. - -## Non corretto - -```python -class Tag(models.Model): - name = models.CharField() -``` - -## Corretto - -```python -class Tag(models.Model): - name = models.CharField(max_length=255) -``` - -## Soppressione - -```python -# django-orm-lens-disable-next-line DOL014 -``` - -In alternativa, disattiva la regola per l'area di lavoro in `.vscode/settings.json`: `{"djangoOrmLens.rules": {"DOL014": "off"}}`. +# DOL014 — CharField senza max_length + +**Gravità predefinita:** error · **Applicabilità:** suggestion · **Categoria:** model + +Rileva una chiamata a `CharField(...)` priva dell'argomento nominato `max_length=`. Django richiede `max_length` per `CharField`; senza questo argomento, il modello non supera i controlli di Django durante il caricamento. Si tratta quindi di un'altra classe di errori che, altrimenti, emergerebbe soltanto al successivo `runserver` o `makemigrations`. La QuickFix ("Add max_length=255 (edit as needed)") inserisce `max_length=255` come modello da adattare: 255 è una convenzione comune, non una costante speciale, quindi la dimensione della colonna deve essere scelta in base ai dati. È un suggerimento perché il limite corretto dipende dal caso d'uso; se il testo è davvero senza limiti, `TextField` è il campo più adatto. + +## Non corretto + +```python +class Tag(models.Model): + name = models.CharField() +``` + +## Corretto + +```python +class Tag(models.Model): + name = models.CharField(max_length=255) +``` + +## Soppressione + +```python +# django-orm-lens-disable-next-line DOL014 +``` + +In alternativa, disattiva la regola per l'area di lavoro in `.vscode/settings.json`: `{"djangoOrmLens.rules": {"DOL014": "off"}}`. From 1750eb9b9ae39baf32032ae9f4cde8edf080804c Mon Sep 17 00:00:00 2001 From: FROWNINGdev Date: Wed, 2 Sep 2026 10:37:15 +0500 Subject: [PATCH 11/12] docs(i18n): restore LF line endings --- docs/i18n/rules/it/DOL015.md | 66 ++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/docs/i18n/rules/it/DOL015.md b/docs/i18n/rules/it/DOL015.md index 26196d6..1ab084b 100644 --- a/docs/i18n/rules/it/DOL015.md +++ b/docs/i18n/rules/it/DOL015.md @@ -1,33 +1,33 @@ -# DOL015 — max_length su TextField non ha effetto sul database - -**Gravità predefinita:** hint · **Applicabilità:** suggestion · **Categoria:** model - -Rileva `max_length=` su un `TextField(...)`. `TextField` viene mappato a `TEXT`/`CLOB`; Django applica il suo `max_length` soltanto nel widget del form generato automaticamente, mai a livello di database. L'argomento sembra quindi definire un limite rigido, ma non è così: le scritture dirette tramite ORM, le operazioni in blocco e i salvataggi diretti dall'area di amministrazione possono tutti superarlo. Se occorre un limite applicato dal database, usa `CharField(max_length=...)`; se il testo è davvero senza limiti, rimuovi l'argomento. La QuickFix ("Remove max_length from TextField") elimina l'argomento; è un suggerimento perché, in alternativa, potrebbe essere preferibile passare a `CharField`. - -## Non corretto - -```python -class Comment(models.Model): - body = models.TextField(max_length=500) -``` - -## Corretto - -```python -class Comment(models.Model): - body = models.TextField() -``` - -Oppure, quando il limite deve essere applicato a livello di database: - -```python - body = models.CharField(max_length=500) -``` - -## Soppressione - -```python -# django-orm-lens-disable-next-line DOL015 -``` - -Sopprimi la regola se usi intenzionalmente `max_length` soltanto come limite a livello di form. In alternativa, disattivala per l'area di lavoro in `.vscode/settings.json`: `{"djangoOrmLens.rules": {"DOL015": "off"}}`. +# DOL015 — max_length su TextField non ha effetto sul database + +**Gravità predefinita:** hint · **Applicabilità:** suggestion · **Categoria:** model + +Rileva `max_length=` su un `TextField(...)`. `TextField` viene mappato a `TEXT`/`CLOB`; Django applica il suo `max_length` soltanto nel widget del form generato automaticamente, mai a livello di database. L'argomento sembra quindi definire un limite rigido, ma non è così: le scritture dirette tramite ORM, le operazioni in blocco e i salvataggi diretti dall'area di amministrazione possono tutti superarlo. Se occorre un limite applicato dal database, usa `CharField(max_length=...)`; se il testo è davvero senza limiti, rimuovi l'argomento. La QuickFix ("Remove max_length from TextField") elimina l'argomento; è un suggerimento perché, in alternativa, potrebbe essere preferibile passare a `CharField`. + +## Non corretto + +```python +class Comment(models.Model): + body = models.TextField(max_length=500) +``` + +## Corretto + +```python +class Comment(models.Model): + body = models.TextField() +``` + +Oppure, quando il limite deve essere applicato a livello di database: + +```python + body = models.CharField(max_length=500) +``` + +## Soppressione + +```python +# django-orm-lens-disable-next-line DOL015 +``` + +Sopprimi la regola se usi intenzionalmente `max_length` soltanto come limite a livello di form. In alternativa, disattivala per l'area di lavoro in `.vscode/settings.json`: `{"djangoOrmLens.rules": {"DOL015": "off"}}`. From 126c609880560548ef6d5e255071a1c7bc614177 Mon Sep 17 00:00:00 2001 From: FROWNINGdev Date: Wed, 2 Sep 2026 10:37:17 +0500 Subject: [PATCH 12/12] docs(i18n): restore LF line endings --- docs/rules/README.md | 120 +++++++++++++++++++++---------------------- 1 file changed, 60 insertions(+), 60 deletions(-) diff --git a/docs/rules/README.md b/docs/rules/README.md index 157bdd4..0d12557 100644 --- a/docs/rules/README.md +++ b/docs/rules/README.md @@ -1,60 +1,60 @@ -# Rule reference - -🌐 [Italiano](../i18n/rules/it/README.md) — model rules (`DOL011`–`DOL015`) -🌐 [Tiếng Việt](../i18n/rules/vi/README.md) — queryset rules (`DOL001`–`DOL007`) - -Django ORM Lens ships two rule surfaces: - -1. **Editor rules (`DOL###`)** — 16 line-oriented static checks that run inside the VS Code extension on every `.py` file. Findings appear in the Problems panel under the source `Django ORM Lens`, link to these pages from the diagnostic code, and — where a fix is safe to express as a text edit — carry a QuickFix lightbulb. Detection is regex-based with bounded windows; no Python process is involved. -2. **CLI / CI analyzers** — AST-based checks in the Python package (`pip install django-orm-lens`) for terminals and pipelines: [`migration-risk`](migrations.md), [`nplusone`](nplusone.md), [`blast-radius`](blast-radius.md) — which joins migration risks with the code that still references what they change — and [`drift`](drift.md), a `makemigrations --check` that needs no Django boot. - -## Severity and applicability - -Severity mirrors VS Code's four diagnostic levels: `error`, `warning`, `info`, `hint`. Every rule has a default (listed below); override per rule in `.vscode/settings.json` via `djangoOrmLens.rules` — e.g. `{"djangoOrmLens.rules": {"DOL013": "error", "DOL007": "off"}}`. - -Applicability follows Clippy's semantics. It is a property of each individual finding and gates whether an editor may apply the fix unattended: - -| Applicability | Meaning | -|---|---| -| `safe` | Semantically equivalent, always correct to apply. Eligible for auto-apply and "Fix All". | -| `suggestion` | Usually right but may need review — offered as a QuickFix, never included in "Fix All". | -| `unsafe` | Likely-correct but breaks in edge cases — surfaced as a diagnostic only, never auto-applied. | - -## Editor rules - -| Code | Rule | Category | Default severity | Applicability | -|---|---|---|---|---| -| [DOL001](DOL001.md) | Prefer `.exists()` over `.count() > 0` | queryset | info | safe | -| [DOL002](DOL002.md) | Prefer `not .exists()` over `.count() == 0` | queryset | info | safe | -| [DOL003](DOL003.md) | Prefer `not .exists()` over `.first() is None` | queryset | info | safe | -| [DOL004](DOL004.md) | Prefer `.exists()` over `.first() is not None` | queryset | info | safe | -| [DOL005](DOL005.md) | Consider `Q(...)` over `.filter().exclude()` chain | queryset | hint | suggestion | -| [DOL006](DOL006.md) | Drop `list()` around a QuerySet in for-loop | queryset | info | safe | -| [DOL007](DOL007.md) | Possible N+1: attribute access inside for-loop | queryset | warning | unsafe | -| [DOL008](DOL008.md) | Field name in a lookup looks misspelled | correctness | warning | suggestion | -| [DOL011](DOL011.md) | `null=True` on CharField/TextField | model | warning | suggestion | -| [DOL012](DOL012.md) | Model without `__str__` method | model | info | suggestion | -| [DOL013](DOL013.md) | ForeignKey without `on_delete` | model | error | suggestion | -| [DOL014](DOL014.md) | CharField without `max_length` | model | error | suggestion | -| [DOL015](DOL015.md) | TextField with `max_length` has no DB effect | model | hint | suggestion | -| [DOL021](DOL021.md) | `datetime.now()` should be `timezone.now()` | datetime | warning | suggestion | -| [DOL022](DOL022.md) | `datetime.utcnow()` is deprecated | datetime | warning | suggestion | -| [DOL031](DOL031.md) | `render()` with `locals()` as context | forms | warning | suggestion | -| [DOL032](DOL032.md) | `fields = '__all__'` in Meta | forms | warning | unsafe | - -### Suppressing findings inline - -```python -# django-orm-lens-disable-next-line DOL007 (next line; comma-separate for several codes) -qs.count() > 0 # django-orm-lens-disable-line DOL001 -# django-orm-lens-disable DOL011 (own line — disables for the rest of the file) -``` - -Ruff-style bulk selection is also available: `djangoOrmLens.rulesSelect` (e.g. `["DOL0"]`) and `djangoOrmLens.rulesIgnore` (e.g. `["DOL03"]`). - -## CLI / CI analyzers - -These run from the Python CLI, not the editor. Both exit non-zero on findings (see each page for exact semantics) and emit `--format sarif` (SARIF 2.1.0 for GitHub Code Scanning) or `--format github` (workflow commands for zero-setup PR annotations). - -- **[Migration risk rules](migrations.md)** — 16 rules over `/migrations/*.py` flagging operations that are dangerous on production databases. Run with `django-orm-lens migration-risk`. -- **[Static N+1 detector](nplusone.md)** — flags FK / O2O / M2M / reverse-manager access inside for-loops when the source queryset has no matching `select_related` / `prefetch_related`. Run with `django-orm-lens nplusone`. +# Rule reference + +🌐 [Italiano](../i18n/rules/it/README.md) — model rules (`DOL011`–`DOL015`) +🌐 [Tiếng Việt](../i18n/rules/vi/README.md) — queryset rules (`DOL001`–`DOL007`) + +Django ORM Lens ships two rule surfaces: + +1. **Editor rules (`DOL###`)** — 16 line-oriented static checks that run inside the VS Code extension on every `.py` file. Findings appear in the Problems panel under the source `Django ORM Lens`, link to these pages from the diagnostic code, and — where a fix is safe to express as a text edit — carry a QuickFix lightbulb. Detection is regex-based with bounded windows; no Python process is involved. +2. **CLI / CI analyzers** — AST-based checks in the Python package (`pip install django-orm-lens`) for terminals and pipelines: [`migration-risk`](migrations.md), [`nplusone`](nplusone.md), [`blast-radius`](blast-radius.md) — which joins migration risks with the code that still references what they change — and [`drift`](drift.md), a `makemigrations --check` that needs no Django boot. + +## Severity and applicability + +Severity mirrors VS Code's four diagnostic levels: `error`, `warning`, `info`, `hint`. Every rule has a default (listed below); override per rule in `.vscode/settings.json` via `djangoOrmLens.rules` — e.g. `{"djangoOrmLens.rules": {"DOL013": "error", "DOL007": "off"}}`. + +Applicability follows Clippy's semantics. It is a property of each individual finding and gates whether an editor may apply the fix unattended: + +| Applicability | Meaning | +|---|---| +| `safe` | Semantically equivalent, always correct to apply. Eligible for auto-apply and "Fix All". | +| `suggestion` | Usually right but may need review — offered as a QuickFix, never included in "Fix All". | +| `unsafe` | Likely-correct but breaks in edge cases — surfaced as a diagnostic only, never auto-applied. | + +## Editor rules + +| Code | Rule | Category | Default severity | Applicability | +|---|---|---|---|---| +| [DOL001](DOL001.md) | Prefer `.exists()` over `.count() > 0` | queryset | info | safe | +| [DOL002](DOL002.md) | Prefer `not .exists()` over `.count() == 0` | queryset | info | safe | +| [DOL003](DOL003.md) | Prefer `not .exists()` over `.first() is None` | queryset | info | safe | +| [DOL004](DOL004.md) | Prefer `.exists()` over `.first() is not None` | queryset | info | safe | +| [DOL005](DOL005.md) | Consider `Q(...)` over `.filter().exclude()` chain | queryset | hint | suggestion | +| [DOL006](DOL006.md) | Drop `list()` around a QuerySet in for-loop | queryset | info | safe | +| [DOL007](DOL007.md) | Possible N+1: attribute access inside for-loop | queryset | warning | unsafe | +| [DOL008](DOL008.md) | Field name in a lookup looks misspelled | correctness | warning | suggestion | +| [DOL011](DOL011.md) | `null=True` on CharField/TextField | model | warning | suggestion | +| [DOL012](DOL012.md) | Model without `__str__` method | model | info | suggestion | +| [DOL013](DOL013.md) | ForeignKey without `on_delete` | model | error | suggestion | +| [DOL014](DOL014.md) | CharField without `max_length` | model | error | suggestion | +| [DOL015](DOL015.md) | TextField with `max_length` has no DB effect | model | hint | suggestion | +| [DOL021](DOL021.md) | `datetime.now()` should be `timezone.now()` | datetime | warning | suggestion | +| [DOL022](DOL022.md) | `datetime.utcnow()` is deprecated | datetime | warning | suggestion | +| [DOL031](DOL031.md) | `render()` with `locals()` as context | forms | warning | suggestion | +| [DOL032](DOL032.md) | `fields = '__all__'` in Meta | forms | warning | unsafe | + +### Suppressing findings inline + +```python +# django-orm-lens-disable-next-line DOL007 (next line; comma-separate for several codes) +qs.count() > 0 # django-orm-lens-disable-line DOL001 +# django-orm-lens-disable DOL011 (own line — disables for the rest of the file) +``` + +Ruff-style bulk selection is also available: `djangoOrmLens.rulesSelect` (e.g. `["DOL0"]`) and `djangoOrmLens.rulesIgnore` (e.g. `["DOL03"]`). + +## CLI / CI analyzers + +These run from the Python CLI, not the editor. Both exit non-zero on findings (see each page for exact semantics) and emit `--format sarif` (SARIF 2.1.0 for GitHub Code Scanning) or `--format github` (workflow commands for zero-setup PR annotations). + +- **[Migration risk rules](migrations.md)** — 16 rules over `/migrations/*.py` flagging operations that are dangerous on production databases. Run with `django-orm-lens migration-risk`. +- **[Static N+1 detector](nplusone.md)** — flags FK / O2O / M2M / reverse-manager access inside for-loops when the source queryset has no matching `select_related` / `prefetch_related`. Run with `django-orm-lens nplusone`.