Skip to content

Commit

Permalink
Break what apparently is a cycle involving custom User model and `Que…
Browse files Browse the repository at this point in the history
…rySet.as_manager()` (#2377)
  • Loading branch information
sterliakov committed Sep 21, 2024
1 parent eb6535f commit 0fb3bfd
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 7 deletions.
19 changes: 12 additions & 7 deletions mypy_django_plugin/transformers/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -511,19 +511,24 @@ def _defer() -> None:
base_as_manager = queryset_info.get("as_manager")
if base_as_manager is None:
return
base_as_manager_type = get_proper_type(base_as_manager.type)
if not isinstance(base_as_manager_type, CallableType):
return
base_as_manager_ret_type = get_proper_type(base_as_manager_type.ret_type)
if not isinstance(base_as_manager_ret_type, Instance):
return

base_ret_type = base_as_manager_ret_type.type
manager_sym = semanal_api.lookup_fully_qualified_or_none(fullnames.MANAGER_CLASS_FULLNAME)
if manager_sym is None or not isinstance(manager_sym.node, TypeInfo):
return _defer()

manager_base = manager_sym.node
base_ret_type = manager_base

if base_as_manager.fullname != f"{fullnames.QUERYSET_CLASS_FULLNAME}.as_manager":
base_as_manager_type = get_proper_type(base_as_manager.type)
if not isinstance(base_as_manager_type, CallableType):
return
base_as_manager_ret_type = get_proper_type(base_as_manager_type.ret_type)
if not isinstance(base_as_manager_ret_type, Instance):
return

base_ret_type = base_as_manager_ret_type.type

manager_class_name = f"{manager_base.name}From{queryset_info.name}"
current_module = semanal_api.modules[semanal_api.cur_mod_id]
existing_sym = current_module.names.get(manager_class_name)
Expand Down
44 changes: 44 additions & 0 deletions tests/typecheck/managers/querysets/test_as_manager.yml
Original file line number Diff line number Diff line change
Expand Up @@ -431,3 +431,47 @@
class MyQuerySet(models.QuerySet):
pass
objects = MyQuerySet.as_manager()
- case: queryset_as_manager_foreignkey_cycle
main: |
from payments.models import Payout
reveal_type(Payout.objects) # N: Revealed type is "payments.models.ManagerFromPayoutQuerySet[payments.models.Payout]"
custom_settings: |
INSTALLED_APPS = ("django.contrib.contenttypes", "django.contrib.auth", "payments", "accounts")
AUTH_USER_MODEL = "accounts.Account"
files:
- path: payments/__init__.py
- path: payments/models.py
content: |
from __future__ import annotations
from typing_extensions import Self
from django.contrib.auth import get_user_model
from django.db import models
UserModel = get_user_model()
class Transaction(models.Model):
user = models.ForeignKey(
UserModel, on_delete=models.CASCADE, related_name="transactions"
)
class PayoutQuerySet(models.QuerySet["Payout"]):
def unapplied(self) -> Self:
return self
class Payout(models.Model):
triggered_by = models.ForeignKey(
Transaction, on_delete=models.CASCADE, related_name="payouts"
)
objects = PayoutQuerySet.as_manager()
- path: accounts/__init__.py
- path: accounts/models.py
content: |
from django.contrib.auth.models import AbstractUser
class Account(AbstractUser): pass

0 comments on commit 0fb3bfd

Please sign in to comment.