[flake8_django] Fix DJ008 false positive for abstract models with type-annotated abstract field (#19221)

Co-authored-by: Micha Reiser <micha@reiser.io>
This commit is contained in:
Dan Parizher 2025-07-11 12:50:59 -04:00 committed by GitHub
parent 78bd73f25a
commit ee88abf77c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 89 additions and 13 deletions

View file

@ -181,3 +181,51 @@ class SubclassTestModel2(TestModel4):
# Subclass without __str__ # Subclass without __str__
class SubclassTestModel3(TestModel1): class SubclassTestModel3(TestModel1):
pass pass
# Test cases for type-annotated abstract models - these should NOT trigger DJ008
from typing import ClassVar
from django_stubs_ext.db.models import TypedModelMeta
class TypeAnnotatedAbstractModel1(models.Model):
"""Model with type-annotated abstract = True - should not trigger DJ008"""
new_field = models.CharField(max_length=10)
class Meta(TypedModelMeta):
abstract: ClassVar[bool] = True
class TypeAnnotatedAbstractModel2(models.Model):
"""Model with type-annotated abstract = True using regular Meta - should not trigger DJ008"""
new_field = models.CharField(max_length=10)
class Meta:
abstract: ClassVar[bool] = True
class TypeAnnotatedAbstractModel3(models.Model):
"""Model with type-annotated abstract = True but without ClassVar - should not trigger DJ008"""
new_field = models.CharField(max_length=10)
class Meta:
abstract: bool = True
class TypeAnnotatedNonAbstractModel(models.Model):
"""Model with type-annotated abstract = False - should trigger DJ008"""
new_field = models.CharField(max_length=10)
class Meta:
abstract: ClassVar[bool] = False
class TypeAnnotatedAbstractModelWithStr(models.Model):
"""Model with type-annotated abstract = True and __str__ method - should not trigger DJ008"""
new_field = models.CharField(max_length=10)
class Meta(TypedModelMeta):
abstract: ClassVar[bool] = True
def __str__(self):
return self.new_field

View file

@ -96,22 +96,43 @@ fn is_model_abstract(class_def: &ast::StmtClassDef) -> bool {
continue; continue;
} }
for element in body { for element in body {
let Stmt::Assign(ast::StmtAssign { targets, value, .. }) = element else { match element {
continue; Stmt::Assign(ast::StmtAssign { targets, value, .. }) => {
}; if targets
for target in targets { .iter()
let Expr::Name(ast::ExprName { id, .. }) = target else { .any(|target| is_abstract_true_assignment(target, Some(value)))
continue; {
}; return true;
if id != "abstract" { }
continue;
} }
if !is_const_true(value) { Stmt::AnnAssign(ast::StmtAnnAssign { target, value, .. }) => {
continue; if is_abstract_true_assignment(target, value.as_deref()) {
return true;
}
} }
return true; _ => {}
} }
} }
} }
false false
} }
fn is_abstract_true_assignment(target: &Expr, value: Option<&Expr>) -> bool {
let Expr::Name(ast::ExprName { id, .. }) = target else {
return false;
};
if id != "abstract" {
return false;
}
let Some(value) = value else {
return false;
};
if !is_const_true(value) {
return false;
}
true
}

View file

@ -1,6 +1,5 @@
--- ---
source: crates/ruff_linter/src/rules/flake8_django/mod.rs source: crates/ruff_linter/src/rules/flake8_django/mod.rs
snapshot_kind: text
--- ---
DJ008.py:6:7: DJ008 Model does not define `__str__` method DJ008.py:6:7: DJ008 Model does not define `__str__` method
| |
@ -31,3 +30,11 @@ DJ008.py:182:7: DJ008 Model does not define `__str__` method
| ^^^^^^^^^^^^^^^^^^ DJ008 | ^^^^^^^^^^^^^^^^^^ DJ008
183 | pass 183 | pass
| |
DJ008.py:215:7: DJ008 Model does not define `__str__` method
|
215 | class TypeAnnotatedNonAbstractModel(models.Model):
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ DJ008
216 | """Model with type-annotated abstract = False - should trigger DJ008"""
217 | new_field = models.CharField(max_length=10)
|