読者です 読者をやめる 読者になる 読者になる

ottatiのブログ

無職学生がネットにクソアプリをまき散らしていく様子

【Django】Emailとパスワードなログインで登録時にメール認証する方法

やり方 python django

f:id:ottati:20150103125718g:plain     🍣

メールアドレスでログイン

以下を満たすDjangoアプリを作る

  • メールアドレス as an usernameでパスワードでログインしたい
  • 登録時にはアクティベーションメール送りたい

f:id:ottati:20150414191016p:plain

フィリピン一ヶ月留学で培ったのはbroken Englishでぶっぱするスキル

Demo

最低限の実装したものを作ってアゲた

github.com

適当にログインとかログアウトとかも憑いている。最低限でないな

Email as an usernameなユーザモデルを作る

基本username必須なのでカスタムユーザを作る。ついでにマネージャも作る。カスタムユーザの定義とかそれに関するViewなんかは全てaccountsっていうアプリにつっこんだ。まあViewについてはプロフィール画面1つしかない

ユーザモデル

usernameの要らないユーザモデルを作る。AbstractBaseUserを継承する。

AbstractBaseUserのソースコード -> https://github.com/django/django/blob/master/django/contrib/auth/models.py

以下コード。

class EmailUser(AbstractBaseUser):
    email = models.EmailField(
        verbose_name="email address",
        max_length=255,
        unique=True
    )
    is_active = models.BooleanField(default=True)
    is_admin = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    date_joined = models.DateTimeField(auto_now_add=True)

    objects = EmailUserManager()
    USERNAME_FIELD = 'email'

    def get_full_name(self):
        return self.email

    def get_short_name(self):
        return self.email

    def __unicode__(self):
        return self.email

    def has_perm(self, perm, obj=None):
        return True

    def has_module_perms(self, perm, obj=None):
        return self.is_admin

    @property
    def is_staff(self):
        return self.is_admin

    def validate_unique(self, exclude=None):
        """Validate the field uniqueness"""
        EmailUser.objects.filter(email=self.email, is_active=False).delete()
        super(EmailUser, self).validate_unique(exclude)

validate_uniqueメソッドにアクティベートされていないかつemailが同じユーザを消す処理を追加している。登録してアクティベートされない限りもう一度そのemailで登録できない仕様となっていたので変えた。正直ここでこの処理はないなと思う。どこにこの削除の処理を書くのが適切なんだろうか。

ユーザマネージャ

以下コード

class EmailUserManager(BaseUserManager):
    def create_user(self, email, password=None):
        if not email:
            raise ValueError('Users must have an email address')
        user = self.model(email=self.normalize_email(email))
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_superuser(self, email, password):
        user = self.create_user(
            email,
            password=password
        )
        user.is_admin = True
        user.save(using=self._db)
        return user

ほぼ公式ドキュメントに書いてあった

django-registration-reduxのインストール

カレーメシさん(id:nwpct1)に教えて頂いたのをインストール

ドキュメントに沿う -> http://django-registration-redux.readthedocs.org/en/latest/

なにこれ

username, email, passwordでユーザの登録、アクティベーションメールとか送信してくれる。アクティベートする前に一度is_active = Falseで保存しておいて、アクティベートされたらTrueにしてくれるとてもシンプルなモジュール。

v1.2と1.1

追記: PyPiに1.2が追加された!

なんかpypiにはv1.1しかない。これだとカスタムしたformを使いたい今回のようなケースで少しだけ面倒くさくなる, まあviewを継承してurlを上書きすればよいのだが, v1.2であるGitHubから直接DLした。

(ちなみに何でv1.2が登録されないかについて作者は言及している(https://github.com/macropin/django-registration/issues/37)

v1.2ではhttps://github.com/macropin/django-registration/blob/master/registration/views.pyで以下が追加されてる

REGISTRATION_FORM_PATH = getattr(settings, 'REGISTRATION_FORM', 'registration.forms.RegistrationForm')
REGISTRATION_FORM = import_string( REGISTRATION_FORM_PATH )

なんで今回はv1.2を使ってsettings.pyでREGISTRATION_FORMを使っちゃうよ

Settings.py

# Setting django-registration-redux

AUTH_USER_MODEL = 'accounts.EmailUser'
ACCOUNT_ACTIVATION_DAYS = 7
REGISTRATION_AUTO_LOGIN = True
REGISTRATION_FORM = 'accounts.forms.EmailUserCreationForm'


# Email settings
# python -m smtpd -n -c DebuggingServer localhost:1025

EMAIL_HOST = "localhost"
EMAIL_PORT = "1025"
# EMAIL_PORT = "25"

REGISTRATION_FORMに自家製フォームを指定するよ

アクティベーション送るためにメールの設定してる。1025という数字は、Pythonのbuilt-inなsmtpサーバのために

$ python -m smtpd -n -c DebuggingServer localhost:1025

カスタムフォーム

今回はusernameとかないんで、自分でフォーム作るよ

from django import forms
from django.contrib.auth import get_user_model


class EmailUserCreationForm(forms.ModelForm):
    password1 = forms.CharField(label='Password',
                                widget=forms.PasswordInput)
    password2 = forms.CharField(label='Password confirmation',
                                widget=forms.PasswordInput)

    class Meta:
        model = get_user_model()
        fields = ('email', )

    def clean_password2(self):
        password1 = self.cleaned_data.get('password1')
        password2 = self.cleaned_data.get('password2')
        if password1 and password2 and password1 != password2:
            raise forms.ValidationError("Passwords don't match")
        return password2

    def save(self, commit=True):
        user = super(EmailUserCreationForm, self).save(commit=False)
        user.set_password(self.cleaned_data["password1"])
        if commit:
            user.save()
        return user

さいごに

django-registration-reduxを使うのが嫌な人は自分で作るといいかも。ユーザテーブルでis_activeをFalseにしてアクティベーションコード用のテーブルを用意する。Python Social Authにもアクティベーションメールを送る機能がついていたけれど、あれは登録時にはユーザを作らず、アクティベーション用テーブルにemailとアクティベーションコードを置くというやり方だった(たしか)。なので最初にパスワードとかプロフィールとか登録するのが面倒な仕様だった気がする。