Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Break what apparently is a cycle involving custom User model and QuerySet.as_manager() #2377

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading