Repository: typeddjango/django-stubs

Issue:
Can't assign AnonymousUser to request.user
# Bug report

I'm implementing my own logout routine and need to assign AnonymousUser to request.user like:

```
        if hasattr(request, 'user'):
            from django.contrib.auth.models import AnonymousUser

            request.user = AnonymousUser()
```


## What's wrong
The code above has the error:

error: Incompatible types in assignment (expression has type "AnonymousUser", variable has type "AbstractBaseUser")

## How is that should be

request.user should allow both AnonymousUser and AbstractBaseUser


## System information

- OS: Ubuntu 18.04
- `python` version: 3.6
- `django` version: 2.2
- `mypy` version: 0.761
- `django-stubs` version: 1.4.0



Discussion:
@lfrodrigues you can always define your own Request type and use it across your database.

@mkurnikov One way to solve this would be to use `Union[AbstractBaseUser, AnonymousUser]` type on request, with [tagged unions](https://mypy.readthedocs.io/en/latest/literal_types.html#tagged-unions) support mypy should discriminate union type based on `is_authenticated` check:
```
if request.user.is_authenticated:
   reveal_type(request.user)  # AbstractBaseUser
else:
   reveal_type(request.user). # AnonymousUser
```

Although that change may be inconvenient for user who have mostly protected views, it will cover general case for Django apps.
Thanks for the suggestion but it adds complexity to my code. 

Django itself can have AbstractBaseUser or AnonymousUser in request.user. I'll wait for a fix :)


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
@@ -1,6 +1,7 @@
 from mypy.plugin import AttributeContext
 from mypy.types import Instance
 from mypy.types import Type as MypyType
+from mypy.types import UnionType
 
 from mypy_django_plugin.django.context import DjangoContext
 from mypy_django_plugin.lib import helpers
@@ -8,9 +9,18 @@
 
 def set_auth_user_model_as_type_for_request_user(ctx: AttributeContext, django_context: DjangoContext) -> MypyType:
     auth_user_model = django_context.settings.AUTH_USER_MODEL
-    model_cls = django_context.apps_registry.get_model(auth_user_model)
-    model_info = helpers.lookup_class_typeinfo(helpers.get_typechecker_api(ctx), model_cls)
-    if model_info is None:
+    user_cls = django_context.apps_registry.get_model(auth_user_model)
+    user_info = helpers.lookup_class_typeinfo(helpers.get_typechecker_api(ctx), user_cls)
+
+    if user_info is None:
         return ctx.default_attr_type
 
-    return Instance(model_info, [])
+    # 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/models/test_contrib_models.yml b/test-data/typecheck/models/test_contrib_models.yml
--- a/test-data/typecheck/models/test_contrib_models.yml
+++ b/test-data/typecheck/models/test_contrib_models.yml
@@ -10,6 +10,12 @@
         reveal_type(User().is_active)  # N: Revealed type is 'builtins.bool*'
         reveal_type(User().date_joined)  # N: Revealed type is 'datetime.datetime*'
         reveal_type(User().last_login)  # N: Revealed type is 'Union[datetime.datetime, None]'
+        reveal_type(User().is_authenticated)  # N: Revealed type is 'Literal[True]'
+        reveal_type(User().is_anonymous)  # N: Revealed type is 'Literal[False]'
+
+        from django.contrib.auth.models import AnonymousUser
+        reveal_type(AnonymousUser().is_authenticated)  # N: Revealed type is 'Literal[False]'
+        reveal_type(AnonymousUser().is_anonymous)  # N: Revealed type is 'Literal[True]'
 
         from django.contrib.auth.models import Permission
         reveal_type(Permission().name)  # N: Revealed type is 'builtins.str*'
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
@@ -2,11 +2,11 @@
     disable_cache: true
     main: |
         from django.http.request import HttpRequest
-        reveal_type(HttpRequest().user)  # N: Revealed type is 'myapp.models.MyUser'
+        reveal_type(HttpRequest().user)  # N: Revealed type is 'Union[myapp.models.MyUser, django.contrib.auth.models.AnonymousUser]'
         # check that other fields work ok
         reveal_type(HttpRequest().method)  # N: Revealed type is 'Union[builtins.str, None]'
     custom_settings: |
-        INSTALLED_APPS = ('django.contrib.contenttypes', 'myapp')
+        INSTALLED_APPS = ('django.contrib.contenttypes', 'django.contrib.auth', 'myapp')
         AUTH_USER_MODEL='myapp.MyUser'
     files:
         -   path: myapp/__init__.py
@@ -14,4 +14,16 @@
             content: |
                 from django.db import models
                 class MyUser(models.Model):
-                    pass
\ No newline at end of file
+                    pass
+-   case: request_object_user_can_be_descriminated
+    disable_cache: true
+    main: |
+        from django.http.request import HttpRequest
+        request = HttpRequest()
+        reveal_type(request.user) # N: Revealed type is 'Union[django.contrib.auth.models.User, django.contrib.auth.models.AnonymousUser]'
+        if not request.user.is_anonymous:
+            reveal_type(request.user) # N: Revealed type is 'django.contrib.auth.models.User'
+        if request.user.is_authenticated:
+            reveal_type(request.user) # N: Revealed type is 'django.contrib.auth.models.User'
+    custom_settings: |
+        INSTALLED_APPS = ('django.contrib.contenttypes', 'django.contrib.auth')