diff --git a/django-stubs/contrib/admin/models.pyi b/django-stubs/contrib/admin/models.pyi index 22ae57033..40eebdcdb 100644 --- a/django-stubs/contrib/admin/models.pyi +++ b/django-stubs/contrib/admin/models.pyi @@ -1,9 +1,12 @@ from typing import Any, ClassVar from uuid import UUID +from django.contrib.auth.models import AbstractUser +from django.contrib.contenttypes.models import ContentType from django.db import models from django.db.models import QuerySet from django.db.models.base import Model +from django.db.models.expressions import Combinable from typing_extensions import deprecated ADDITION: int @@ -28,13 +31,19 @@ class LogEntryManager(models.Manager[LogEntry]): queryset: QuerySet[Model], action_flag: int, change_message: str | list[Any] = "", + *, + single_object: bool = False, ) -> list[LogEntry] | LogEntry: ... class LogEntry(models.Model): + id: models.AutoField + pk: models.AutoField action_time: models.DateTimeField - user: models.ForeignKey - content_type: models.ForeignKey - object_id: models.TextField + user: models.ForeignKey[AbstractUser | Combinable, AbstractUser] + user_id: int + content_type: models.ForeignKey[ContentType | Combinable | None, ContentType | None] + content_type_id: int | None + object_id: models.TextField[str | int | Combinable | None, str | None] object_repr: models.CharField action_flag: models.PositiveSmallIntegerField change_message: models.TextField diff --git a/django-stubs/contrib/auth/base_user.pyi b/django-stubs/contrib/auth/base_user.pyi index 76cca4ff9..e16c14626 100644 --- a/django-stubs/contrib/auth/base_user.pyi +++ b/django-stubs/contrib/auth/base_user.pyi @@ -1,4 +1,5 @@ from collections.abc import Iterable +from datetime import date, datetime from typing import Any, ClassVar, Literal, TypeAlias, TypeVar, overload from django.db import models @@ -17,7 +18,7 @@ class AbstractBaseUser(models.Model): REQUIRED_FIELDS: ClassVar[list[str]] password = models.CharField(max_length=128) - last_login = models.DateTimeField(blank=True, null=True) + last_login = models.DateTimeField[str | datetime | date | Combinable, datetime | None](blank=True, null=True) is_active: bool | BooleanField[bool | Combinable, bool] def get_username(self) -> str: ... diff --git a/django-stubs/contrib/auth/models.pyi b/django-stubs/contrib/auth/models.pyi index 57a594952..9f16d1605 100644 --- a/django-stubs/contrib/auth/models.pyi +++ b/django-stubs/contrib/auth/models.pyi @@ -1,5 +1,5 @@ from collections.abc import Iterable -from typing import Any, ClassVar, Literal, TypeAlias, TypeVar +from typing import Any, ClassVar, Literal, TypeAlias, TypeVar, type_check_only from django.contrib.auth.base_user import AbstractBaseUser as AbstractBaseUser from django.contrib.auth.base_user import BaseUserManager as BaseUserManager @@ -8,6 +8,8 @@ from django.contrib.contenttypes.models import ContentType from django.db import models from django.db.models import QuerySet from django.db.models.base import Model +from django.db.models.expressions import Combinable +from django.db.models.fields.related_descriptors import ManyToManyDescriptor from django.db.models.manager import EmptyManager from django.utils.functional import _StrOrPromise from typing_extensions import Self @@ -20,22 +22,68 @@ class PermissionManager(models.Manager[Permission]): def get_by_natural_key(self, codename: str, app_label: str, model: str) -> Permission: ... class Permission(models.Model): - content_type_id: int objects: ClassVar[PermissionManager] + id: models.AutoField + pk: models.AutoField name = models.CharField(max_length=255) - content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) + content_type: models.ForeignKey[ContentType | Combinable, ContentType] + content_type_id: int codename = models.CharField(max_length=100) + group_set: ManyToManyDescriptor[Group, _Group_permissions] + user_set: ManyToManyDescriptor[User, _User_permissions] def natural_key(self) -> tuple[str, str, str]: ... class GroupManager(models.Manager[Group]): def get_by_natural_key(self, name: str) -> Group: ... +# This is a model that only exists in Django's model registry and doesn't have any +# class statement form. It's the through model between 'Group' and 'Permission'. +@type_check_only +class _Group_permissions(models.Model): + objects: ClassVar[models.Manager[Self]] + + id: models.AutoField + pk: models.AutoField + group: models.ForeignKey[Group | Combinable, Group] + group_id: int + permission: models.ForeignKey[Permission | Combinable, Permission] + permission_id: int + +# This is a model that only exists in Django's model registry and doesn't have any +# class statement form. It's the through model between 'User' and 'Group'. +@type_check_only +class _User_groups(models.Model): + objects: ClassVar[models.Manager[Self]] + + id: models.AutoField + pk: models.AutoField + user: models.ForeignKey[User | Combinable, User] + user_id: int + group: models.ForeignKey[Group | Combinable, Group] + group_id: int + +# This is a model that only exists in Django's model registry and doesn't have any +# class statement form. It's the through model between 'User' and 'Permission'. +@type_check_only +class _User_permissions(models.Model): + objects: ClassVar[models.Manager[Self]] + + id: models.AutoField + pk: models.AutoField + user: models.ForeignKey[User | Combinable, User] + user_id: int + permission: models.ForeignKey[Permission | Combinable, Permission] + permission_id: int + class Group(models.Model): objects: ClassVar[GroupManager] + id: models.AutoField + pk: models.AutoField name = models.CharField(max_length=150) - permissions = models.ManyToManyField(Permission) + permissions = models.ManyToManyField[Permission, _Group_permissions](Permission) + user_set: ManyToManyDescriptor[User, _User_groups] def natural_key(self) -> tuple[str]: ... _T = TypeVar("_T", bound=Model) @@ -58,8 +106,8 @@ class UserManager(BaseUserManager[_T]): class PermissionsMixin(models.Model): is_superuser = models.BooleanField() - groups = models.ManyToManyField(Group) - user_permissions = models.ManyToManyField(Permission) + groups = models.ManyToManyField[Group, _User_groups](Group) + user_permissions = models.ManyToManyField[Permission, _User_permissions](Permission) def get_user_permissions(self, obj: _AnyUser | None = ...) -> set[str]: ... async def aget_user_permissions(self, obj: _AnyUser | None = ...) -> set[str]: ... @@ -93,10 +141,12 @@ class AbstractUser(AbstractBaseUser, PermissionsMixin): def get_full_name(self) -> str: ... def get_short_name(self) -> str: ... def email_user( - self, subject: _StrOrPromise, message: _StrOrPromise, from_email: str = ..., **kwargs: Any + self, subject: _StrOrPromise, message: _StrOrPromise, from_email: str | None = ..., **kwargs: Any ) -> None: ... -class User(AbstractUser): ... +class User(AbstractUser): + id: models.AutoField + pk: models.AutoField class AnonymousUser: id: None @@ -130,3 +180,4 @@ class AnonymousUser: @property def is_authenticated(self) -> Literal[False]: ... def get_username(self) -> str: ... + def __int__(self) -> None: ... diff --git a/django-stubs/contrib/contenttypes/models.pyi b/django-stubs/contrib/contenttypes/models.pyi index 1c15422e1..6fe3cf5c6 100644 --- a/django-stubs/contrib/contenttypes/models.pyi +++ b/django-stubs/contrib/contenttypes/models.pyi @@ -1,10 +1,14 @@ from typing import Any, ClassVar +from django.contrib.admin.models import LogEntry +from django.contrib.auth.models import Permission from django.db import models from django.db.models.base import Model +from django.db.models.fields.related_descriptors import ReverseManyToOneDescriptor from django.db.models.query import QuerySet class ContentTypeManager(models.Manager[ContentType]): + def __init__(self, *args: Any, **kwargs: Any) -> None: ... def get_by_natural_key(self, app_label: str, model: str) -> ContentType: ... def get_for_model(self, model: type[Model] | Model, for_concrete_model: bool = ...) -> ContentType: ... def get_for_models(self, *models: Any, for_concrete_models: bool = ...) -> dict[type[Model], ContentType]: ... @@ -12,13 +16,18 @@ class ContentTypeManager(models.Manager[ContentType]): def clear_cache(self) -> None: ... class ContentType(models.Model): - id: int + id: models.AutoField + pk: models.AutoField app_label = models.CharField(max_length=100) model = models.CharField(max_length=100) + logentry_set: ReverseManyToOneDescriptor[LogEntry] + permission_set: ReverseManyToOneDescriptor[Permission] objects: ClassVar[ContentTypeManager] @property def name(self) -> str: ... + @property + def app_labeled_name(self) -> str: ... def model_class(self) -> type[Model] | None: ... def get_object_for_this_type(self, **kwargs: Any) -> Model: ... - def get_all_objects_for_this_type(self, **kwargs: Any) -> QuerySet: ... + def get_all_objects_for_this_type(self, **kwargs: Any) -> QuerySet[Model]: ... def natural_key(self) -> tuple[str, str]: ... diff --git a/django-stubs/contrib/flatpages/models.pyi b/django-stubs/contrib/flatpages/models.pyi index 4b578fe03..ddbbd4da5 100644 --- a/django-stubs/contrib/flatpages/models.pyi +++ b/django-stubs/contrib/flatpages/models.pyi @@ -1,12 +1,31 @@ +from typing import ClassVar, type_check_only + from django.contrib.sites.models import Site from django.db import models +from django.db.models.expressions import Combinable +from typing_extensions import Self + +# This is a model that only exists in Django's model registry and doesn't have any +# class statement form. It's the through model between 'FlatPage' and 'Site'. +@type_check_only +class _FlatPage_sites(models.Model): + objects: ClassVar[models.Manager[Self]] + + id: models.AutoField + pk: models.AutoField + site: models.ForeignKey[Site | Combinable, Site] + site_id: int + flatpage: models.ForeignKey[FlatPage | Combinable, FlatPage] + flatpage_id: int class FlatPage(models.Model): + id: models.AutoField + pk: models.AutoField url: models.CharField title: models.CharField content: models.TextField enable_comments: models.BooleanField template_name: models.CharField registration_required: models.BooleanField - sites: models.ManyToManyField[Site, Site] + sites: models.ManyToManyField[Site, _FlatPage_sites] def get_absolute_url(self) -> str: ... diff --git a/django-stubs/contrib/gis/db/backends/oracle/models.pyi b/django-stubs/contrib/gis/db/backends/oracle/models.pyi index 7b002731b..ba20a51f8 100644 --- a/django-stubs/contrib/gis/db/backends/oracle/models.pyi +++ b/django-stubs/contrib/gis/db/backends/oracle/models.pyi @@ -6,9 +6,9 @@ from django.db.models.manager import Manager from typing_extensions import Self class OracleGeometryColumns(models.Model): - table_name: Any - column_name: Any - srid: Any + table_name: models.CharField + column_name: models.CharField + srid: models.IntegerField objects: ClassVar[Manager[Self]] @classmethod @@ -17,12 +17,12 @@ class OracleGeometryColumns(models.Model): def geom_col_name(cls) -> Any: ... class OracleSpatialRefSys(models.Model, SpatialRefSysMixin): - cs_name: Any - srid: Any - auth_srid: Any - auth_name: Any - wktext: Any - cs_bounds: Any + cs_name: models.CharField + srid: models.IntegerField + auth_srid: models.IntegerField + auth_name: models.CharField + wktext: models.TextField + cs_bounds: models.PolygonField objects: ClassVar[Manager[Self]] @property diff --git a/django-stubs/contrib/gis/db/backends/postgis/models.pyi b/django-stubs/contrib/gis/db/backends/postgis/models.pyi index 4bd4da16f..cf4559093 100644 --- a/django-stubs/contrib/gis/db/backends/postgis/models.pyi +++ b/django-stubs/contrib/gis/db/backends/postgis/models.pyi @@ -5,13 +5,13 @@ from django.db import models from typing_extensions import Self class PostGISGeometryColumns(models.Model): - f_table_catalog: Any - f_table_schema: Any - f_table_name: Any - f_geometry_column: Any - coord_dimension: Any - srid: Any - type: Any + f_table_catalog: models.CharField + f_table_schema: models.CharField + f_table_name: models.CharField + f_geometry_column: models.CharField + coord_dimension: models.IntegerField + srid: models.IntegerField + type: models.CharField objects: ClassVar[models.Manager[Self]] @classmethod @@ -20,11 +20,11 @@ class PostGISGeometryColumns(models.Model): def geom_col_name(cls) -> Any: ... class PostGISSpatialRefSys(models.Model, SpatialRefSysMixin): - srid: Any - auth_name: Any - auth_srid: Any - srtext: Any - proj4text: Any + srid: models.IntegerField + auth_name: models.CharField + auth_srid: models.IntegerField + srtext: models.CharField + proj4text: models.CharField objects: ClassVar[models.Manager[Self]] @property diff --git a/django-stubs/contrib/gis/db/backends/spatialite/models.pyi b/django-stubs/contrib/gis/db/backends/spatialite/models.pyi index 5585d2e17..56f2602d0 100644 --- a/django-stubs/contrib/gis/db/backends/spatialite/models.pyi +++ b/django-stubs/contrib/gis/db/backends/spatialite/models.pyi @@ -5,12 +5,12 @@ from django.db import models from typing_extensions import Self class SpatialiteGeometryColumns(models.Model): - f_table_name: Any - f_geometry_column: Any - coord_dimension: Any - srid: Any - spatial_index_enabled: Any - type: Any + f_table_name: models.CharField + f_geometry_column: models.CharField + coord_dimension: models.IntegerField + srid: models.IntegerField + spatial_index_enabled: models.IntegerField + type: models.IntegerField objects: ClassVar[models.Manager[Self]] @classmethod @@ -19,12 +19,12 @@ class SpatialiteGeometryColumns(models.Model): def geom_col_name(cls) -> Any: ... class SpatialiteSpatialRefSys(models.Model, SpatialRefSysMixin): - srid: Any - auth_name: Any - auth_srid: Any - ref_sys_name: Any - proj4text: Any - srtext: Any + srid: models.IntegerField + auth_name: models.CharField + auth_srid: models.IntegerField + ref_sys_name: models.CharField + proj4text: models.CharField + srtext: models.CharField objects: ClassVar[models.Manager[Self]] @property diff --git a/django-stubs/contrib/gis/db/models/fields.pyi b/django-stubs/contrib/gis/db/models/fields.pyi index fe79d2666..683d965fc 100644 --- a/django-stubs/contrib/gis/db/models/fields.pyi +++ b/django-stubs/contrib/gis/db/models/fields.pyi @@ -1,5 +1,5 @@ from collections.abc import Iterable -from typing import Any, NamedTuple, TypeVar +from typing import Any, Generic, NamedTuple, TypeVar, type_check_only from django.contrib.gis import forms from django.contrib.gis.geos import ( @@ -22,6 +22,10 @@ from django.utils.functional import _StrOrPromise _ST = TypeVar("_ST") # __get__ return type _GT = TypeVar("_GT") +# Form class type +_Form_ClassT = TypeVar("_Form_ClassT", bound=forms.GeometryField) +# Geometry class type +_GEOM_ClassT = TypeVar("_GEOM_ClassT", bound=GEOSGeometry) class SRIDCacheEntry(NamedTuple): units: Any @@ -30,11 +34,13 @@ class SRIDCacheEntry(NamedTuple): geodetic: bool def get_srid_info(srid: int, connection: Any) -> SRIDCacheEntry: ... +@type_check_only +class _SpatialClassField(Generic[_Form_ClassT, _GEOM_ClassT]): + form_class: type[_Form_ClassT] + geom_class: type[_GEOM_ClassT] | None -class BaseSpatialField(Field[_ST, _GT]): - form_class: type[forms.GeometryField] +class BaseSpatialField(Field[_ST, _GT], _SpatialClassField[_Form_ClassT, _GEOM_ClassT]): geom_type: str - geom_class: type[GEOSGeometry] | None geography: bool spatial_index: bool srid: int @@ -52,7 +58,7 @@ class BaseSpatialField(Field[_ST, _GT]): null: bool = ..., db_index: bool = ..., default: Any = ..., - db_default: type[NOT_PROVIDED] | Expression | _ST = ..., + db_default: type[NOT_PROVIDED] | Expression | _ST = ..., # pyright: ignore[reportInvalidTypeVarUse] editable: bool = ..., auto_created: bool = ..., serialize: bool = ..., @@ -78,7 +84,7 @@ class BaseSpatialField(Field[_ST, _GT]): def get_raster_prep_value(self, value: Any, is_candidate: Any) -> Any: ... def get_prep_value(self, value: Any) -> Any: ... -class GeometryField(BaseSpatialField[_ST, _GT]): +class GeometryField(BaseSpatialField[_ST, _GT, _Form_ClassT, _GEOM_ClassT]): dim: int def __init__( self, @@ -98,7 +104,7 @@ class GeometryField(BaseSpatialField[_ST, _GT]): null: bool = ..., db_index: bool = ..., default: Any = ..., - db_default: type[NOT_PROVIDED] | Expression | _ST = ..., + db_default: type[NOT_PROVIDED] | Expression | _ST = ..., # pyright: ignore[reportInvalidTypeVarUse] editable: bool = ..., auto_created: bool = ..., serialize: bool = ..., @@ -122,62 +128,101 @@ class GeometryField(BaseSpatialField[_ST, _GT]): **kwargs: Any, ) -> forms.GeometryField: ... -class PointField(GeometryField[_ST, _GT]): +_ST_PointField = TypeVar("_ST_PointField", default=Point | Combinable) +_GT_PointField = TypeVar("_GT_PointField", default=Point) +_Form_PointField = TypeVar("_Form_PointField", bound=forms.PointField, default=forms.PointField) +_Geom_PointField = TypeVar("_Geom_PointField", bound=Point, default=Point) + +class PointField(GeometryField[_ST_PointField, _GT_PointField, _Form_PointField, _Geom_PointField]): _pyi_private_set_type: Point | Combinable _pyi_private_get_type: Point _pyi_lookup_exact_type: Point - geom_class: type[Point] - form_class: type[forms.PointField] +_ST_LineStringField = TypeVar("_ST_LineStringField", default=LineString | Combinable) +_GT_LineStringField = TypeVar("_GT_LineStringField", default=LineString) +_Form_LineStringField = TypeVar("_Form_LineStringField", bound=forms.LineStringField, default=forms.LineStringField) +_Geom_LineStringField = TypeVar("_Geom_LineStringField", bound=LineString, default=LineString) -class LineStringField(GeometryField[_ST, _GT]): +class LineStringField( + GeometryField[_ST_LineStringField, _GT_LineStringField, _Form_LineStringField, _Geom_LineStringField] +): _pyi_private_set_type: LineString | Combinable _pyi_private_get_type: LineString _pyi_lookup_exact_type: LineString - geom_class: type[LineString] - form_class: type[forms.LineStringField] +_ST_PolygonField = TypeVar("_ST_PolygonField", default=Polygon | Combinable) +_GT_PolygonField = TypeVar("_GT_PolygonField", default=Polygon) +_Form_PolygonField = TypeVar("_Form_PolygonField", bound=forms.PolygonField, default=forms.PolygonField) +_Geom_PolygonField = TypeVar("_Geom_PolygonField", bound=Polygon, default=Polygon) -class PolygonField(GeometryField[_ST, _GT]): +class PolygonField(GeometryField[_ST_PolygonField, _GT_PolygonField, _Form_PolygonField, _Geom_PolygonField]): _pyi_private_set_type: Polygon | Combinable _pyi_private_get_type: Polygon _pyi_lookup_exact_type: Polygon - geom_class: type[Polygon] - form_class: type[forms.PolygonField] +_ST_MultiPointField = TypeVar("_ST_MultiPointField", default=MultiPoint | Combinable) +_GT_MultiPointField = TypeVar("_GT_MultiPointField", default=MultiPoint) +_Form_MultiPointField = TypeVar("_Form_MultiPointField", bound=forms.MultiPointField, default=forms.MultiPointField) +_Geom_MultiPointField = TypeVar("_Geom_MultiPointField", bound=MultiPoint, default=MultiPoint) -class MultiPointField(GeometryField[_ST, _GT]): +class MultiPointField( + GeometryField[_ST_MultiPointField, _GT_MultiPointField, _Form_MultiPointField, _Geom_MultiPointField] +): _pyi_private_set_type: MultiPoint | Combinable _pyi_private_get_type: MultiPoint _pyi_lookup_exact_type: MultiPoint - geom_class: type[MultiPoint] - form_class: type[forms.MultiPointField] +_ST_MultiLineStringField = TypeVar("_ST_MultiLineStringField", default=MultiLineString | Combinable) +_GT_MultiLineStringField = TypeVar("_GT_MultiLineStringField", default=MultiLineString) +_Form_MultiLineStringField = TypeVar( + "_Form_MultiLineStringField", bound=forms.MultiLineStringField, default=forms.MultiLineStringField +) +_Geom_MultiLineStringField = TypeVar("_Geom_MultiLineStringField", bound=MultiLineString, default=MultiLineString) -class MultiLineStringField(GeometryField[_ST, _GT]): +class MultiLineStringField( + GeometryField[ + _ST_MultiLineStringField, _GT_MultiLineStringField, _Form_MultiLineStringField, _Geom_MultiLineStringField + ] +): _pyi_private_set_type: MultiLineString | Combinable _pyi_private_get_type: MultiLineString _pyi_lookup_exact_type: MultiLineString - geom_class: type[MultiLineString] - form_class: type[forms.MultiLineStringField] +_ST_MultiPolygonField = TypeVar("_ST_MultiPolygonField", default=MultiPolygon | Combinable) +_GT_MultiPolygonField = TypeVar("_GT_MultiPolygonField", default=MultiPolygon) +_Form_MultiPolygonField = TypeVar( + "_Form_MultiPolygonField", bound=forms.MultiPolygonField, default=forms.MultiPolygonField +) +_Geom_MultiPolygonField = TypeVar("_Geom_MultiPolygonField", bound=MultiPolygon, default=MultiPolygon) -class MultiPolygonField(GeometryField[_ST, _GT]): +class MultiPolygonField( + GeometryField[_ST_MultiPolygonField, _GT_MultiPolygonField, _Form_MultiPolygonField, _Geom_MultiPolygonField] +): _pyi_private_set_type: MultiPolygon | Combinable _pyi_private_get_type: MultiPolygon _pyi_lookup_exact_type: MultiPolygon - geom_class: type[MultiPolygon] - form_class: type[forms.MultiPolygonField] +_ST_GeometryCollectionField = TypeVar("_ST_GeometryCollectionField", default=GeometryCollection | Combinable) +_GT_GeometryCollectionField = TypeVar("_GT_GeometryCollectionField", default=GeometryCollection) +_Form_GeometryCollectionField = TypeVar( + "_Form_GeometryCollectionField", bound=forms.GeometryCollectionField, default=forms.GeometryCollectionField +) +_Geom_GeometryCollectionField = TypeVar( + "_Geom_GeometryCollectionField", bound=GeometryCollection, default=GeometryCollection +) -class GeometryCollectionField(GeometryField[_ST, _GT]): +class GeometryCollectionField( + GeometryField[ + _ST_GeometryCollectionField, + _GT_GeometryCollectionField, + _Form_GeometryCollectionField, + _Geom_GeometryCollectionField, + ] +): _pyi_private_set_type: GeometryCollection | Combinable _pyi_private_get_type: GeometryCollection _pyi_lookup_exact_type: GeometryCollection - geom_class: type[GeometryCollection] - form_class: type[forms.GeometryCollectionField] - class ExtentField(Field): def get_internal_type(self) -> Any: ... diff --git a/django-stubs/contrib/redirects/models.pyi b/django-stubs/contrib/redirects/models.pyi index 444960881..9801fba2b 100644 --- a/django-stubs/contrib/redirects/models.pyi +++ b/django-stubs/contrib/redirects/models.pyi @@ -1,6 +1,11 @@ +from django.contrib.sites.models import Site from django.db import models +from django.db.models.expressions import Combinable class Redirect(models.Model): - site: models.ForeignKey + id: models.AutoField + pk: models.AutoField + site: models.ForeignKey[Site | Combinable, Site] + site_id: int old_path: models.CharField new_path: models.CharField diff --git a/django-stubs/contrib/sessions/base_session.pyi b/django-stubs/contrib/sessions/base_session.pyi index 4da1bdb26..1c24846ea 100644 --- a/django-stubs/contrib/sessions/base_session.pyi +++ b/django-stubs/contrib/sessions/base_session.pyi @@ -3,6 +3,7 @@ from typing import Any, ClassVar, TypeVar from django.contrib.sessions.backends.base import SessionBase from django.db import models +from django.db.models.expressions import Combinable from typing_extensions import Self _T = TypeVar("_T", bound=AbstractBaseSession) @@ -12,7 +13,8 @@ class BaseSessionManager(models.Manager[_T]): def save(self, session_key: str, session_dict: dict[str, Any], expire_date: datetime) -> _T: ... class AbstractBaseSession(models.Model): - session_key = models.CharField(primary_key=True) + pk: models.CharField[str | int | Combinable | None, str] + session_key = models.CharField[str | int | Combinable | None, str](primary_key=True) session_data = models.TextField() expire_date = models.DateTimeField() objects: ClassVar[BaseSessionManager[Self]] diff --git a/django-stubs/contrib/sessions/models.pyi b/django-stubs/contrib/sessions/models.pyi index 492c9c34d..013e4915e 100644 --- a/django-stubs/contrib/sessions/models.pyi +++ b/django-stubs/contrib/sessions/models.pyi @@ -1,8 +1,11 @@ -from typing import TypeVar +from typing import ClassVar, TypeVar from django.contrib.sessions.base_session import AbstractBaseSession, BaseSessionManager +from typing_extensions import Self _T = TypeVar("_T", bound=Session) class SessionManager(BaseSessionManager[_T]): ... -class Session(AbstractBaseSession): ... + +class Session(AbstractBaseSession): + objects: ClassVar[SessionManager[Self]] # type: ignore[assignment] diff --git a/django-stubs/contrib/sites/models.pyi b/django-stubs/contrib/sites/models.pyi index e4181d66f..a6680d0f1 100644 --- a/django-stubs/contrib/sites/models.pyi +++ b/django-stubs/contrib/sites/models.pyi @@ -1,6 +1,9 @@ from typing import Any, ClassVar +from django.contrib.flatpages.models import FlatPage, _FlatPage_sites +from django.contrib.redirects.models import Redirect from django.db import models +from django.db.models.fields.related_descriptors import ManyToManyDescriptor, ReverseManyToOneDescriptor from django.http.request import HttpRequest SITE_CACHE: Any @@ -13,8 +16,12 @@ class SiteManager(models.Manager[Site]): class Site(models.Model): objects: ClassVar[SiteManager] + id: models.AutoField + pk: models.AutoField domain = models.CharField(max_length=100) name = models.CharField(max_length=50) + flatpage_set: ManyToManyDescriptor[FlatPage, _FlatPage_sites] + redirect_set: ReverseManyToOneDescriptor[Redirect] def natural_key(self) -> tuple[str]: ... def clear_site_cache(sender: type[Site], **kwargs: Any) -> None: ... diff --git a/django-stubs/db/models/fields/__init__.pyi b/django-stubs/db/models/fields/__init__.pyi index e676277e9..ecb9838a5 100644 --- a/django-stubs/db/models/fields/__init__.pyi +++ b/django-stubs/db/models/fields/__init__.pyi @@ -3,7 +3,7 @@ import uuid from collections.abc import Callable, Iterable, Mapping, Sequence from datetime import date, time, timedelta from datetime import datetime as real_datetime -from typing import Any, ClassVar, Generic, Literal, Protocol, TypeAlias, TypeVar, overload, type_check_only +from typing import Any, ClassVar, Generic, Literal, Protocol, TypeAlias, overload, type_check_only from django import forms from django.core import validators # due to weird mypy.stubtest error @@ -18,7 +18,7 @@ from django.forms import Widget from django.utils.choices import BlankChoiceIterator, _Choice, _ChoiceNamedGroup, _ChoicesCallable, _ChoicesInput from django.utils.datastructures import DictWrapper from django.utils.functional import _Getter, _StrOrPromise, cached_property -from typing_extensions import Self +from typing_extensions import Self, TypeVar class Empty: ... class NOT_PROVIDED: ... @@ -178,7 +178,7 @@ class Field(RegisterLookupMixin, Generic[_ST, _GT]): validators: Iterable[validators._ValidatorCallable] = (), error_messages: _ErrorMessagesMapping | None = None, db_comment: str | None = None, - db_default: type[NOT_PROVIDED] | Expression | _ST = ..., + db_default: type[NOT_PROVIDED] | Expression | _ST = ..., # pyright: ignore[reportInvalidTypeVarUse] ) -> None: ... def __set__(self, instance: Any, value: _ST) -> None: ... # class access @@ -230,7 +230,7 @@ class Field(RegisterLookupMixin, Generic[_ST, _GT]): def has_default(self) -> bool: ... def get_default(self) -> Any: ... def check(self, **kwargs: Any) -> list[CheckMessage]: ... - def get_col(self, alias: str, output_field: Field | None = None) -> Col: ... + def get_col(self, alias: str, output_field: Field[Any, Any] | None = None) -> Col: ... @cached_property def cached_col(self) -> Col: ... def value_from_object(self, obj: Model) -> _GT: ... @@ -239,7 +239,10 @@ class Field(RegisterLookupMixin, Generic[_ST, _GT]): def value_to_string(self, obj: Model) -> str: ... def slice_expression(self, expression: Expression, start: int, length: int | None) -> Func: ... -class IntegerField(Field[_ST, _GT]): +_ST_IntegerField = TypeVar("_ST_IntegerField", default=float | int | str | Combinable) +_GT_IntegerField = TypeVar("_GT_IntegerField", default=int) + +class IntegerField(Field[_ST_IntegerField, _GT_IntegerField]): _pyi_private_set_type: float | int | str | Combinable _pyi_private_get_type: int _pyi_lookup_exact_type: str | int @@ -247,21 +250,29 @@ class IntegerField(Field[_ST, _GT]): class PositiveIntegerRelDbTypeMixin: def rel_db_type(self, connection: BaseDatabaseWrapper) -> str: ... -class SmallIntegerField(IntegerField[_ST, _GT]): ... +class SmallIntegerField(IntegerField[_ST_IntegerField, _GT_IntegerField]): ... -class BigIntegerField(IntegerField[_ST, _GT]): +class BigIntegerField(IntegerField[_ST_IntegerField, _GT_IntegerField]): MAX_BIGINT: ClassVar[int] -class PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField[_ST, _GT]): ... -class PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, SmallIntegerField[_ST, _GT]): ... -class PositiveBigIntegerField(PositiveIntegerRelDbTypeMixin, BigIntegerField[_ST, _GT]): ... +class PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField[_ST_IntegerField, _GT_IntegerField]): ... +class PositiveSmallIntegerField( + PositiveIntegerRelDbTypeMixin, SmallIntegerField[_ST_IntegerField, _GT_IntegerField] +): ... +class PositiveBigIntegerField(PositiveIntegerRelDbTypeMixin, BigIntegerField[_ST_IntegerField, _GT_IntegerField]): ... + +_ST_FloatField = TypeVar("_ST_FloatField", default=float | int | str | Combinable) +_GT_FloatField = TypeVar("_GT_FloatField", default=float) -class FloatField(Field[_ST, _GT]): +class FloatField(Field[_ST_FloatField, _GT_FloatField]): _pyi_private_set_type: float | int | str | Combinable _pyi_private_get_type: float _pyi_lookup_exact_type: float -class DecimalField(Field[_ST, _GT]): +_ST_DecimalField = TypeVar("_ST_DecimalField", default=str | float | decimal.Decimal | Combinable) +_GT_DecimalField = TypeVar("_GT_DecimalField", default=decimal.Decimal) + +class DecimalField(Field[_ST_DecimalField, _GT_DecimalField]): _pyi_private_set_type: str | float | decimal.Decimal | Combinable _pyi_private_get_type: decimal.Decimal _pyi_lookup_exact_type: str | decimal.Decimal @@ -281,7 +292,7 @@ class DecimalField(Field[_ST, _GT]): null: bool = ..., db_index: bool = ..., default: Any = ..., - db_default: type[NOT_PROVIDED] | Expression | _ST = ..., + db_default: type[NOT_PROVIDED] | Expression | _ST_DecimalField = ..., editable: bool = ..., auto_created: bool = ..., serialize: bool = ..., @@ -294,7 +305,10 @@ class DecimalField(Field[_ST, _GT]): error_messages: _ErrorMessagesMapping | None = ..., ) -> None: ... -class CharField(Field[_ST, _GT]): +_ST_CharField = TypeVar("_ST_CharField", default=str | int | Combinable) +_GT_CharField = TypeVar("_GT_CharField", default=str) + +class CharField(Field[_ST_CharField, _GT_CharField]): _pyi_private_set_type: str | int | Combinable _pyi_private_get_type: str # objects are converted to string before comparison @@ -310,7 +324,7 @@ class CharField(Field[_ST, _GT]): null: bool = ..., db_index: bool = ..., default: Any = ..., - db_default: type[NOT_PROVIDED] | Expression | _ST = ..., + db_default: type[NOT_PROVIDED] | Expression | _ST_CharField = ..., editable: bool = ..., auto_created: bool = ..., serialize: bool = ..., @@ -328,9 +342,9 @@ class CharField(Field[_ST, _GT]): db_collation: str | None = None, ) -> None: ... -class CommaSeparatedIntegerField(CharField[_ST, _GT]): ... +class CommaSeparatedIntegerField(CharField[_ST_CharField, _GT_CharField]): ... -class SlugField(CharField[_ST, _GT]): +class SlugField(CharField[_ST_CharField, _GT_CharField]): def __init__( self, verbose_name: _StrOrPromise | None = ..., @@ -340,7 +354,7 @@ class SlugField(CharField[_ST, _GT]): blank: bool = ..., null: bool = ..., default: Any = ..., - db_default: type[NOT_PROVIDED] | Expression | _ST = ..., + db_default: type[NOT_PROVIDED] | Expression | _ST_CharField = ..., editable: bool = ..., auto_created: bool = ..., serialize: bool = ..., @@ -360,10 +374,13 @@ class SlugField(CharField[_ST, _GT]): allow_unicode: bool = False, ) -> None: ... -class EmailField(CharField[_ST, _GT]): +_ST_EmailField = TypeVar("_ST_EmailField", default=str | Combinable) +_GT_EmailField = TypeVar("_GT_EmailField", default=str) + +class EmailField(CharField[_ST_EmailField, _GT_EmailField]): _pyi_private_set_type: str | Combinable -class URLField(CharField[_ST, _GT]): +class URLField(CharField[_ST_CharField, _GT_CharField]): def __init__( self, verbose_name: _StrOrPromise | None = None, @@ -377,7 +394,7 @@ class URLField(CharField[_ST, _GT]): db_index: bool = ..., rel: ForeignObjectRel | None = ..., default: Any = ..., - db_default: type[NOT_PROVIDED] | Expression | _ST = ..., + db_default: type[NOT_PROVIDED] | Expression | _ST_CharField = ..., editable: bool = ..., serialize: bool = ..., unique_for_date: str | None = ..., @@ -393,7 +410,10 @@ class URLField(CharField[_ST, _GT]): error_messages: _ErrorMessagesMapping | None = ..., ) -> None: ... -class TextField(Field[_ST, _GT]): +_ST_TextField = TypeVar("_ST_TextField", default=str | Combinable) +_GT_TextField = TypeVar("_GT_TextField", default=str) + +class TextField(Field[_ST_TextField, _GT_TextField]): _pyi_private_set_type: str | Combinable _pyi_private_get_type: str # objects are converted to string before comparison @@ -409,7 +429,7 @@ class TextField(Field[_ST, _GT]): null: bool = ..., db_index: bool = ..., default: Any = ..., - db_default: type[NOT_PROVIDED] | Expression | _ST = ..., + db_default: type[NOT_PROVIDED] | Expression | _ST_TextField = ..., editable: bool = ..., auto_created: bool = ..., serialize: bool = ..., @@ -427,21 +447,30 @@ class TextField(Field[_ST, _GT]): db_collation: str | None = None, ) -> None: ... -class BooleanField(Field[_ST, _GT]): +_ST_BooleanField = TypeVar("_ST_BooleanField", default=bool | Combinable) +_GT_BooleanField = TypeVar("_GT_BooleanField", default=bool) + +class BooleanField(Field[_ST_BooleanField, _GT_BooleanField]): _pyi_private_set_type: bool | Combinable _pyi_private_get_type: bool _pyi_lookup_exact_type: bool -class NullBooleanField(BooleanField[_ST, _GT]): +class NullBooleanField(BooleanField[_ST_BooleanField, _GT_BooleanField]): _pyi_private_set_type: bool | Combinable | None # type: ignore[assignment] _pyi_private_get_type: bool | None # type: ignore[assignment] _pyi_lookup_exact_type: bool | None # type: ignore[assignment] -class IPAddressField(Field[_ST, _GT]): +_ST_IPAddressField = TypeVar("_ST_IPAddressField", default=str | Combinable) +_GT_IPAddressField = TypeVar("_GT_IPAddressField", default=str) + +class IPAddressField(Field[_ST_IPAddressField, _GT_IPAddressField]): _pyi_private_set_type: str | Combinable _pyi_private_get_type: str -class GenericIPAddressField(Field[_ST, _GT]): +_ST_GenericIPAddressField = TypeVar("_ST_GenericIPAddressField", default=str | int | Callable[..., Any] | Combinable) +_GT_GenericIPAddressField = TypeVar("_GT_GenericIPAddressField", default=str) + +class GenericIPAddressField(Field[_ST_GenericIPAddressField, _GT_GenericIPAddressField]): _pyi_private_set_type: str | int | Callable[..., Any] | Combinable _pyi_private_get_type: str @@ -460,7 +489,7 @@ class GenericIPAddressField(Field[_ST, _GT]): null: bool = ..., db_index: bool = ..., default: Any = ..., - db_default: type[NOT_PROVIDED] | Expression | _ST = ..., + db_default: type[NOT_PROVIDED] | Expression | _ST_GenericIPAddressField = ..., editable: bool = ..., auto_created: bool = ..., serialize: bool = ..., @@ -475,7 +504,10 @@ class GenericIPAddressField(Field[_ST, _GT]): class DateTimeCheckMixin: ... -class DateField(DateTimeCheckMixin, Field[_ST, _GT]): +_ST_DateField = TypeVar("_ST_DateField", default=str | date | Combinable) +_GT_DateField = TypeVar("_GT_DateField", default=date) + +class DateField(DateTimeCheckMixin, Field[_ST_DateField, _GT_DateField]): _pyi_private_set_type: str | date | Combinable _pyi_private_get_type: date _pyi_lookup_exact_type: str | date @@ -495,7 +527,7 @@ class DateField(DateTimeCheckMixin, Field[_ST, _GT]): null: bool = ..., db_index: bool = ..., default: Any = ..., - db_default: type[NOT_PROVIDED] | Expression | _ST = ..., + db_default: type[NOT_PROVIDED] | Expression | _ST_DateField = ..., editable: bool = ..., auto_created: bool = ..., serialize: bool = ..., @@ -508,7 +540,10 @@ class DateField(DateTimeCheckMixin, Field[_ST, _GT]): error_messages: _ErrorMessagesMapping | None = ..., ) -> None: ... -class TimeField(DateTimeCheckMixin, Field[_ST, _GT]): +_ST_TimeField = TypeVar("_ST_TimeField", default=str | time | real_datetime | Combinable) +_GT_TimeField = TypeVar("_GT_TimeField", default=time) + +class TimeField(DateTimeCheckMixin, Field[_ST_TimeField, _GT_TimeField]): _pyi_private_set_type: str | time | real_datetime | Combinable _pyi_private_get_type: time auto_now: bool @@ -526,7 +561,7 @@ class TimeField(DateTimeCheckMixin, Field[_ST, _GT]): null: bool = ..., db_index: bool = ..., default: Any = ..., - db_default: type[NOT_PROVIDED] | Expression | _ST = ..., + db_default: type[NOT_PROVIDED] | Expression | _ST_TimeField = ..., editable: bool = ..., auto_created: bool = ..., serialize: bool = ..., @@ -539,12 +574,18 @@ class TimeField(DateTimeCheckMixin, Field[_ST, _GT]): error_messages: _ErrorMessagesMapping | None = ..., ) -> None: ... -class DateTimeField(DateField[_ST, _GT]): +_ST_DateTimeField = TypeVar("_ST_DateTimeField", default=str | real_datetime | date | Combinable) +_GT_DateTimeField = TypeVar("_GT_DateTimeField", default=real_datetime) + +class DateTimeField(DateField[_ST_DateTimeField, _GT_DateTimeField]): _pyi_private_set_type: str | real_datetime | date | Combinable _pyi_private_get_type: real_datetime _pyi_lookup_exact_type: str | real_datetime -class UUIDField(Field[_ST, _GT]): +_ST_UUIDField = TypeVar("_ST_UUIDField", default=str | uuid.UUID) +_GT_UUIDField = TypeVar("_GT_UUIDField", default=uuid.UUID) + +class UUIDField(Field[_ST_UUIDField, _GT_UUIDField]): _pyi_private_set_type: str | uuid.UUID _pyi_private_get_type: uuid.UUID _pyi_lookup_exact_type: uuid.UUID | str @@ -561,7 +602,7 @@ class UUIDField(Field[_ST, _GT]): db_index: bool = ..., rel: ForeignObjectRel | None = ..., default: Any = ..., - db_default: type[NOT_PROVIDED] | Expression | _ST = ..., + db_default: type[NOT_PROVIDED] | Expression | _ST_UUIDField = ..., editable: bool = ..., serialize: bool = ..., unique_for_date: str | None = ..., @@ -577,7 +618,10 @@ class UUIDField(Field[_ST, _GT]): error_messages: _ErrorMessagesMapping | None = ..., ) -> None: ... -class FilePathField(Field[_ST, _GT]): +_ST_FileField = TypeVar("_ST_FileField", default=str | bytes | memoryview) +_GT_FileField = TypeVar("_GT_FileField", default=str) + +class FilePathField(Field[_ST_FileField, _GT_FileField]): path: Any match: str | None recursive: bool @@ -600,7 +644,7 @@ class FilePathField(Field[_ST, _GT]): null: bool = ..., db_index: bool = ..., default: Any = ..., - db_default: type[NOT_PROVIDED] | Expression | _ST = ..., + db_default: type[NOT_PROVIDED] | Expression | _ST_FileField = ..., editable: bool = ..., auto_created: bool = ..., serialize: bool = ..., @@ -613,10 +657,16 @@ class FilePathField(Field[_ST, _GT]): error_messages: _ErrorMessagesMapping | None = ..., ) -> None: ... -class BinaryField(Field[_ST, _GT]): +_ST_BinaryField = TypeVar("_ST_BinaryField", default=bytes | memoryview) +_GT_BinaryField = TypeVar("_GT_BinaryField", default=bytes | memoryview) + +class BinaryField(Field[_ST_BinaryField, _GT_BinaryField]): _pyi_private_get_type: bytes | memoryview -class DurationField(Field[_ST, _GT]): +_ST_DurationField = TypeVar("_ST_DurationField", default=timedelta) +_GT_DurationField = TypeVar("_GT_DurationField", default=timedelta) + +class DurationField(Field[_ST_DurationField, _GT_DurationField]): _pyi_private_get_type: timedelta class AutoFieldMixin: @@ -625,10 +675,11 @@ class AutoFieldMixin: class AutoFieldMeta(type): ... -class AutoField(AutoFieldMixin, IntegerField[_ST, _GT], metaclass=AutoFieldMeta): +_ST_AutoField = TypeVar("_ST_AutoField", default=Combinable | int | str | None) +_GT_AutoField = TypeVar("_GT_AutoField", default=int) + +class AutoField(AutoFieldMixin, IntegerField[_ST_AutoField, _GT_AutoField], metaclass=AutoFieldMeta): _pyi_private_set_type: Combinable | int | str - _pyi_private_get_type: int - _pyi_lookup_exact_type: str | int -class BigAutoField(AutoFieldMixin, BigIntegerField[_ST, _GT]): ... -class SmallAutoField(AutoFieldMixin, SmallIntegerField[_ST, _GT]): ... +class BigAutoField(AutoFieldMixin, BigIntegerField[_ST_AutoField, _GT_AutoField]): ... +class SmallAutoField(AutoFieldMixin, SmallIntegerField[_ST_AutoField, _GT_AutoField]): ... diff --git a/mypy_django_plugin/lib/fullnames.py b/mypy_django_plugin/lib/fullnames.py index 5338e2275..f47720772 100644 --- a/mypy_django_plugin/lib/fullnames.py +++ b/mypy_django_plugin/lib/fullnames.py @@ -62,3 +62,6 @@ "django.contrib.sessions.base_session.AbstractBaseSession", ) ) + +DATETIME_FIELD_FULLNAME = "django.db.models.fields.DateTimeField" +DATE_FIELD_FULLNAME = "django.db.models.fields.DateField" diff --git a/mypy_django_plugin/transformers/models.py b/mypy_django_plugin/transformers/models.py index f5b0d1da0..d6b816c2e 100644 --- a/mypy_django_plugin/transformers/models.py +++ b/mypy_django_plugin/transformers/models.py @@ -8,6 +8,7 @@ from django.db.models.fields.reverse_related import ForeignObjectRel, ManyToManyRel, OneToOneRel from mypy.checker import TypeChecker from mypy.nodes import ( + ARG_NAMED_OPT, ARG_STAR2, MDEF, Argument, @@ -629,22 +630,55 @@ def run_with_model_cls(self, model_cls: type[Model]) -> None: if field.choices: info = self.lookup_typeinfo_or_incomplete_defn_error("builtins.str") return_type = Instance(info, []) - common.add_method(self.ctx, name=f"get_{field.attname}_display", args=[], return_type=return_type) + field_t = self.lookup_typeinfo(f"django.db.models.fields.{field.__class__.__name__}") + args = [] + if field_t is not None: + field_type = fill_typevars(field_t) + args = [ + Argument( + Var("field", field_type), + field_type, + initializer=None, + kind=ARG_NAMED_OPT, + ), + ] + common.add_method(self.ctx, name=f"get_{field.attname}_display", args=args, return_type=return_type) # get_next_by, get_previous_by for Date, DateTime + # def get_next_by_(self, *, field=, is_next=True, **kwargs) + # def get_previous_by_(self, *, field=, is_next=False, **kwargs) for field in self.django_context.get_model_fields(model_cls): if isinstance(field, DateField | DateTimeField) and not field.null: return_type = Instance(self.model_classdef.info, []) + date_or_datetime_field = self.lookup_typeinfo_or_incomplete_defn_error( + fullname=fullnames.DATETIME_FIELD_FULLNAME + if isinstance(field, DateTimeField) + else fullnames.DATE_FIELD_FULLNAME + ) + datetime_field_type = fill_typevars(date_or_datetime_field) + bool_type = self.ctx.api.named_type("builtins.bool") common.add_method( self.ctx, name=f"get_next_by_{field.attname}", args=[ + Argument( + Var("field", datetime_field_type), + datetime_field_type, + initializer=None, + kind=ARG_NAMED_OPT, + ), + Argument( + Var("is_next", bool_type), + bool_type, + initializer=None, + kind=ARG_NAMED_OPT, + ), Argument( Var("kwargs", AnyType(TypeOfAny.implementation_artifact)), AnyType(TypeOfAny.implementation_artifact), initializer=None, kind=ARG_STAR2, - ) + ), ], return_type=return_type, ) @@ -652,12 +686,24 @@ def run_with_model_cls(self, model_cls: type[Model]) -> None: self.ctx, name=f"get_previous_by_{field.attname}", args=[ + Argument( + Var("field", datetime_field_type), + datetime_field_type, + initializer=None, + kind=ARG_NAMED_OPT, + ), + Argument( + Var("is_next", bool_type), + bool_type, + initializer=None, + kind=ARG_NAMED_OPT, + ), Argument( Var("kwargs", AnyType(TypeOfAny.implementation_artifact)), AnyType(TypeOfAny.implementation_artifact), initializer=None, kind=ARG_STAR2, - ) + ), ], return_type=return_type, ) diff --git a/scripts/stubtest/allowlist.txt b/scripts/stubtest/allowlist.txt index 4be63fbf2..26e0d6ed2 100644 --- a/scripts/stubtest/allowlist.txt +++ b/scripts/stubtest/allowlist.txt @@ -27,13 +27,6 @@ django.core.files.storage.default_storage django.contrib.admin.models.LogEntry_RelatedManager django.contrib.auth.models.Permission_RelatedManager -# '_ManyRelatedManager' entries are plugin generated and these subclasses only exist -# _locally/dynamically_ runtime -- Created via -# 'django.db.models.fields.related_descriptors.create_forward_many_to_many_manager' -django.contrib.auth.models.Group_ManyRelatedManager -django.contrib.auth.models.Permission_ManyRelatedManager -django.contrib.auth.models.User_ManyRelatedManager - # BaseArchive abstract methods that take no argument, but typed with arguments to match the Archive and TarArchive Implementations django.utils.archive.BaseArchive.list django.utils.archive.BaseArchive.extract @@ -522,3 +515,110 @@ django.db.models.fields.generated.GeneratedField.__init__ # https://github.com/python/cpython/blob/5abff6960b4aecb0d5c81c7482cf3faa74e1983d/Lib/copyreg.py#L112-L161 # Some Django classes get copied at import time, which leads stubtest to detect it. .*\.__slotnames__ + +# Built-in Model fields, during Runtime Model. is a DeferredAttribute object +# an instance of the Model class, would provide field with the expected type, see PR #2590 for more details +django.contrib.admin.models.LogEntry.action_flag +django.contrib.admin.models.LogEntry.action_time +django.contrib.admin.models.LogEntry.change_message +django.contrib.admin.models.LogEntry.content_type +django.contrib.admin.models.LogEntry.content_type_id +django.contrib.admin.models.LogEntry.id +django.contrib.admin.models.LogEntry.object_id +django.contrib.admin.models.LogEntry.object_repr +django.contrib.admin.models.LogEntry.user +django.contrib.admin.models.LogEntry.user_id +django.contrib.auth.models.AbstractBaseUser.last_login +django.contrib.auth.models.AbstractBaseUser.password +django.contrib.auth.models.AbstractUser.date_joined +django.contrib.auth.models.AbstractUser.email +django.contrib.auth.models.AbstractUser.first_name +django.contrib.auth.models.AbstractUser.groups +django.contrib.auth.models.AbstractUser.is_active +django.contrib.auth.models.AbstractUser.is_staff +django.contrib.auth.models.AbstractUser.is_superuser +django.contrib.auth.models.AbstractUser.last_login +django.contrib.auth.models.AbstractUser.last_name +django.contrib.auth.models.AbstractUser.objects +django.contrib.auth.models.AbstractUser.password +django.contrib.auth.models.AbstractUser.user_permissions +django.contrib.auth.models.AbstractUser.username +django.contrib.auth.models.Group.id +django.contrib.auth.models.Group.name +django.contrib.auth.models.Group.permissions +django.contrib.auth.models.Permission.codename +django.contrib.auth.models.Permission.content_type +django.contrib.auth.models.Permission.content_type_id +django.contrib.auth.models.Permission.id +django.contrib.auth.models.Permission.name +django.contrib.auth.models.PermissionsMixin.groups +django.contrib.auth.models.PermissionsMixin.is_superuser +django.contrib.auth.models.PermissionsMixin.user_permissions +django.contrib.auth.models.User.date_joined +django.contrib.auth.models.User.email +django.contrib.auth.models.User.first_name +django.contrib.auth.models.User.groups +django.contrib.auth.models.User.id +django.contrib.auth.models.User.is_active +django.contrib.auth.models.User.is_staff +django.contrib.auth.models.User.is_superuser +django.contrib.auth.models.User.last_login +django.contrib.auth.models.User.last_name +django.contrib.auth.models.User.password +django.contrib.auth.models.User.user_permissions +django.contrib.auth.models.User.username +django.contrib.contenttypes.models.ContentType.app_label +django.contrib.contenttypes.models.ContentType.id +django.contrib.contenttypes.models.ContentType.model +django.contrib.flatpages.models.FlatPage.content +django.contrib.flatpages.models.FlatPage.enable_comments +django.contrib.flatpages.models.FlatPage.id +django.contrib.flatpages.models.FlatPage.registration_required +django.contrib.flatpages.models.FlatPage.sites +django.contrib.flatpages.models.FlatPage.template_name +django.contrib.flatpages.models.FlatPage.title +django.contrib.flatpages.models.FlatPage.url +django.contrib.gis.db.backends.oracle.models.OracleGeometryColumns.table_name +django.contrib.gis.db.backends.oracle.models.OracleGeometryColumns.column_name +django.contrib.gis.db.backends.oracle.models.OracleGeometryColumns.srid +django.contrib.gis.db.backends.oracle.models.OracleSpatialRefSys.cs_name +django.contrib.gis.db.backends.oracle.models.OracleSpatialRefSys.srid +django.contrib.gis.db.backends.oracle.models.OracleSpatialRefSys.auth_srid +django.contrib.gis.db.backends.oracle.models.OracleSpatialRefSys.auth_name +django.contrib.gis.db.backends.oracle.models.OracleSpatialRefSys.wktext +django.contrib.gis.db.backends.oracle.models.OracleSpatialRefSys.cs_bounds +django.contrib.gis.db.backends.postgis.models.PostGISGeometryColumns.f_table_catalog +django.contrib.gis.db.backends.postgis.models.PostGISGeometryColumns.f_table_schema +django.contrib.gis.db.backends.postgis.models.PostGISGeometryColumns.f_table_name +django.contrib.gis.db.backends.postgis.models.PostGISGeometryColumns.f_geometry_column +django.contrib.gis.db.backends.postgis.models.PostGISGeometryColumns.coord_dimension +django.contrib.gis.db.backends.postgis.models.PostGISGeometryColumns.srid +django.contrib.gis.db.backends.postgis.models.PostGISGeometryColumns.type +django.contrib.gis.db.backends.postgis.models.PostGISSpatialRefSys.srid +django.contrib.gis.db.backends.postgis.models.PostGISSpatialRefSys.auth_srid +django.contrib.gis.db.backends.postgis.models.PostGISSpatialRefSys.auth_name +django.contrib.gis.db.backends.postgis.models.PostGISSpatialRefSys.srtext +django.contrib.gis.db.backends.postgis.models.PostGISSpatialRefSys.proj4text +django.contrib.gis.db.backends.spatialite.models.SpatialiteGeometryColumns.f_table_name +django.contrib.gis.db.backends.spatialite.models.SpatialiteGeometryColumns.f_geometry_column +django.contrib.gis.db.backends.spatialite.models.SpatialiteGeometryColumns.coord_dimension +django.contrib.gis.db.backends.spatialite.models.SpatialiteGeometryColumns.srid +django.contrib.gis.db.backends.spatialite.models.SpatialiteGeometryColumns.spatial_index_enabled +django.contrib.gis.db.backends.spatialite.models.SpatialiteGeometryColumns.type +django.contrib.gis.db.backends.spatialite.models.SpatialiteSpatialRefSys.srid +django.contrib.gis.db.backends.spatialite.models.SpatialiteSpatialRefSys.auth_srid +django.contrib.gis.db.backends.spatialite.models.SpatialiteSpatialRefSys.auth_name +django.contrib.gis.db.backends.spatialite.models.SpatialiteSpatialRefSys.ref_sys_name +django.contrib.gis.db.backends.spatialite.models.SpatialiteSpatialRefSys.proj4text +django.contrib.gis.db.backends.spatialite.models.SpatialiteSpatialRefSys.srtext +django.contrib.redirects.models.Redirect.id +django.contrib.redirects.models.Redirect.new_path +django.contrib.redirects.models.Redirect.old_path +django.contrib.redirects.models.Redirect.site +django.contrib.redirects.models.Redirect.site_id +django.contrib.sessions.models.Session.expire_date +django.contrib.sessions.models.Session.session_data +django.contrib.sessions.models.Session.session_key +django.contrib.sites.models.Site.domain +django.contrib.sites.models.Site.id +django.contrib.sites.models.Site.name diff --git a/scripts/stubtest/allowlist_todo.txt b/scripts/stubtest/allowlist_todo.txt index ed52316c9..304ec6585 100644 --- a/scripts/stubtest/allowlist_todo.txt +++ b/scripts/stubtest/allowlist_todo.txt @@ -13,19 +13,6 @@ django.contrib.admin.helpers.AdminForm.is_bound django.contrib.admin.helpers.AdminReadonlyField.get_admin_url django.contrib.admin.helpers.InlineAdminFormSet.is_bound django.contrib.admin.helpers.InlineAdminFormSet.total_form_count -django.contrib.admin.models.LogEntry.action_flag -django.contrib.admin.models.LogEntry.action_time -django.contrib.admin.models.LogEntry.change_message -django.contrib.admin.models.LogEntry.content_type -django.contrib.admin.models.LogEntry.content_type_id -django.contrib.admin.models.LogEntry.get_action_flag_display -django.contrib.admin.models.LogEntry.get_next_by_action_time -django.contrib.admin.models.LogEntry.get_previous_by_action_time -django.contrib.admin.models.LogEntry.id -django.contrib.admin.models.LogEntry.object_id -django.contrib.admin.models.LogEntry.object_repr -django.contrib.admin.models.LogEntry.user -django.contrib.admin.models.LogEntry.user_id django.contrib.admin.options.BaseModelAdmin django.contrib.admin.options.BaseModelAdmin.form django.contrib.admin.options.BaseModelAdmin.media @@ -73,51 +60,6 @@ django.contrib.auth.hashers.reset_hashers django.contrib.auth.management.commands.createsuperuser.Command.get_input_data django.contrib.auth.management.commands.createsuperuser.Command.username_is_unique django.contrib.auth.management.commands.createsuperuser.PASSWORD_FIELD -django.contrib.auth.models.AbstractBaseUser.last_login -django.contrib.auth.models.AbstractBaseUser.password -django.contrib.auth.models.AbstractUser.date_joined -django.contrib.auth.models.AbstractUser.email -django.contrib.auth.models.AbstractUser.email_user -django.contrib.auth.models.AbstractUser.first_name -django.contrib.auth.models.AbstractUser.get_next_by_date_joined -django.contrib.auth.models.AbstractUser.get_previous_by_date_joined -django.contrib.auth.models.AbstractUser.groups -django.contrib.auth.models.AbstractUser.is_active -django.contrib.auth.models.AbstractUser.is_staff -django.contrib.auth.models.AbstractUser.is_superuser -django.contrib.auth.models.AbstractUser.last_login -django.contrib.auth.models.AbstractUser.last_name -django.contrib.auth.models.AbstractUser.objects -django.contrib.auth.models.AbstractUser.password -django.contrib.auth.models.AbstractUser.user_permissions -django.contrib.auth.models.AbstractUser.username -django.contrib.auth.models.AnonymousUser.__int__ -django.contrib.auth.models.Group.id -django.contrib.auth.models.Group.name -django.contrib.auth.models.Group.permissions -django.contrib.auth.models.Permission.codename -django.contrib.auth.models.Permission.content_type -django.contrib.auth.models.Permission.content_type_id -django.contrib.auth.models.Permission.id -django.contrib.auth.models.Permission.name -django.contrib.auth.models.PermissionsMixin.groups -django.contrib.auth.models.PermissionsMixin.is_superuser -django.contrib.auth.models.PermissionsMixin.user_permissions -django.contrib.auth.models.User.date_joined -django.contrib.auth.models.User.email -django.contrib.auth.models.User.first_name -django.contrib.auth.models.User.get_next_by_date_joined -django.contrib.auth.models.User.get_previous_by_date_joined -django.contrib.auth.models.User.groups -django.contrib.auth.models.User.id -django.contrib.auth.models.User.is_active -django.contrib.auth.models.User.is_staff -django.contrib.auth.models.User.is_superuser -django.contrib.auth.models.User.last_login -django.contrib.auth.models.User.last_name -django.contrib.auth.models.User.password -django.contrib.auth.models.User.user_permissions -django.contrib.auth.models.User.username django.contrib.auth.password_validation.CommonPasswordValidator.DEFAULT_PASSWORD_LIST_PATH django.contrib.auth.password_validation.CommonPasswordValidator.__init__ django.contrib.auth.password_validation.exceeds_maximum_length_ratio @@ -139,20 +81,7 @@ django.contrib.contenttypes.fields.GenericRelation.contribute_to_class django.contrib.contenttypes.fields.GenericRelation.get_extra_restriction django.contrib.contenttypes.forms.generic_inlineformset_factory django.contrib.contenttypes.management.inject_rename_contenttypes_operations -django.contrib.contenttypes.models.ContentType.app_label -django.contrib.contenttypes.models.ContentType.app_labeled_name -django.contrib.contenttypes.models.ContentType.id -django.contrib.contenttypes.models.ContentType.model -django.contrib.contenttypes.models.ContentTypeManager.__init__ django.contrib.flatpages.admin.FlatPageAdmin -django.contrib.flatpages.models.FlatPage.content -django.contrib.flatpages.models.FlatPage.enable_comments -django.contrib.flatpages.models.FlatPage.id -django.contrib.flatpages.models.FlatPage.registration_required -django.contrib.flatpages.models.FlatPage.sites -django.contrib.flatpages.models.FlatPage.template_name -django.contrib.flatpages.models.FlatPage.title -django.contrib.flatpages.models.FlatPage.url django.contrib.gis.admin.GISModelAdmin django.contrib.gis.admin.GISModelAdmin.gis_widget django.contrib.gis.admin.ModelAdmin @@ -428,29 +357,13 @@ django.contrib.postgres.fields.ranges.RangeField.formfield django.contrib.postgres.forms.BaseRangeField.hidden_widget django.contrib.postgres.forms.ranges.BaseRangeField.hidden_widget django.contrib.redirects.admin.RedirectAdmin -django.contrib.redirects.models.Redirect.id -django.contrib.redirects.models.Redirect.new_path -django.contrib.redirects.models.Redirect.old_path -django.contrib.redirects.models.Redirect.site -django.contrib.redirects.models.Redirect.site_id django.contrib.sessions.backends.signed_cookies.SessionStore.exists django.contrib.sessions.base_session.AbstractBaseSession.expire_date -django.contrib.sessions.base_session.AbstractBaseSession.get_next_by_expire_date -django.contrib.sessions.base_session.AbstractBaseSession.get_previous_by_expire_date django.contrib.sessions.base_session.AbstractBaseSession.objects django.contrib.sessions.base_session.AbstractBaseSession.session_data django.contrib.sessions.base_session.AbstractBaseSession.session_key -django.contrib.sessions.models.Session.expire_date -django.contrib.sessions.models.Session.get_next_by_expire_date -django.contrib.sessions.models.Session.get_previous_by_expire_date -django.contrib.sessions.models.Session.session_data -django.contrib.sessions.models.Session.session_key django.contrib.sitemaps.views.SitemapIndexItem django.contrib.sites.admin.SiteAdmin -django.contrib.sites.models.Site.domain -django.contrib.sites.models.Site.flatpage_set -django.contrib.sites.models.Site.id -django.contrib.sites.models.Site.name django.contrib.staticfiles.finders.BaseStorageFinder.storage django.contrib.staticfiles.finders.DefaultStorageFinder.storage django.contrib.staticfiles.storage.staticfiles_storage diff --git a/scripts/stubtest/allowlist_todo_django52.txt b/scripts/stubtest/allowlist_todo_django52.txt index a493baa30..c687d365f 100644 --- a/scripts/stubtest/allowlist_todo_django52.txt +++ b/scripts/stubtest/allowlist_todo_django52.txt @@ -1,7 +1,6 @@ # Autogenerated by `stubtest` # Created for 5.2 update: -django.contrib.admin.models.LogEntryManager.log_actions django.contrib.admin.tests.AdminSeleniumTestCase.has_css_class django.contrib.admin.tests.AdminSeleniumTestCase.is_disabled django.contrib.admin.utils.display_for_field diff --git a/tests/assert_type/contrib/admin/test_admin_models.py b/tests/assert_type/contrib/admin/test_admin_models.py new file mode 100644 index 000000000..5bb258fd1 --- /dev/null +++ b/tests/assert_type/contrib/admin/test_admin_models.py @@ -0,0 +1,20 @@ +from datetime import datetime + +from django.contrib.admin.models import LogEntry, LogEntryManager +from django.contrib.auth.models import AbstractUser +from django.contrib.contenttypes.models import ContentType +from typing_extensions import assert_type + +log_entry = LogEntry() +assert_type(log_entry.id, int) +assert_type(log_entry.pk, int) +assert_type(log_entry.action_time, datetime) +assert_type(log_entry.user, AbstractUser) +assert_type(log_entry.content_type, ContentType | None) +assert_type(log_entry.content_type_id, int | None) +assert_type(log_entry.object_id, str | None) +assert_type(log_entry.object_repr, str) +assert_type(log_entry.action_flag, int) +assert_type(log_entry.change_message, str) +assert_type(log_entry.user_id, int) +assert_type(LogEntry.objects, LogEntryManager) diff --git a/tests/assert_type/contrib/admin/test_utils.py b/tests/assert_type/contrib/admin/test_utils.py index bc57e15d5..72cc95eb9 100644 --- a/tests/assert_type/contrib/admin/test_utils.py +++ b/tests/assert_type/contrib/admin/test_utils.py @@ -10,13 +10,13 @@ @admin.display(description="Name") def upper_case_name(obj: Person) -> str: - return f"{obj.first_name} {obj.last_name}".upper() # pyright: ignore[reportUnknownMemberType] + return f"{obj.first_name} {obj.last_name}".upper() class Person(models.Model): - first_name = models.CharField(max_length=None) # pyright: ignore[reportUnknownVariableType] - last_name = models.CharField(max_length=None) # pyright: ignore[reportUnknownVariableType] - birthday = models.DateField() # pyright: ignore[reportUnknownVariableType] + first_name = models.CharField(max_length=None) + last_name = models.CharField(max_length=None) + birthday = models.DateField() class PersonListAdmin(admin.ModelAdmin[Person]): diff --git a/tests/assert_type/contrib/auth/test_auth_models.py b/tests/assert_type/contrib/auth/test_auth_models.py new file mode 100644 index 000000000..fecfc90a1 --- /dev/null +++ b/tests/assert_type/contrib/auth/test_auth_models.py @@ -0,0 +1,64 @@ +from datetime import datetime + +from django.contrib.auth.models import ( + Group, + Permission, + User, + UserManager, + _Group_permissions, + _User_groups, + _User_permissions, +) +from django.contrib.contenttypes.models import ContentType +from django.db.models import Manager +from typing_extensions import assert_type + +user = User() +assert_type(user.id, int) +assert_type(user.pk, int) +assert_type(user.password, str) +assert_type(user.last_login, datetime | None) +assert_type(user.is_active, bool) +assert_type(user.username, str) +assert_type(user.first_name, str) +assert_type(user.last_name, str) +assert_type(user.email, str) +assert_type(user.is_staff, bool) +assert_type(user.is_active, bool) +assert_type(user.date_joined, datetime) +assert_type(User.objects, UserManager[User]) +assert_type(user.groups.get(), Group) +assert_type(user.groups.through, type[_User_groups]) +assert_type(user.user_permissions.get(), Permission) +assert_type(user.user_permissions.through, type[_User_permissions]) + +group = Group() +assert_type(group.id, int) +assert_type(group.pk, int) +assert_type(group.name, str) +assert_type(group.permissions.get(), Permission) +assert_type(group.permissions.through, type[_Group_permissions]) +assert_type(Group.permissions.through, type[_Group_permissions]) +assert_type(Group.permissions.through.objects, Manager[_Group_permissions]) + +group_permissions = Group.permissions.through.objects.get() +assert_type(group_permissions.id, int) +assert_type(group_permissions.pk, int) +assert_type(group_permissions.group, Group) +assert_type(group_permissions.group_id, int) +assert_type(group_permissions.permission, Permission) +assert_type(group_permissions.permission_id, int) + +permission = Permission() +assert_type(permission.id, int) +assert_type(permission.pk, int) +assert_type(permission.name, str) +assert_type(permission.content_type, ContentType) +assert_type(permission.content_type_id, int) +assert_type(permission.codename, str) +assert_type(permission.user_set.get(), User) +assert_type(permission.user_set.through, type[_User_permissions]) +assert_type(permission.user_set.through.objects.get(), _User_permissions) +assert_type(permission.group_set.get(), Group) +assert_type(permission.group_set.through, type[_Group_permissions]) +assert_type(permission.group_set.through.objects.get(), _Group_permissions) diff --git a/tests/assert_type/contrib/contenttypes/test_contenttypes_models.py b/tests/assert_type/contrib/contenttypes/test_contenttypes_models.py new file mode 100644 index 000000000..b7459a23e --- /dev/null +++ b/tests/assert_type/contrib/contenttypes/test_contenttypes_models.py @@ -0,0 +1,12 @@ +from django.contrib.admin.models import LogEntry +from django.contrib.auth.models import Permission +from django.contrib.contenttypes.models import ContentType +from typing_extensions import assert_type + +content_type = ContentType() +assert_type(content_type.id, int) +assert_type(content_type.pk, int) +assert_type(content_type.app_label, str) +assert_type(content_type.model, str) +assert_type(content_type.logentry_set.get(), LogEntry) +assert_type(content_type.permission_set.get(), Permission) diff --git a/tests/assert_type/contrib/flatpages/test_flatpages_models.py b/tests/assert_type/contrib/flatpages/test_flatpages_models.py new file mode 100644 index 000000000..e5e03bb3d --- /dev/null +++ b/tests/assert_type/contrib/flatpages/test_flatpages_models.py @@ -0,0 +1,24 @@ +from django.contrib.flatpages.models import FlatPage, _FlatPage_sites +from django.contrib.sites.models import Site +from django.db.models import Manager +from typing_extensions import assert_type + +flat_page = FlatPage() +assert_type(flat_page.id, int) +assert_type(flat_page.pk, int) +assert_type(flat_page.url, str) +assert_type(flat_page.title, str) +assert_type(flat_page.content, str) +assert_type(flat_page.enable_comments, bool) +assert_type(flat_page.template_name, str) +assert_type(flat_page.registration_required, bool) +assert_type(flat_page.sites.get(), Site) +assert_type(FlatPage.sites.through.objects, Manager[_FlatPage_sites]) + +flat_page_sites = FlatPage.sites.through.objects.get() +assert_type(flat_page_sites.id, int) +assert_type(flat_page_sites.pk, int) +assert_type(flat_page_sites.site, Site) +assert_type(flat_page_sites.site_id, int) +assert_type(flat_page_sites.flatpage, FlatPage) +assert_type(flat_page_sites.flatpage_id, int) diff --git a/tests/assert_type/contrib/gis/db/backends/oracle/test_oracle_models.py b/tests/assert_type/contrib/gis/db/backends/oracle/test_oracle_models.py new file mode 100644 index 000000000..1b0b8f637 --- /dev/null +++ b/tests/assert_type/contrib/gis/db/backends/oracle/test_oracle_models.py @@ -0,0 +1,19 @@ +from django.contrib.gis.db.backends.oracle.models import OracleGeometryColumns, OracleSpatialRefSys +from django.contrib.gis.geos.polygon import Polygon +from django.db.models.manager import Manager +from typing_extensions import assert_type + +columns = OracleGeometryColumns() +assert_type(columns.table_name, str) +assert_type(columns.column_name, str) +assert_type(columns.srid, int) +assert_type(columns.objects, Manager[OracleGeometryColumns]) + +spatial_ref_sys = OracleSpatialRefSys() +assert_type(spatial_ref_sys.cs_name, str) +assert_type(spatial_ref_sys.srid, int) +assert_type(spatial_ref_sys.auth_srid, int) +assert_type(spatial_ref_sys.auth_name, str) +assert_type(spatial_ref_sys.wktext, str) +assert_type(spatial_ref_sys.cs_bounds, Polygon) +assert_type(spatial_ref_sys.objects, Manager[OracleSpatialRefSys]) diff --git a/tests/assert_type/contrib/gis/db/backends/postgis/test_postgis_models.py b/tests/assert_type/contrib/gis/db/backends/postgis/test_postgis_models.py new file mode 100644 index 000000000..dfbba1f1f --- /dev/null +++ b/tests/assert_type/contrib/gis/db/backends/postgis/test_postgis_models.py @@ -0,0 +1,21 @@ +from django.contrib.gis.db.backends.postgis.models import PostGISGeometryColumns, PostGISSpatialRefSys +from django.db.models.manager import Manager +from typing_extensions import assert_type + +columns = PostGISGeometryColumns() +assert_type(columns.f_table_catalog, str) +assert_type(columns.f_table_schema, str) +assert_type(columns.f_table_name, str) +assert_type(columns.f_geometry_column, str) +assert_type(columns.coord_dimension, int) +assert_type(columns.srid, int) +assert_type(columns.type, str) +assert_type(columns.objects, Manager[PostGISGeometryColumns]) + +spatial_ref_sys = PostGISSpatialRefSys() +assert_type(spatial_ref_sys.srid, int) +assert_type(spatial_ref_sys.auth_name, str) +assert_type(spatial_ref_sys.auth_srid, int) +assert_type(spatial_ref_sys.srtext, str) +assert_type(spatial_ref_sys.proj4text, str) +assert_type(spatial_ref_sys.objects, Manager[PostGISSpatialRefSys]) diff --git a/tests/assert_type/contrib/gis/db/backends/spatialite/test_spatialite_models.py b/tests/assert_type/contrib/gis/db/backends/spatialite/test_spatialite_models.py new file mode 100644 index 000000000..d15d82669 --- /dev/null +++ b/tests/assert_type/contrib/gis/db/backends/spatialite/test_spatialite_models.py @@ -0,0 +1,21 @@ +from django.contrib.gis.db.backends.spatialite.models import SpatialiteGeometryColumns, SpatialiteSpatialRefSys +from django.db.models.manager import Manager +from typing_extensions import assert_type + +columns = SpatialiteGeometryColumns() +assert_type(columns.f_table_name, str) +assert_type(columns.f_geometry_column, str) +assert_type(columns.coord_dimension, int) +assert_type(columns.srid, int) +assert_type(columns.spatial_index_enabled, int) +assert_type(columns.type, int) +assert_type(columns.objects, Manager[SpatialiteGeometryColumns]) + +spatial_ref_sys = SpatialiteSpatialRefSys() +assert_type(spatial_ref_sys.srid, int) +assert_type(spatial_ref_sys.auth_name, str) +assert_type(spatial_ref_sys.auth_srid, int) +assert_type(spatial_ref_sys.ref_sys_name, str) +assert_type(spatial_ref_sys.proj4text, str) +assert_type(spatial_ref_sys.srtext, str) +assert_type(spatial_ref_sys.objects, Manager[SpatialiteSpatialRefSys]) diff --git a/tests/assert_type/contrib/redirects/test_redirects_models.py b/tests/assert_type/contrib/redirects/test_redirects_models.py new file mode 100644 index 000000000..e24de0020 --- /dev/null +++ b/tests/assert_type/contrib/redirects/test_redirects_models.py @@ -0,0 +1,13 @@ +from django.contrib.redirects.models import Redirect +from django.contrib.sites.models import Site +from django.db.models import Manager +from typing_extensions import assert_type + +redirect = Redirect() +assert_type(redirect.id, int) +assert_type(redirect.pk, int) +assert_type(redirect.site_id, int) +assert_type(redirect.old_path, str) +assert_type(redirect.new_path, str) +assert_type(redirect.site, Site) +assert_type(redirect.objects, Manager[Redirect]) diff --git a/tests/assert_type/contrib/sessions/test_sessions_models.py b/tests/assert_type/contrib/sessions/test_sessions_models.py new file mode 100644 index 000000000..d7e719076 --- /dev/null +++ b/tests/assert_type/contrib/sessions/test_sessions_models.py @@ -0,0 +1,11 @@ +from datetime import datetime + +from django.contrib.sessions.models import Session, SessionManager +from typing_extensions import assert_type + +session = Session() +assert_type(session.session_key, str) +assert_type(session.pk, str) +assert_type(session.session_data, str) +assert_type(session.expire_date, datetime) +assert_type(session.objects, SessionManager[Session]) diff --git a/tests/assert_type/contrib/sites/test_site_models.py b/tests/assert_type/contrib/sites/test_site_models.py new file mode 100644 index 000000000..b2db7d374 --- /dev/null +++ b/tests/assert_type/contrib/sites/test_site_models.py @@ -0,0 +1,13 @@ +from django.contrib.flatpages.models import FlatPage, _FlatPage_sites +from django.contrib.redirects.models import Redirect +from django.contrib.sites.models import Site +from typing_extensions import assert_type + +site = Site() +assert_type(site.id, int) +assert_type(site.pk, int) +assert_type(site.domain, str) +assert_type(site.name, str) +assert_type(site.flatpage_set.get(), FlatPage) +assert_type(site.redirect_set.get(), Redirect) +assert_type(site.flatpage_set.through, type[_FlatPage_sites]) diff --git a/tests/typecheck/fields/test_base.yml b/tests/typecheck/fields/test_base.yml index 40bab2593..9cd1b2fa0 100644 --- a/tests/typecheck/fields/test_base.yml +++ b/tests/typecheck/fields/test_base.yml @@ -81,15 +81,19 @@ MyModel(nulltext=None) MyModel().nulltext=None reveal_type(MyModel().nulltext) # N: Revealed type is "Union[builtins.str, None]" + monkeypatch: true installed_apps: - myapp files: - path: myapp/__init__.py - path: myapp/models.py content: | + from typing import Optional, Union from django.db import models + from django.db.models.expressions import Combinable + class MyModel(models.Model): - nulltext=models.CharField(max_length=1, blank=True, null=True) + nulltext = models.CharField[Optional[Union[str, int, Combinable]], Optional[str]](max_length=1, blank=True, null=True) - case: blank_and_not_null_charfield_does_not_allow_none main: | diff --git a/tests/typecheck/fields/test_custom_fields.yml b/tests/typecheck/fields/test_custom_fields.yml index 335de9a55..c50e59c8e 100644 --- a/tests/typecheck/fields/test_custom_fields.yml +++ b/tests/typecheck/fields/test_custom_fields.yml @@ -32,12 +32,17 @@ content: | from django.db import models from django.db.models import fields + from django.db.models.expressions import Combinable - from typing import Any, TypeVar, Generic, Union + from typing import Any, Generic, Union + from typing_extensions import TypeVar _ST = TypeVar("_ST", contravariant=True) _GT = TypeVar("_GT", covariant=True) + _ST_IntegerField = TypeVar("_ST_IntegerField", default=Union[float, int, str, Combinable]) + _GT_IntegerField = TypeVar("_GT_IntegerField", default=int) + T = TypeVar("T") class CustomFieldValue: ... @@ -50,7 +55,7 @@ class AdditionalTypeVarField(fields.Field[_ST, _GT], Generic[_ST, _GT, T]): ... - class CustomSmallIntegerField(fields.SmallIntegerField[_ST, _GT]): ... + class CustomSmallIntegerField(fields.SmallIntegerField[_ST_IntegerField, _GT_IntegerField]): ... class FieldImplicitAny(fields.Field): ... class FieldExplicitAny(fields.Field[Any, Any]): ... diff --git a/tests/typecheck/fields/test_nullable.yml b/tests/typecheck/fields/test_nullable.yml index 5a4f63f28..f80901d59 100644 --- a/tests/typecheck/fields/test_nullable.yml +++ b/tests/typecheck/fields/test_nullable.yml @@ -38,15 +38,19 @@ reveal_type(MyModel().text_nullable) # N: Revealed type is "Union[builtins.str, None]" MyModel().text = None # E: Incompatible types in assignment (expression has type "None", variable has type "Union[str, int, Combinable]") [assignment] MyModel().text_nullable = None + monkeypatch: true installed_apps: - myapp files: - path: myapp/__init__.py - path: myapp/models.py content: | + from typing import Optional, Union from django.db import models + from django.db.models.expressions import Combinable + class MyModel(models.Model): - text_nullable = models.CharField(max_length=100, null=True) + text_nullable = models.CharField[Optional[Union[str, int, Combinable]], Optional[str]](max_length=100, null=True) text = models.CharField(max_length=100) - case: nullable_array_field diff --git a/tests/typecheck/managers/querysets/test_values_list.yml b/tests/typecheck/managers/querysets/test_values_list.yml index 9315babf6..e4ddfc087 100644 --- a/tests/typecheck/managers/querysets/test_values_list.yml +++ b/tests/typecheck/managers/querysets/test_values_list.yml @@ -52,6 +52,7 @@ from myapp.models import Concrete ret = list(Concrete.objects.values_list('id', 'data')) reveal_type(ret) # N: Revealed type is "builtins.list[Tuple[builtins.int, builtins.dict[builtins.str, builtins.str]]]" + monkeypatch: true installed_apps: - myapp files: @@ -61,7 +62,7 @@ from __future__ import annotations from django.db import models - class JSONField(models.TextField): pass # incomplete + class JSONField(models.TextField[dict[str, str], dict[str, str]]): pass # incomplete class Concrete(models.Model): id = models.IntegerField() diff --git a/tests/typecheck/models/test_contrib_models.yml b/tests/typecheck/models/test_contrib_models.yml index 07165a8b2..e91582532 100644 --- a/tests/typecheck/models/test_contrib_models.yml +++ b/tests/typecheck/models/test_contrib_models.yml @@ -1,3 +1,16 @@ +- case: contrib_admin_model_fields + main: | + from django.contrib.admin.models import LogEntry + from django.contrib.auth.models import User + from django.contrib.contenttypes.models import ContentType + reveal_type(LogEntry().action_time) # N: Revealed type is "datetime.datetime" + reveal_type(LogEntry().user) # N: Revealed type is "django.contrib.auth.models.AbstractUser" + reveal_type(LogEntry().content_type) # N: Revealed type is "Union[django.contrib.contenttypes.models.ContentType, None]" + reveal_type(LogEntry().content_type_id) # N: Revealed type is "Union[builtins.int, None]" + reveal_type(LogEntry().object_id) # N: Revealed type is "Union[builtins.str, None]" + reveal_type(LogEntry().object_repr) # N: Revealed type is "builtins.str" + reveal_type(LogEntry().action_flag) # N: Revealed type is "builtins.int" + reveal_type(LogEntry().change_message) # N: Revealed type is "builtins.str" - case: contrib_auth_model_fields main: | from typing import Type @@ -15,8 +28,8 @@ reveal_type(User().is_anonymous) # N: Revealed type is "Literal[False]" reveal_type(User().groups.get()) # N: Revealed type is "django.contrib.auth.models.Group" reveal_type(User().user_permissions.get()) # N: Revealed type is "django.contrib.auth.models.Permission" - reveal_type(User.groups) # N: Revealed type is "django.db.models.fields.related_descriptors.ManyToManyDescriptor[django.contrib.auth.models.Group, django.contrib.auth.models.User_groups]" - reveal_type(User.user_permissions) # N: Revealed type is "django.db.models.fields.related_descriptors.ManyToManyDescriptor[django.contrib.auth.models.Permission, django.db.models.base.Model]" + reveal_type(User.groups) # N: Revealed type is "django.db.models.fields.related_descriptors.ManyToManyDescriptor[django.contrib.auth.models.Group, django.contrib.auth.models._User_groups]" + reveal_type(User.user_permissions) # N: Revealed type is "django.db.models.fields.related_descriptors.ManyToManyDescriptor[django.contrib.auth.models.Permission, django.contrib.auth.models._User_permissions]" from django.contrib.auth.models import AnonymousUser reveal_type(AnonymousUser().is_authenticated) # N: Revealed type is "Literal[False]" @@ -24,7 +37,11 @@ from django.contrib.auth.models import Permission reveal_type(Permission().name) # N: Revealed type is "builtins.str" + reveal_type(Permission().content_type) # N: Revealed type is "django.contrib.contenttypes.models.ContentType" + reveal_type(Permission().content_type_id) # N: Revealed type is "builtins.int" reveal_type(Permission().codename) # N: Revealed type is "builtins.str" + reveal_type(Permission.user_set) # N: Revealed type is "django.db.models.fields.related_descriptors.ManyToManyDescriptor[django.contrib.auth.models.User, django.contrib.auth.models._User_permissions]" + reveal_type(Permission.group_set) # N: Revealed type is "django.db.models.fields.related_descriptors.ManyToManyDescriptor[django.contrib.auth.models.Group, django.contrib.auth.models._Group_permissions]" from django.contrib.auth.models import PermissionsMixin p: Type[PermissionsMixin] @@ -33,7 +50,114 @@ from django.contrib.auth.models import Group reveal_type(Group().name) # N: Revealed type is "builtins.str" reveal_type(Group().permissions.get()) # N: Revealed type is "django.contrib.auth.models.Permission" - reveal_type(Group.permissions) # N: Revealed type is "django.db.models.fields.related_descriptors.ManyToManyDescriptor[django.contrib.auth.models.Permission, django.contrib.auth.models.Group_permissions]" + reveal_type(Group.permissions) # N: Revealed type is "django.db.models.fields.related_descriptors.ManyToManyDescriptor[django.contrib.auth.models.Permission, django.contrib.auth.models._Group_permissions]" + +- case: contrib_contenttype_model_fields + main: | + from django.contrib.contenttypes.models import ContentType + from django.contrib.auth.models import User + reveal_type(ContentType().app_label) # N: Revealed type is "builtins.str" + reveal_type(ContentType().model) # N: Revealed type is "builtins.str" + reveal_type(ContentType.logentry_set) # N: Revealed type is "django.db.models.fields.related_descriptors.ReverseManyToOneDescriptor[django.contrib.admin.models.LogEntry]" + reveal_type(ContentType.permission_set) # N: Revealed type is "django.db.models.fields.related_descriptors.ReverseManyToOneDescriptor[django.contrib.auth.models.Permission]" + reveal_type(ContentType.objects) # N: Revealed type is "django.contrib.contenttypes.models.ContentTypeManager" + reveal_type(ContentType.objects.all()) # N: Revealed type is "django.db.models.query.QuerySet[django.contrib.contenttypes.models.ContentType, django.contrib.contenttypes.models.ContentType]" + +- case: contrib_flatpages_model_fields + main: | + from django.contrib.flatpages.models import FlatPage + from django.contrib.auth.models import User + reveal_type(FlatPage().url) # N: Revealed type is "builtins.str" + reveal_type(FlatPage().title) # N: Revealed type is "builtins.str" + reveal_type(FlatPage().content) # N: Revealed type is "builtins.str" + reveal_type(FlatPage().enable_comments) # N: Revealed type is "builtins.bool" + reveal_type(FlatPage().template_name) # N: Revealed type is "builtins.str" + reveal_type(FlatPage().registration_required) # N: Revealed type is "builtins.bool" + reveal_type(FlatPage.sites) # N: Revealed type is "django.db.models.fields.related_descriptors.ManyToManyDescriptor[django.contrib.sites.models.Site, django.contrib.flatpages.models._FlatPage_sites]" + +- case: contrib_redirects_model_fields + main: | + from django.contrib.redirects.models import Redirect + from django.contrib.auth.models import User + reveal_type(Redirect().site) # N: Revealed type is "django.contrib.sites.models.Site" + reveal_type(Redirect().old_path) # N: Revealed type is "builtins.str" + reveal_type(Redirect().new_path) # N: Revealed type is "builtins.str" + reveal_type(Redirect().site_id) # N: Revealed type is "builtins.int" + +- case: contrib_sessions_model_fields + main: | + from django.contrib.sessions.models import Session + from django.contrib.auth.models import User + reveal_type(Session().session_key) # N: Revealed type is "builtins.str" + reveal_type(Session().session_data) # N: Revealed type is "builtins.str" + reveal_type(Session().expire_date) # N: Revealed type is "datetime.datetime" + reveal_type(Session.objects) # N: Revealed type is "django.contrib.sessions.models.SessionManager[django.contrib.sessions.models.Session]" + +- case: contrib_sites_model_fields + main: | + from django.contrib.sites.models import Site + reveal_type(Site().domain) # N: Revealed type is "builtins.str" + reveal_type(Site().name) # N: Revealed type is "builtins.str" + reveal_type(Site.flatpage_set) # N: Revealed type is "django.db.models.fields.related_descriptors.ManyToManyDescriptor[django.contrib.flatpages.models.FlatPage, django.contrib.flatpages.models._FlatPage_sites]" + reveal_type(Site.redirect_set) # N: Revealed type is "django.db.models.fields.related_descriptors.ReverseManyToOneDescriptor[django.contrib.redirects.models.Redirect]" + reveal_type(Site.objects) # N: Revealed type is "django.contrib.sites.models.SiteManager" + +- case: contrib_gis_db_backends_model_fields + main: | + from django.contrib.gis.db.backends.oracle.models import ( + OracleGeometryColumns, + OracleSpatialRefSys, + ) + from django.contrib.gis.db.backends.postgis.models import ( + PostGISGeometryColumns, + PostGISSpatialRefSys, + ) + from django.contrib.gis.db.backends.spatialite.models import ( + SpatialiteGeometryColumns, + SpatialiteSpatialRefSys, + ) + + reveal_type(OracleGeometryColumns().table_name) # N: Revealed type is "builtins.str" + reveal_type(OracleGeometryColumns().column_name) # N: Revealed type is "builtins.str" + reveal_type(OracleGeometryColumns().srid) # N: Revealed type is "builtins.int" + reveal_type(OracleGeometryColumns.objects) # N: Revealed type is "django.db.models.manager.Manager[django.contrib.gis.db.backends.oracle.models.OracleGeometryColumns]" + reveal_type(OracleSpatialRefSys().cs_name) # N: Revealed type is "builtins.str" + reveal_type(OracleSpatialRefSys().srid) # N: Revealed type is "builtins.int" + reveal_type(OracleSpatialRefSys().auth_name) # N: Revealed type is "builtins.str" + reveal_type(OracleSpatialRefSys().auth_srid) # N: Revealed type is "builtins.int" + reveal_type(OracleSpatialRefSys().wktext) # N: Revealed type is "builtins.str" + reveal_type(OracleSpatialRefSys().cs_bounds) # N: Revealed type is "django.contrib.gis.geos.polygon.Polygon" + reveal_type(OracleSpatialRefSys.objects) # N: Revealed type is "django.db.models.manager.Manager[django.contrib.gis.db.backends.oracle.models.OracleSpatialRefSys]" + + reveal_type(PostGISGeometryColumns().f_table_catalog) # N: Revealed type is "builtins.str" + reveal_type(PostGISGeometryColumns().f_table_schema) # N: Revealed type is "builtins.str" + reveal_type(PostGISGeometryColumns().f_table_name) # N: Revealed type is "builtins.str" + reveal_type(PostGISGeometryColumns().f_geometry_column) # N: Revealed type is "builtins.str" + reveal_type(PostGISGeometryColumns().coord_dimension) # N: Revealed type is "builtins.int" + reveal_type(PostGISGeometryColumns().srid) # N: Revealed type is "builtins.int" + reveal_type(PostGISGeometryColumns().type) # N: Revealed type is "builtins.str" + reveal_type(PostGISGeometryColumns.objects) # N: Revealed type is "django.db.models.manager.Manager[django.contrib.gis.db.backends.postgis.models.PostGISGeometryColumns]" + reveal_type(PostGISSpatialRefSys().srid) # N: Revealed type is "builtins.int" + reveal_type(PostGISSpatialRefSys().auth_name) # N: Revealed type is "builtins.str" + reveal_type(PostGISSpatialRefSys().auth_srid) # N: Revealed type is "builtins.int" + reveal_type(PostGISSpatialRefSys().srtext) # N: Revealed type is "builtins.str" + reveal_type(PostGISSpatialRefSys().proj4text) # N: Revealed type is "builtins.str" + reveal_type(PostGISSpatialRefSys.objects) # N: Revealed type is "django.db.models.manager.Manager[django.contrib.gis.db.backends.postgis.models.PostGISSpatialRefSys]" + + reveal_type(SpatialiteGeometryColumns().f_table_name) # N: Revealed type is "builtins.str" + reveal_type(SpatialiteGeometryColumns().f_geometry_column) # N: Revealed type is "builtins.str" + reveal_type(SpatialiteGeometryColumns().coord_dimension) # N: Revealed type is "builtins.int" + reveal_type(SpatialiteGeometryColumns().srid) # N: Revealed type is "builtins.int" + reveal_type(SpatialiteGeometryColumns().spatial_index_enabled) # N: Revealed type is "builtins.int" + reveal_type(SpatialiteGeometryColumns().type) # N: Revealed type is "builtins.int" + reveal_type(SpatialiteGeometryColumns.objects) # N: Revealed type is "django.db.models.manager.Manager[django.contrib.gis.db.backends.spatialite.models.SpatialiteGeometryColumns]" + reveal_type(SpatialiteSpatialRefSys().srid) # N: Revealed type is "builtins.int" + reveal_type(SpatialiteSpatialRefSys().auth_name) # N: Revealed type is "builtins.str" + reveal_type(SpatialiteSpatialRefSys().auth_srid) # N: Revealed type is "builtins.int" + reveal_type(SpatialiteSpatialRefSys().ref_sys_name) # N: Revealed type is "builtins.str" + reveal_type(SpatialiteSpatialRefSys().proj4text) # N: Revealed type is "builtins.str" + reveal_type(SpatialiteSpatialRefSys().srtext) # N: Revealed type is "builtins.str" + reveal_type(SpatialiteSpatialRefSys.objects) # N: Revealed type is "django.db.models.manager.Manager[django.contrib.gis.db.backends.spatialite.models.SpatialiteSpatialRefSys]" - case: can_override_abstract_user_manager main: | @@ -112,6 +236,7 @@ main:7: note: Revealed type is "myapp.models.MyUser" main:9: note: Revealed type is "myapp.models.MyUser" + monkeypatch: true custom_settings: | INSTALLED_APPS = ('django.contrib.contenttypes', 'django.contrib.auth', 'myapp', 'other') AUTH_USER_MODEL='myapp.MyUser' @@ -126,11 +251,15 @@ - path: other/__init__.py - path: other/models.py content: | + from myapp.models import MyUser from django.conf import settings from django.db import models + class Other_users(models.Model): + myuser = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) + class Other(models.Model): - users = models.ManyToManyField(settings.AUTH_USER_MODEL) + users = models.ManyToManyField[MyUser, Other_users](settings.AUTH_USER_MODEL, through='Other_users') user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) unq_user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) @@ -164,8 +293,8 @@ main: | from django.contrib.auth.models import Group, Permission from django.contrib.contenttypes.models import ContentType - reveal_type(Permission().user_set) # N: Revealed type is "django.contrib.auth.models.User_ManyRelatedManager[django.contrib.auth.models.User_user_permissions]" - reveal_type(Group().user_set) # N: Revealed type is "django.contrib.auth.models.User_ManyRelatedManager[django.contrib.auth.models.User_groups]" + reveal_type(Permission().user_set) # N: Revealed type is "django.db.models.fields.related_descriptors.ManyRelatedManager[django.contrib.auth.models.User, django.contrib.auth.models._User_permissions]" + reveal_type(Group().user_set) # N: Revealed type is "django.db.models.fields.related_descriptors.ManyRelatedManager[django.contrib.auth.models.User, django.contrib.auth.models._User_groups]" reveal_type(ContentType.permission_set) # N: Revealed type is "django.db.models.fields.related_descriptors.ReverseManyToOneDescriptor[django.contrib.auth.models.Permission]" reveal_type(ContentType().permission_set) # N: Revealed type is "django.contrib.auth.models.Permission_RelatedManager" custom_settings: | diff --git a/tests/typecheck/models/test_create.yml b/tests/typecheck/models/test_create.yml index 0192a5505..4b34a17ea 100644 --- a/tests/typecheck/models/test_create.yml +++ b/tests/typecheck/models/test_create.yml @@ -64,6 +64,8 @@ main: | from myapp.models import Child Child.objects.create(dct={"hello": "world"}) # no errors + reveal_type(Child().dct) # N: Revealed type is "builtins.dict[builtins.str, builtins.str]" + monkeypatch: true installed_apps: - myapp files: @@ -74,10 +76,10 @@ from django.db import models - class JSONField(models.TextField): pass # incomplete + class JSONField(models.TextField[dict[str, str], dict[str, str]]): pass # incomplete class Base(models.Model): - dct: models.Field[dict[str, str], dict[str, str]] = JSONField() + dct = JSONField() class Child(Base): pass @@ -140,17 +142,21 @@ third = MyModel3() third.default = None # E: Incompatible types in assignment (expression has type "None", variable has type "Union[float, int, str, Combinable]") [assignment] reveal_type(third.default) # N: Revealed type is "builtins.int" + monkeypatch: true installed_apps: - myapp files: - path: myapp/__init__.py - path: myapp/models.py content: | + from typing import Optional, Union from django.db import models + from django.db.models.expressions import Combinable + def return_int() -> int: return 0 class MyModel(models.Model): - id = models.IntegerField(primary_key=True, default=return_int) + id = models.IntegerField[Optional[Union[float, int, str, Combinable]], int](default=return_int, primary_key=True) class MyModel2(models.Model): id = models.IntegerField(primary_key=True) class MyModel3(models.Model): diff --git a/tests/typecheck/models/test_extra_methods.yml b/tests/typecheck/models/test_extra_methods.yml index d37e37a27..90d698801 100644 --- a/tests/typecheck/models/test_extra_methods.yml +++ b/tests/typecheck/models/test_extra_methods.yml @@ -53,12 +53,17 @@ from myapp.models import MyUser MyUser().get_next_by_date() # E: "MyUser" has no attribute "get_next_by_date" [attr-defined] MyUser().get_previous_by_date() # E: "MyUser" has no attribute "get_previous_by_date" [attr-defined] + monkeypatch: true installed_apps: - myapp files: - path: myapp/__init__.py - path: myapp/models.py content: | + from datetime import date + from typing import Optional, Union from django.db import models + from django.db.models.expressions import Combinable + class MyUser(models.Model): - date = models.DateField(null=True) + date = models.DateField[Optional[Union[str, date, Combinable]], Optional[date]](null=True)