Skip to content

Commit

Permalink
fixup! Add a generic type parameter 'through' to ManyRelatedManager
Browse files Browse the repository at this point in the history
  • Loading branch information
flaeppe committed Mar 22, 2024
1 parent 814ba09 commit 06422a5
Show file tree
Hide file tree
Showing 5 changed files with 20 additions and 9 deletions.
2 changes: 1 addition & 1 deletion django-stubs/db/models/fields/related_descriptors.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ from typing_extensions import Self
_M = TypeVar("_M", bound=Model)
_F = TypeVar("_F", bound=Field)
_From = TypeVar("_From", bound=Model)
_Through = TypeVar("_Through", bound=Model)
_Through = TypeVar("_Through", bound=Model, default=Model)
_To = TypeVar("_To", bound=Model)

class ForeignKeyDeferredAttribute(DeferredAttribute):
Expand Down
4 changes: 2 additions & 2 deletions ext/django_stubs_ext/db/models/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@
from typing import Protocol, TypeVar

_T = TypeVar("_T")
_M = TypeVar("_M")
_Through = TypeVar("_Through")

# Define as `Protocol` to prevent them being used with `isinstance()`.
# These actually inherit from `BaseManager`.
class RelatedManager(Protocol[_T]):
pass

class ManyRelatedManager(Protocol[_T, _M]):
class ManyRelatedManager(Protocol[_T, _Through]):
pass
11 changes: 11 additions & 0 deletions mypy_django_plugin/transformers/manytomany.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,17 @@ def get_model_from_expression(


def get_related_manager_and_model(ctx: MethodContext) -> Optional[Tuple[Instance, Instance, Instance]]:
"""
Returns a 3-tuple consisting of:
1. A `ManyRelatedManager` instance
2. The first type parameter instance of 1. when it's a model
3. The second type parameter instance of 1. when it's a model
When encountering a `ManyRelatedManager` that has populated its 2 first type
parameters with models. Otherwise `None` is returned.
For example: if given a `ManyRelatedManager[A, B]` where `A` and `B` are models the
following 3-tuple is returned: `(ManyRelatedManager[A, B], A, B)`.
"""
if (
isinstance(ctx.default_return_type, Instance)
and ctx.default_return_type.type.fullname == fullnames.MANY_RELATED_MANAGER
Expand Down
8 changes: 4 additions & 4 deletions tests/typecheck/fields/test_related.yml
Original file line number Diff line number Diff line change
Expand Up @@ -603,7 +603,7 @@
from myapp.models import Author, Blog, Publisher, Profile
reveal_type(Author.blogs) # N: Revealed type is "django.db.models.fields.related_descriptors.ManyToManyDescriptor[myapp.models.Blog, myapp.models.Author_blogs]"
reveal_type(Author.blogs.through) # N: Revealed type is "Type[myapp.models.Author_blogs]"
reveal_type(Author().blogs) # N: Revealed type is "myapp.models.Blog_ManyRelatedManager"
reveal_type(Author().blogs) # N: Revealed type is "myapp.models.Blog_ManyRelatedManager[myapp.models.Author_blogs]"
reveal_type(Blog.publisher) # N: Revealed type is "django.db.models.fields.related_descriptors.ForwardManyToOneDescriptor[django.db.models.fields.related.ForeignKey[Union[myapp.models.Publisher, django.db.models.expressions.Combinable], myapp.models.Publisher]]"
reveal_type(Publisher.profile) # N: Revealed type is "django.db.models.fields.related_descriptors.ForwardOneToOneDescriptor[django.db.models.fields.related.OneToOneField[Union[myapp.models.Profile, django.db.models.expressions.Combinable], myapp.models.Profile]]"
reveal_type(Author.file) # N: Revealed type is "django.db.models.fields.files.FileDescriptor"
Expand Down Expand Up @@ -946,8 +946,8 @@
main: |
from myapp.models import SalesMan
sales_man = SalesMan()
reveal_type(sales_man.client) # N: Revealed type is "myapp.models.CustomUser_ManyRelatedManager"
reveal_type(sales_man.client(manager="staffs")) # N: Revealed type is "django.db.models.fields.related_descriptors.ManyRelatedManager[myapp.models.CustomUser]"
reveal_type(sales_man.client) # N: Revealed type is "myapp.models.CustomUser_ManyRelatedManager[myapp.models.SalesMan_client]"
reveal_type(sales_man.client(manager="staffs")) # N: Revealed type is "django.db.models.fields.related_descriptors.ManyRelatedManager[myapp.models.CustomUser, myapp.models.SalesMan_client]"
installed_apps:
- myapp
files:
Expand Down Expand Up @@ -1279,7 +1279,7 @@
instance = arg(field=1, unknown=2)
reveal_type(instance.field) # N: Revealed type is "Any"
reveal_type(instance.unknown) # N: Revealed type is "Any"
reveal_type(instance.bad) # N: Revealed type is "django.db.models.fields.related_descriptors.ManyRelatedManager[Any]"
reveal_type(instance.bad) # N: Revealed type is "django.db.models.fields.related_descriptors.ManyRelatedManager[Any, Any]"
return instance
installed_apps:
- myapp
Expand Down
4 changes: 2 additions & 2 deletions tests/typecheck/models/test_contrib_models.yml
Original file line number Diff line number Diff line change
Expand Up @@ -163,10 +163,10 @@
- case: test_permissions_inherited_reverese_relations
main: |
from django.contrib.auth.models import Permission
reveal_type(Permission().user_set) # N: Revealed type is "django.contrib.auth.models.User_ManyRelatedManager"
reveal_type(Permission().user_set) # N: Revealed type is "django.contrib.auth.models.User_ManyRelatedManager[django.contrib.auth.models.User_user_permissions]"
from django.contrib.auth.models import Group
reveal_type(Group().user_set) # N: Revealed type is "django.contrib.auth.models.User_ManyRelatedManager"
reveal_type(Group().user_set) # N: Revealed type is "django.contrib.auth.models.User_ManyRelatedManager[django.contrib.auth.models.User_groups]"
custom_settings: |
INSTALLED_APPS = ('django.contrib.contenttypes', 'django.contrib.auth')
AUTH_USER_MODEL='auth.User'

0 comments on commit 06422a5

Please sign in to comment.