DRF使用SlugRelatedField双下划线slug_field触发AttributeError
解决DRF中UserImpression创建时的AttributeError问题
问题场景
使用Django REST Framework发送POST请求创建UserImpression实例时,数据已成功存入数据库,但触发以下错误:
AttributeError: 'UserProfile' object has no attribute 'user__username'
相关代码
模型定义
class UserProfile(models.Model): user = models.OneToOneField(User, related_name='profile', on_delete=models.CASCADE, primary_key=True) avatar = models.ImageField(upload_to=user_directory_path, null=True, blank=True) imp_laptop = models.ManyToManyField(Laptop, related_name="laptop_impression", through="UserImpression", through_fields=('profile', 'laptop')) objects = models.Manager() custom_manager = UserProfileCustomManager() def __str__(self): return self.user.username + ' profile' class UserImpression(models.Model): profile = models.ForeignKey(UserProfile, on_delete=models.CASCADE) laptop = models.ForeignKey(Laptop, on_delete=models.CASCADE) liked = models.BooleanField(default=True) class Meta: constraints = [ models.UniqueConstraint(fields=['profile', 'laptop'], name='unique_profile_laptop_impression'), ]
序列化器定义
class UserImpressionSerializer(serializers.ModelSerializer): profile = serializers.SlugRelatedField(queryset=UserProfile.objects.all(), slug_field="user__username") laptop = serializers.SlugRelatedField(queryset=Laptop.objects.all(), slug_field="slug") class Meta: model = UserImpression fields = ['profile', 'laptop', 'liked'] validators = [ UniqueTogetherValidator( queryset=UserImpression.objects.all(), fields=['profile', 'laptop'] ) ] def create(self, validated_data): profile = validated_data.get("profile", None) laptop = validated_data.get("laptop", None) if profile is None: raise serializers.ValidationError( {"profile": "Cannot retrieve the profile instance with the username"} ) if laptop is None: raise serializers.ValidationError( {"laptop": "Cannot retrieve the laptop instance with the laptop slug"} ) liked = validated_data.get("liked", True) return UserImpression.objects.create(profile=profile, laptop=laptop, liked=liked)
视图定义
class UserLikeDislike(generics.CreateAPIView, generics.UpdateAPIView, generics.DestroyAPIView): permission_classes = [IsAuthenticated, TokenHasReadWriteScope] queryset = UserImpression.objects.all() serializer_class = UserImpressionSerializer
错误原因
SlugRelatedField的slug_field参数仅支持当前模型的直接字段名,无法识别跨关系的双下划线语法(user__username)。序列化器在创建实例后返回序列化结果时,会尝试从UserProfile实例中直接读取user__username属性,而该属性不存在,因此抛出AttributeError。
解决方案
方案一:自定义SlugRelatedField支持跨关系字段
继承SlugRelatedField,重写to_representation方法,手动解析跨关系字段路径:
class CrossRelationalSlugRelatedField(serializers.SlugRelatedField): def to_representation(self, instance): # 拆分双下划线路径,逐层获取关联属性 fields = self.slug_field.split('__') value = instance for field in fields: value = getattr(value, field) return value class UserImpressionSerializer(serializers.ModelSerializer): profile = CrossRelationalSlugRelatedField( queryset=UserProfile.objects.all(), slug_field="user__username" ) laptop = serializers.SlugRelatedField(queryset=Laptop.objects.all(), slug_field="slug") # 其余代码保持不变
方案二:在UserProfile中添加代理属性
在UserProfile模型中新增一个代理属性,直接返回关联User的username,让SlugRelatedField可以直接引用:
class UserProfile(models.Model): user = models.OneToOneField(User, related_name='profile', on_delete=models.CASCADE, primary_key=True) avatar = models.ImageField(upload_to=user_directory_path, null=True, blank=True) imp_laptop = models.ManyToManyField(Laptop, related_name="laptop_impression", through="UserImpression", through_fields=('profile', 'laptop')) objects = models.Manager() custom_manager = UserProfileCustomManager() @property def user_username(self): return self.user.username def __str__(self): return self.user.username + ' profile'
然后修改序列化器的slug_field参数:
class UserImpressionSerializer(serializers.ModelSerializer): profile = serializers.SlugRelatedField( queryset=UserProfile.objects.all(), slug_field="user_username" ) # 其余代码保持不变
方案三:利用当前登录用户简化逻辑(推荐)
由于视图已配置IsAuthenticated权限,可直接从请求上下文获取当前用户的UserProfile,无需前端传递username,减少冗余逻辑:
- 修改序列化器,移除
profile字段的显式定义:
class UserImpressionSerializer(serializers.ModelSerializer): laptop = serializers.SlugRelatedField(queryset=Laptop.objects.all(), slug_field="slug") class Meta: model = UserImpression fields = ['laptop', 'liked'] validators = [ UniqueTogetherValidator( queryset=UserImpression.objects.all(), fields=['profile', 'laptop'] ) ] def create(self, validated_data): # 从请求上下文获取当前登录用户的profile profile = self.context['request'].user.profile laptop = validated_data.get("laptop") liked = validated_data.get("liked", True) return UserImpression.objects.create(profile=profile, laptop=laptop, liked=liked)
- 前端请求时只需传递
laptop和liked字段即可,无需提交profile相关参数。
内容的提问来源于stack exchange,提问作者G.Huan
相关产品推荐
相关产品推荐

