Django自定义用户模型更新密码URL参数报错问题咨询
问题解答
这是Django的预期行为,原因如下:
- 你定义的
UserViewSet继承自ModelViewSet,它的update方法是为单个对象更新设计的,默认依赖视图的lookup_field(这里你设置为email)从URL的关键字参数中获取要更新的对象标识。 - 当你把
put: update绑定到根路径''时,这个URL规则里没有<str:email>参数,Django无法获取到lookup_field所需的email关键字参数,因此抛出该错误。
解决方案
如果想实现“通过请求体中的email来更新用户”的需求,不能直接复用ModelViewSet的默认update方法,需要自定义视图逻辑:
方法一:自定义action(推荐,符合REST规范)
在UserViewSet中添加一个自定义动作,专门处理这种通过请求体标识用户的更新:
from rest_framework.decorators import action from rest_framework.response import Response class UserViewSet(viewsets.ModelViewSet): lookup_field = 'email' queryset = User.objects.all() serializer_class = UserSerializer @action(detail=False, methods=['put']) def update_by_email(self, request): email = request.data.get('email') password = request.data.get('password') if not email or not password: return Response({'error': 'email和password为必填项'}, status=400) try: user = User.objects.get(email=email) user.set_password(password) user.save() serializer = self.get_serializer(user) return Response(serializer.data) except User.DoesNotExist: return Response({'error': '用户不存在'}, status=404)
然后在accounts应用的urls.py中配置该action的路由:
urlpatterns = [ path('', views.UserViewSet.as_view({'get': 'list', 'post': 'create'}), name='user-list'), path('update-by-email/', views.UserViewSet.as_view({'put': 'update_by_email'}), name='user-update-by-email'), path('<str:email>/', views.UserViewSet.as_view({'get': 'retrieve'}), name='user-detail'), ]
方法二:重写update方法(不推荐,破坏REST规范)
如果坚持要在根路径用PUT请求,可重写update方法,手动从请求体获取email定位用户,但这种方式不符合RESTful API设计规范(单个对象更新应使用带标识的URL):
class UserViewSet(viewsets.ModelViewSet): lookup_field = 'email' queryset = User.objects.all() serializer_class = UserSerializer def update(self, request, *args, **kwargs): # 忽略URL中的lookup参数,从请求体获取email email = request.data.get('email') if not email: return Response({'error': 'email为必填项'}, status=400) try: self.object = User.objects.get(email=email) except User.DoesNotExist: return Response({'error': '用户不存在'}, status=404) # 复用父类的更新逻辑 return super().update(request, *args, **kwargs)
补充说明
RESTful API的最佳实践中,单个资源的更新应通过带资源标识的URL(如/accounts/<email>/)发起PUT请求,请求体无需重复传递email。你的初始URL配置其实更符合规范,若觉得请求体带email冗余,可修改UserSerializer将email设为只读:
class UserSerializer(ModelSerializer): class Meta: model = User fields = ['id', 'email', 'password'] extra_kwargs = { 'password': {'write_only': True}, 'email': {'read_only': True} # 设置为只读 } def create(self, validated_data): user = User.objects.create_user(**validated_data) return user
这样更新时只需在URL中传email,请求体仅传新密码即可。
内容的提问来源于stack exchange,提问作者Omid Shojaee
相关产品推荐
相关产品推荐

