Python
Django
Twitter
OAuth
python3

PythonのWebアプリフレームワーク「Django」を使ってTwitter認証してみた

概要

以前、Pythonスクリプトで直接TwitterAPIを叩いてみる記事を投稿しました。
PythonでTwitter API を利用していろいろ遊んでみる

しかし、これだと

  • Consumer Key
  • Consumer Secret

に加え、

  • Access Token
  • Access Token Secret

が必要なので特定のユーザーでしかAPI操作を出来ませんでした。

なので、今回はPythonのWebアプリフレームワークの「Django」を使って、ブラウザ上でTwitter認証をして各アカウント単位の認証処理を行うところまでを作ってみました。

Djangoとは

ジャンゴと読みます。

思想としては、model-view-templateデザインパターンに沿って開発を行います。
model-view-controllerと似ていますが少し違うようです。

(@udoooom さんご指摘ありがとうございます!)

また、非常に開発をしやすいフレームワークとなっており、インストールTwitter認証成功までを数時間程度で動くものを作ることが出来ました。

手順

開発環境

  • Python == 3.5.1
  • Django == 2.0.1
  • social-auth-app-django == 2.1.0

また、TwitterAPIのConsumer情報は
PythonでTwitter API を利用していろいろ遊んでみる
で取得したものをそのまま流用しますので、まだ作成されていない方はまずはコチラの記事を参考にTwitterアプリを作成するところまで進めてください。

モジュールのインストール

とりあえず大元のDjangoフレームワークをインストールします。

command
pip install django

プロジェクトの作成

まずはプロジェクトを作成します。

django-admin startproject project

プロジェクト作成直後は以下のようなディレクトリ構成になっています。

tree
.
└── project
    ├── manage.py
    └── project
        ├── __init__.py
        ├── settings.py
        ├── urls.py
        └── wsgi.py

DBのmigrate

DjangoにはDBも含まれています。
デフォルトではsqlite3が使われるようです。

まずはmigrateを行いましょう。

command
cd myApp
python manage.py migrate

正常に完了します。

screencapture- 2018-01-10 14.55.28.png

管理ユーザーの作成

次に管理ユーザーを作成します。

command
python manage.py createsuperuser

ユーザー名/メールアドレス/パスワード/パスワード(確認)の入力を求められるので良しなに。

screencapture- 2018-01-10 14.56.46.png

サーバー起動

一旦、この状態でサーバーを起動してみましょう。

command
python manage.py runserver

screencapture- 2018-01-10 20.23.39.png

ブラウザで、http://localhost:8000 にアクセスすると以下のようなアプリ画面が表示されると思います。

screencapture- 2018-01-10 15.10.17.png

管理画面へのログイン

DJangoは標準で管理画面を用意してくれています。

http://localhost:8000/admin にアクセスします。

先ほど作成したユーザー名/パスワードを入力してください。

screencapture- 2018-01-10 15.11.33.png

ログイン成功!!!

アプリケーションの作成

次はアプリケーションを作成します。

Djangoでは、プロジェクトの中に複数のアプリケーションを作成することが出来るらしいです。

command
python manage.py startapp twitterManager

すると、こんなディレクトリ構成になります。

tree
.
└── project
    ├── db.sqlite3
    ├── manage.py
    ├── project  # 共通
    │   ├── __init__.py
    │   ├── settings.py
    │   ├── urls.py
    │   └── wsgi.py
    └── twitterManager  # アプリ①
        ├── __init__.py
        ├── admin.py
        ├── apps.py
        ├── migrations
        │   └── __init__.py
        ├── models.py
        ├── tests.py
        └── views.py

そして、作成したアプリをプロジェクト管理対象に含めるために、以下ファイルを編集します。

project/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
+   'twitterManager.apps.TwittermanagerConfig',
]

そして、以下のコマンドでmigration対象に入れておきます。

command
python manage.py makemigrations twitterManager
python manage.py migrate twitterManager

※何も変更してないので差分は発生しませんが、念のためです。

Twitter認証に使うモジュールのインストール

今回はsocial-auth-app-djangoを使います。

command
pip install social-auth-app-django

認証情報の作成

以下のファイルを作成し、TwitterアプリケーションのConsumer情報を記載してください。

