Skip to content

Commit 75396f7

Browse files
committed
feat: Update allowlists, type all model fields
1 parent 5a34c13 commit 75396f7

File tree

10 files changed

+180
-133
lines changed

10 files changed

+180
-133
lines changed

django-stubs/contrib/admin/models.pyi

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ class LogEntryManager(models.Manager[LogEntry]):
3232
action_flag: int,
3333
change_message: str | list[Any] = "",
3434
*,
35-
# Commit 27b68bc, django https://github.com/django/django/pull/19233
3635
single_object: bool = False,
3736
) -> list[LogEntry] | LogEntry: ...
3837

@@ -41,7 +40,7 @@ class LogEntry(models.Model):
4140
pk: models.AutoField
4241
action_time: models.DateTimeField
4342
user: models.ForeignKey[AbstractUser | Combinable, AbstractUser]
44-
user_id: Any
43+
user_id: int
4544
content_type: models.ForeignKey[ContentType | Combinable | None, ContentType | None]
4645
content_type_id: int | None
4746
object_id: models.TextField[str | int | Combinable | None, str | None]

django-stubs/contrib/auth/models.pyi

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ class AbstractUser(AbstractBaseUser, PermissionsMixin):
141141
def get_full_name(self) -> str: ...
142142
def get_short_name(self) -> str: ...
143143
def email_user(
144-
self, subject: _StrOrPromise, message: _StrOrPromise, from_email: str = ..., **kwargs: Any
144+
self, subject: _StrOrPromise, message: _StrOrPromise, from_email: str | None = ..., **kwargs: Any
145145
) -> None: ...
146146

147147
class User(AbstractUser):
@@ -180,3 +180,4 @@ class AnonymousUser:
180180
@property
181181
def is_authenticated(self) -> Literal[False]: ...
182182
def get_username(self) -> str: ...
183+
def __int__(self) -> None: ...

django-stubs/contrib/contenttypes/models.pyi

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ from django.db.models.fields.related_descriptors import ReverseManyToOneDescript
88
from django.db.models.query import QuerySet
99

1010
class ContentTypeManager(models.Manager[ContentType]):
11+
def __init__(self, *args: Any, **kwargs: Any) -> None: ...
1112
def get_by_natural_key(self, app_label: str, model: str) -> ContentType: ...
1213
def get_for_model(self, model: type[Model] | Model, for_concrete_model: bool = ...) -> ContentType: ...
1314
def get_for_models(self, *models: Any, for_concrete_models: bool = ...) -> dict[type[Model], ContentType]: ...
@@ -24,6 +25,8 @@ class ContentType(models.Model):
2425
objects: ClassVar[ContentTypeManager]
2526
@property
2627
def name(self) -> str: ...
28+
@property
29+
def app_labeled_name(self) -> str: ...
2730
def model_class(self) -> type[Model] | None: ...
2831
def get_object_for_this_type(self, **kwargs: Any) -> Model: ...
2932
def get_all_objects_for_this_type(self, **kwargs: Any) -> QuerySet[Model]: ...

mypy_django_plugin/lib/fullnames.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,6 @@
6262
"django.contrib.sessions.base_session.AbstractBaseSession",
6363
)
6464
)
65+
66+
DATETIME_FIELD_FULLNAME = "django.db.models.fields.DateTimeField"
67+
DATE_FIELD_FULLNAME = "django.db.models.fields.DateField"

