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

Django Rest Serializer:增改用ID、查询返回超链接的实现方法

解决DRF中HyperlinkedModelSerializer关联字段增改用ID、查询返回超链接的问题

问题场景

使用Django Rest Framework时,现有如下模型与序列化器代码:

models.py

class User(models.Model):
    name = models.CharField(max_length=300)

class Booking(models.Model):
    time = models.DateTimeField()
    user_id = models.ForeignKey(User, related_name='booking', on_delete=models.CASCADE)

serializers.py

class UserSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = User
        fields = '__all__'

class BookingSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Booking
        fields = '__all__'

创建Booking时会报错:user_id ":["Incorrect type. Expected URL string, received int."]。原因是HyperlinkedModelSerializer对关联字段默认要求传入URL,但实际开发中增改数据时传入ID更便捷,需要实现增改时接受ID,仅查询时返回超链接的效果。

解决方案

方法一:自定义兼容字段

自定义一个继承自HyperlinkedRelatedField的字段,重写to_internal_value方法,让它同时支持ID和URL两种输入格式,查询时依然返回超链接。

from rest_framework import serializers
from .models import User, Booking

class HybridHyperlinkedRelatedField(serializers.HyperlinkedRelatedField):
    def to_internal_value(self, data):
        try:
            # 优先按ID解析输入
            return self.queryset.get(pk=data)
        except (TypeError, ValueError, self.queryset.model.DoesNotExist):
            # 解析失败则回退到默认的URL处理逻辑
            return super().to_internal_value(data)

class BookingSerializer(serializers.HyperlinkedModelSerializer):
    user_id = HybridHyperlinkedRelatedField(
        view_name='user-detail',  # 替换为你项目中User详情视图的URL名称
        queryset=User.objects.all()
    )

    class Meta:
        model = Booking
        fields = '__all__'

注意:view_name需要与项目中User对应的详情视图URL名称一致,若使用DefaultRouter自动生成路由,默认名称为user-detail。

方法二:动态切换字段类型

通过重写序列化器的__init__方法,根据请求方法判断当前是读操作还是写操作,动态设置字段类型:查询时用超链接字段返回URL,增改时用主键字段接受ID。

from rest_framework import serializers
from .models import User, Booking

class BookingSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Booking
        fields = '__all__'

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        request = self.context.get('request')
        # 判断当前是否为写操作(创建/更新)
        if request and request.method in ('POST', 'PUT', 'PATCH'):
            # 写操作时使用主键字段接受ID
            self.fields['user_id'] = serializers.PrimaryKeyRelatedField(
                queryset=User.objects.all()
            )
        else:
            # 读操作时使用超链接字段返回URL
            self.fields['user_id'] = serializers.HyperlinkedRelatedField(
                view_name='user-detail',
                queryset=User.objects.all(),
                read_only=False
            )

额外建议

模型中外键字段建议命名为user而非user_id,因为Django会自动在数据库生成user_id字段,模型内用user更符合Django命名规范,避免混淆。修改后的模型代码:

class Booking(models.Model):
    time = models.DateTimeField()
    user = models.ForeignKey(User, related_name='booking', on_delete=models.CASCADE)

对应的序列化器字段也需改为user,代码逻辑会更清晰。

内容的提问来源于stack exchange,提问作者Apezzz

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 02:49:59