Skip to content

Commit

Permalink
Add fallback related manager in final iteration of AddRelatedManagers
Browse files Browse the repository at this point in the history
If a django model has a Manager class that cannot be resolved statically
(if it is generated in a way where we cannot import it, like `objects =
my_manager_factory()`), we fallback to the default related manager, so
you at least get a base level of working type checking.
  • Loading branch information
aleksanb committed Jun 15, 2022
1 parent ae5b1a4 commit dab26d0
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 0 deletions.
1 change: 1 addition & 0 deletions mypy_django_plugin/errorcodes.py
@@ -1,3 +1,4 @@
from mypy.errorcodes import ErrorCode

MANAGER_UNTYPED = ErrorCode("django-manager", "Untyped manager disallowed", "Django")
MANAGER_MISSING = ErrorCode("django-manager-missing", "Couldn't resolve related manager for model", "Django")
22 changes: 22 additions & 0 deletions mypy_django_plugin/transformers/models.py
Expand Up @@ -14,6 +14,7 @@
from mypy.types import TypedDictType, TypeOfAny

from mypy_django_plugin.django.context import DjangoContext
from mypy_django_plugin.errorcodes import MANAGER_MISSING
from mypy_django_plugin.lib import fullnames, helpers
from mypy_django_plugin.lib.fullnames import ANNOTATIONS_FULLNAME, ANY_ATTR_ALLOWED_CLASS_FULLNAME, MODEL_CLASS_FULLNAME
from mypy_django_plugin.lib.helpers import add_new_class_for_module
Expand Down Expand Up @@ -341,6 +342,7 @@ def run_with_model_cls(self, model_cls: Type[Model]) -> None:
continue

if isinstance(relation, (ManyToOneRel, ManyToManyRel)):
related_manager_info = None
try:
related_manager_info = self.lookup_typeinfo_or_incomplete_defn_error(
fullnames.RELATED_MANAGER_CLASS
Expand All @@ -352,6 +354,26 @@ def run_with_model_cls(self, model_cls: Type[Model]) -> None:
if not self.api.final_iteration:
raise exc
else:
if related_manager_info:
"""
If a django model has a Manager class that cannot be
resolved statically (if it is generated in a way
where we cannot import it, like `objects = my_manager_factory()`),
we fallback to the default related manager, so you
at least get a base level of working type checking.
See https://github.com/typeddjango/django-stubs/pull/993
for more information on when this error can occur.
"""
self.add_new_node_to_model_class(
attname, Instance(related_manager_info, [Instance(related_model_info, [])])
)
related_model_fullname = related_model_cls.__module__ + "." + related_model_cls.__name__
self.ctx.api.fail(
f"Couldn't resolve related manager for relation {relation.name!r} (from {related_model_fullname}.{relation.field}).",
self.ctx.cls,
code=MANAGER_MISSING,
)
continue

# Check if the related model has a related manager subclassed from the default manager
Expand Down
36 changes: 36 additions & 0 deletions tests/typecheck/fields/test_related.yml
Expand Up @@ -640,6 +640,42 @@
pass
- case: test_related_managers_when_manager_is_dynamically_generated_and_cannot_be_imported
main: |
from myapp import models
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class User(models.Model):
name = models.TextField()
def DynamicManager() -> models.Manager:
class InnerManager(models.Manager):
def some_method(self, arg: str) -> None:
return None
return InnerManager()
class Booking(models.Model):
renter = models.ForeignKey(User, on_delete=models.PROTECT)
owner = models.ForeignKey(User, on_delete=models.PROTECT, related_name='bookingowner_set')
objects = DynamicManager()
def process_booking(user: User):
reveal_type(user.bookingowner_set)
reveal_type(user.booking_set)
out: |
myapp/models:3: error: Couldn't resolve related manager for relation 'booking' (from myapp.models.Booking.myapp.Booking.renter).
myapp/models:3: error: Couldn't resolve related manager for relation 'bookingowner_set' (from myapp.models.Booking.myapp.Booking.owner).
myapp/models:20: note: Revealed type is "django.db.models.manager.RelatedManager[myapp.models.Booking]"
myapp/models:21: note: Revealed type is "django.db.models.manager.RelatedManager[myapp.models.Booking]"
- case: foreign_key_relationship_for_models_with_custom_manager
main: |
from myapp.models import Transaction
Expand Down

0 comments on commit dab26d0

Please sign in to comment.