如何进行 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。
考虑到 Profile
和 User
模型与 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)
假设我有一个 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
希望对您有所帮助!
使用@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
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。
考虑到 Profile
和 User
模型与 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)