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
相关产品推荐
相关产品推荐

