Add support for inline from_queryset in model classes (#1045)

* Add support for inline from_queryset in model classes

This adds support for calling <Manager>.from_queryset(<QuerySet>)()
inline in models, for example like this:

    class MyModel(models.Model):
        objects = MyManager.from_queryset(MyQuerySet)()

This is done by inspecting the class body in the transform_class_hook

* Fix missing methods on copied manager

* Add test and other minor tweaks

* Always create manager at module level

When the manager is added at the class level, which happened when it was
created inline in the model body, it's not possible to retrieve the
manager again based on fullname. That lead to problems with inheritance
and the default manager.
This commit is contained in:
Sigurd Ljødal
2022-07-13 09:04:44 +02:00
committed by GitHub
parent 2e84c03632
commit 830d74b493
7 changed files with 251 additions and 186 deletions

View File

@@ -678,7 +678,6 @@
def custom(self) -> None:
pass
# Note, that we cannot resolve dynamic calls for custom managers:
class Transaction(models.Model):
objects = BaseManager.from_queryset(TransactionQuerySet)
def test(self) -> None:
@@ -689,9 +688,8 @@
class TransactionLog(models.Model):
transaction = models.ForeignKey(Transaction, on_delete=models.CASCADE)
out: |
myapp/models:9: error: `.from_queryset` called from inside model class body
myapp/models:11: note: Revealed type is "django.db.models.manager.RelatedManager[myapp.models.TransactionLog]"
myapp/models:13: note: Revealed type is "Any"
myapp/models:10: note: Revealed type is "django.db.models.manager.RelatedManager[myapp.models.TransactionLog]"
myapp/models:12: note: Revealed type is "Any"
- case: resolve_primary_keys_for_foreign_keys_with_abstract_self_model

View File

@@ -359,12 +359,17 @@
class MyModel(models.Model):
objects = NewManager()
- case: from_queryset_in_model_class_body_yields_message
- case: test_queryset_in_model_class_body
main: |
from myapp.models import MyModel
reveal_type(MyModel.base_manager) # N: Revealed type is "myapp.models.BaseManagerFromMyQuerySet[myapp.models.MyModel]"
reveal_type(MyModel.manager) # N: Revealed type is "myapp.models.ManagerFromMyQuerySet[myapp.models.MyModel]"
reveal_type(MyModel.custom_manager) # N: Revealed type is "myapp.models.MyManagerFromMyQuerySet[myapp.models.MyModel]"
reveal_type(MyModel.objects) # N: Revealed type is "myapp.models.MyManagerFromMyQuerySet[myapp.models.MyModel]"
reveal_type(MyModel._default_manager) # N: Revealed type is "myapp.models.MyManagerFromMyQuerySet[myapp.models.MyModel]"
reveal_type(MyModel.objects.all) # N: Revealed type is "def () -> myapp.models.MyQuerySet[myapp.models.MyModel]"
reveal_type(MyModel.objects.custom) # N: Revealed type is "def () -> myapp.models.MyQuerySet"
reveal_type(MyModel.objects.all().filter) # N: Revealed type is "def (*args: Any, **kwargs: Any) -> myapp.models.MyQuerySet[myapp.models.MyModel]"
reveal_type(MyModel.objects.custom().filter) # N: Revealed type is "def (*args: Any, **kwargs: Any) -> myapp.models.MyQuerySet"
reveal_type(MyModel.objects2) # N: Revealed type is "myapp.models.MyManagerFromMyQuerySet[myapp.models.MyModel]"
reveal_type(MyModel._default_manager) # N: Revealed type is "myapp.models.MyManagerFromMyQuerySet[myapp.models.MyModel]"
installed_apps:
- myapp
files:
@@ -372,29 +377,42 @@
- path: myapp/models.py
content: |
from django.db import models
from django.db.models.manager import BaseManager
class MyManager(models.Manager["MyModel"]):
pass
class MyQuerySet(models.QuerySet["MyModel"]):
def queryset_method(self) -> int:
return 1
def custom(self) -> "MyQuerySet":
pass
class MyManager(BaseManager):
...
BaseManagerFromMyQuerySet = BaseManager.from_queryset(MyQuerySet)
ManagerFromMyQuerySet = models.Manager.from_queryset(MyQuerySet)
MyManagerFromMyQuerySet = MyManager.from_queryset(MyQuerySet)
class MyModel(models.Model):
objects1 = BaseManager.from_queryset(MyQuerySet)() # E: `.from_queryset` called from inside model class body
objects2 = BaseManager.from_queryset(MyQuerySet) # E: `.from_queryset` called from inside model class body
objects3 = models.Manager.from_queryset(MyQuerySet)() # E: `.from_queryset` called from inside model class body
objects4 = models.Manager.from_queryset(MyQuerySet) # E: `.from_queryset` called from inside model class body
objects5 = MyManager.from_queryset(MyQuerySet) # E: `.from_queryset` called from inside model class body
objects6 = MyManager.from_queryset(MyQuerySet)() # E: `.from_queryset` called from inside model class body
# Initiating the manager type is fine
base_manager = BaseManagerFromMyQuerySet()
manager = ManagerFromMyQuerySet()
custom_manager = MyManagerFromMyQuerySet()
objects = MyManager.from_queryset(MyQuerySet)()
objects2 = MyManager.from_queryset(MyQuerySet)()
- case: test_queryset_in_model_class_body_subclass
main: |
from myapp.models import MyModel
reveal_type(MyModel.objects) # N: Revealed type is "myapp.models.BaseManagerFromBaseQuerySet[myapp.models.MyModel]"
installed_apps:
- myapp
files:
- path: myapp/__init__.py
- path: myapp/models.py
content: |
from django.db import models
class BaseManager(models.Manager["BaseModel"]):
pass
class BaseQuerySet(models.QuerySet["BaseModel"]):
def custom(self) -> "BaseQuerySet":
pass
class BaseModel(models.Model):
objects = BaseManager.from_queryset(BaseQuerySet)()
class MyModel(BaseModel):
pass
- case: from_queryset_includes_methods_returning_queryset
main: |

View File

@@ -332,14 +332,14 @@
- case: custom_manager_returns_proper_model_types
main: |
from myapp.models import User
reveal_type(User.objects) # N: Revealed type is "myapp.models.User_MyManager2[myapp.models.User]"
reveal_type(User.objects) # N: Revealed type is "myapp.models.User_MyManager[myapp.models.User]"
reveal_type(User.objects.select_related()) # N: Revealed type is "django.db.models.query._QuerySet[myapp.models.User, myapp.models.User]"
reveal_type(User.objects.get()) # N: Revealed type is "myapp.models.User"
reveal_type(User.objects.get_instance()) # N: Revealed type is "builtins.int"
reveal_type(User.objects.get_instance_untyped('hello')) # N: Revealed type is "Any"
from myapp.models import ChildUser
reveal_type(ChildUser.objects) # N: Revealed type is "myapp.models.ChildUser_MyManager2[myapp.models.ChildUser]"
reveal_type(ChildUser.objects) # N: Revealed type is "myapp.models.ChildUser_MyManager[myapp.models.ChildUser]"
reveal_type(ChildUser.objects.select_related()) # N: Revealed type is "django.db.models.query._QuerySet[myapp.models.ChildUser, myapp.models.ChildUser]"
reveal_type(ChildUser.objects.get()) # N: Revealed type is "myapp.models.ChildUser"
reveal_type(ChildUser.objects.get_instance()) # N: Revealed type is "builtins.int"