Skip to content

Commit eb6535f

Browse files
authored
Allow lookups on .annotate fields (#2376)
1 parent f2f7bb5 commit eb6535f

File tree

2 files changed

+31
-7
lines changed

2 files changed

+31
-7
lines changed

mypy_django_plugin/django/context.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -482,12 +482,12 @@ def resolve_lookup_expected_type(
482482
try:
483483
solved_lookup = self.solve_lookup_type(model_cls, lookup)
484484
except FieldError as exc:
485-
if (
486-
helpers.is_annotated_model(model_instance.type)
487-
and model_instance.extra_attrs
488-
and lookup in model_instance.extra_attrs.attrs
489-
):
490-
return model_instance.extra_attrs.attrs[lookup]
485+
if helpers.is_annotated_model(model_instance.type) and model_instance.extra_attrs:
486+
# If the field comes from .annotate(), we assume Any for it
487+
# and allow chaining any lookups.
488+
lookup_base_field, *_ = lookup.split("__")
489+
if lookup_base_field in model_instance.extra_attrs.attrs:
490+
return model_instance.extra_attrs.attrs[lookup_base_field]
491491

492492
msg = exc.args[0]
493493
if model_instance.extra_attrs:

tests/typecheck/managers/querysets/test_annotate.yml

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@
257257
username = models.CharField(max_length=100)
258258
259259
260-
- case: annotate_currently_allows_lookups_of_non_existent_field
260+
- case: annotate_rejects_lookups_of_non_existent_field
261261
main: |
262262
from myapp.models import User
263263
from django.db.models.expressions import F
@@ -395,3 +395,27 @@
395395
396396
class Entry(models.Model):
397397
blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
398+
399+
- case: test_annotate_allows_any_lookups_in_filter
400+
main: |
401+
from django.db import models
402+
from myapp.models import Blog
403+
404+
qs = Blog.objects.annotate(distance=0)
405+
reveal_type(qs)
406+
reveal_type(qs.filter(distance=10))
407+
reveal_type(qs.filter(distance__lt=10))
408+
qs.filter(distance__lt__lt=10)
409+
qs.filter(distance__unknown_lookup=10)
410+
out: |
411+
main:5: note: Revealed type is "django.db.models.query.QuerySet[myapp.models.Blog@AnnotatedWith[TypedDict({'distance': Any})], myapp.models.Blog@AnnotatedWith[TypedDict({'distance': Any})]]"
412+
main:6: note: Revealed type is "django.db.models.query.QuerySet[myapp.models.Blog@AnnotatedWith[TypedDict({'distance': Any})], myapp.models.Blog@AnnotatedWith[TypedDict({'distance': Any})]]"
413+
main:7: note: Revealed type is "django.db.models.query.QuerySet[myapp.models.Blog@AnnotatedWith[TypedDict({'distance': Any})], myapp.models.Blog@AnnotatedWith[TypedDict({'distance': Any})]]"
414+
installed_apps:
415+
- myapp
416+
files:
417+
- path: myapp/__init__.py
418+
- path: myapp/models.py
419+
content: |
420+
from django.db import models
421+
class Blog(models.Model): pass

0 commit comments

Comments
 (0)