この動画の内容を、最後まで終わらせることができました。
やったことを最大限忘れないようにするために、ここに作業の履歴を残しておきます。
今日取り組んだこと
今日取り組んだことは以下の通り。
- データの更新機能の実装
- データの削除機能の実装
- ログイン・ログアウト機能の実装
- CSS
- JavaScript
データの更新機能の実装
一度アプリに投稿したデータを、後から編集できるようにする。
具体体には、日記の詳細画面に「編集」ボタンを表示して、それを押すとデータの編集画面が表示され、編集と更新をすることができる、という機能を実装する。
まず、更新画面のhtmlを作成する。templates/diary/page_update.htmlを新規作成。
{% extends "base/diary_base.html" %}
{% block body %}
<h2>日記を編集</h2>
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">更新</button>
</form>
{% endblock %}
テンプレートの継承ブロックの中に、templates/diary/page_form.htmlと同じフォームを張り付け、ボタンの名前を更新に変えておく。
次にビューの作成。
class PageUpdateView(View):
def get(self, request, id):
page = get_object_or_404(Page, id=id)
form = PageForm(instance=page) # もともと登録されていたデータをフォームに含める
return render(
request, "diary/page_update.html", {"form": form}
)
def post(self, request, id):
page = get_object_or_404(Page, id=id)
form = PageForm(request.POST, request.FILES, instance=page)
if form.is_valid():
form.save()
return redirect("diary:page_detail", id=id)
return render(request, "diary/page_form.html", {"form": form})
page_update = PageUpdateView.as_view()
専用のクラスを作成。
getメソッドで、PageFormクラスのinstance引数にpageを渡すことで、指定したデータがあらかじめ入っているフォームオブジェクトを作成することができる。
更新用のpostメソッドも作成。こちらは更新画面の「更新」ボタンを押すと動くメソッド。指定したデータを扱っている点以外はPageCreateViewクラスのメソッドとほぼ同じ。
form.save()メソッドで、対象ページのデータが新しいデータで更新される。
diary/urls.pyにurlパターンを追加。
path("page/<uuid:id>/update/", views.page_update, name="page_update"), # リストに追加
そして、日記の詳細画面に、更新画面に遷移するためのボタンを作成。
<br>
<a href="{% url 'diary:page_update' page.id %}">編集</a>
データの削除機能の実装
データ操作系の最後の要素として、日記ページを削除する機能を実装。
日記ページの詳細画面に削除ボタンを付けて、そこを押すと削除確認画面が表示され、実行すると対象の日記のデータが削除される機能を実装した。
まず、削除確認用のhtmlファイルを新規作成。
{% extends "base/diary_base.html" %}
{% block body %}
<form method="post">
{% csrf_token %}
<p>このページを削除していいですか?</p>
<button type="submit">削除</button>
</form>
<h2>{{ page.title }}</h2>
<div>{{ page.page_date }}</div>
<div>{{ page.body | linebreaksbr }}</div>
{% if page.picture %}
<img src="{{ page.picture.url }}" />
{% endif %}
{% endblock %}
ページ詳細画面と同じ画面の上に、削除していいか確認するボタンが付いている。
ボタンはformタグで囲み、methodはpost、csrf_tokenを加え、buttonタグのtypeはsubmit。
次に、データを削除するためのビューを作成する。
class PageDeleteView(View):
def get(self, request, id):
page = get_object_or_404(Page, id=id)
return render(
request, "diary/page_confirm_delete.html", {"page": page}
)
def post(self, request, id):
page = get_object_or_404(Page, id=id)
page.delete()
return redirect("diary:page_list")
page_delete = PageDeleteView.as_view()
getメソッドは、削除確認画面を表示するためのもの。そして削除するためのメソッドはpost。
page.delete()メソッドを呼び出すだけでデータを削除することができる。
削除後は、ページの一覧画面に遷移する。
ただし、このままだとデータベースのデータは削除されるが、写真のファイルは残ったままになってしまうので、それを削除する操作を加える。
diary/models.pyに手を加える。Pageクラスのdeleteメソッドをオーバーライドする。
やり方は簡単で、子クラスにも新しくdeleteメソッドを作るだけ。
from pathlib import Path # 追加
class Page(models.Model):
# 略
def delete(self, *args, **kwargs):
picture = self.picture
super().delete(*args, **kwargs)
if picture: # 対象の写真のデータがあれば、ファイルの削除を行う
Path(picture.path).unlink(missing_ok=True)
*args、**kwargsについては知らないので、後で別途調べる必要あり。
まず、このオブジェクト自身のself.pictureを、このメソッドのローカル変数pictureに代入。
そして、元々このデリートが呼ばれた時に動く基底クラスのデリートを呼び出したいので、super()のdelete()を呼び出す。その後、写真のデータがある時だけ、ファイルの削除を実行するコードを加えたら、ok!
diary/urls.pyにパターンを追加。
path("page/<uuid:id>/delete/", views.page_delete, name="page_delete"), # 追加
ページの詳細画面に削除ボタンを追加。
<a href="{% url 'diary:page_delete' page.id %}">削除</a>
ログイン・ログアウト機能の実装
この日記を見れる人を制限するために、ログイン機能を実装する。
Djangoがあらかじめ備えているログイン・ログアウト機能をそのまま使う事ができる。
比較的簡単にログイン機能が実装できる。
まず、accountsというアプリを作成する。
python manage.py startapp accounts
accountsフォルダが作成されるので、urls.pyファイルを作成。
from django.contrib.auth.views import LoginView, LogoutView
from django.urls import path
app_name = "accounts"
urlpatterns = [
path("login/", LoginView.as_view(), name="login"),
path("logout/", LogoutView.as_view(), name="logout"),
]
LoginView、LogoutViewクラスを使用して、このように実装する。
続いて、config.urls.pyのurlpatternsに、accountsアプリのパスを追加。
path("accounts/", include("accounts.urls")),
path("", include('django.contrib.auth.urls')),
加えて、Djangoがもともと用意しているauthという認証のパスも追加している。
続いて、config.settings.pyに、ログイン後、ログアウト後にリダイレクトされるパスを設定する。
LOGIN_REDIRECT_URL = "/diary/"
LOGOUT_REDIRECT_URL = "/login/"
今回は日記アプリしか存在していないので、LOGIN_REDIRECT_URL = "/diary/"としている。
これにより、ログインすると日記アプリのトップページに遷移し、ログアウトすると、ログイン画面に遷移するようになる。
次にログイン用のhtmlファイルを新規作成。
ログイン用のhtmlは、デフォルトでregistration/login.htmlを探す。なので、templates/registration/login.htmlを手動で作成。
{% extends "base/diary_base.html" %}
{% block body %}
<h2>ログイン</h2>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">ログイン</button>
</form>
{% endblock %}
最後に、ログアウトする用のリンクを作る。
form action="{% url 'accounts:logout' %}" method="post">
{% csrf_token %}
<button type="submit">ログアウト</button>
</form>
これでログイン機能の実装は完了。
Webブラウザで、ローカルホストのドメインにうしろにlogin/パスを渡すと、ログイン画面に遷移する。
ユーザー名とパスワードを入力すると、日記アプリのトップページに遷移する。
しかし、このままではログインしなくても、diary/パスを渡せば直接日記アプリの画面に遷移できてしまう。
なので、ログインしなければ日記アプリのビューを動かせないようにする設定を行う。
そのためにMixInというものを使う。これも別途確認するといいかもしれない。
from django.contrib.auth.mixins import LoginRequiredMixin
class IndexView(LoginRequiredMixin, View):
# 略
class PageCreateView(LoginRequiredMixin, View):
# 略 ....
ビューで設定した全てのクラスに、LoginRequiredMixinクラスを継承させる。これが、ログインを必須とするMixInのクラスである。
この時、一番最初の継承のクラスとして書く必要があるので注意。
これで、直接日記アプリの画面に移動しようと思っても、ログインしていないと、ログイン画面に戻されてしまう、という仕様にすることができる。
CSS
このアプリにCSSを適用する。
トップフォルダから、static/diary/css/フォルダを作成し、そこにbase.cssファイルを作成。内容を記述する。
このCSSを日記アプリ全体に適用する設定を行っていく。
まずはcongif/settings.pyに以下の内容を記載。
STATIC_URL = "static/"
STATICFILES_DIRS = [
BASE_DIR/ "static"
]
STATICFILES_DIRS定数を新しく追加し、STATIC_URL = "static/"となっているかを確認する。
次にtemplates\base/diary_base.htmlのヘッダー内に、以下の記述を追加。
{% load static %}
<link rel="stylesheet" href="{% static 'diary/css/base.css' %}">
他にも、CSSを適用させたいbuttonタグに必要な設定を行うと良い。
これで、アプリにCSSを適用させることができる。
JavaScript
最後に、JavaScriptも使えるようにしていく。
static/diary/js/フォルダを新規作成。その中にbase.jsを作って、内容を記述する。
動画で書いていたのは、現在の日時を取得して画面に表示するプログラムである。
document.addEventListener('DOMContentLoaded', function() {
function updateTime() {
const now = new Date();
const year = now.getFullYear();
const month = (now.getMonth() + 1).toString().padStart(2, '0');
const day = now.getHours().toString().padStart(2, '0');
const hours = now.getHours().toString().padStart(2, '0');
const minutes = now.getMinutes().toString().padStart(2, '0');
const seconds = now.getSeconds().toString().padStart(2, '0');
const dateString = `${year}年${month}月${day}日`;
const timeString = `${hours}時${minutes}分${seconds}秒`;
const dateTimeString = `${dateString} ${timeString}`;
document.getElementById('clock').textContent = dateTimeString;
}
updateTime();
setInterval(updateTime, 1000);
})
templates\base/diary_base.htmlのヘッダー内に、以下の記述を追加。
{% load static %}
<link rel="stylesheet" href="{% static 'diary/css/base.css' %}">
<script src="{% static 'diary/js/base.js' %}"></script> # 追加
templates/diary/index.htmlの、現在時刻を表示したい箇所に以下の記述を加える。
<div id="clock"></div>
これで、JavaScriptの機能をアプリに実装することができました。
所感・感想
少し時間がかかりましたが、Djangoの概要を学ぶことができました。
書いたものが動いてくれると、とても嬉しいものがありますね。
ゆくゆくは、色々なことができるようになってみたいです。