django.contrib.auth.admin.UserAdmin の email をガラケーに対応させる

Djagno の EmailField は「.」 連続とか「@」の直前に「.」があるような、いわゆる日本のガラケー特有のRFC違反メールアドレスは問答無用で Invalid にしてしまう。

なので、ガラケー対応が必要な部分はそういうのを許容した validation を持つ Field を独自に定義して使ったりするんだけど、django.contrib.auth の UserAdmin で email の validate を変えたい場合はちょい一工夫が必要だったんでメモ。

ガラケー用のvalidatorを定義する

django.core.validators の validte_email を参考に適当なとこに RFC違反の "." 連続とかを許容した validator を作る

from django.utils.translation import ugettext_lazy as _
from django.core import validators

# ガラケー対応のEmail Validation
# ..@example.com とかでも OK になるけど、気にしない
japanese_email_re = re.compile(
    r"(^[-.!#$%&'*+/=?^_`{}|~0-9A-Z]+"  # dot-atom
    r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"' # quoted-string
    r')@(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?$', re.IGNORECASE)  # domain
validate_japanese_email = validators.EmailValidator(
            japanese_email_re, _(u'Enter a valid e-mail address.'), 'invalid')

普通の Form の場合は、これを利用して forms.py にこんな感じに定義して使う。

class JapaneseEmailField(forms.EmailField):
    default_validators = [validate_japanese_email]

UserAdminは置き換えるだけじゃダメ

あとは単純に admin.py で UserAdmin の form に上で作った JapaneseEmailField を含んだ Form に置き換えるだけで終了。

…と、思ってましたが…
UserAdmin は ModelForm を利用してるらしく、models.EmailField の default_validatorsが効いてしまってまんま置き換えるだけでは、普通の EmailValidator のままになってしまうよう。

clean_Field を上書きしとく

models の Field で定義された validators を無効にするために、clean_** メソッドを上書きして、admin.py で ModelAdmin を置き換える

from django.contrib.auth.models import User
from django.contrib.auth.admin import UserAdmin


class MyUserForm(forms.ModelForm):

    def clean_email(self):
        from django.core.validators import EmailValidator
        field = self.instance._meta.get_field('email')
        new_validators = []
        for v in field.validators:
            if isinstance(v, EmailValidator):
                new_validators.append(utils.validate_japanese_email)
            else:
                new_validators.append(v)
        field.validators = new_validators
        return self.cleaned_data['email']


class MyUserAdmin(UserAdmin):
     form = MyUserForm


admin.site.unregister(User)
admin.site.register(User, MyUserAdmin)

なんか、isinstance で判定してから無効にするって微妙な感じもするけど…一応これでガラケーアドレスでも admin で通るようになる。

なんか

一年以上前に書いた記事が下書きのままになってたんでとりあえず公開してみた
当時使っていたDjangoのバージョンはおそらく、1.2 だったと思う…