如何进行 Django REST 框架 /me/ 调用?

How can I make a Django REST framework /me/ call?

假设我有一个 ViewSet:

class ProfileViewSet(viewsets.ModelViewSet):
    """
    API endpoint that allows a user's profile to be viewed or edited.
    """
    permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly)
    queryset = Profile.objects.all()
    serializer_class = ProfileSerializer

    def perform_create(self, serializer):
        serializer.save(user=self.request.user)

...和一个 HyperlinkedModelSerializer:

class ProfileSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Profile
        read_only_fields = ('user',)

我的 urls.py 设置为:

router.register(r'profiles', api.ProfileViewSet, base_name='profile')

这让我可以访问例如/api/profile/1/ 很好。

我想在我的 API 上设置一个新端点(类似于 Facebook API 的 /me/ 调用)在 /api/profile/me/ 以访问当前用户的配置文件 - 如何使用 Django REST Framework 执行此操作?

您可以通过按登录用户过滤查询集来覆盖 get_queryset 方法,这将 return 登录用户的配置文件显示在列表视图 (/api/profile/) 中。

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

def get_queryset(self):
    qs = super(ProfileViewSet, self).get_queryset()
    return qs.filter(user=self.request.user)

或像这样覆盖检索方法,这将 return 当前用户的个人资料。

def retrieve(self, request, *args, **kwargs):
    self.object = get_object_or_404(Profile, user=self.request.user)
    serializer = self.get_serializer(self.object)
    return Response(serializer.data)

您可以使用 list_route 装饰器在您的视图 class 中创建一个新方法,例如:

class ProfileViewSet(viewsets.ModelViewSet):

    @list_route()
    def me(self, request, *args, **kwargs):
        # assumes the user is authenticated, handle this according your needs
        user_id = request.user.id
        return self.retrieve(request, user_id)

有关 @list_route

的更多信息,请参阅 docs on this

希望对您有所帮助!

使用@Gerard 的解决方案给我带来了麻烦:

Expected view UserViewSet to be called with a URL keyword argument named "pk". Fix your URL conf, or set the .lookup_field attribute on the view correctly..

查看 source code for retrieve() 似乎 user_id 未使用(未使用 *args

此解决方案有效:

from django.contrib.auth import get_user_model
from django.shortcuts import get_object_or_404

from rest_framework import filters
from rest_framework import viewsets
from rest_framework import mixins
from rest_framework.decorators import list_route
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response

from ..serializers import UserSerializer


class UserViewSet(viewsets.ModelViewSet):
    """
    A viewset for viewing and editing user instances.
    """
    serializer_class = UserSerializer
    User = get_user_model()
    queryset = User.objects.all()
    filter_backends = (filters.DjangoFilterBackend, filters.SearchFilter)
    filter_fields = ('username', 'email', 'usertype')
    search_fields = ('username', 'email', 'usertype')

    @list_route(permission_classes=[IsAuthenticated])
    def me(self, request, *args, **kwargs):
        User = get_user_model()
        self.object = get_object_or_404(User, pk=request.user.id)
        serializer = self.get_serializer(self.object)
        return Response(serializer.data)

访问 /api/users/me 回复与 /api/users/1 相同的数据(当 logged-in 用户是 pk=1 的用户时)

只是提供了一种不同的方式。我是这样做的:

def get_object(self):
    pk = self.kwargs['pk']
    if pk == 'me':
        return self.request.user
    else:
        return super().get_object()

这允许 ViewSet 中的其他 detail_routes 像 /api/users/me/activate

一样工作

根据Gerard's and looking at the error pointed out by delavnog,我开发了以下解决方案:

class ProfileViewSet(viewsets.ModelViewSet):

    @list_route(methods=['GET'], permission_classes=[IsAuthenticated])        
    def me(self, request, *args, **kwargs):
        self.kwargs.update(pk=request.user.id)
        return self.retrieve(request,*args, **kwargs)

备注:

  • ModelViewSet继承了GenericAPIView,获取对象的逻辑都在里面实现了
  • 您需要检查用户是否通过身份验证,否则request.user将无法使用。至少使用 permission_classes=[IsAuthenticated].
  • 此解决方案适用于 GET,但您可以对其他方法应用相同的逻辑。
  • DRY 放心!

只需覆盖 get_object()

例如

def get_object(self):
    return self.request.user

我见过很多脆弱的解决方案,所以我想我会用更新和更安全的东西来回应。更重要的是,您不需要单独的视图,因为 me 只是充当重定向。

    @action(detail=False, methods=['get', 'patch'])
    def me(self, request):
        self.kwargs['pk'] = request.user.pk

        if request.method == 'GET':
            return self.retrieve(request)
        elif request.method == 'PATCH':
            return self.partial_update(request)
        else:
            raise Exception('Not implemented')

重要的是不要像我在某些答案中看到的那样重复 retrieve 的行为。如果函数 retrieve 发生变化怎么办?然后你会得到 /me/<user pk>

的不同行为

如果你只需要处理GET请求,你也可以使用Django的redirect。但这不适用于 POST 或 PATCH。

考虑到 ProfileUser 模型与 related_name='profile' 之间的 OneToOneField 关系,我建议如下,因为 @list_route 已经 deprecated since DRF 3.9

class ProfileViewSet(viewsets.GenericViewSet):
    serializer_class = ProfileSerializer

    @action(methods=('GET',), detail=False, url_path='me', url_name='me')
    def me(self, request, *args, **kwargs):
        serializer = self.get_serializer(self.request.user.profile)
        return response.Response(serializer.data)