django-allauthでログイン機能を実装する

前回の続きです。
http://qiita.com/yukidallas/items/9cd0ca5cda7f459533c3

# django-allauth
Djangoアプリケーション上でソーシャルログインを簡単に実装できるライブラリです。
https://github.com/pennersr/django-allauth

現在以下のソーシャルログインに対応しているようです。

- Amazon (OAuth2)
- AngelList (OAuth2)
- Bitly (OAuth2)
- Dropbox (OAuth)
- Facebook (both OAuth2 and JS SDK)
- Feedly (OAuth2)
- Github (OAuth2)
- Google (OAuth2)
- Instagram (OAuth2)
- LinkedIn (OAuth, OAuth2)
- OpenId
- Paypal (OAuth2)
- Persona
- SoundCloud (OAuth2)
- Stack Exchange (OAuth2)
- Twitch (OAuth2)
- Twitter (OAuth)
- Vimeo (OAuth)
- VK (OAuth2)
Weibo (OAuth2)


# インストール
requirements.txtにdjango-allauthを追加してpip installします。

```:requirements.txt
django==1.11
django-allauth==0.31.0 # 追加
django-extensions==1.7.8
mysqlclient==1.3.10

```

```bash:terminal
pip install -r requirements.txt -t libs
```

# メールアドレスで登録/ログイン
INSTALLED_APPSにdjango adminとallauthを追加します。

```py:django-sample/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.messages',
    'django.contrib.sessions',
    'django.contrib.staticfiles',
    'django.contrib.sites',
    'django_extensions',

    'allauth',
    'allauth.account'
]

AUTHENTICATION_BACKENDS = [
   'allauth.account.auth_backends.AuthenticationBackend'
]
```

登録/ログイン周りのViewクラスを作成します。

```py3:django-sample/views.py
from allauth.account import views


class SigninView(views.LoginView):
    template_name = 'signin/index.html'

    def dispatch(self, request, *args, **kwargs):
        response = super(SigninView, self).dispatch(request, *args, **kwargs)
        return response

    def form_valid(self, form):
      return super(SigninView, self).form_valid(form)

signin_view = SigninView.as_view()


class SignupView(views.SignupView):
    template_name = 'signup/index.html'

    def get_context_data(self, **kwargs):
        context = super(SignupView, self).get_context_data(**kwargs)
        return context

signup_view = SignupView.as_view()
```

対応するテンプレートを作成します。
allauthにもデフォルトテンプレートがありますが、カスタマイズする場合がほとんどなので別途用意しました。
コードはallauthライブラリ内にあるものをコピーしてきました。

```py3:templates/signin/index.html
{% block content %}
<form method="post" action="{% url 'account_login' %}">
{% csrf_token %}
{{form.as_p}}
<button type="submit">Sign In</button>
</form>
{% endblock %}

```

```py3:templates/signup/index.html
{% block content %}
<form method="post" action="{% url 'account_signup' %}">
  {% csrf_token %}
  {{ form.as_p }}
  {% if redirect_field_value %}
  <input type="hidden" name="{{ redirect_field_name }}" value="{{ redirect_field_value }}" />
  {% endif %}
  <button type="submit">Signup</button>
</form>
{% endblock %}
```

ルーティングを追加します。

```py3:django-sample/urls.py
urlpatterns = [
  url(r'^$', views.home_view),
  url(r'^auth/', include('allauth.urls')),
  url(r'^signin/?$', views.signin_view),
  url(r'^signup/?$', views.signup_view),
]
```