mypy_django_plugin/transformers/models.py

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from django.db.models.fields.reverse_related import ForeignObjectRel, ManyToManyRel, OneToOneRel
99
from mypy.checker import TypeChecker
1010
from mypy.nodes import (
11+
ARG_NAMED_OPT,
1112
ARG_STAR2,
1213
MDEF,
1314
Argument,
@@ -629,35 +630,80 @@ def run_with_model_cls(self, model_cls: type[Model]) -> None:
629630
if field.choices:
630631
info = self.lookup_typeinfo_or_incomplete_defn_error("builtins.str")
631632
return_type = Instance(info, [])
632-
common.add_method(self.ctx, name=f"get_{field.attname}_display", args=[], return_type=return_type)
633+
field_type = self.lookup_typeinfo(f"django.db.models.fields.{field.__class__.__name__}")
634+
args = []
635+
if field_type is not None:
636+
field_type = fill_typevars(field_type)
637+
args = [
638+
Argument(
639+
Var("field", field_type),
640+
field_type,
641+
initializer=None,
642+
kind=ARG_NAMED_OPT,
643+
),
644+
]
645+
common.add_method(self.ctx, name=f"get_{field.attname}_display", args=args, return_type=return_type)
633646

634647
# get_next_by, get_previous_by for Date, DateTime
648+
# def get_next_by_<attname>(self, *, field=<DateTimeField | DateField>, is_next=True, **kwargs)
649+
# def get_previous_by_<attname>(self, *, field=<DateTimeField | DateField>, is_next=False, **kwargs)
635650
for field in self.django_context.get_model_fields(model_cls):
636651
if isinstance(field, DateField | DateTimeField) and not field.null:
637652
return_type = Instance(self.model_classdef.info, [])
653+
date_or_datetime_field = self.lookup_typeinfo_or_incomplete_defn_error(
654+
fullname=fullnames.DATETIME_FIELD_FULLNAME
655+
if isinstance(field, DateTimeField)
656+
else fullnames.DATE_FIELD_FULLNAME
657+
)
658+
datetime_field_type = fill_typevars(date_or_datetime_field)
659+
bool_type = self.ctx.api.named_type("builtins.bool")
638660
common.add_method(
639661
self.ctx,
640662
name=f"get_next_by_{field.attname}",
641663
args=[
664+
Argument(
665+
Var("field", datetime_field_type),
666+
datetime_field_type,
667+
initializer=None,
668+
kind=ARG_NAMED_OPT,
669+
),
670+
Argument(
671+
Var("is_next", bool_type),
672+
bool_type,
673+
initializer=None,
674+
kind=ARG_NAMED_OPT,
675+
),
642676
Argument(
643677
Var("kwargs", AnyType(TypeOfAny.implementation_artifact)),
644678
AnyType(TypeOfAny.implementation_artifact),
645679
initializer=None,
646680
kind=ARG_STAR2,
647-
)
681+
),
648682
],
649683
return_type=return_type,
650684
)
651685
common.add_method(
652686
self.ctx,
653687
name=f"get_previous_by_{field.attname}",
654688
args=[
689+
Argument(
690+
Var("field", datetime_field_type),
691+
datetime_field_type,
692+
initializer=None,
693+
kind=ARG_NAMED_OPT,
694+
),
695+
Argument(
696+
Var("is_next", bool_type),
697+
bool_type,
698+
initializer=None,
699+
kind=ARG_NAMED_OPT,
700+
),
655701
Argument(
656702
Var("kwargs", AnyType(TypeOfAny.implementation_artifact)),
657703
AnyType(TypeOfAny.implementation_artifact),
658704
initializer=None,
659705
kind=ARG_STAR2,
660-
)
706+
),
661707
],
662708
return_type=return_type,
663709
)

