众所周知, Django自带的Auth backend username最大只支持30个字符, 对应的Django admin也是这么校验的。 最近项目换了Oauth, 想把Oauth id直接存到username上方便登陆和管理。问题来了。。 auth模块是随Django一起安装的, 我想没人会有想法去改site-packages吧? 考虑到项目的部署方便, 因此只能在项目代码中想办法把这个可恶的限制去掉。
尝试1: 找Django提供的接口:
很遗憾,这个长度是Django直接hard code的:
class AbstractUser(AbstractBaseUser, PermissionsMixin):
"""
An abstract base class implementing a fully featured User model with
admin-compliant permissions.
Username, password and email are required. Other fields are optional.
"""
username = models.CharField(_('username'), max_length=30, unique=True,
help_text=_('Required. 30 characters or fewer. Letters, numbers and '
'@/./+/-/_ characters'),
validators=[
validators.RegexValidator(re.compile('^[\[email protected]+-]+$'), _('Enter a valid username.'), 'invalid')
])
first_name = models.CharField(_('first name'), max_length=30, blank=True)
last_name = models.CharField(_('last name'), max_length=30, blank=True)
email = models.EmailField(_('email address'), blank=True)
is_staff = models.BooleanField(_('staff status'), default=False,
help_text=_('Designates whether the user can log into this admin '
'site.'))
is_active = models.BooleanField(_('active'), default=True,
help_text=_('Designates whether this user should be treated as '
'active. Unselect this instead of deleting accounts.'))
date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
objects = UserManager()
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email']
class Meta:
verbose_name = _('user')
verbose_name_plural = _('users')
abstract = True
User 是继承于AbstractUser, 而username max length已在此写死, 没有提供配置的接口。
尝试2: 继承重写User model:
其实就是写一个自己的User Model, 然后在各处import这个自定义的Model. 不过这个似乎有些不妙,问题有如下4:
1. 弃用Django auth会导致数据库多几张废表,而且Django的权限机制包括Group, Permission什么的都是和默认的User Model关联的,一一修改十分麻烦。
2. 将来引入的第三方库有auth模块依赖怎么办?第三方库可不知道我们重写了User Model.
3. 老代码一大堆, 挨个改也是个烦人的dirty work.
4. 实际上重写了User model还没完, Admin保存的时候照样给你抛Validate Error. 因为牛逼的Django Admin Form也是hard code的。。。
尝试3: Black Magic —— MonkeyPatch:
关于这个黑魔法的原理就不赘述了. 动态语言的特性, 实现如下:
1. 实现一个patch 函数如下:
# -*- coding: utf-8 -*-
from django.contrib.auth import models as auth_models
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.forms import UserChangeForm, UserCreationForm
def patch_auth_username_field():
NEW_USERNAME_LENGTH = 40
HELP_TEXT = _('Required. %d characters or fewer.'
' Letters, numbers and @/./+/-/_ characters'
) % NEW_USERNAME_LENGTH
model_field = auth_models.User._meta.get_field("username")
model_field.max_length = NEW_USERNAME_LENGTH
model_field.validators[1].limit_value = NEW_USERNAME_LENGTH
model_field.help_text = HELP_TEXT
for form in (UserChangeForm, UserCreationForm):
form_field = form.base_fields['username']
form_field.max_length = NEW_USERNAME_LENGTH
form_field.widget.attrs.update({
'maxlength': NEW_USERNAME_LENGTH,
'style': "width: 410px;"
})
form_field.validators[0].limit_value = NEW_USERNAME_LENGTH
form_field.help_text = HELP_TEXT
此处分别patch了 User Model 和相关的两个Admin Form. 修改了max_length属性, validators, 和提示内容。我此处是讲username长度patch为40个字符, 你可以根据你的需要修改NEW_USERNAME_LENGTH变量来完成你的patch. Google上大部分答案都忽略了Form和validators. 当使用Django Admin时, 这两处是会抛出validation error的。
2. 新建一个app, 在该APP的__init__.py中执行该函数。 这样是保证在初始化时打上patch, 又不会导致settings.py的循环导入:
# coding=utf-8
from patches import (
patch_auth_username_field,
)
patch_auth_username_field()
3. 最后, 在settings.py里install 该APP打上patch:
INSTALLED_APPS = (
'UI.monkeypatch',
...
)
注意, patch必须放在INSTALLED_APPS的最上面。 否则可能导致部分app不生效。
大功告成。
PS: Patch之后由于数据最大长度发生变化, 需要migrate一下表结构。