Django auth: self.request.user is always Anonymous

2019-08-26 06:31发布

问题:

I'm trying to make the server only return documents which were created by the logged-in user. I'm following this post and this one but the logged in user is returned as "Anonymous".

I am using Django Rest Framework with Django Rest Auth, and a custom user, but no other customisations.

Django 2.0.10

Here is my viewset in api.py:

from rest_framework import viewsets, permissions

from .models import List, Item
from .serializers import ListSerializer, ItemSerializer


class ListViewSet(viewsets.ModelViewSet):
    # queryset = List.objects.all()
    # permission_classes = [permissions.AllowAny, ]
    model = List
    serializer_class = ListSerializer

    def get_queryset(self):
        print(self.request.user)
        return List.objects.filter(created_by=self.request.user)

    def pre_save(self, obj):
        obj.created_by = self.request.user

Settings.py:

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',
    ],
}

AUTH_USER_MODEL = 'users.CustomUser'

# django rest auth
ACCOUNT_AUTHENTICATION_METHOD = 'email'

I've read other posts about this issue which all talk about custom middleware, but I haven't created a custom middleware unless django-rest-framework or django-rest-auth is actually such a thing? And the posts don't seem to show how to get the user in the viewset.

Also the posts are old, so Django has probably changed.

From this post I tried the following, it did not work:

class ListViewSet(viewsets.ModelViewSet):
    # queryset = List.objects.all()
    # permission_classes = [permissions.AllowAny, ]
    model = List
    serializer_class = ListSerializer

    def get_queryset(self):
        print(self.request.user)
        self.request.custom_prop = SimpleLazyObject(lambda: get_actual_value(self.request))
        print(self.request.custom_prop)
        return List.objects.filter(created_by=self.request.user)

    def pre_save(self, obj):
        obj.created_by = self.request.user

I'd be very grateful for any help. This is such a basic requirement, it ought to be simple but I'm absolutely stuck.

Edit: in case it helps somebody else, below is my working code based on Lucas Weyne's answer. I extended the logic so the user sees all Lists they created, AND all lists that have the flag "is_public", but can only modify lists they created.

I have set permissions.AllowAny because I want users who are not logged in to see public lists. In the client code, I check whether the user is logged in, and if they are I send the token in the query header.

Note the use of the Q object which is the easiest way I could find to return records that satisfy either of the two conditions. I couldn't find any mention of the Q object in the Django rest framework docs. I eventually found it in the main Django docs.

This all seems to work but if you spot something I've done wrong, please comment! I am not sure if this is the Django way to approach the requirement but I like the fact that the permissions logic is all in one place.

from rest_framework import viewsets, permissions
from .models import List
from .serializers import ListSerializer
from django.db.models import Q


class ListViewSet(viewsets.ModelViewSet):
    """
    ViewSet for lists. Before allowing any operation, the user's status is checked.

    Anybody can view a public list.
    A logged-in user can create lists.
    A logged-in user can view, edit and delete the lists they created.
    """
    permission_classes = [permissions.AllowAny, ]
    model = List
    serializer_class = ListSerializer

    def get_queryset(self):
        # restrict any method that can alter a record
        restricted_methods = ['POST', 'PUT', 'PATCH', 'DELETE']
        if self.request.method in restricted_methods:
            # if you are not logged in you cannot modify any list
            if not self.request.user.is_authenticated:
              return List.objects.none()

            # you can only modify your own lists
            # only a logged-in user can create a list and view the returned data
            return List.objects.filter(created_by=self.request.user)

        # GET method (view list) is available to owner and for public lists
        if self.request.method == 'GET':
          if not self.request.user.is_authenticated:
            return List.objects.filter(is_public__exact=True)

          return List.objects.filter(Q(created_by=self.request.user) | Q(is_public__exact=True))

        # explicitly refuse any non-handled methods
        return List.objects.none()

    def pre_save(self, obj):
        obj.created_by = self.request.user

回答1:

For clients to authenticate via TokenAuthentication, the token key should be included in the Authorization HTTP header. Browseable API is only able to pass user credentials through Basic or Session authentication. To test your API, you'll need a HTTP client like cURL

curl -H "Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b" <url>

Allow any access to your view will raise an internal server error at List.objects.filter(created_by=self.request.user) for unauthorized users instead 401 Unauthorized. If the queryset is user dependant, you should add a permission class to require user credentials.

class ListViewSet(viewsets.ModelViewSet):
    permission_classes = [permissions.IsAuthenticated, ]
    serializer_class = ListSerializer

    def get_queryset(self):
        return List.objects.filter(created_by=self.request.user)

    def pre_save(self, obj):
        obj.created_by = self.request.user