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字段属性进行修改,可以实现我们想要的效果。

1
2
3
4
5
6
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字段的值发生了变化。

1
2
3
4
5
6
7
8
9
10
11
# 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方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 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字段的值进行了修改。

1
2
3
4
5
# 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中实现的一个静态方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 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方法。

1
2
3
4
5
6
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了。

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

https://blog.dennic365.com/2019/03/19/django-user-model-email-null/

作者

Dennic

发布于

2019-03-19

更新于

2022-02-24

许可协议

评论