Django注册表单:如何同时显示字段错误与非字段错误?
问题描述
我有一个包含email、password和confirm password字段的UserRegistrationForm。提交表单时,当Email和密码字段均存在错误(如无效Email、密码不匹配),希望同时显示Form.non_field_errors()和Form.errors。但目前的问题是:当所有字段都不正确时,仅先显示字段错误(如“Enter a valid email address”),修复邮箱后再次提交才会显示非字段错误(如“passwords do not match”),我希望两类错误能同时展示。
相关截图:
- 输入无效邮箱和不匹配密码后的截图:

- 邮箱修正后密码仍不匹配的截图:

相关代码
forms.py
class UserRegistrationForm(forms.ModelForm): password2 = forms.CharField( label="New password confirmation", widget=forms.PasswordInput(attrs={"autocomplete": "new-password"}), ) class Meta: model = User fields = [ "email", "password", ] def _validate_passwords(self, password1, password2): password_errors = [] try: validate_password(password1, self.instance) except ValidationError as e: password_errors.append( ValidationError(_("Weak password provided"), code="weak-password"), ) if password1 and password2 and password1 != password2: password_errors.append( ValidationError(_("Passwords mismatch"), code="password-mismatch"), ) if len(password_errors) > 0: raise ValidationError(password_errors) def clean(self): cleaned_data = super().clean() email = cleaned_data.get("email") password1 = cleaned_data.get("password") password2 = cleaned_data.get("password2") if email and password1 and password2: # email is already validated at Field level self._validate_passwords(password1, password2) return super().clean()
registration.html
<div>{{ form.non_field_errors }}</div> <form method="post"> {% csrf_token %} {% for field in form %} <div class="fieldWrapper"> {{ field.errors }} {{ field.label_tag }} {{ field }} </div> {% endfor %} <input type="submit" /> </form>
models.py
class CustomEmailField(models.EmailField): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def run_validators(self, value): pass def validate(self, value, model_instance): if value is not None and not value.endswith("@example.com"): raise ValidationError("Email must end with @example.com") class CustomUserManager(auth_models.BaseUserManager): def create_user(self, email, password, **extra_fields): if not email: raise ValueError("The Email field must be set") try: email = self.normalize_email(email) validate_email(email) except ValidationError as e: print("errors: ", e) else: # execute if no exception user = self.model(email=email, **extra_fields) user.set_password(password) user.save() return user def create_superuser(self, email, password, **extra_fields): # Almost indentical implementation to Django's UserManager class extra_fields.setdefault("is_staff", 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_superuser") is not True: raise ValueError("Superuser must have is_superuser=True.") return self.create_user(email, password, **extra_fields) class User(auth_models.AbstractUser): username = None email = CustomEmailField(unique=True) email_is_verified = models.BooleanField(default=False) USERNAME_FIELD = "email" REQUIRED_FIELDS = ["password"] # required fields when creating a user obj objects = CustomUserManager() def __str__(self): return self.email
views.py
class RegistrationView(TemplateView): template_name = "user/registration.html" def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["form"] = UserRegistrationForm return context def post(self, request, *args, **kwargs): form = UserRegistrationForm(request.POST) context = {"form": form} if form.is_valid(): user = form.save() context["success_message"] = "We have sent you an email verification." return HttpResponseRedirect(reverse("user:email-verification")) # form should contain some errors at this point return render(request, self.template_name, context)
解决方案
问题根源在clean方法的判断逻辑:原代码仅当邮箱字段验证通过(即cleaned_data中存在email值)时,才会执行密码验证。如果邮箱验证失败,cleaned_data.get("email")返回None,导致密码验证逻辑跳过,非字段错误无法触发。
修改forms.py中的clean方法:
def clean(self): cleaned_data = super().clean() password1 = cleaned_data.get("password") password2 = cleaned_data.get("password2") # 只要密码字段有值就执行验证,不再依赖邮箱字段的验证结果 if password1 and password2: self._validate_passwords(password1, password2) return cleaned_data
同时注意:原代码最后返回super().clean()会覆盖当前的cleaned_data,改为直接返回处理后的cleaned_data。
修改后,即使邮箱字段存在错误,只要密码字段填写了内容,密码匹配和强度验证就会执行,对应的非字段错误会和邮箱的字段错误同时显示在页面上。
内容的提问来源于stack exchange,提问作者Jun Wen Soh
相关产品推荐
相关产品推荐

