1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Django】認証機能付きのTODOアプリを開発する

Posted at

はじめに

Djangoで簡単なTODOアプリを作ったので、セットアップから開発までの流れを記事にしました。

index.png

セットアップ

仮想環境の作成にuvを使うので、まずuvをインストールします。

pip install uv
uv init django_todo
cd django_todo

Djangoをインストールします。

uv add django

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

uv run django-admin startproject django_todo .

アプリを追加します。

uv run manage.py startapp todo

main.pyは不要なので削除します。

rm ./main.py

マイグレーション後に開発用サーバーを起動してhttp://localhost:8000/にアクセスします。
ページが表示されたら成功です。

uv run manage.py migrate
uv run manage.py runserver

image.png

Conventional Commitsを強制させる

コミット時にConvetional Commits形式のコメントを強制させます。
pre-commitをインストールします。

uv add pre-commit

.pre-commit-config.yamlを追加して以下を追記します。

.pre-commit-config.yaml
default_install_hook_types:
  - pre-commit
  - commit-msg
repos:
  - repo: https://github.com/compilerla/conventional-pre-commit
    rev: v4.2.0
    hooks:
      - id: conventional-pre-commit
        stages: [commit-msg]

以下のコマンドを実行します。

uv run pre-commit install

管理者ユーザを追加

以下のコマンドを実行してadminアカウントを作成します。

uv run .\manage.py createsuperuser

ログイン、ログアウト機能を実装

最初にDjangoの標準機能を使ってログイン、ログアウトを実装します。
django_todo/urls.pyに以下を追加します。

django_todo/urls.py
from django.contrib import admin
from django.urls import path
+ from django.contrib.auth.views import LoginView, LogoutView

urlpatterns = [
    path("admin/", admin.site.urls),
+   path("login/", LoginView.as_view(), name="login"),
+   path("logout/", LogoutView.as_view(), name="logout"),
]

django_todo/settings.pyに設定を追加します。

django_todo/settings.py
INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
+   "todo",
]
...
TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
+       "DIRS": [BASE_DIR / "templates"],
        "APP_DIRS": True,
        "OPTIONS": {
            "context_processors": [
                "django.template.context_processors.request",
                "django.contrib.auth.context_processors.auth",
                "django.contrib.messages.context_processors.messages",
            ],
        },
    },
]
...
+ LANGUAGE_CODE = "ja"

+ TIME_ZONE = "Asia/Tokyo"
...
+ LOGIN_URL = "login"
+ LOGIN_REDIRECT_URL = "index"
+ LOGOUT_REDIRECT_URL = "logout"

リポジトリ直下にtemplates/registrationフォルダを作成して以下のhtmlを追加します。

templates/registration/login.html
<!DOCTYPE html>
<html lang="jp">
    <head>
        <meta charset="UTF-8">
        <title>ログイン</title>
    </head>
    <body>
        <h2>ログイン</h2>
        <form method="post">
            {% csrf_token %}
            {{ form.as_p }}
            <button type="submit">ログイン</button>
        </form>
    </body>
</html>
templates/registration/logged_out.html
<!DOCTYPE html>
<html lang="jp">
    <head>
        <meta charset="UTF-8">
        <title>ログアウト</title>
    </head>
    <body>
        <h2>ログアウトしました</h2>
        <a href="{% url 'login' %}">再ログイン</a>
    </body>
</html>

http://localhost:8000/login/を開いてadminアカウントを入力します。
認証が成功すると以下のエラーが表示されます。
これはログイン後に遷移するページが用意されていないためです。

image.png

ログイン後の画面を追加

templatesフォルダ内にtodoフォルダを作成して以下のhtmlファイルを追加します。

templates/todo/index.html
<!DOCTYPE html>
<html lang="jp">
    <head>
        <meta charset="UTF-8">
        <title>TODOアプリ</title>
    </head>
    <body>
        <h2>Hello, {{ user.username }}</h2>
        <form method="post" action="{% url 'logout' %}">
            {% csrf_token %}
            <button type="submit">ログアウト</button>
        </form>
    </body>
