Djangoでログイン機能を試す

Django
Lawrence MonkによるPixabayからの画像

今回はDjangoの勉強がてらログイン機能を実装してみたよ

環境

  • OS: windows10 home
  • エディタ: VSCode
  • フレームワーク: Django 3.1.2

ディレクトリ構成

fetch_project/
  ├ fetch_project/
  │   ├ settings.py
  │   └ urls.py
  └ fetch_apps/
      ├ templates/
      │   └ fetch_apps/
      │       ├ index.html
      │       └ login.html
      ├ urls.py
      └ views.py

※今回扱うファイルのみ表示しています。

また、djangoの環境は既に用意されていることを前提として話を進めます。

準備

公式のチュートリアルを見れば簡単に準備できます。
一応、準備段階から載せます。

djangoアプリを作りたいディレクトリに移動した後に以下のコマンドを実行

$ django-admin startproject fetch_project
$ cd fetch_project
fetch_project$ python manage.py startapp fetch_apps
# fetch_project/fetch_project/settings.py
~~~~~~~~~~~~~~~ 省略 ~~~~~~~~~~~~~~~~~~~~
INSTALLED_APPS = [
    'fetch_apps.apps.FetchAppsConfig',  # ...1
~~~~~~~~~~~~~~~~ 省略 ~~~~~~~~~~~~~~~~~~~
]
~~~~~~~~~~~~~~~~ 省略 ~~~~~~~~~~~~~~~~~~~
  • 1: 作成したアプリを追加
# fetch_project/fetch_project/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('fetch_apps/', include('fetch_apps.urls')),  # ...1
    path('admin/', admin.site.urls),
]
  • 1: http://127.0.0.1:8000/fetch_apps/ でfetch_appsアプリにアクセスできるように追加します

これでとりあえず準備はできました。

次はいよいよ本命のログイン機能を実装していきます。

ログイン機能ver1

まずは公式のドキュメントを読んでなんとなくログイン機能を実装してみました。

ページを表示

# fetch_project/fetch_apps/views.py
from django.shortcuts import render, redirect
from django.contrib.auth import authenticate, logout
from django.contrib.auth import login as auth_login

def index(request):
    if not request.session.keys():  # ...1
        return redirect('/fetch_apps/login')
    if 'logout' in request.POST:
        logout(request)  # ...2
        return redirect('/fetch_apps/login')
    return render(request, 'polls/index.html')

def login(request):
    if 'login' in request.POST:
        name = request.POST['name']
        password = request.POST['password']
        user = authenticate(username=name, password=password)  # ...3

        if user is not None:
            auth_login(request, user)  # ...4
            return redirect('/fetch_apps/')

    return render(request, 'fetch_apps/login.html')
  • 1: この部分ではユーザーがログインをしているかの確認をしています。
    もしログインをしていたのであればsessionの中には以下の3つのキーが含まれます
     _auth_user_id, _auth_user_backend, _auth_user_hash
  • 2: メイン画面でログアウトボタンを押した際にログインユーザーの情報を消すことができます。
    公式のドキュメントにも記載がありますがlogout()が呼ばれたときにsessionの中にある全てのデータが消されます。
  • 3: authenticate()はUserに登録していないユーザー名とパスワードを入れて実行するとNoneが返って来ます。
    また、存在するユーザーを入力するとユーザー名だけが裸で返って来ます。
    (例:ユーザー名=admin, 戻り値=admin)
  • 4: django.contrib.auth.loginを実行したときにsession内に1の情報が追加されます。

fetch_appsに関するurlの設定

# fetch_project/fetch_apps/urls.py
from django.urls import path

from fetch_apps import views

app_name = 'fetch_apps'  # ...1
urlpatterns = [
    path('', views.index, name='index'),
    path('login/', views.login, name='login'),
]
  • 1: htmlでformタグのactionを設定する際にapp_name:各urlごとに設定した名前で指定する

ログイン画面

<!-- fetch_project/fetch_apps/templates/fetch_apps/login.html -->
<form action="{% url 'fetch_apps:login' %}" method="post">  <!-- 1 -->
    {% csrf_token %}
    <input type="text" name="name" placeholder="名前">
    <input type="text" name="password" placeholder="パスワード">
    <input type="submit" name="login" value="login">
</form>
  • 1: urls.pyで設定したapp_nameのネームスペースと各urlごとに設定したnameを使ってフォームの送信先を指定します。

ログイン後表示するメイン画面

<!-- fetch_project/fetch_apps/templates/fetch_apps/index.html -->
<p>fetchkun</p>
<form action="{% url 'fetch_apps:index' %}" method="post">
    {% csrf_token %}
    <input type="submit" name="logout" value="logout">
</form>

特に説明すること無し

とりあえず実装自体はとても単純に作れました。しかし・・
なんかあまりスマートな書き方では無いですよね~💦

ログイン機能ver2

ということでもっと綺麗にコンパクトにまとめてみたいと思います。

ページを表示するver2

# fetch_project/fetch_apps/views.py
from django.shortcuts import render, redirect
from django.contrib.auth import views, logout


def index(request):
    if not request.user.is_authenticated:  # ...1
        return redirect('/fetch_apps/login')
    if 'logout' in request.POST:
        logout(request)
        return redirect('/fetch_apps/login')
    return render(request, 'fetch_apps/index.html')

