ajaxやreactを使わなくても非同期通信できるjsライブラリのHTMX(HTML Extensions)とDjangoでメモアプリを作ってみた。
参考URL
https://qiita.com/tsmd/items/0d07feb8e02cfa213cc4
HTMX + Djangoで、簡単なログインユーザ別のメモアプリを作ってみたい
認証アプリを作って、ログイン・ログアウト・ユーザ登録もできるようにする
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
# 最初にプロジェクト名をconfigにして設定フォルダだと分かりやすくする django-admin startproject config # その後にプロジェクト名に変更 mv config memo_project cd memo_project # ログインユーザのアプリ作成 python manage.py startapp accounts # メモアプリ作成 python manage.py startapp notes # テンプレートの構成 memo_project/ ├── accounts/ │ └── templates/ │ └── accounts/ │ ├── login.html │ └── register.html ├── notes/ │ └── templates/ │ └── notes/ │ ├── note_page.html │ └── partials/ │ └── note_list.html ├── config/ │ └── settings.py ├── templates/ │ └── base.html ← プロジェクト共通のベーステンプレート └── manage.py # サーバサイド用でhtmx送信判定に使う pip install django-htmx |
config/settings.py に追記
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
INSTALLED_APPS = [ ... 'django_htmx', 'accounts', 'notes', ] import os TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates')], # 共通テンプレートの場所 'APP_DIRS': True, # アプリ内テンプレート(accounts/templates など)も読み込む ] |
プロジェクト全体(memo_project/urls.py):
1 2 3 4 5 6 7 8 |
from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('accounts/', include('accounts.urls')), path('', include('notes.urls')), ] |
accounts/urls.py:
1 2 3 4 5 6 7 8 |
from django.urls import path from .views import register_view, login_view, logout_view urlpatterns = [ path('register/', register_view, name='register'), path('login/', login_view, name='login'), path('logout/', logout_view, name='logout'), ] |
notes/urls.py:
1 2 3 4 5 6 |
from django.urls import path from .views import note_edit urlpatterns = [ path("", note_edit, name="note_edit"), ] |
base.html(共通レイアウト)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>メモアプリ</title> <script src="https://unpkg.com/htmx.org@1.9.2"></script> </head> <body> {% if user.is_authenticated %} <p>ログイン中: {{ user.username }} | <a href="{% url 'logout' %}">ログアウト</a></p> {% endif %} {% block content %}{% endblock %} </body> </html> |
accounts/register.html(ユーザー登録画面)
1 2 3 4 5 6 7 8 9 10 |
{% extends 'base.html' %} {% block content %} <h2>ユーザー登録</h2> <form method="post"> {% csrf_token %} {{ form.as_p }} <button type="submit">登録</button> </form> <p>すでにアカウントをお持ちですか? <a href="{% url 'login' %}">ログインはこちら</a></p> {% endblock %} |
accounts/login.html(ログイン画面)
1 2 3 4 5 6 7 8 9 10 |
{% extends 'base.html' %} {% block content %} <h2>ログイン</h2> <form method="post"> {% csrf_token %} {{ form.as_p }} <button type="submit">ログイン</button> </form> <p>アカウントをお持ちでないですか? <a href="{% url 'register' %}">新規登録はこちら</a></p> {% endblock %} |
accounts/views.py ログイン・ユーザ処理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
from django.contrib.auth import login, logout, authenticate from django.contrib.auth.forms import UserCreationForm, AuthenticationForm from django.shortcuts import render, redirect # ユーザー登録ビュー def register_view(request): if request.method == 'POST': form = UserCreationForm(request.POST) if form.is_valid(): user = form.save() login(request, user) return redirect('note_edit') else: form = UserCreationForm() return render(request, 'accounts/register.html', {'form': form}) # ログインビュー def login_view(request): if request.method == 'POST': form = AuthenticationForm(request, data=request.POST) if form.is_valid(): login(request, form.get_user()) return redirect('note_edit') else: form = AuthenticationForm() return render(request, 'accounts/login.html', {'form': form}) # ログアウトビュー def logout_view(request): logout(request) return redirect('login') |
notes/models.py メモのモデル定義
1 2 3 4 5 6 7 8 9 10 11 |
from django.db import models from django.contrib.auth.models import User # 1ユーザーに1つのメモを関連づける(OneToOneField) class Note(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) content = models.TextField(blank=True) updated_at = models.DateTimeField(auto_now=True) def __str__(self): return f"{self.user.username}のメモ" |
notes/views.py メモの保存処理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
from django.contrib.auth.decorators import login_required from django.shortcuts import render from django.http import HttpResponse from .models import Note @login_required def note_edit(request): note, _ = Note.objects.get_or_create(user=request.user) if request.method == "POST": note.content = request.POST.get("content", "") note.save() return HttpResponse("") # 空レスポンスでOK return render(request, "notes/note_edit.html", {"note": note}) |
notes/templates/notes/note_edit.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
{% extends 'base.html' %} {% block content %} <h2>あなたのメモ</h2> <form method="post"> {% csrf_token %} <textarea name="content" rows="10" cols="60" hx-post="." hx-trigger="keyup changed delay:500ms" hx-swap="none" >{{ note.content }}</textarea> </form> <p><small>500msごとに自動保存されます。</small></p> {% endblock %} |
htmxで非同期処理で自動保存。
textarea自体がpostしている。formはcsrf_tokenを一緒に送信するための器。
これがやりたかった!