0
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の基本 #4

0
Posted at

この動画の内容を、最後まで終わらせることができました。
やったことを最大限忘れないようにするために、ここに作業の履歴を残しておきます。

今日取り組んだこと

今日取り組んだことは以下の通り。

  • データの更新機能の実装
  • データの削除機能の実装
  • ログイン・ログアウト機能の実装
  • CSS
  • JavaScript

データの更新機能の実装

一度アプリに投稿したデータを、後から編集できるようにする。
具体体には、日記の詳細画面に「編集」ボタンを表示して、それを押すとデータの編集画面が表示され、編集と更新をすることができる、という機能を実装する。

まず、更新画面のhtmlを作成する。templates/diary/page_update.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と同じフォームを張り付け、ボタンの名前を更新に変えておく。

次にビューの作成。

diary/viewes.py
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パターンを追加。

diary/urls.py
path("page/<uuid:id>/update/", views.page_update, name="page_update"),  # リストに追加

そして、日記の詳細画面に、更新画面に遷移するためのボタンを作成。

templates/diary/page_detail.html
<br>
<a href="{% url 'diary:page_update' page.id %}">編集</a>

データの削除機能の実装

データ操作系の最後の要素として、日記ページを削除する機能を実装。
日記ページの詳細画面に削除ボタンを付けて、そこを押すと削除確認画面が表示され、実行すると対象の日記のデータが削除される機能を実装した。

まず、削除確認用のhtmlファイルを新規作成。

diary/page_confirm_delete.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タグで囲み、methodpostcsrf_tokenを加え、buttonタグのtypesubmit

次に、データを削除するためのビューを作成する。

diary/views.py
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メソッドを作るだけ。

diary/models.py
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にパターンを追加。

diary/urls.py
path("page/<uuid:id>/delete/", views.page_delete, name="page_delete"),  # 追加

ページの詳細画面に削除ボタンを追加。

templates/diary/page_detail.html
<a href="{% url 'diary:page_delete' page.id %}">削除</a>

ログイン・ログアウト機能の実装

この日記を見れる人を制限するために、ログイン機能を実装する。
Djangoがあらかじめ備えているログイン・ログアウト機能をそのまま使う事ができる。
比較的簡単にログイン機能が実装できる。

まず、accountsというアプリを作成する。

python manage.py startapp accounts

accountsフォルダが作成されるので、urls.pyファイルを作成。

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"),
]

LoginViewLogoutViewクラスを使用して、このように実装する。

続いて、config.urls.pyurlpatternsに、accountsアプリのパスを追加。

config.urls.py
path("accounts/", include("accounts.urls")),
path("", include('django.contrib.auth.urls')),

加えて、Djangoがもともと用意しているauthという認証のパスも追加している。

続いて、config.settings.pyに、ログイン後、ログアウト後にリダイレクトされるパスを設定する。

config.settings.py
LOGIN_REDIRECT_URL = "/diary/"

LOGOUT_REDIRECT_URL = "/login/"

今回は日記アプリしか存在していないので、LOGIN_REDIRECT_URL = "/diary/"としている。
これにより、ログインすると日記アプリのトップページに遷移し、ログアウトすると、ログイン画面に遷移するようになる。

次にログイン用のhtmlファイルを新規作成。
ログイン用のhtmlは、デフォルトでregistration/login.htmlを探す。なので、templates/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 %}

最後に、ログアウトする用のリンクを作る。

templates/diary/index.html
form action="{% url 'accounts:logout' %}" method="post">
    {% csrf_token %}
    <button type="submit">ログアウト</button>
</form>

これでログイン機能の実装は完了。
Webブラウザで、ローカルホストのドメインにうしろにlogin/パスを渡すと、ログイン画面に遷移する。
ユーザー名とパスワードを入力すると、日記アプリのトップページに遷移する。

しかし、このままではログインしなくても、diary/パスを渡せば直接日記アプリの画面に遷移できてしまう。
なので、ログインしなければ日記アプリのビューを動かせないようにする設定を行う。
そのためにMixInというものを使う。これも別途確認するといいかもしれない。

diary/views.py
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に以下の内容を記載。

congif/settings.py
STATIC_URL = "static/"

STATICFILES_DIRS = [
    BASE_DIR/ "static"
]

STATICFILES_DIRS定数を新しく追加し、STATIC_URL = "static/"となっているかを確認する。

次にtemplates\base/diary_base.htmlのヘッダー内に、以下の記述を追加。

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を作って、内容を記述する。
動画で書いていたのは、現在の日時を取得して画面に表示するプログラムである。

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のヘッダー内に、以下の記述を追加。

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の、現在時刻を表示したい箇所に以下の記述を加える。

templates/diary/index.html
<div id="clock"></div>

これで、JavaScriptの機能をアプリに実装することができました。

所感・感想

少し時間がかかりましたが、Djangoの概要を学ぶことができました。
書いたものが動いてくれると、とても嬉しいものがありますね。
ゆくゆくは、色々なことができるようになってみたいです。

0
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
0
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?