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

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,减少冗余逻辑:

  1. 修改序列化器,移除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)
  1. 前端请求时只需传递laptop和liked字段即可,无需提交profile相关参数。

内容的提问来源于stack exchange,提问作者G.Huan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 07:43:25