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

Django CreateView通过3个关联模型的ModelForm无法创建新用户问题咨询

我在Django项目中创建了多个模型,其中包含一个扩展的User模型。最初我通过UserCreationForm和基于ModelForm的EmployeeForm两个表单,使用函数视图完成数据验证和用户创建,流程完全正常。直到我引入第三个表单并改用CreateView实现后,就出现了数据不保存、也不跳转的问题。

模型代码

class Employee(models.Model):
    SENIOR = 'Sr'
    JUNIOR = 'Jr'
    FIRST = 'I'
    SECOND = 'II'
    THIRD = 'III'

    GENERATIONAL_SUFFIX = [
        (SENIOR, 'Senior'),
        (JUNIOR, 'Junior'),
        (FIRST, 'First'),
        (SECOND, 'Second'),
        (THIRD, 'Third'),
    ]

    MALE = 'male'
    FEMALE = 'female'
    OTHER = 'other'

    SEX = [
        (MALE, 'Male'),
        (FEMALE, 'Female'),
        (OTHER, 'Other'),
    ]

    user = models.OneToOneField(User, on_delete=models.CASCADE)
    phone_regex = RegexValidator(regex=r'^\d{11}$', message="Phone number must be entered in the format: '09151234567'.")
    phone_number = models.CharField(validators=[phone_regex], max_length=11, blank=True)
    middle_name = models.CharField(max_length=20, blank=True)
    sex = models.CharField(max_length=6, choices=SEX, blank=True)
    suffix = models.CharField(max_length=3, choices=GENERATIONAL_SUFFIX, blank=True)
    birthday = models.DateField(null=True, blank=True)
    hire_date = models.DateField(null=True, blank=True)
    image = models.ImageField(blank=True, default='blank_profile_picture.jpg')
    slug = models.SlugField(max_length=60, blank=True, null=True)
    updated = models.DateTimeField(auto_now=True)

    @property
    def get_full_name(self):
        first_name = self.user.first_name
        middle_name = self.middle_name
        last_name = self.user.last_name

        if middle_name is None:
            full_name = f'{first_name} {last_name}'
            return full_name
        else:
            full_name = f'{first_name} {middle_name} {last_name}'
            return full_name

    def save(self, *args, **kwargs):
        self.slug = slugify(self.get_full_name)
        super().save(*args, **kwargs)

    def __str__(self):
        return self.get_full_name

    
class EmployeePosition(models.Model):
    employee = models.ForeignKey(Employee, on_delete=models.CASCADE)
    position = models.ForeignKey(Position, on_delete=models.CASCADE)
    position_change_reason = models.ForeignKey(PositionChangeReason, on_delete=models.CASCADE, default='Hired As')
    effective_date = models.DateField()

    def __str__(self):
        return str(self.position)

表单代码

class EmployeeRegistrationForm(UserCreationForm):
    email = forms.EmailField(required=True, widget=forms.EmailInput(attrs={'class': 'form-control mb-2',
                                                                           'placeholder': 'juandelacruz@email.com'}))
    first_name = forms.CharField(widget=forms.TextInput(attrs={'class': 'form-control mb-2', 'placeholder': 'Juan'}))
    last_name = forms.CharField(
        widget=forms.TextInput(attrs={'class': 'form-control mb-2', 'placeholder': 'Dela Cruz'}))

    password1 = None
    password2 = None

    class Meta:
        model = User
        fields = ['email', 'first_name', 'last_name']

    def clean(self):
        password = User.objects.make_random_password(length=10,
                                                  allowed_chars='abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789')
        self.cleaned_data['password1'] = password
        self.cleaned_data['password2'] = password

        return super().clean()

    def save(self, commit=True):
        user = super().save(commit=False)

        user.email = self.cleaned_data['email']
        user.first_name = self.cleaned_data['first_name']
        user.last_name = self.cleaned_data['last_name']
        user.is_active = False
        if len(user.first_name.split()) and len(user.last_name.split()) > 1:
            username = f'{"".join(user.first_name.split()[:2])}.{"".join(user.last_name.split())}'
        elif len(user.first_name.split()) > 1:
            username = f'{"".join(user.first_name.split()[:2])}.{user.last_name}'
        elif len(user.last_name.split()) > 1:
            username = f'{user.first_name}.{"".join(user.last_name.split())}'
        else:
            username = f'{user.first_name}.{user.last_name}'
        username = username.lower()
        user.username = username
        user.set_password(self.cleaned_data['password1'])

        if commit:
            user.save()
        return user