project/configs/twitter.py
SOCIAL_AUTH_TWITTER_KEY = 'XXXXXXXXXXXXXXX'
SOCIAL_AUTH_TWITTER_SECRET = 'YYYYYYYYYYYYYY'

※gitリポジトリの場合、認証情報はgit管理外にしたいので、.gitignoreに追加しておきましょう。

.gitignore
**twitter.py

settings.pyの修正

こんな感じに変えます。

project/settings.py
"""
Django settings for project project.

Generated by 'django-admin startproject' using Django 2.0.1.

For more information on this file, see
https://docs.djangoproject.com/en/2.0/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.0/ref/settings/
"""

import os
+ from project.configs import twitter

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []


# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'twitterManager.apps.TwittermanagerConfig',
+   'social_django',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'project.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
+               'social_django.context_processors.backends',
+               'social_django.context_processors.login_redirect',
            ],
        },
    },
]

WSGI_APPLICATION = 'project.wsgi.application'


# Database
# https://docs.djangoproject.com/en/2.0/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}


# Password validation
# https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


# Internationalization
# https://docs.djangoproject.com/en/2.0/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.0/howto/static-files/

STATIC_URL = '/static/'

+ AUTHENTICATION_BACKENDS = [
+       'social_core.backends.twitter.TwitterOAuth',
+       'django.contrib.auth.backends.ModelBackend',
+   ]

+ SOCIAL_AUTH_TWITTER_KEY = twitter.SOCIAL_AUTH_TWITTER_KEY
+ SOCIAL_AUTH_TWITTER_SECRET = twitter.SOCIAL_AUTH_TWITTER_SECRET

ログイン/ログアウト画面の追加

次にログイン画面ログアウト画面を表示するための準備をします。

以下のファイルを作成します。

twitterManager/urls.py
from django.contrib import admin
from django.urls import path
from django.conf.urls import include
import django.contrib.auth.views

urlpatterns = [
    path('', include('social_django.urls', namespace = 'social')),
    path('login/',
            django.contrib.auth.views.login,
            {
                'template_name': 'login/index.html',
            },
            name='login'),
    path('logout/',
        django.contrib.auth.views.logout,
        {
            'template_name': 'logout/index.html',
        },
        name='logout'),

]

ログイン画面/ログアウト画面それぞれのHTMLを作成します。
取り急ぎの表示確認レベルなので適当で。

twitterManager/templates/login/index.html
<html>
<head>
 <title>ログイン</title>
</head>
<body>
  <div>
    <button type="button" onclick="location.href='{% url 'social:begin' 'twitter' %}'">ログイン</button>
  </div>
</body>
</html>
twitterManager/templates/logout/index.html
<html>
<head>
 <title>ログアウト</title>
</head>
<body>
  <div>
    <p>
      ログアウトしました。
    </p>
    <p>
      <a href="/twitterManager/login"><button type="button" >ログインページへ</button></a>
    </p>
  </div>
</body>
</html>

最後に既存のproject/urls.pyを編集します。

project/urls.py
from django.contrib import admin
from django.urls import path
+ from django.conf.urls import include

urlpatterns = [
    path('admin/', admin.site.urls),
+   path('twitterManager/', include('twitterManager.urls')),
]

migrateの実行

social-auth-app-djangoを新たに追加しているので、migrateをする必要があります。
migrateしないままサーバー起動をすると、

screencapture- 2018-01-10 17.42.31.png

が発生してしまいます。

migrateが必要なタイミングはサーバ起動時のログに出るので気にしておきましょう。

screencapture- 2018-01-10 21.11.30.png

command
python manage.py migrate

screencapture- 2018-01-10 20.27.22.png

確認

この状態でサーバー起動します。

command
python manage.py runserver

ログイン画面

screencapture- 2018-01-10 16.23.34.png

ログアウト画面

screencapture- 2018-01-10 16.23.45.png

とてもシンプルなページが表示されると思います。笑

しかしここで問題が・・・

意気揚々とログインボタンを押したところ、

screencapture- 2018-01-10 21.09.31.png

401エラーが発生しました。

原因はCallbackURLの未入力

Twitterアプリケーション作成時Callback URL未入力にすると、Webシステムとして使う場合は401エラーが起きてしまうようです。

特に利用する項目ではないので、とりあえずダミーURLでも入れておきましょう。

screencapture- 2018-01-10 17.38.58.png

