【问题标题】:How to implement HATEOAS-style links with Django REST framework's ModelViewSets?如何使用 Django REST 框架 ModelViewSet 实现 HATEOAS 样式的链接?
【发布时间】:2021-08-12 19:27:41
【问题描述】:

我有这个产品型号

import uuid
from django.db import models


class Product(models.Model):
    """Product in the store."""

    title = models.CharField(max_length=120)
    description = models.TextField(blank=True)
    price = models.DecimalField(decimal_places=2, max_digits=10)
    inventory = models.IntegerField(default=0)
    uuid = models.UUIDField(default=uuid.uuid4, editable=False)

    def __str__(self):
        return f'{self.title}'

我为它定义了一个序列化器

from rest_framework import serializers
from .models import Product


class ProductSerializer(serializers.ModelSerializer):
    class Meta:
        model = Product
        fields = ['title', 'description', 'price', 'inventory', 'uuid']

这是我的观点

from rest_framework import viewsets
from .serializers import ProductSerializer

from .models import Product


class ProductViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer
    lookup_field = 'uuid'

要开始这个问题,请记住此视图路由到/store/products/。 现在,例如,我可以执行 GET http://localhost/store/products/ 并返回

[
    {
        "title": "Computer",
        "description": "",
        "price": "50.00",
        "inventory": 10,
        "uuid": "2d849f18-7dea-42b9-9dac-2ea8a17444c2"
    }
]

但我希望它返回类似的东西


[
    {
        "href": "http://localhost/store/products/2d849f18-7dea-42b9-9dac-2ea8a17444c2"
    }
]

然后让http://localhost/store/products/2d849f18-7dea-42b9-9dac-2ea8a17444c2返回

{
    "title": "Computer",
    "description": "",
    "price": "50.00",
    "inventory": 10,
    "uuid": "2d849f18-7dea-42b9-9dac-2ea8a17444c2"
}

就像它已经做到的那样。这是否可以使用内置序列化,或者我如何定义一个这样做的?我玩过list_serializer_class 属性,但我什么都做不了。

【问题讨论】:

标签: django django-rest-framework


【解决方案1】:

超链接的序列化程序可以返回指向另一个视图或同一视图集的另一部分的属性:

序列化器:

from rest_framework import serializers

class ProductListSerializer(serializers.ModelSerializer):
    href = serializers.HyperlinkedIdentityField(view_name='<app_name>:product-detail', lookup_field='uuid')

    class Meta:
        model = Product
        fields = ['href']

class ProductDetailSerializer(serializers.ModelSerializer):
    class Meta:
        model = Product
        fields = ['title', 'description', 'price', 'inventory', 'uuid']

网址:

from rest_framework import routers

app_name ='<app_name>'

router = routers.DefaultRouter()
router.register(r'products', ProductViewSet, basename='product')
urlpatterns = router.urls

视图集:

from rest_framework.response import Response
from django.shortcuts import get_object_or_404

class ProductViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = Product.objects.all()
    lookup_field = 'uuid'

    def list(self, request, **kwargs):
        serializer = ProductListSerializer(self.get_queryset(), many=True, context={'request': request})
        return Response(serializer.data)

    def retrieve(self, request, uuid=None, *args, **kwargs):
        record = get_object_or_404(self.get_queryset(), uuid=uuid)
        serializer = ProductDetailSerializer(record)
        return Response(serializer.data)

编辑:更新为使用 uuid,而不是 pk,如您的示例所示

【讨论】:

  • Could not resolve URL for hyperlinked relationship using view name "product-detail". You may have failed to include the related model in your API, or incorrectly configured the 'lookup_field' attribute on this field.。我很早就得到了这个,但还没有找到解决它的方法。
  • 这很好奇。只要 ViewSet 和 HyperlinkedIdentityField 中的 lookup_field 相同,它就应该工作。我在一个测试项目中循环了几个不同的lookup_fields,只要这两个同步,这个错误就会得到解决。
  • 哦,我使用了“产品详细信息”,因为我不知道您的网址结构。如果您在命名空间中,它可能需要是 ':product-detail'
  • 看来问题是在完全加载urlconf之前导入了ProductListSerializer,这就是product-detail名称不可用的原因。
  • 我错误地在 def list 和 def 检索中使用了 self.queryset,而不是 self.get_queryset(),这可能会阻止它在每次调用时重新初始化查询集。看看这是否解决了您的问题。
【解决方案2】:

对于 URL,您可以有 4 个选项

1- 在您的模型中使用 SlugField 并使用 uuid 预填充它。你可以在Auto Populate Slug field django阅读它

2- 在您的序列化程序中使用 SerializerMethodField 并在其中创建 URL:

class ProductListSerializer(serializers.ModelSerializer):
    url = serializers.SerializerMethodField()
    ...
    def get_url(self, obj):
        return self.request.build_absolute_url() + obj.uuid

您可以在https://www.django-rest-framework.org/api-guide/fields/#serializermethodfield 中阅读有关SerializerMethodField 的信息

3- 在您的模型中覆盖 get_absolute_url 并在您的 SerializerMethodField 中使用它。阅读https://docs.djangoproject.com/en/3.2/ref/models/instances/#get-absolute-url

4- 在序列化程序中使用HyperlinkedIdentityFieldhttps://www.django-rest-framework.org/api-guide/relations/#hyperlinkedidentityfield中的文档


对于详细信息和列表部分,请使用 2 个单独的序列化程序,例如:

class ProductDetailSerializer(serializers.ModelSerializer):
    class Meta:
        model = Product
        fields = ['title', 'description', 'price', 'inventory', 'uuid']

class ProductListSerializer(serializers.Serializer):
    url = serializers.SerializerMethodField()
    
    def get_url(self, obj):
        ...

然后,在你的views.py 覆盖get_serializer_class

class ProductViewSet(viewsets.ReadOnlyModelViewSet):
    ...
    def get_serializer_class():
        if self.action == 'retrieve':
            return ProductDetailSerializer
        if self.action == 'list':
            return ProductListSerializer

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-01-26
    • 1970-01-01
    • 2016-03-29
    • 2018-10-16
    • 1970-01-01
    • 2020-03-24
    • 2017-11-06
    • 2019-03-03
    相关资源
    最近更新 更多