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

重写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

额外验证步骤

  1. 执行数据库迁移:运行python manage.py makemigrations和python manage.py migrate,确保所有模型变更已同步到数据库。
  2. 查看Django日志:检查是否有隐藏的错误(如字段验证失败、外键约束错误)。
  3. 测试接口:用Postman或curl发送请求,查看返回的错误信息,定位具体问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 23:52:03