class Login(views.LoginView):  # ...2
    template_name = 'fetch_apps/login.html'
    extra_context = {
        'next': '/fetch_apps/'
    }
  • 1: ver1のときはsessionを直接のぞき込みログインの有無を確認していました。
    ver2ではもっと簡単にis_authenticatedを利用することでログインの有無をbooleanで受け取れるようになりました。
  • 2: ver1では自前でlogin関数を用意し、authenticateだなんだと色々自分でコードを組んで作っていました。
    ところがここではたった2つ設定するだけでできてしまいました。
    • template_name: ログイン後に表示するページを指定
      (デフォルトではregistration/login.htmlになるっぽい)
    • extra_context: template_nameに指定したページに渡すコンテキストデータを追加すること
      ができる。
      ログインした後、メインページに遷移するために’next’にはメインページのurlの一部を設定

fetch_appsに関するurlの設定ver2

# fetch_project/fetch_apps/urls.py
from django.urls import path

from fetch_apps import views

app_name = 'fetch_apps'
urlpatterns = [
    path('', views.index, name='index'),
    path('login/', views.Login.as_view(), name='login'),  # ...1
]
  • 1: django.contrib.auth.views.LoginViewを継承したLoginクラス用に変更
    GET, POSTそれぞれのメソッドを使ったときにログイン画面を表示または認証情報を送信することができます。

ログイン画面ver2

<!-- fetch_project/fetch_apps/templates/fetch_apps/login.html -->
<form action="{% url 'fetch_apps:login' %}" method="post">  <!-- 1 -->
    {% csrf_token %}
    <table>
        <tr>
            <td>{{ form.username.label_tag }}</td>
            <td>{{ form.username }}</td>
        </tr>
        <tr>
            <td>{{ form.password.label_tag }}</td>
            <td>{{ form.password }}</td>
        </tr>
    </table>

    <input type="submit" value="login">
    <input type="hidden" name="next" value="{{ next }}">  <!-- 2 -->
</form>
  • 1: urls.pyに設定した名前空間と各urlごとのnameを指定することでデフォルトの送信先を決めることができます。
  • 2: 先ほどのviews.pyのLoginクラスで設定したextra_contextのnextがここに入ります。
    ログインの認証が成功したときはnextに設定したurlに飛びます。

結構まとまってきたんじゃないでしょうか。

ver1の時と比べて全体の見通しが良くなったと思うんですが、

もうちょっとスマートな書き方がありそう・・?

ログイン機能ver3

もう少しだけ編集してみます。

# fetch_project/fetch_apps/views.py
from django.shortcuts import render, redirect
from django.contrib.auth import views, logout
from django.contrib.auth.decorators import permission_required, login_required

@login_required(login_url='/polls/login')  # ...1
def index(request):
    if 'logout' in request.POST:
        logout(request)
        return redirect('/polls/login')
    return render(request, 'polls/index.html')

class Login(views.LoginView):
    template_name = 'polls/login.html'
    redirect_authenticated_user = True
    extra_context = {
        'next': '/polls/'
    }
  • 1: ver2まではrequest.userの認証状態を確認してログインできていなかったらログイン画面に
    リダイレクトする機能を自分で作っていました。
    しかしここではdecoratorsのlogin_required機能を使うことでログインurlを指定するだけで
    ログインしていないユーザーをリダイレクトすることができました。
    たった一行でできます。

ログイン機能verおまけ

おまけとしてこういう書き方も見つけたので一応載せます。

ページを表示するverおまけ

# fetch_project/fetch_apps/views.py
from django.shortcuts import render, redirect
from django.contrib.auth import views, logout, mixins
from django.views import View

class Index(mixins.LoginRequiredMixin, View):  # ...1
    login_url = '/fetch_apps/login'

    def get(self, request, *args, **kwargs):  # ...2
        return render(request, 'fetch_apps/index.html')

    def post(self, request, *args, **kwargs):  # ...3
        # 考える余地あり
        logout(request)
        return redirect('/fetch_apps/login')

class Login(views.LoginView):
    template_name = 'fetch_apps/login.html'
    extra_context = {
        'next': '/fetch_apps/'
    }
  • 1: ここでLoginRequiredMixinを使ってみました。
    django.contrib.auth.decoratorslogin_requiredと同じようにログインurlを指定するだけで
    ログインしていないユーザーが直接メイン画面に行こうとしてもログイン画面に強制的に
  • 遷移させることができます。
    LoginRequiredMixinViewよりも前に書きましょう
    Viewよりも後ろに書くとログイン画面へのリダイレクトが機能しなくなります。
  • 2, 3: ver2までのindex処理はgetもpostも同じindex関数の中で処理してました。
    ver3ではそれぞれのメソッドに分けて処理をすることで明確に見分けやすくなりましたね。
    ※3の部分に関しては今回postメソッドで扱っているのがログアウト機能のみなので
    簡易的に実装しましたがこの部分はもっと考える必要がありそうですね

fetch_apps内の各urlを設定

# fetch_project/fetch_apps/urls.py
from django.urls import path

from fetch_apps import views

app_name = 'fetch_apps'
urlpatterns = [
    path('', views.Index.as_view(), name='index'),
    path('login/', views.Login.as_view(), name='login'),
]

まとめ

今更ながらDjangoのログイン機能を実装してみました。

まだまだスマートに書く方法は沢山あるかと思いますが、少しでも参考になれば幸いです。

ここまで読んでくれてありがとう!!

タイトルとURLをコピーしました