class EmployeeForm(forms.ModelForm):
    class Meta:
        model = Employee
        fields = ['phone_number', 'middle_name', 'sex', 'suffix', 'birthday', 'hire_date']
        widgets = {
            'phone_number': forms.TextInput(attrs={'class': 'form-control mb-2', 'placeholder': '09151234567'}),
            'middle_name': forms.TextInput(attrs={'class': 'form-control mb-2', 'placeholder': 'Rodriguez'}),
            'suffix': forms.Select(attrs={'class': 'form-select mb-2'}),
            'sex': forms.Select(attrs={'class': 'form-select mb-2'}),
            'birthday': forms.DateInput(attrs={'type': 'date', 'class': 'form-control mb-2'}, format='%m/%d/%Y'),
            'hire_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control mb-2'}, format='%m/%d/%Y'),
        }

class EmployeePositionForm(forms.ModelForm):
    position_change_reason = forms.Select()
    effective_date = forms.DateField()

    class Meta:
        model = EmployeePosition
        fields = ['employee', 'position', 'position_change_reason', 'effective_date']
        widgets = {
            'position': forms.Select(attrs={'class': 'form-select mb-2', 'id': 'position'}),
        }

    def check_position_change_reason(self):
        position_change_reason = self.cleaned_data['position_change_reason']
        if not position_change_reason:
            position_change_reason = EmployeePosition.position_change_reason.get_default()
        return position_change_reason

    def check_effective_date(self):
        effective_date = self.cleaned_data['effective_date']
        if not effective_date:
            effective_date = Employee.hire_date
        return effective_date

    def save(self, commit=True):
        position = super().save(commit=False)
        position.position_change_reason = self.cleaned_data['position_change_reason']
        position.effective_date = self.cleaned_data['effective_date']
        position.position = self.cleaned_data['position']
        return position

我在第三个表单中尝试为两个字段设置了默认值,用于处理用户未输入的场景。

视图代码

class RegisterEmployeeView(LoginRequiredMixin, CreateView):
    model = User
    form_class = EmployeeRegistrationForm
    profile_form_class = EmployeeForm
    position_form_class = EmployeePositionForm
    template_name = 'accounts/register.html'
    success_url = '/'

    def get(self, request, *args, **kwargs):
        form = self.form_class(**self.get_form_kwargs())
        profile_form = self.profile_form_class(prefix='profile_form')
        position_form = self.position_form_class(prefix='position_form')
        return render(request, self.template_name,
                      {'form': form, 'profile_form': profile_form, 'position_form': position_form})

    def post(self, request, *args, **kwargs):
        form = self.form_class(request.POST)
        profile_form = self.profile_form_class(request.POST)
        position_form = self.position_form_class(request.POST)

        if form.is_valid() and profile_form.is_valid() and position_form.is_valid():
            user = form.save()
            profile = profile_form.save(commit=False)
            position_profile = position_form.save(commit=False)
            profile.user = user
            position_profile.employee = profile.employee
            profile.save()
            position_profile.save()
            messages.success(self.request, f'{User.first_name} {User.last_name} was registered successfully.')
            return HttpResponseRedirect(self.get_success_url())
        else:
            context = {'form': form, 'profile_form': profile_form, 'position_form': position_form}
        return render(request, self.template_name, context)

我查阅了大量同类问题的资料仍未解决,已知该现象通常是验证错误导致,但我作为Django初学者仍无法定位问题。

更新

我遵循建议对表单做了子类化以便后续复用,请问如下的子类化方式是否正确?

class EmployeeInitialPositionForm(EmployeePositionForm):

    class Meta:
        model = EmployeePosition
        exclude = ['employee']

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 14:36:04