天天看点

Django 用户名支持30个以上字符。 (Monkeypatch版)

众所周知, 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一下表结构。