[http://localhost:8000/signup](http://localhost:8000/signup) にアクセスしユーザー登録、
[http://localhost:8000/signin](http://localhost:8000/signin) でログインができるようになります。

このままではサインアウトができないのでViewとルーティングを追加します。

```py3:django-sample/views.py
from django.shortcuts import redirect


class SignoutView(views.LogoutView):

    def get(self, *args, **kwargs):
        return self.post(*args, **kwargs)

    def post(self, *args, **kwargs):
        if self.request.user.is_authenticated():
            self.logout()
        return redirect('/')

signout_view = SignoutView.as_view()
```

```django-sample/urls.py
urlpatterns = [
  # {...}
  url(r'^signout/?$', views.signout_view)
]

```
[http://localhost:8000/signout](http://localhost:8000/signout) でサインアウトできるようになります。

# TwitterアカウントでのSNSログイン

今回はTwitterでのソーシャルログインを実装します。事前にTwitter Developersでアプリを作成しておいて下さい。 電話番号を登録していないとできないみたいです。
https://apps.twitter.com

Create New App → Application Detailsに必要な情報を入力します。
Callback URLには以下を値を設定してください。

```text:callback_url
http://localhost:8000/auth/twitter/login/callback/
```

Django側でallauth周りの設定を追加していきます。

```py3:django-sample/settings.py
INSTALLED_APPS = [
    # {...}
    'allauth.socialaccount',
    'allauth.socialaccount.providers.twitter'
]
```

Django adminにアクセスするためのルーティングを追加します。

```py3:django-sample/urls.py
# {...}
from django.contrib import admin
admin.autodiscover()

urlpatterns = [
  # {...}
  url(r'^admin/', include(admin.site.urls))
]
```

管理画面にログインするためのアカウントを作成します。

```bash:terminal
$ python manage.py createsuperuser
```

[http://localhost:8000/admin](http://localhost:8000/admin)にアクセスしログインします。

SOCIAL ACCOUNTS > [social applications](http://localhost:8000/admin/socialaccount/socialapp/add/) にアクセスし各種設定をします。

- Providor → Twitter
- Name → Twitter
- Client id →　{Your Consumer Key (API Key)}
- Secret key → {Your Consumer Secret (API Secret)}
- Sites → Chosen sitesにexample.comを追加

保存後、一度管理画面からログアウトし、[http://localhost:8000/auth/twitter/login/](http://localhost:8000/auth/twitter/login/)にアクセスするとTwitter連携画面に飛ばされてSNSログイン(登録)ができます。

画面上にリンクを出したい場合は以下のようにします。

```django:templates/home/index.html
<a href="/auth/twitter/login/">Twitterでログイン</a>
<!-- または -->
<a href="{% url 'twitter_login' %}">Twitterでログイン</a>
```

rails routesみたいにルーティング周りを確認したい場合は以下のコマンドを使用します。
(django-extensions)

```bash:terminal
$ python manage.py show_urls

# 省略
/auth/twitter/login/	allauth.socialaccount.providers.oauth.views.view	twitter_login	
/auth/twitter/login/callback/	allauth.socialaccount.providers.oauth.views.view	twitter_callback
```

ログインしているかどうかで出し分けるには以下のようにします。

```py3:templates/home/index.html
{% if user.is_authenticated %}
<p>こんにちは {{ user.username }}さん</p>
{% else %}
<p>ログインしてください</p>
{% endif %}
```

# おまけ
ログインに使用するモデルを自由にカスタマイズすることができます。

```bash:terminal
$ cd ./django-sample/django-sample
$ python ../manage.py startapp accounts
```

```py:django-sample/settings.py
INSTALLED_APPS = [
     # {...}
    'django-sample.accounts'
]
```

```py3:django-sample/accounts/models.py
from django.db import models
from django.contrib.auth.models import AbstractUser, UserManager


class User(AbstractUser):
    objects = UserManager()

    class Meta(object):
        app_label = 'accounts'
```

```py3:django-sample/settings.py
AUTH_USER_MODEL = 'accounts.User'
```

データベースのマイグレーションをします。
migrateでコケる場合は一度データベースを消してやり直すといいかもしれません。

```bash:terminal
$ python manage.py makemigrations
$ python manage.py migrate
```

# 最後に
一応Githubにソースを載せています(前回のものを更新してます)
https://github.com/yukidallas/django-sample