diff --git a/django-stubs/contrib/auth/models.pyi b/django-stubs/contrib/auth/models.pyi index babfb17c3..8407551fe 100644 --- a/django-stubs/contrib/auth/models.pyi +++ b/django-stubs/contrib/auth/models.pyi @@ -15,6 +15,11 @@ if sys.version_info < (3, 8): else: from typing import Literal +if sys.version_info < (3, 11): + from typing_extensions import Self +else: + from typing import Self + _AnyUser = Union[Model, "AnonymousUser"] def update_last_login( @@ -104,7 +109,7 @@ class AbstractUser(AbstractBaseUser, PermissionsMixin): ) -> None: ... class User(AbstractUser): - objects: UserManager[User] + objects: UserManager[Self] class AnonymousUser: id: Any = ... diff --git a/django-stubs/db/models/base.pyi b/django-stubs/db/models/base.pyi index 5d063aa94..76e7c801b 100644 --- a/django-stubs/db/models/base.pyi +++ b/django-stubs/db/models/base.pyi @@ -67,7 +67,10 @@ class Model(metaclass=ModelBase): self, using: Any = ..., keep_parents: bool = ... ) -> Tuple[int, Dict[str, int]]: ... def full_clean( - self, exclude: Optional[Collection[str]] = ..., validate_unique: bool = True, validate_constraints: bool = True + self, + exclude: Optional[Collection[str]] = ..., + validate_unique: bool = True, + validate_constraints: bool = True, ) -> None: ... def clean(self) -> None: ... def clean_fields(self, exclude: Optional[Collection[str]] = ...) -> None: ... diff --git a/django-stubs/db/transaction.pyi b/django-stubs/db/transaction.pyi index 60db02161..8e41d64ab 100644 --- a/django-stubs/db/transaction.pyi +++ b/django-stubs/db/transaction.pyi @@ -28,7 +28,9 @@ _C = TypeVar("_C", bound=Callable[..., Any]) class Atomic: using: Optional[str] = ... savepoint: bool = ... - def __init__(self, using: Optional[str], savepoint: bool, durable: bool = ...) -> None: ... + def __init__( + self, using: Optional[str], savepoint: bool, durable: bool = ... + ) -> None: ... # When decorating, return the decorated function as-is, rather than clobbering it as ContextDecorator does. def __call__(self, func: _C) -> _C: ... def __enter__(self) -> None: ... @@ -40,7 +42,9 @@ def atomic(using: _C) -> _C: ... # Decorator or context-manager with parameters @overload -def atomic(using: Optional[str] = ..., savepoint: bool = ..., durable: bool = ...) -> Atomic: ... +def atomic( + using: Optional[str] = ..., savepoint: bool = ..., durable: bool = ... +) -> Atomic: ... # Bare decorator @overload diff --git a/tests/pyright/test_user.py b/tests/pyright/test_user.py new file mode 100644 index 000000000..409fee743 --- /dev/null +++ b/tests/pyright/test_user.py @@ -0,0 +1,25 @@ +from .base import Result, run_pyright + + +def test_user_manager_specialises_to_self() -> None: + results = run_pyright( + """\ +from django.db import models +from django.contrib.auth import models as auth_models + +class MyUser(auth_models.User): + age = models.IntegerField() + +u = MyUser() +reveal_type(u.objects) +""" + ) + + assert results == [ + Result( + type="information", + message='Type of "u.objects" is "UserManager[MyUser]"', + line=8, + column=13, + ), + ]