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

DRF序列化器嵌套关系报错:UserProfile无user_profile属性

解决嵌套序列化器引发的AttributeError问题

错误详情

访问/api/register/时触发AttributeError:

尝试在序列化器UserRegistrationSerializer上获取字段user_profile的值时触发该错误。
序列化器字段名称可能不正确,与UserProfile实例的属性或键不匹配。
原始异常信息为:'UserProfile'对象没有属性'user_profile'

相关代码

UserProfile模型定义

from django.db import models
from django.contrib.auth.models import User

# Create your models here.
class UserProfile(models.Model):
    user = models.OneToOneField(User, models.CASCADE)
    date_of_birth = models.DateField()
    phone_number = models.CharField(max_length=20, blank=False)
    is_phone_verified = models.BooleanField(default=False)
    is_email_verified = models.BooleanField(default=False)
    phone_code = models.CharField(max_length=20, blank=False)
    email_code = models.CharField(max_length=20, blank=False)

serializers.py代码

from django.contrib.auth.models import User
from .models import UserProfile
from django.core.exceptions import ValidationError
from datetime import datetime
from rest_framework.validators import UniqueValidator
from rest_framework import serializers
from django.core import validators

class UserModelSerializer(serializers.ModelSerializer):
    first_name = serializers.CharField(
        required=True,
        max_length=50,
        error_messages={
            'required': 'Please enter your name.',
            'max_length': 'Please enter a valid name.'
        }
    )

    last_name = serializers.CharField(
        required=True,
        max_length=50,
        error_messages={
            'required': 'Please enter your name.',
            'max_length': 'Please enter a valid name.'
        }
    )

    username = serializers.CharField(
        required=True,
        max_length=30,
        validators=[UniqueValidator(queryset=User.objects.all())],
        error_messages={
            'required': 'Please enter a username.',
            'max_length': 'Username should be no more than 30 characters.',
            'unique': 'This username is already taken. Please choose a different one.',
        }
    )

    email = serializers.EmailField(
        required=True,
        max_length=50,
        validators=[UniqueValidator(queryset=User.objects.all())],
        error_messages={
            'required': 'Please enter your email address.',
            'max_length': 'Please enter a valid email address.'
        }
    )

    password = serializers.CharField(
        required=True,
        write_only=True,
        max_length=128,  # Maximum length for Django password
        error_messages={
            'required': 'Please enter a password.',
            'max_length': 'Password should be no more than 128 characters.'
        },
        validators=[
            validators.MinLengthValidator(8, message='Password must be at least 8 characters.'),
            validators.RegexValidator(
                regex=r'[A-Z]',
                message='Password must contain at least one uppercase letter.',
                code='invalid_password'
            ),
            validators.RegexValidator(
                regex=r'[a-z]',
                message='Password must contain at least one lowercase letter.',
                code='invalid_password'
            ),
            validators.RegexValidator(
                regex=r'[0-9]',
                message='Password must contain at least one number.',
                code='invalid_password'
            ),
            validators.RegexValidator(
                regex=r'[!@#$%^&*()]',
                message='Password must contain at least one special character.',
                code='invalid_password'
            )
        ]
    )

    registration_date = serializers.DateTimeField(
        read_only=True,
        default=serializers.CreateOnlyDefault(datetime.now())
    )

    class Meta:
        model = User
        fields = ('first_name', 'last_name', 'username', 'email', 'password', 'registration_date')


class UserProfileModelSerializer(serializers.ModelSerializer):
    phone_number = serializers.CharField(
        required=True,
        max_length=20,
        validators=[UniqueValidator(queryset=UserProfile.objects.all())],
        error_messages={
            'required': 'Please enter your phone number.',
            'max_length': 'Please enter a valid phone number.',
            'unique': 'This phone number is already taken. Please choose a different one.',
        }
    )

    date_of_birth = serializers.DateField(
        required=True,
        error_messages={
            'required': 'Please enter your date of birth.'
        }
    )

    class Meta:
        model = UserProfile
        fields = ['phone_number', 'date_of_birth']


class UserRegistrationSerializer(serializers.ModelSerializer):
    user = UserModelSerializer(required=True)
    user_profile = UserProfileModelSerializer(required=True)

    def create(self, validated_data):
        user_data = validated_data.pop('user')
        user_profile_data = validated_data.pop('user_profile')

        user = User.objects.create_user(**user_data)
        user_profile = UserProfile.objects.create(user=user, **user_profile_data)
        return user_profile

    class Meta:
        model = UserProfile
        fields = ('user', 'user_profile')

views.py代码

from rest_framework import generics
from .serializers import UserRegistrationSerializer
from rest_framework.response import Response
from rest_framework import status
# Create your views here.

class RegisterAPIView(generics.CreateAPIView):
    serializer_class = UserRegistrationSerializer
    
    def post(self, request):
        serializer = self.serializer_class(data=request.data)

        if serializer.is_valid():
            # print(serializer.data)
            serializer.save()

            # user = serializer.save()
            # refresh = RefreshToken.for_user(user)
            # return Response({
            #     'refresh': str(refresh),
            #     'access': str(refresh.access_token),
            #     'user': serializer.data
            # }, status=status.HTTP_201_CREATED)

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

问题原因

UserRegistrationSerializer的Meta类绑定了UserProfile模型,同时又在序列化器中定义了user_profile嵌套字段——但UserProfile实例本身并没有user_profile这个属性,序列化器在尝试读取返回对象的该属性时自然抛出错误。另外这个设计逻辑冗余:序列化器本身就是基于UserProfile模型的,没必要再嵌套自身的序列化器。

解决方案

修改UserRegistrationSerializer,移除多余的user_profile字段,直接整合UserProfile的字段逻辑:

修正后的UserRegistrationSerializer

class UserRegistrationSerializer(serializers.ModelSerializer):
    user = UserModelSerializer(required=True)
    # 直接定义UserProfile的字段,替代嵌套的序列化器
    phone_number = serializers.CharField(
        required=True,
        max_length=20,
        validators=[UniqueValidator(queryset=UserProfile.objects.all())],
        error_messages={
            'required': 'Please enter your phone number.',
            'max_length': 'Please enter a valid phone number.',
            'unique': 'This phone number is already taken. Please choose a different one.',
        }
    )
    date_of_birth = serializers.DateField(
        required=True,
        error_messages={
            'required': 'Please enter your date of birth.'
        }
    )

    def create(self, validated_data):
        user_data = validated_data.pop('user')
        # 直接取出剩余的profile字段数据,无需再处理嵌套的user_profile
        user = User.objects.create_user(**user_data)
        user_profile = UserProfile.objects.create(user=user, **validated_data)
        return user_profile

    class Meta:
        model = UserProfile
        fields = ('user', 'phone_number', 'date_of_birth')

如果想保留嵌套序列化器的写法,也可以将UserRegistrationSerializer的Meta模型改为User,并调整create方法的返回逻辑,但上面的修改是最直接简洁的方案,完全贴合注册接口创建User与关联UserProfile的业务逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 17:08:07