天天看點

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一下表結構。