一人もくもく会 α verでサービス開始しました。

Twitterを作る 第2回 認証

まずはユーザー登録。これがなければ何も話が進まない。 ひとまず下記の第3回までを見てさくっと動作確認とユーザーテーブル作成まで行う。

Python Django入門 (1) - Qiita

Python Django入門 (3) - Qiita

ログイン前トップページ

material-uiでさくっと作成。Twitterのトップ画面を見ると今回必要そうなコンテンツはこれだけ。 アカウント作成でsignupページヘ。

f:id:dala:20160723231524p:plain

ユーザー登録ページ

f:id:dala:20160723232219p:plain

こちらも同様にぱぱっと作成。 このページはユーザー登録とログインのサーバーへの通信が必要なのだが、 そのままだとCSRFでエラーになるのでensure_csrf_cookieを設定している。 また、後述するconfigライブラリを作成している。

ちなみにログインを押すとモーダルが出てくる。

f:id:dala:20160723234536p:plain

configライブラリ

全体の共通機能として下記のようなファイルを作成した。

app/lib/config.js

const config = {
    defaultHeaders: {
        "X-CSRFToken": getCookie('csrftoken'),
        "Accept": "application/json",
    },
    url: (url) => {
        if (url.charAt(0) === '/') {
            url = url.substr(1);
        }
        return '/dumitter/' + url;
    },
}

module.exports = config;

現在さくらサーバーのそのままのURL(SSL)なので、 今後独自ドメインを使う場合全箇所の修正は大変になるためurl補正メソッドを作成。 (最初から独自ドメインなら絶対パスで良いのだが)

また、ajaxの際にCSRFのトークンを送信するため、共通のデフォルトヘッダを定義している。 (getCookieは検索すれば出てくる)

バリデーション & 登録処理

postするとjsonを返却するアクション。 resultがtrueなら成功でログイン済みなのでトップページへリダイレクト。 falseならjsonに各フィールド名のエラーメッセージが入っているのでmaterial-uiのTextFieldのerrorTextにセット。

バリデーションの処理は全部python側でやっている。 js側でもいいかと思ったのだが、パスワードの細かいバリデーションがdjango側に入っているのでそちらに統一した。 (設定ファイルで細かいパスワードのバリデーション方法を指定することができる)

本当はもっとちゃんとした書き方がありそうだが、この処理はここだけだしいいかと思いバリデーションもベタ書き。

from django.http.response import JsonResponse
from django.contrib.auth import authenticate, login as authLogin
from django.contrib.auth.models import User
from django.core.validators import validate_email
from django.contrib.auth.password_validation import validate_password
from django.db import IntegrityError
from django.core.exceptions import ValidationError
import re

def create(request):
    errors = {}

    if re.match(r'^[\da-zA-Z_]+$', request.POST['name']) is None:
        errors['name'] = '半角英数字で入力して下さい。'

    try:
        validate_email(request.POST['email'])
    except ValidationError as e:
        errors['email'] = e.messages

    try:
        validate_password(request.POST['password'])
    except ValidationError as e:
        errors['password'] = e.messages

    if len(errors) == 0:
        try:
            user = User.objects.create_user(
                username=request.POST['name'],
                email=request.POST['email'],
            )
        except IntegrityError as e:
            if 'email' in str(e):
                errors['email'] = 'そのメールアドレスは既に使用されています。'
            elif 'name' in str(e):
                errors['name'] = 'そのユーザー名は既に使用されています。'

    if len(errors) != 0:
        errors['result'] = False
        return JsonResponse(errors)

    user.set_password(request.POST['password'])
    user.save()

    return login(request)


def login(request):
    authed = authenticate(
        username=request.POST['name'],
        password=request.POST['password'],
    )
    result = {'result': True}

    if authed is not None:
        authLogin(request, authed)
    else:
        result['result'] = False

    return JsonResponse(result)

新規登録後は自動的にログインするが、通常のログインのプログラムと全く同じだったので関数を流用している。

ログイン後はトップページにリダイレクトするが、 ここではログインの場合別のテンプレートを表示するようにしている。

def index(request):
    if request.user.is_authenticated():
        template = 'main.html'
    else:
        template = 'index.html'
    return render(request, template)

React側

多分邪道かも知れないが下記のようなことを行っている。

const element = document.getElementById('auth-regist');
if (element != undefined) {
    ReactDOM.render(<AuthRegist />, element);
}

認証側はこれ以上ページを作るつもりはないので適当。 認証後はルーティングを使いたい。

これで認証が完成したので次は投稿を作っていく予定。

https://alphabrend.sakura.ne.jp/dumitter/

Twitterを作る 第3回 投稿 - アルファブレンド プログラミングチップス