mirror of
https://github.com/davidhalter/django-stubs.git
synced 2025-12-13 23:41:55 +08:00
Add fallback related manager in final iteration of AddRelatedManagers (#993)
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.
This commit is contained in:
committed by
GitHub
parent
32e13c37a6
commit
9044a354cb
@@ -1,3 +1,4 @@
|
|||||||
from mypy.errorcodes import ErrorCode
|
from mypy.errorcodes import ErrorCode
|
||||||
|
|
||||||
MANAGER_UNTYPED = ErrorCode("django-manager", "Untyped manager disallowed", "Django")
|
MANAGER_UNTYPED = ErrorCode("django-manager", "Untyped manager disallowed", "Django")
|
||||||
|
MANAGER_MISSING = ErrorCode("django-manager-missing", "Couldn't resolve related manager for model", "Django")
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from mypy.types import Type as MypyType
|
|||||||
from mypy.types import TypedDictType, TypeOfAny
|
from mypy.types import TypedDictType, TypeOfAny
|
||||||
|
|
||||||
from mypy_django_plugin.django.context import DjangoContext
|
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 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.fullnames import ANNOTATIONS_FULLNAME, ANY_ATTR_ALLOWED_CLASS_FULLNAME, MODEL_CLASS_FULLNAME
|
||||||
from mypy_django_plugin.lib.helpers import add_new_class_for_module
|
from mypy_django_plugin.lib.helpers import add_new_class_for_module
|
||||||
@@ -345,6 +346,7 @@ class AddRelatedManagers(ModelClassInitializer):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
if isinstance(relation, (ManyToOneRel, ManyToManyRel)):
|
if isinstance(relation, (ManyToOneRel, ManyToManyRel)):
|
||||||
|
related_manager_info = None
|
||||||
try:
|
try:
|
||||||
related_manager_info = self.lookup_typeinfo_or_incomplete_defn_error(
|
related_manager_info = self.lookup_typeinfo_or_incomplete_defn_error(
|
||||||
fullnames.RELATED_MANAGER_CLASS
|
fullnames.RELATED_MANAGER_CLASS
|
||||||
@@ -356,6 +358,26 @@ class AddRelatedManagers(ModelClassInitializer):
|
|||||||
if not self.api.final_iteration:
|
if not self.api.final_iteration:
|
||||||
raise exc
|
raise exc
|
||||||
else:
|
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
|
continue
|
||||||
|
|
||||||
# Check if the related model has a related manager subclassed from the default manager
|
# Check if the related model has a related manager subclassed from the default manager
|
||||||
|
|||||||
@@ -640,6 +640,42 @@
|
|||||||
pass
|
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
|
- case: foreign_key_relationship_for_models_with_custom_manager
|
||||||
main: |
|
main: |
|
||||||
from myapp.models import Transaction
|
from myapp.models import Transaction
|
||||||
|
|||||||
Reference in New Issue
Block a user