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