scripts/stubtest/allowlist.txt

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,3 +515,110 @@ django.db.models.fields.generated.GeneratedField.__init__
515515
# https://github.com/python/cpython/blob/5abff6960b4aecb0d5c81c7482cf3faa74e1983d/Lib/copyreg.py#L112-L161
516516
# Some Django classes get copied at import time, which leads stubtest to detect it.
517517
.*\.__slotnames__
518+
519+
# Built-in Model fields, during Runtime Model.<some_field> is a DeferredAttribute object
520+
# an instance of the Model class, would provide field with the expected type, see PR #2590 for more details
521+
django.contrib.admin.models.LogEntry.action_flag
522+
django.contrib.admin.models.LogEntry.action_time
523+
django.contrib.admin.models.LogEntry.change_message
524+
django.contrib.admin.models.LogEntry.content_type
525+
django.contrib.admin.models.LogEntry.content_type_id
526+
django.contrib.admin.models.LogEntry.id
527+
django.contrib.admin.models.LogEntry.object_id
528+
django.contrib.admin.models.LogEntry.object_repr
529+
django.contrib.admin.models.LogEntry.user
530+
django.contrib.admin.models.LogEntry.user_id
531+
django.contrib.auth.models.AbstractBaseUser.last_login
532+
django.contrib.auth.models.AbstractBaseUser.password
533+
django.contrib.auth.models.AbstractUser.date_joined
534+
django.contrib.auth.models.AbstractUser.email
535+
django.contrib.auth.models.AbstractUser.first_name
536+
django.contrib.auth.models.AbstractUser.groups
537+
django.contrib.auth.models.AbstractUser.is_active
538+
django.contrib.auth.models.AbstractUser.is_staff
539+
django.contrib.auth.models.AbstractUser.is_superuser
540+
django.contrib.auth.models.AbstractUser.last_login
541+
django.contrib.auth.models.AbstractUser.last_name
542+
django.contrib.auth.models.AbstractUser.objects
543+
django.contrib.auth.models.AbstractUser.password
544+
django.contrib.auth.models.AbstractUser.user_permissions
545+
django.contrib.auth.models.AbstractUser.username
546+
django.contrib.auth.models.Group.id
547+
django.contrib.auth.models.Group.name
548+
django.contrib.auth.models.Group.permissions
549+
django.contrib.auth.models.Permission.codename
550+
django.contrib.auth.models.Permission.content_type
551+
django.contrib.auth.models.Permission.content_type_id
552+
django.contrib.auth.models.Permission.id
553+
django.contrib.auth.models.Permission.name
554+
django.contrib.auth.models.PermissionsMixin.groups
555+
django.contrib.auth.models.PermissionsMixin.is_superuser
556+
django.contrib.auth.models.PermissionsMixin.user_permissions
557+
django.contrib.auth.models.User.date_joined
558+
django.contrib.auth.models.User.email
559+
django.contrib.auth.models.User.first_name
560+
django.contrib.auth.models.User.groups
561+
django.contrib.auth.models.User.id
562+
django.contrib.auth.models.User.is_active
563+
django.contrib.auth.models.User.is_staff
564+
django.contrib.auth.models.User.is_superuser
565+
django.contrib.auth.models.User.last_login
566+
django.contrib.auth.models.User.last_name
567+
django.contrib.auth.models.User.password
568+
django.contrib.auth.models.User.user_permissions
569+
django.contrib.auth.models.User.username
570+
django.contrib.contenttypes.models.ContentType.app_label
571+
django.contrib.contenttypes.models.ContentType.id
572+
django.contrib.contenttypes.models.ContentType.model
573+
django.contrib.flatpages.models.FlatPage.content
574+
django.contrib.flatpages.models.FlatPage.enable_comments
575+
django.contrib.flatpages.models.FlatPage.id
576+
django.contrib.flatpages.models.FlatPage.registration_required
577+
django.contrib.flatpages.models.FlatPage.sites
578+
django.contrib.flatpages.models.FlatPage.template_name
579+
django.contrib.flatpages.models.FlatPage.title
580+
django.contrib.flatpages.models.FlatPage.url
581+
django.contrib.gis.db.backends.oracle.models.OracleGeometryColumns.table_name
582+
django.contrib.gis.db.backends.oracle.models.OracleGeometryColumns.column_name
583+
django.contrib.gis.db.backends.oracle.models.OracleGeometryColumns.srid
584+
django.contrib.gis.db.backends.oracle.models.OracleSpatialRefSys.cs_name
585+
django.contrib.gis.db.backends.oracle.models.OracleSpatialRefSys.srid
586+
django.contrib.gis.db.backends.oracle.models.OracleSpatialRefSys.auth_srid
587+
django.contrib.gis.db.backends.oracle.models.OracleSpatialRefSys.auth_name
588+
django.contrib.gis.db.backends.oracle.models.OracleSpatialRefSys.wktext
589+
django.contrib.gis.db.backends.oracle.models.OracleSpatialRefSys.cs_bounds
590+
django.contrib.gis.db.backends.postgis.models.PostGISGeometryColumns.f_table_catalog
591+
django.contrib.gis.db.backends.postgis.models.PostGISGeometryColumns.f_table_schema
592+
django.contrib.gis.db.backends.postgis.models.PostGISGeometryColumns.f_table_name
593+
django.contrib.gis.db.backends.postgis.models.PostGISGeometryColumns.f_geometry_column
594+
django.contrib.gis.db.backends.postgis.models.PostGISGeometryColumns.coord_dimension
595+
django.contrib.gis.db.backends.postgis.models.PostGISGeometryColumns.srid
596+
django.contrib.gis.db.backends.postgis.models.PostGISGeometryColumns.type
597+
django.contrib.gis.db.backends.postgis.models.PostGISSpatialRefSys.srid
598+
django.contrib.gis.db.backends.postgis.models.PostGISSpatialRefSys.auth_srid
599+
django.contrib.gis.db.backends.postgis.models.PostGISSpatialRefSys.auth_name
600+
django.contrib.gis.db.backends.postgis.models.PostGISSpatialRefSys.srtext
601+
django.contrib.gis.db.backends.postgis.models.PostGISSpatialRefSys.proj4text
602+
django.contrib.gis.db.backends.spatialite.models.SpatialiteGeometryColumns.f_table_name
603+
django.contrib.gis.db.backends.spatialite.models.SpatialiteGeometryColumns.f_geometry_column
604+
django.contrib.gis.db.backends.spatialite.models.SpatialiteGeometryColumns.coord_dimension
605+
django.contrib.gis.db.backends.spatialite.models.SpatialiteGeometryColumns.srid
606+
django.contrib.gis.db.backends.spatialite.models.SpatialiteGeometryColumns.spatial_index_enabled
607+
django.contrib.gis.db.backends.spatialite.models.SpatialiteGeometryColumns.type
608+
django.contrib.gis.db.backends.spatialite.models.SpatialiteSpatialRefSys.srid
609+
django.contrib.gis.db.backends.spatialite.models.SpatialiteSpatialRefSys.auth_srid
610+
django.contrib.gis.db.backends.spatialite.models.SpatialiteSpatialRefSys.auth_name
611+
django.contrib.gis.db.backends.spatialite.models.SpatialiteSpatialRefSys.ref_sys_name
612+
django.contrib.gis.db.backends.spatialite.models.SpatialiteSpatialRefSys.proj4text
613+
django.contrib.gis.db.backends.spatialite.models.SpatialiteSpatialRefSys.srtext
614+
django.contrib.redirects.models.Redirect.id
615+
django.contrib.redirects.models.Redirect.new_path
616+
django.contrib.redirects.models.Redirect.old_path
617+
django.contrib.redirects.models.Redirect.site
618+
django.contrib.redirects.models.Redirect.site_id
619+
django.contrib.sessions.models.Session.expire_date
620+
django.contrib.sessions.models.Session.session_data
621+
django.contrib.sessions.models.Session.session_key
622+
django.contrib.sites.models.Site.domain
623+
django.contrib.sites.models.Site.id
624+
django.contrib.sites.models.Site.name

0 commit comments

Comments
 (0)