【Django】自作フォームを作成しログイン機能を使って権限を切り替える


投稿日 2019年6月5日 >> 更新日 2023年3月3日

今回はオリジナルの新規作成フォームとログイン・ログアウト機能を実装していきます。Djangoにはそれらの機能を簡単に実装できるようサポートされており短いコードで作成することができます。

ちなみに上の画像は【Django】高速でTODOアプリを作成するでの作業で新規作成用のフォームとログイン機能を付けたものであります。

もちろんフォームとログイン機能だけが目当てであっても大丈夫なように解説していきます。

フォームとはその画面でテキストを保存したりログインをする為のパスワードを入力するといった専用に設けられたページです。

Djangoの場合は管理画面が設けられており非常に便利でありますがオリジナルを制作したい事もあります。他にもSNSのように自分以外が使用する第三者にも使い勝手の良さがあった方がサイトに訪れてもらえる回数が多くなると思います。

ということでDjango管理画面で使われている機能を引っ張ってくるような感覚でフォームとログイン機能を取り付けて行きたいと思います。

実行環境&使用ライブラリ

実行環境
Windows Subsystem for Linux
Python 3.6.8
pip 9.0.1
使用ライブラリ ライセンス
Django==2.2.6 BSD

フォーム用にPythonファイルを作成

Djangoでフォームを自作するためにはDjangoが用意しているmodelFormを使います。

これをアプリディレクトリ下でforms.pyというPythonファイルを作成し、その中で必要なフォームを子クラスとして定義してきます。

今回カテゴリー用とTODOリスト用にフォームを作りたいので以下のように記述していきます。

# /todo/forms.py

from django.forms import ModelForm
from .models import Category, Todo


"""カテゴリー用のフォーム"""
class CategoryForm(ModelForm):

    class Meta:
        model = Category
        """
        フォーム画面で表示するラベル
        モデルのテーブル名
        """
        fields = ['title']


"""TODOリスト用のフォーム"""
class TodoForm(ModelForm):

    class Meta:
        model = Todo
        fields = ['title', 'category']

このようにModelFormクラスにそれぞれのカテゴリーやTODOのモデルを紐づけることによってオリジナルを作成することができます。次からはアプリを作成していく上でごく単純な順番でテンプレートへ渡していきます。

views.pyで定義しテンプレートへ渡す

forms.pyをviews.pyに渡していきますが、先にアプリディレクトリ下のurls.pyに以下を追記します。

# /todo/urls.py

from django.urls import path
from . import views


app_name = 'todo'

urlpatterns = [
        path('', views.index, name='index'),
        path('<int:id>/delete/', views.delete, name='delete'),
        path('todo/<str:category>/', views.todo_category, name='todo_category'),
        path('new_category/', views.new_category, name='new_category'),  # ←ここ
        path('new_todo/', views.new_todo, name='new_todo'),  # ←ここ
]

新規作成用のページへ飛ぶようにそれぞれnew_categoryとnew_todoを新しく追加しました。

そしてテンプレートへ渡すためのviews.pyを追記します。先ほど作成したforms.pyをインポートし、特定のフォームがPOSTされたときに保存するといった関数を定義していきます。

# /todo/views.py

from django.shortcuts import render, redirect, get_object_or_404
from .models import Todo, Category
from .forms import CategoryForm, TodoForm  # ←インポート


def index(request):
    ..........


def delete(request, id):
   ............


def todo_category(request, category):
    ...........


"""カテゴリ"""
def new_category(request):

    """もしもPOSTされたら"""
    if request.method == 'POST':
        form = CategoryForm(request.POST)

        """もしも検証を通過できれば"""
        if form.is_valid():
           """保存され"""
            form.save()
           """トップ画面にリダイレクトする"""
            return redirect('todo:index')

    else:
        form = CategoryForm

    return render(request, 'todo/new_category.html', {'form': form})


"""TODOリスト"""
def new_todo(request):

    if request.method == 'POST':
        form = TodoForm(request.POST)

        if form.is_valid():
            form.save()
            return redirect('todo:index')

    else:
        form = TodoForm

    return render(request, 'todo/new_todo.html', {'form': form})

それぞれの関数にリクエストされているようにnew_category.htmlとnew_todo.htmlを新しく作成します。

ここではbase.htmlを継承させずに新規作成画面を表示させることにします。

<!-- /todo/templates/todo/new_category.html -->

<h2>カテゴリー</h2>

<a href="{% url 'todo:index' %}">戻る</a>

<form action="{% url 'todo:new_category' %}" method="POST">
    {% csrf_token %}
    {{ form.title }}
    <button>保存</button>
</form>
<!-- /todo/templates/todo/new_todo.html -->

<h2>TODOリスト</h2>
<a href="{% url 'todo:index' %}">戻る</a>

<form action="{% url 'todo:new_todo' %}" method="POST">
    {% csrf_token %}
    {{ form.title }}
    {{ form.category }}
    <button>保存</button>
</form>

{% csrf_token %}(クロスサイトリクエストフォージェリ)は、ウィルスなどの攻撃を防ぐための「おまじない」としてDjangoから提供されています。これを組まなければエラーとなってしまいます。

views.pyにてそれぞれの関数のreturnとした辞書型である{{ form. }}の部分ですが、formタグのactionとしてそれぞれのモデルに当てはまった内容を返すように定義されています。

そしてbase.htmlとindex.htmlでそれぞれのnew_.....htmlに飛ぶようリンクを追記します。