</html>

todo/views.pyにビューを追加します。

todo/views.py
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import TemplateView


class TaskListView(LoginRequiredMixin, TemplateView):
    template_name = "todo/index.html"

todo/urls.pyを作成して以下を追加します。

todo/urls.py
from django.urls import path
from todo.views import TaskListView

urlpatterns = [
    path("", TaskListView.as_view(), name="index"),
]

django_todo/urls.pyに以下を追加します。

django_todo/urls.py
urlpatterns = [
    path("admin/", admin.site.urls),
    path("login/", LoginView.as_view(), name="login"),
    path("logout/", LogoutView.as_view(), name="logout"),
+   path("", include("todo.urls")),
]

ログイン画面でadminアカウントを入力すると、以下のページにログインします。
ログアウトボタンを押すとログアウトされます。

login.gif

テンプレートの分割

各htmlファイルでページ全体のhtmlタグを記述するのは冗長なので、テンプレート機能を使います。

templates/base.html
<!DOCTYPE html>
<html lang="jp">
    <head>
        <meta charset="UTF-8">
        <title>{% block title %}{% endblock %}</title>
    </head>
    <body>
        {% block content %}
        {% endblock %}
    </body>
</html>

各ページはタイトルとボディの部分だけ記述するようにします。

templates/registration/login.html
{% extends "base.html" %}

{% block title %}ログイン{% endblock %}

{% block content %}
    <h2>ログイン</h2>
    <form method="post">
        {% csrf_token %}
        {{ form.as_p }}
        <button type="submit">ログイン</button>
    </form>
{% endblock %}
templates/registration/logged_out.html
{% extends "base.html" %}

{% block title %}ログアウト{% endblock %}

{% block content %}
    <h2>ログアウトしました</h2>
    <a href="{% url 'login' %}">再ログイン</a>
{% endblock %}
templates/todo/index.html
{% extends "base.html" %}

{% block title %}TODOアプリ{% endblock %}

{% block content %}
    <h2>Hello, {{ user.username }}</h2>
    <form method="post" action="{% url 'logout' %}">
        {% csrf_token %}
        <button type="submit">ログアウト</button>
    </form>
{% endblock %}

サインアップ機能の実装

次はブラウザ上からアカウントを追加できるようにします。

todo/views.pyにサインアップ用のビューを追加します。

todo/views.py
from django.views.generic import TemplateView, CreateView
from django.contrib.auth.forms import UserCreationForm
from django.urls import reverse_lazy
from django.contrib.auth import login

class SignupView(CreateView):
    template_name = "registration/signup.html"
    form_class = UserCreationForm
    success_url = reverse_lazy("index")

    def form_valid(self, form):
        response = super().form_valid(form)
        user = form.save()
        login(self.request, user)
        return response

todo/urls.pyにURLを追加します。

todo/urls.py
urlpatterns = [
    path("", TaskListView.as_view(), name="index"),
+   path("signup/", SignupView.as_view(), name="signup"),
]

templates/registrationにHTMLを追加します。

templates/registration/signup.html
{% extends "base.html" %}

{% block title %}新規登録{% endblock %}

{% block content %}
    <h2>新規登録</h2>
    <form method="post">
        {% csrf_token %}
        {{ form.as_p }}
        <button type="submit">登録</button>
    </form>
    <a href="{% url 'login' %}">ログイン</a>
{% endblock %}

ログインページにリンクを追加します。

templates/registration/login.html
{% extends "base.html" %}

{% block title %}ログイン{% endblock %}

{% block content %}
    <h2>ログイン</h2>
    <form method="post">
        {% csrf_token %}
        {{ form.as_p }}
        <button type="submit">ログイン</button>
    </form>
+   <a href="{% url 'signup' %}">新規登録</a>
{% endblock %}

サインアップページでIDとパスワードを入力すると、アカウントが作成されてそのままログインできます。

signup.gif

タスクを追加する

タスクを管理するモデルを追加します。
ユーザー毎にタスクを管理するために、Userモデルを外部キーとして紐づけます。

