在Django REST Framework中为get_queryset添加contact_id参数及路由问题
解决Django REST Framework中ContactPhoneNumberViewSet的路由与查询集问题
我来帮你一步步搞定这个问题,先从错误原因说起,再给你完整的实现方案:
先解释你遇到的AttributeError
你碰到的'function' object has no attribute 'get_extra_actions'错误,大概率是因为你在配置路由时,错误地把ViewSet的某个方法(比如list)直接传给了as_view(),或者用了不正确的路由器注册方式。DRF的ViewSet需要通过字典映射HTTP方法到对应的动作,直接传方法会导致返回的是一个普通函数,而不是带有ViewSet特性的视图类,自然就没有get_extra_actions这个方法了。
完整实现方案
1. 先确认模型关联(假设你的模型是这样的,可根据实际调整)
# contacts/models.py from django.db import models from django.contrib.auth.models import User class Contact(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="contacts") name = models.CharField(max_length=100) # 其他字段:邮箱、地址等 class ContactPhoneNumber(models.Model): contact = models.ForeignKey(Contact, on_delete=models.CASCADE, related_name="phone_numbers") number = models.CharField(max_length=20) type = models.CharField(max_length=20, choices=[("home", "家庭"), ("work", "工作"), ("mobile", "手机")])
2. 调整ViewSet逻辑
我们需要让get_queryset能读取路由里的contact_id参数,同时确保所有操作都只针对当前登录用户的联系人:
# contacts/views.py from rest_framework import viewsets from rest_framework.permissions import IsAuthenticated from rest_framework.exceptions import PermissionDenied from .models import ContactPhoneNumber, Contact from .serializers import ContactPhoneNumberSerializer class ContactPhoneNumberViewSet(viewsets.ModelViewSet): serializer_class = ContactPhoneNumberSerializer permission_classes = [IsAuthenticated] lookup_field = "id" # 指定PUT/DELETE用的是电话号码的id def get_queryset(self): # 先获取当前用户的所有联系人 user_contacts = Contact.objects.filter(user=self.request.user) # 读取路由中的contact_id参数 contact_id = self.kwargs.get("contact_id") if contact_id: # 验证该联系人属于当前用户 target_contact = user_contacts.filter(id=contact_id).first() if not target_contact: return ContactPhoneNumber.objects.none() # 返回空结果,或抛出404 return target_contact.phone_numbers.all() # 如果没有contact_id(备用逻辑,你可以根据需求去掉) return ContactPhoneNumber.objects.filter(contact__in=user_contacts) # 重写create方法,确保添加的号码属于当前用户的指定联系人 def perform_create(self, serializer): contact_id = self.kwargs.get("contact_id") target_contact = Contact.objects.filter(user=self.request.user, id=contact_id).first() if not target_contact: raise PermissionDenied("你无权为该联系人添加电话号码") serializer.save(contact=target_contact)
3. 正确配置路由
因为我们有两种URL模式(带contact_id的列表/创建,带电话号码id的更新/删除),所以需要手动配置路由,而不是用DRF的默认路由器注册:
# contacts/urls.py from django.urls import path from .views import ContactPhoneNumberViewSet urlpatterns = [ # GET: 列出指定联系人的电话号码;POST: 为指定联系人添加号码 path( "contact-phone/<int:contact_id>/", ContactPhoneNumberViewSet.as_view({"get": "list", "post": "create"}), name="contact-phone-list" ), # PUT: 更新指定电话号码;DELETE: 删除指定电话号码 path( "contact-phone/<int:pk>/", ContactPhoneNumberViewSet.as_view({"put": "update", "delete": "destroy"}), name="contact-phone-detail" ), ]
关键注意点
- 用
self.kwargs.get("contact_id")从路由中获取参数,这是DRF ViewSet读取URL参数的标准方式 - 所有操作都要验证联系人属于当前用户,避免越权访问
- 路由配置必须用字典映射HTTP方法到ViewSet的动作(比如
{"get": "list"}),不能直接传方法名
内容的提问来源于stack exchange,提问作者Anuj TBE
相关产品推荐
相关产品推荐