<!-- /todo/templates/todo/base.html -->

<h2>カテゴリー</h2>
<a href="{% url 'todo:new_category' %}">新規作成</a>
<!-- /todo/templates/todo/index.html -->

<h2>TODOリスト</h2>
<a href="{% url 'todo:new_todo' %}">新規作成</a>

貼り付けたらさっそく自作フォームから新規カテゴリーやTODOを保存してみましょう。

ログイン機能の搭載

djangoでは簡単にログイン・ログアウト機能を追加することができます。views.pyでの記述をせずに、settings.py・アプリディレクトリ下のurls.py・ログイン用のhtmlを作成するだけです。

今回は管理者に限ったログイン機能を実装していきます。なので以下のようにログアウト時は自分以外の閲覧者は新規作成・削除ができない状態にしていきます。

まずはsettings.pyにてログイン・ログアウト機能を使えるように設定します。

# /project/settings.py

LANGUAGE_CODE = 'ja'

TIME_ZONE = 'Asia/Tokyo'

USE_I18N = True

USE_L10N = True

USE_TZ = True


STATIC_URL = '/static/'

LOGIN_URL = 'todo:login'  # ←ここ
LOGIN_REDIRECT_URL = 'todo:index'  # ←ここ
LOGOUT_REDIRECT_URL = 'todo:index'  # ←ここ

LOGIN_URLはアプリディレクトリ下のurls.pyにて追記するもので、それ以外の設定はどちらかの機能が呼ばれたときにリダイレクトされる場所を指定しています。ここではトップ画面のtodo:indexとしています。

次にアプリディレクトリ下のurls.pyに追記していきます。

# /todo/urls.py

from django.urls import path
from django.contrib.auth import views as auth_views  # ←ここ
from . import views


app_name = 'todo'

urlpatterns = [
        path('', views.index, name='index'),
        path('login/', auth_views.LoginView.as_view(template_name='todo/login.html'),
                                                                name='login'),  # ←ここ
        path('logout/', auth_views.LogoutView.as_view(), name='logout'),  # ←ここ
        path('<int:id>/delete/', views.delete, name='delete'),
        path('todo/<str:category>/', views.todo_category, name='todo_category'),
        path('new_category/', views.new_category, name='new_category'),
        path('new_todo/', views.new_todo, name='new_todo'),
]

Djangoのログイン機能などが組み込まれているdjango.contrib.authをインポートします。

そしてログイン・ログアウトのパスを通すように記述していきます。

見ての通りloginが呼ばれたときtemplate_nameにてそのままlogin.htmlに飛ぶように設定しているので、login.htmlを作成します。

<!-- /todo/templates/todo/login.html -->

<form method="POST">
    {% csrf_token %}
    <input name="username">
    <br>

    <input type="password" name="password">
    <br>

    <input type="submit" value="ログイン">
</form>

そしてトップ画面のbase.htmlにてログインされたらログアウトボタンを表示し新規作成・削除が表れるようにし、ログアウトされたらログインボタンを表示し新規作成・削除が消えるように実装したいと思います。

それを行うには、{% if request.user.is_superuser %}{% endif %}をそれぞれの機能に当てはめて行きます。これはDjango管理者にしか当てはめないようにするテンプレートタグです。

<!-- /todo/templates/todo/base.html -->

<!doctype html>
<html>
    <head>

        <title>TODO</title>

    </head>
    <body>

        {% if request.user.is_superuser %}
            <a href="{% url 'todo:logout' %}">
                <button>ログアウト</button>
            </a>
        {% else %}
            <a href="{% url 'todo:login' %}">
                <button>ログイン</button>
            </a>
        {% endif %}
            <h2>カテゴリー</h2>

        {% if request.user.is_superuser %}
            <a href="{% url 'todo:new_category' %}">新規作成</a>
        {% endif %}
            <ul>
                <a href="{% url 'todo:index' %}">
                    <li>トップ</li>
                </a>
                {% for category in category_list %}
                    <a href="{% url 'todo:todo_category' category %}">
                        <li>{{ category }}</li>
                    </a>
                {% endfor %}
            </ul>

        {% block content %}
        {% endblock %}

    </body>
</html>

ログイン・ログアウト部分では相対的になっていることに注意してください。削除機能にも同じようにテンプレートタグで囲ってください。

<!-- /todo/templates/todo/index.html -->

{% extends 'todo/base.html' %}

{% block content %}

    <h2>TODOリスト</h2>

    {% if request.user.is_superuser %}
        <a href="{% url 'todo:new_todo' %}">新規作成</a>
    {% endif %}

    {% if category %}
        <h2>カテゴリー: {{ category.title }}</h2>
    {% endif %}

    {% for todos in todo %}
        <ul>
            <li>{{ todos.title }}--{{ todos.created_at }}</li>
            {% if request.user.is_superuser %}
                <form method="POST" action="{% url 'todo:delete' todos.pk %}">
                    {% csrf_token %}
                    <button type="submit">削除</button>
                </form>
            {% endif %}
        </ul>
    {% endfor %}

{% endblock %}

上手く読み込まれればこのような結果になります。

Djangoではさまざまな機能のサポートがあり充実したフレームワークであります。このような機能は複雑なアルゴリズムが必要とされると思いますがこのように簡単に実装できてしまいます。

まだまだDjangoでは面白くて実用的な機能もあるのでどんどん探求していきましょう。

それでは以上となります。

最後までご覧頂きありがとうございました。