todo/models.py
from django.db import models
from django.contrib.auth.models import User


class Task(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    title = models.CharField(max_length=200)
    completed = models.BooleanField(default=False)

    def __str__(self):
        return self.title

モデルを追加したのでマイグレーションを実行します。

uv run manage.py makemigrations
uv run manage.py migrate

Taskを管理サイトに登録します。

todo/views.py
from django.contrib import admin
from .models import Task

admin.site.register(Task)

TaskListViewにタスクを取得する処理と、タスクを追加する処理を追加します。

todo/views.py
class TaskListView(LoginRequiredMixin, TemplateView):
    template_name = "todo/index.html"

+   def get_context_data(self, **kwargs):
+       context = super().get_context_data(**kwargs)
+       context["tasks"] = Task.objects.filter(user=self.request.user)
+       return context

+   def post(self, request, *args, **kwargs):
+       title = request.POST.get("title")
+       if title:
+           Task.objects.create(user=request.user, title=title)
+       return redirect("index")

完了フラグを更新するビューとタスクを削除するビューも追加します。

todo/views.py
class TaskToggleView(View):
    def post(self, request, *args, **kwargs):
        task = Task.objects.get(id=kwargs["id"])
        task.completed = not task.completed
        task.save()
        return redirect("index")


class TaskDeleteView(View):
    def post(self, request, *args, **kwargs):
        task = Task.objects.get(id=kwargs["id"])
        task.delete()
        return redirect("index")

todo/urls.pyにリンクを追加します。

todo/urls.py
urlpatterns = [
    path("", TaskListView.as_view(), name="index"),
    path("signup/", SignupView.as_view(), name="signup"),
+   path("toggle/<int:id>", TaskToggleView.as_view(), name="toggle"),
+   path("delete/<int:id>", TaskDeleteView.as_view(), name="delete"),
]

index.htmlを修正します。

templates/todo/index.html
{% block content %}
-   <h2>Hello, {{ user.username }}</h2>
+   <h2>{{ user.username }} のTODOリスト</h2>
+   <form method="post">
+       {% csrf_token %}
+       <input type="text" name="title" placeholder="新しいタスク" required>
+       <button type="submit">追加</button>
+   </form>
+   <ul>
+       {% for task in tasks %}
+       <li>
+           <span class="{% if task.completed %}completed{% endif %}">{{ task.title }}</span>
+           <form action="{% url 'toggle' task.id %}" style="display: inline;" method="post">
+               {% csrf_token %}
+               <button type="submit"></button>
+           </form>
+           <form action="{% url 'delete' task.id %}" style="display: inline;" method="post">
+               {% csrf_token %}
+               <button type="submit">削除</button>
+           </form>
+       </li>
+       {% endfor %}
+   </ul>
+   <hr />
    <form method="post" action="{% url 'logout' %}">
        {% csrf_token %}
        <button type="submit">ログアウト</button>
    </form>
{% endblock %}

タスクの追加、更新、削除ができるようになりました。

index.png

CSSを適用させる

TODOリストのページにCSSを適用させます。

django_todo/settings.pyに設定項目を追加します。

django_todo/settings.py
...
STATIC_URL = "static/"
+ STATICFILES_DIRS = [BASE_DIR / "static"]
...

リポジトリ直下にstatic/css作成して、以下のCSSを作成します。

static/css/style.css
span {
    display: inline-block;
    min-width: 300px;
}

.completed {
    text-decoration: line-through;
    color: gray;
}

templates/base.htmlでCSSを読み込むようにします。

templates/base.html
+ {% load static %}
<!DOCTYPE html>
<html lang="jp">
    <head>
        <meta charset="UTF-8">
        <title>{% block title %}{% endblock %}</title>
+       <link rel="stylesheet" type="text/css" href="{% static 'css/style.css' %}">
    </head>
    <body>
        {% block content %}
        {% endblock %}
    </body>
</html>

「✔」ボタンを押すと打消し線が表示されるようになりました。

todo.gif

参考文献

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?