Repository: typeddjango/django-stubs

Issue:
Patch View to be generic to support djangorestframework-stubs?
This isn't actually needed for django-stubs, but djangorestframework-stubs uses generic views, so could we add it to the classes monkeypatched by django_stubs_ext? If so I'd be happy to create a PR for that


Discussion:
Or perhaps we could allow passing in additional classes to `monkeypatch`?
Yes, extra classes to `monkeypatch` seem like a good idea.

Resolving patch:
diff --git a/django_stubs_ext/django_stubs_ext/patch.py b/django_stubs_ext/django_stubs_ext/patch.py
--- a/django_stubs_ext/django_stubs_ext/patch.py
+++ b/django_stubs_ext/django_stubs_ext/patch.py
@@ -1,4 +1,4 @@
-from typing import Any, Generic, List, Optional, Tuple, Type, TypeVar
+from typing import Any, Generic, Iterable, List, Optional, Tuple, Type, TypeVar
 
 from django import VERSION as VERSION
 from django.contrib.admin import ModelAdmin
@@ -58,7 +58,7 @@ def __repr__(self) -> str:
 ]
 
 
-def monkeypatch() -> None:
+def monkeypatch(extra_classes: Optional[Iterable[type]] = None) -> None:
     """Monkey patch django as necessary to work properly with mypy."""
 
     # Add the __class_getitem__ dunder.
@@ -68,6 +68,9 @@ def monkeypatch() -> None:
     )
     for el in suited_for_this_version:
         el.cls.__class_getitem__ = classmethod(lambda cls, *args, **kwargs: cls)
+    if extra_classes:
+        for cls in extra_classes:
+            cls.__class_getitem__ = classmethod(lambda cls, *args, **kwargs: cls)  # type: ignore[attr-defined]
 
 
 __all__ = ["monkeypatch"]


Test patch:
diff --git a/django_stubs_ext/tests/test_monkeypatching.py b/django_stubs_ext/tests/test_monkeypatching.py
--- a/django_stubs_ext/tests/test_monkeypatching.py
+++ b/django_stubs_ext/tests/test_monkeypatching.py
@@ -1,12 +1,13 @@
 import builtins
 from contextlib import suppress
-from typing import Optional
+from typing import Iterable, Optional
 
 import pytest
 from _pytest.fixtures import FixtureRequest
 from _pytest.monkeypatch import MonkeyPatch
 from django.db.models import Model
 from django.forms.models import ModelForm
+from django.views import View
 from typing_extensions import Protocol
 
 import django_stubs_ext
@@ -17,7 +18,9 @@
 class _MakeGenericClasses(Protocol):
     """Used to represent a type of ``make_generic_classes`` fixture."""
 
-    def __call__(self, django_version: Optional[_VersionSpec] = None) -> None:
+    def __call__(
+        self, django_version: Optional[_VersionSpec] = None, extra_classes: Optional[Iterable[type]] = None
+    ) -> None:
         ...
 
 
@@ -26,15 +29,22 @@ def make_generic_classes(
     request: FixtureRequest,
     monkeypatch: MonkeyPatch,
 ) -> _MakeGenericClasses:
+    _extra_classes: list[type] = []
+
     def fin() -> None:
         for el in _need_generic:
             with suppress(AttributeError):
                 delattr(el.cls, "__class_getitem__")
+        for cls in _extra_classes:
+            with suppress(AttributeError):
+                delattr(cls, "__class_getitem__")
 
-    def factory(django_version: Optional[_VersionSpec] = None) -> None:
+    def factory(django_version: Optional[_VersionSpec] = None, extra_classes: Optional[Iterable[type]] = None) -> None:
+        if extra_classes:
+            _extra_classes.extend(extra_classes)
         if django_version is not None:
             monkeypatch.setattr(patch, "VERSION", django_version)
-        django_stubs_ext.monkeypatch()
+        django_stubs_ext.monkeypatch(extra_classes)
 
     request.addfinalizer(fin)
     return factory
@@ -52,6 +62,19 @@ class TestForm(ModelForm[Model]):
         pass
 
 
+def test_patched_extra_classes_generics(make_generic_classes: _MakeGenericClasses) -> None:
+    """Test that the generics actually get patched for extra classes."""
+    extra_classes = [View]
+
+    make_generic_classes(django_version=None, extra_classes=extra_classes)
+
+    for cls in extra_classes:
+        assert cls[type] is cls  # type: ignore[misc]
+
+    class TestView(View[Model]):  # type: ignore[type-arg]
+        pass
+
+
 @pytest.mark.parametrize(
     "django_version",
     [