【问题标题】:add parameter to get_queryset request in Django REST Framework在 Django REST Framework 中向 get_queryset 请求添加参数
【发布时间】:2018-05-19 05:27:33
【问题描述】:

我正在使用Django 2.0Django REST Framework

我在联系人应用程序中有两个模型

contacts/models.py

class Contact(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    first_name = models.CharField(max_length=100)
    last_name = models.CharField(max_length=100, blank=True, null=True, default='')


class ContactPhoneNumber(models.Model):
    contact = models.ForeignKey(Contact, on_delete=models.CASCADE)
    phone = models.CharField(max_length=100)
    primary = models.BooleanField(default=False)

    def __str__(self):
        return self.phone

contacts/serializers.py

class ContactPhoneNumberSerializer(serializers.ModelSerializer):
    class Meta:
        model = ContactPhoneNumber
        fields = ('id', 'phone', 'primary', 'created', 'modified')

contacts/views.py

class ContactPhoneNumberViewSet(viewsets.ModelViewSet):
    serializer_class = ContactPhoneNumberSerializer

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

urls.py

router.register(r'contact-phone', ContactPhoneNumberViewSet, 'contact_phone_numbers')

我想要的是跟随端点

  • GET: /contact-phone/{contact_id}/ 列出特定联系人的电话号码
  • POST:/contact-phone/{contact_id}/ 将电话号码添加到特定联系人
  • PUT: /contact-phone/{contact_phone_number_id}/ 更新特定电话号码
  • 删除: /contact-phone/{contact_phone_number_id}/ 删除特定电话号码

PUTDelete 可以作为ModelViewSet 的默认操作实现,但是如何使get_queryset 接受contact_id 作为必需参数?

编辑 2

我关注了文档Binding ViewSets to URLs explicitly

更新app/urls.py

router = routers.DefaultRouter()
router.register(r'contacts', ContactViewSet, 'contacts')
contact_phone_number_view_set = ContactPhoneNumberViewSet.as_view({
    'get': 'list/<contact_pk>/',
    'post': 'create/<contact_pk>/',
    'put': 'update',
    'delete': 'destroy'
})
router.register(r'contact-phone-number', contact_phone_number_view_set, 'contact_phone_numbers')

urlpatterns = [
    path('api/', include(router.urls)),
    url(r'^admin/', admin.site.urls),
]

但是报错了

AttributeError: 'function' object has no attribute 'get_extra_actions'

【问题讨论】:

    标签: django django-rest-framework


    【解决方案1】:

    您可以使用@action 装饰器向视图集添加额外的操作:

    class ContactPhoneNumberViewSet(viewsets.ModelViewSet):
        serializer_class = ContactPhoneNumberSerializer
    
        def get_queryset(self):
            return ContactPhoneNumber.objects.filter(
                contact__user=self.request.user
            )
    
        @action(methods=['post'], detail=False)
        def add_to_contact(self, request, contact_id=None):
            contact = Contact.objects.get(id=contact_id)
            serializer = ContactPhoneNumberSerializer(data=request.data)
            if serializer.is_valid():
                serializer.save(contact=contact)
                return Response(serializer.data)
            else:
                return Response(serializer.errors,
                                status=status.HTTP_400_BAD_REQUEST)
    
        @action(methods=['get'], detail=False)
        def set_password(self, request, contact_id=None):
            contact = Contact.objects.get(id=contact_id)
            serializer = PasswordSerializer(contact.contactphonenumber_set.all(), many=True)
            return Response(serializer.data)
    

    UPD

    由于您不需要额外的操作,您可以覆盖retrievecreate 默认方法:

    class ContactPhoneNumberViewSet(viewsets.ModelViewSet):
            serializer_class = ContactPhoneNumberSerializer
    
            def get_queryset(self):
                return ContactPhoneNumber.objects.filter(
                    contact__user=self.request.user
                )
    
            def create(self, request, pk=None):
                contact = Contact.objects.get(id=contact_id)
                serializer = ContactPhoneNumberSerializer(data=request.data)
                if serializer.is_valid():
                    serializer.save(contact=contact)
                    return Response(serializer.data)
                else:
                    return Response(serializer.errors,
                                    status=status.HTTP_400_BAD_REQUEST)
    
            def retrieve(self, request, pk=None):
                contact = Contact.objects.get(pk=pk)
                serializer = PasswordSerializer(contact.contactphonenumber_set.all(), many=True)
                return Response(serializer.data)
    

    要更改标准 create url,请使用明确的 url 绑定:

    contact_list = ContactPhoneNumberViewSet.as_view({
        'get': 'list',
        'post': 'create',
        'put': 'update',
        'delete': 'destroy'
    })
    
    urlpatterns = [
        path('api//contact-phone/<int:pk>/', contact_list, name='contact-list'),
        url(r'^admin/', admin.site.urls),
    ]
    

    【讨论】:

    • 那么我将不得不禁用get_querysetcreate 方法,因为我不想一次列出所有电话号码。我也想要干净的 URL 而不添加额外的操作。我不能将所需的附加参数传递给get_querset吗?
    • @AnujTBE 不确定,但我认为这是不可能的。问题是如果选择kwargcontact_phone_number_idcontact_id,您需要以某种方式决定get_queryset 方法。但是如果没有额外的操作,您将无法区分它们。
    • 区分基于请求方法。请求方法 GETPOST contact_id 将被传递,其余的 contact_phone_number_id 将被传递。
    • 感谢您的帮助。通过定义额外的操作来使用你的第一个答案。
    • @AnujTBE 没有机会测试它。尝试像这样使用操作的 url_path 参数:@action(methods=['post'], detail=False, url_path='delete_phone/&lt;phone_pk&gt;/').