重写Djoser的UserCreateSerializer后无法创建模型实例求助
问题描述
自定义Djoser提供的UserCreateSerializer,尝试在方法中创建UserProfile与AddressDetails模型实例,服务器运行正常,但关联数据未按预期持久化到数据库,求排查及修正方案。
相关代码
accounts.serializers.py
from djoser.serializers import UserCreateSerializer from django.contrib.auth import get_user_model from rest_framework import serializers from django.db import transaction from django.db.utils import IntegrityError from django.contrib.auth.password_validation import validate_password from .models import AddressDetails, UserProfile User = get_user_model() class UserCreateSerializer(UserCreateSerializer): address_line_one = serializers.CharField(write_only=True) address_line_two = serializers.CharField(write_only=True) address_line_three = serializers.CharField(write_only=True) province = serializers.CharField(write_only=True, required=True) barangay = serializers.CharField(write_only=True, required=True) city = serializers.CharField(write_only=True, required=True) zip_code = serializers.CharField(write_only=True, required=True) first_name = serializers.CharField(write_only=True, required=True) middle_name = serializers.CharField(write_only=True, required=True) last_name = serializers.CharField(write_only=True, required=True) date_of_birth = serializers.DateField() gender = serializers.CharField(write_only=True, required=True) relationship_status = serializers.CharField(write_only=True, required=True) phone_number = serializers.CharField(write_only=True, required=True) class Meta(UserCreateSerializer.Meta): model = User fields = ( "id", "email", "user_role", "password", "address_line_one", "address_line_two", "address_line_three", "province", "barangay", "city", "zip_code", "first_name", "middle_name", "last_name", "date_of_birth", "gender", "relationship_status", "phone_number" ) @transaction.atomic def perform_create(self, validated_data): address_line_one = validated_data.pop("address_line_one") address_line_two = validated_data.pop("address_line_two") address_line_three = validated_data.pop("address_line_three") province = validated_data.pop("province") barangay = validated_data.pop("barangay") city = validated_data.pop("city") zip_code = validated_data.pop("zip_code") first_name = validated_data.pop("first_name") middle_name = validated_data.pop("middle_name") last_name = validated_data.pop("last_name") date_of_birth = validated_data.pop("date_of_birth") gender = validated_data.pop("gender") relationship_status = validated_data.pop("relationship_status") phone_number = validated_data.pop("phone_number") with transaction.atomic(): user = User.objects.create_user(**validated_data) address_details = AddressDetails.objects.create( address_line_one = address_line_one, address_line_two = address_line_two, address_line_three = address_line_three, province = province, barangay = barangay, city = city, zip_code = zip_code ) UserProfile.objects.create( user = user, first_name = first_name, middle_name = middle_name, last_name = last_name, date_of_birth = date_of_birth, gender = gender, relationship_status = relationship_status, phone_number = phone_number, address_details = address_details ) return user def validate_password(self, value): user = self.user if self.user else self.Meta.model() try: validate_password(value, user) except serializers.ValidationError as e: serializer_error = serializers.as_serializer_error(e) raise serializers.ValidationError( {"password": serializer_error[api_setting.NON_FIELDS_ERRORS_KEY]} ) return value
accounts.models.py
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin from django.db import models #type: ignore from django.utils.translation import gettext_lazy as _ from django.utils import timezone from phonenumber_field.modelfields import PhoneNumberField #type: ignore # Create your models here. class UserAccountManager(BaseUserManager): def create_user(self, email, user_role, password=None, **extra_fields): if not email: raise ValueError("Users must have an email address.") if not user_role: raise ValueError("Users must have a user role.") email = self.normalize_email(email) user = self.model(email=email, user_role=user_role, **extra_fields) user.set_password(password) user.save(using=self._db) return user def create_superuser(self, email, user_role="admin", password=None, **extra_fields): extra_fields.setdefault("is_staff", True) extra_fields.setdefault("is_admin", True) extra_fields.setdefault("is_active", True) extra_fields.setdefault("is_superuser", True) if extra_fields.get("is_staff") is not True: raise ValueError(_("Superuser must have is_staff=True.")) if extra_fields.get("is_admin") is not True: raise ValueError(_("Superuser must have is_admin=True.")) if extra_fields.get("is_superuser") is not True: raise ValueError(_("Superuser must have is_superuser=True.")) return self.create_user(email, user_role, password, **extra_fields) class UserAccount(AbstractBaseUser, PermissionsMixin): USER_ROLES = ( ("resident", "Resident"), ("healthworker", "Health Worker"), ("admin", "Admin") ) email = models.EmailField(_("email address"), unique=True) user_role = models.CharField(max_length=100, choices=USER_ROLES) is_staff = models.BooleanField(default=False) is_admin = models.BooleanField(default=False) is_active = models.BooleanField(default=True) date_joined = models.DateTimeField(default=timezone.now) USERNAME_FIELD = "email" REQUIRED_FIELDS = ["user_role",] objects = UserAccountManager() def __str__(self): return self.email def has_perm(self, perm, obj=None): return True def has_module_perms(self, app_label): return True def save(self, *args, **kwargs): if self.user_role == "admin": self.is_staff = True self.is_admin = True elif self.user_role == "healthworker": self.is_staff = True self.is_admin = False else: self.is_staff = False self.is_admin = False super().save(*args, **kwargs) class AddressDetails(models.Model): address_line_one = models.CharField(max_length=150, blank=True,) address_line_two = models.CharField(max_length=150, blank=True,) address_line_three = models.CharField(max_length=150, blank=True,) province = models.CharField(max_length=150, blank=True,) barangay = models.CharField(max_length=150, blank=True,) city = models.CharField(max_length=150, blank=True,) zip_code = models.CharField(max_length=5, blank=True,) class UserProfile(models.Model): GENDER_CHOICES = ( ("F", "Female",), ("M", "Male",), ("U", "Unsure",), ) user = models.OneToOneField(UserAccount, on_delete=models.CASCADE) first_name = models.CharField(max_length=150,) middle_name = models.CharField(max_length=150,) last_name = models.CharField(max_length=150,) date_of_birth = models.DateField() gender = models.CharField(max_length=1, choices=GENDER_CHOICES,) relationship_status = models.CharField(max_length=50,) phone_number = PhoneNumberField() address_details = models.OneToOneField(AddressDetails, on_delete=models.CASCADE)
排查与修正方案
核心问题1:Serializer误用perform_create方法
DRF的序列化器(Serializer)类没有perform_create方法,这个方法属于视图层(如CreateAPIView或ViewSet)。Djoser的UserCreateSerializer作为序列化器,不会触发你定义的perform_create,导致创建关联数据的代码完全未执行。
修正:重写序列化器的create方法,替换perform_create。
核心问题2:未导入api_settings导致报错
validate_password方法中使用了api_setting但未导入,会引发NameError,导致序列化失败,数据无法持久化。
修正:新增from rest_framework.settings import api_settings导入,并将api_setting改为api_settings。
核心问题3:PhoneNumberField字段不匹配
UserProfile模型的phone_number是PhoneNumberField,但序列化器中用了CharField,可能导致数据格式验证失败,无法创建实例。
修正:导入phonenumber_field.serializerfields.PhoneNumberField,将序列化器中的phone_number字段替换为该类型。
修正后的完整Serializer代码
from djoser.serializers import UserCreateSerializer from django.contrib.auth import get_user_model from rest_framework import serializers from django.db import transaction from django.contrib.auth.password_validation import validate_password from rest_framework.settings import api_settings from phonenumber_field.serializerfields import PhoneNumberField from .models import AddressDetails, UserProfile User = get_user_model() class UserCreateSerializer(UserCreateSerializer): address_line_one = serializers.CharField(write_only=True) address_line_two = serializers.CharField(write_only=True) address_line_three = serializers.CharField(write_only=True) province = serializers.CharField(write_only=True, required=True) barangay = serializers.CharField(write_only=True, required=True) city = serializers.CharField(write_only=True, required=True) zip_code = serializers.CharField(write_only=True, required=True) first_name = serializers.CharField(write_only=True, required=True) middle_name = serializers.CharField(write_only=True, required=True) last_name = serializers.CharField(write_only=True, required=True) date_of_birth = serializers.DateField() gender = serializers.CharField(write_only=True, required=True) relationship_status = serializers.CharField(write_only=True, required=True) phone_number = PhoneNumberField(write_only=True, required=True) class Meta(UserCreateSerializer.Meta): model = User fields = ( "id", "email", "user_role", "password", "address_line_one", "address_line_two", "address_line_three", "province", "barangay", "city", "zip_code", "first_name", "middle_name", "last_name", "date_of_birth", "gender", "relationship_status", "phone_number" ) @transaction.atomic def create(self, validated_data): # 提取关联数据字段 address_data = { k: validated_data.pop(k) for k in [ "address_line_one", "address_line_two", "address_line_three", "province", "barangay", "city", "zip_code" ] } profile_data = { k: validated_data.pop(k) for k in [ "first_name", "middle_name", "last_name", "date_of_birth", "gender", "relationship_status", "phone_number" ] } # 原子事务内创建所有实例 user = User.objects.create_user(**validated_data) address_details = AddressDetails.objects.create(**address_data) UserProfile.objects.create(user=user, address_details=address_details, **profile_data) return user def validate_password(self, value): user = self.user if self.user else self.Meta.model() try: validate_password(value, user) except serializers.ValidationError as e: serializer_error = serializers.as_serializer_error(e) raise serializers.ValidationError( {"password": serializer_error[api_settings.NON_FIELDS_ERRORS_KEY]} ) return value
额外验证步骤
- 执行数据库迁移:运行
python manage.py makemigrations和python manage.py migrate,确保所有模型变更已同步到数据库。 - 查看Django日志:检查是否有隐藏的错误(如字段验证失败、外键约束错误)。
- 测试接口:用Postman或curl发送请求,查看返回的错误信息,定位具体问题。
内容的提问来源于stack exchange,提问作者juanl4zy
相关产品推荐
相关产品推荐

