Repository: typeddjango/django-stubs

Issue:
Unable to subclass HttpRequest to override type of User
I'd like to create a `AuthenticatedHttpRequest` to indicate that a user has been authenticated (for example to annotate the `request` parameter for views with the `login_required` decorator).

I'd expect this to be done like so:
```python
class AuthenticatedHttpRequest(HttpRequest):
    user: User
```

The problem is that the type of `user` is changed by this transformer https://github.com/typeddjango/django-stubs/blob/574a87e68cfb2e6ff66b3d7e1a0bb75f51148a8c/mypy_django_plugin/transformers/request.py#L10-L26
That's called here https://github.com/typeddjango/django-stubs/blob/574a87e68cfb2e6ff66b3d7e1a0bb75f51148a8c/mypy_django_plugin/main.py#L244-L245

rendering it somewhat impossible to declare these kinds of subclasses.

--------------------------------
Not sure what a solution would be here. 

Only changing the type of `user` if the object it's accessed on is an instance of `HttpRequest` (not for subclasses) would possibly be a solution, but would require that all `HttpRequest`-subclasses declare the type of `user` to not fall back to the imprecise type annotations in the `.pyi` file.

Would it be possible to check the type and only change it if it's what's expected from the `.pyi`-file (`Union[AbstractBaseUser, AnonymousUser]`)?


Discussion:
We should probably also update the readme and remove/update this section: https://github.com/typeddjango/django-stubs#how-can-i-use-httprequest-with-custom-user-model
This is a critical bug, because without custom user models - these stubs do not make much sense. 
@kszmigiel can you please have a look? 
@sobolevn Sure thing, as soon as I solve dining philosophers problem for Uni 😝 
> This is a critical bug, because without custom user models - these stubs do not make much sense.

Custom user models work fine (the type becomes `Union[<custom user model>, AnonymousUser]`), it's just that creating subclasses of `HttpRequest` with only authenticated users doesn't work. 

I'd say it's important but it's not critical, more a quality of life thing to avoid having to have non-needed asserts/if-statements.

In regards to the readme the mentioned section should probably be removed as the problem that it answers just works™ with the mypy-plugin (thus becoming a red herring when debugging).
Oh, I see. This indeed is not critical. 

But, we still need to fix that as well.
Can you please post the output from `mypy`?

Resolving patch:
diff --git a/mypy_django_plugin/transformers/request.py b/mypy_django_plugin/transformers/request.py
--- a/mypy_django_plugin/transformers/request.py
+++ b/mypy_django_plugin/transformers/request.py
@@ -8,6 +8,22 @@
 
 
 def set_auth_user_model_as_type_for_request_user(ctx: AttributeContext, django_context: DjangoContext) -> MypyType:
+    # Imported here because django isn't properly loaded yet when module is loaded
+    from django.contrib.auth.base_user import AbstractBaseUser
+    from django.contrib.auth.models import AnonymousUser
+
+    abstract_base_user_info = helpers.lookup_class_typeinfo(helpers.get_typechecker_api(ctx), AbstractBaseUser)
+    anonymous_user_info = helpers.lookup_class_typeinfo(helpers.get_typechecker_api(ctx), AnonymousUser)
+
+    # This shouldn't be able to happen, as we managed to import the models above.
+    assert abstract_base_user_info is not None
+    assert anonymous_user_info is not None
+
+    if ctx.default_attr_type != UnionType([Instance(abstract_base_user_info, []), Instance(anonymous_user_info, [])]):
+        # Type has been changed from the default in django-stubs.
+        # I.e. HttpRequest has been subclassed and user-type overridden, so let's leave it as is.
+        return ctx.default_attr_type
+
     auth_user_model = django_context.settings.AUTH_USER_MODEL
     user_cls = django_context.apps_registry.get_model(auth_user_model)
     user_info = helpers.lookup_class_typeinfo(helpers.get_typechecker_api(ctx), user_cls)
@@ -15,12 +31,4 @@ def set_auth_user_model_as_type_for_request_user(ctx: AttributeContext, django_c
     if user_info is None:
         return ctx.default_attr_type
 
-    # Imported here because django isn't properly loaded yet when module is loaded
-    from django.contrib.auth.models import AnonymousUser
-
-    anonymous_user_info = helpers.lookup_class_typeinfo(helpers.get_typechecker_api(ctx), AnonymousUser)
-    if anonymous_user_info is None:
-        # This shouldn't be able to happen, as we managed to import the model above...
-        return Instance(user_info, [])
-
     return UnionType([Instance(user_info, []), Instance(anonymous_user_info, [])])


Test patch:
diff --git a/test-data/typecheck/test_request.yml b/test-data/typecheck/test_request.yml
--- a/test-data/typecheck/test_request.yml
+++ b/test-data/typecheck/test_request.yml
@@ -27,3 +27,28 @@
             reveal_type(request.user) # N: Revealed type is 'django.contrib.auth.models.User'
     custom_settings: |
         INSTALLED_APPS = ('django.contrib.contenttypes', 'django.contrib.auth')
+-   case: subclass_request_not_changed_user_type
+    disable_cache: true
+    main: |
+        from django.http.request import HttpRequest
+        class MyRequest(HttpRequest):
+            foo: int # Just do something
+
+        request = MyRequest()
+        reveal_type(request.user) # N: Revealed type is 'Union[django.contrib.auth.models.User, django.contrib.auth.models.AnonymousUser]'
+    custom_settings: |
+        INSTALLED_APPS = ('django.contrib.contenttypes', 'django.contrib.auth')
+
+-   case: subclass_request_changed_user_type
+    disable_cache: true
+    main: |
+        from django.http.request import HttpRequest
+        from django.contrib.auth.models import User
+        class MyRequest(HttpRequest):
+            user: User # Override the type of user
+
+        request = MyRequest()
+        reveal_type(request.user) # N: Revealed type is 'django.contrib.auth.models.User'
+    custom_settings: |
+        INSTALLED_APPS = ('django.contrib.contenttypes', 'django.contrib.auth')
+