これで連携確認画面が表示されました!!!

screencapture- 2018-01-10 17.39.21.png

次こそ・・・!

連携アプリを認証ボタンをクリックすると、

screencapture- 2018-01-10 17.44.50.png

おぉ・・・!!(wktk)

screencapture- 2018-01-10 21.17.44.png

image.png

原因はソーシャル認証成功後のリダイレクト指定

認証成功時に遷移させるリダイレクトURLを指定する必要があります。
デフォルトでは /accounts/profile/ に飛ばされてしまうようですね。

取り急ぎ、会員トップ画面を用意してリダイレクトさせてみましょう。

twitterManager/urls.py
+ from . import views

urlpatterns = [
    path('', include('social_django.urls', namespace = 'social')),
+   path('top/', views.top_page, name="top_page"),
    path('login/',
            django.contrib.auth.views.login,
            {
                'template_name': 'login/index.html',
            },
            name='login'),
    path('logout/',
        django.contrib.auth.views.logout,
        {
            'template_name': 'logout/index.html',
        },
        name='logout'),

]
twitterManager/views.py
from django.shortcuts import render

# Create your views here.

from django.shortcuts import render
from django.http.response import HttpResponse
from django.contrib.auth.decorators import login_required
from social_django.models import UserSocialAuth

@login_required
def top_page(request):
    user = UserSocialAuth.objects.get(user_id=request.user.id)
    pageDic = {
        'hoge': 'fuga',
        'user': user
    }
    return render(request, 'top/index.html', pageDic)
twitterManager/templates/top/index.html
<html>
<head>
 <title>会員トップ</title>
</head>
<body>
  <div>
    <h1>会員トップ</h1>
    <h2>{{ user }}</h2>
    <div>
      <p>UserId:{{ user.access_token.user_id }}</p>
      <p>OAuthTokenSecret:{{ user.access_token.oauth_token_secret }}</p>
      <p>OAuthToken:{{ user.access_token.oauth_token }}</p>
      <p>ScreenName:{{ user.access_token.screen_name }}</p>
    </div>
    <h3>hoge:{{ hoge }}</h3>
    <p>
      <a href="/twitterManager/logout"><button type="button" >ログアウト</button></a>
    </p>
  </div>
</body>
</html>
project/settings/py
+ SOCIAL_AUTH_LOGIN_REDIRECT_URL = '/twitterManager/top'

これでどうだ・・・!

screencapture- 2018-01-10 20.10.52.png

取れた!!!

他のアカウントでも確認

いや待てよ?
Twitterアプリケーションの所有者と認証アカウントがBakira_Techで同じだから正常に動いているだけであって、他のアカウントではうまくいかないのでは?という不安が頭を過ぎりました。。。

screencapture- 2018-01-10 20.14.24.png

余 計 な お 世 話 で し た ! ! ! 

これでTwitter認証が完成したので、あとはOAuthTokenを使ってロジックなりデザインを組み込んでいけばそれなりのものが作れそうですね♪

最終的な構成

以下のようになりました!
(ベストプラクティスではないので悪しからず・・・orz)

ディレクトリ構成
.
└── project
    ├── db.sqlite3
    ├── manage.py
    ├── project
    │   ├── __init__.py
    │   ├── configs
    │   │   └── twitter.py
    │   ├── settings.py
    │   ├── urls.py
    │   └── wsgi.py
    └── twitterManager
        ├── __init__.py
        ├── admin.py
        ├── apps.py
        ├── migrations
        │   └── __init__.py
        ├── models.py
        ├── templates
        │   ├── login
        │   │   └── index.html
        │   ├── logout
        │   │   └── index.html
        │   └── top
        │       └── index.html
        ├── tests.py
        ├── urls.py
        └── views.py

終わりに

知識0からの作業だったので、調べながら試行錯誤しましたが、それでも会員トップ画面が表示されるまで5時間くらいの学習コストで動くものが作れました。

Djangoフレームワーク、楽しいです♪

参考にさせて頂いた記事

非常に助かりました!!!
- DjangoでTwitter OAuth
- DjangoでOauth認証をしたあとにTwitter APIを叩く
- 社会性フィルター実装したtwitterクライアントを作った
- OAuth::Unauthorized 401 Unauthorizedなんてエラーがでたら