Django自定义认证User模型email字段不能保存为空的解决办法

在一个项目中直接使用了自定义Django AdminSite作为后台管理网站。

在修改自定义User模型的信息后,点击保存按钮提交Form时,提示email字段不能为空。

这是因为我们的UserAdmin类继承自django.contrib.auth.admin.UserAdmin,在父类UserAdmin的form中将email字段定义为了required,即必填字段。

通过继承django.contrib.auth.forms.UserChangeForm类,并在init方法中对email字段属性进行修改,可以实现我们想要的效果。

from django.contrib.auth.forms import UserChangeForm

class UserForm(UserChangeForm):
    def __init__(self, *args, **kwargs):
        super(UserForm, self).__init__(*args, **kwargs)
        self.fields["email"].required = False

这样在保存时就不会提示email字段不能为空了。

但是测试中发现,如果不填写email字段内容,保存后发现email字段的值变成了空字符串,而不是None,但是我们在自定义User模型中指定了email字段null=True,即我们期望的它应该是一个空值None。

经过调试后发现,在django.contrib.admin.options.ModelAdmin类的_changeform_view方法代码中,调用form.is_valid()方法后,email字段的值发生了变化。

# django.contrib.admin.options.ModelAdmin

ModelForm = self.get_form(request, obj)
if request.method == 'POST':
    form = ModelForm(request.POST, request.FILES, instance=obj)
    if form.is_valid(): # 这句以后email字段值发生了变化
        form_validated = True
        new_object = self.save_form(request, form, change=not add)
    else:
        form_validated = False
        new_object = form.instance

于是顺藤摸瓜,找到django.forms.models.BaseModelForm类中_post_clean方法,发现在此方法中调用了Model实例的full_clean方法。

# django.forms.models.BaseModelForm

try:
    self.instance = construct_instance(self, self.instance, opts.fields, opts.exclude)
except ValidationError as e:
    self._update_errors(e)

try:
    self.instance.full_clean(exclude=exclude, validate_unique=False)
except ValidationError as e:
    self._update_errors(e)

# Validate uniqueness if needed.
if self._validate_unique:
    self.validate_unique()

继续跟进发现在自定义User模型的父类AbstractUser中,实现了clean方法,并且对email字段的值进行了修改。

# django.contrib.auth.models.AbstractUser

def clean(self):
    super().clean()
    self.email = self.__class__.objects.normalize_email(self.email)

这里调用的normalize_email是django.contrib.auth.base_user.BaseUserManager中实现的一个静态方法。

# django.contrib.auth.base_user

class BaseUserManager(models.Manager):

    @classmethod
    def normalize_email(cls, email):
        """
        Normalize the email address by lowercasing the domain part of it.
        """
        email = email or '' # 就是这句话导致email值为None时被替换成了空字符串
        try:
            email_name, domain_part = email.strip().rsplit('@', 1)
        except ValueError:
            pass
        else:
            email = email_name + '@' + domain_part.lower()
        return email

所以我们只需要自己实现一个UserManager类并继承BaseUserManager类,并重写normalize_email方法。

from django.contrib.auth.models import BaseUserManager

class UserManager(BaseUserManager):
    @classmethod
    def normalize_email(cls, email):
        return super().normalize_email(email) or None

这样,当提交Form中缺少email值时,就能正确的把email字段值保存为None了。

发表评论

电子邮件地址不会被公开。