You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 06:40:06