Djangoで読書メモアプリを作る

Djangoで読書メモアプリを作りました。
はやたすさんの記事を参考にしました。

https://tech-diary.net/django-todo-tutorial/

この記事では、Pythonのインストール、仮想環境の構築、Djangoのインストール、アプリ作成の流れを書きます。
実行環境：MacBook Air M1, 2020

# Pythonのインストール
以下のサイトにアクセスし、Latest Python 3 Release - Python 3.12.1をクリックします。
https://www.python.org/downloads/macos/
![スクリーンショット 2023-12-09 1.58.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3569835/a99c13f0-63a0-26d8-d755-ddbd24cd2ea4.png)

開いたページを下にスクロールし、macOS 64-bit universal2 installerをクリックします。

![スクリーンショット 2023-12-09 1.59.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3569835/de2e9e8c-97fe-df1b-bf5b-7202e5691a42.png)

インストーラをダウンロードしたら、インストールを進めます。

![スクリーンショット 2023-12-09 2.00.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3569835/51bc1c2e-fbba-a468-09ac-b8722f983a3f.png)

ターミナルでPythonのバージョンを確認します。
```zsh
% python3 -V
Python 3.12.1
```
Python 3.12.1をインストールすることができました。

# 仮想環境の構築
今回は、仮想環境を構築し、アプリ作成を行います。
ターミナルで以下のコマンドを実行します。
```zsh
% mkdir django-reading
% cd django-reading
% python3 -m venv .venv
% source .venv/bin/activate
```
django-readingディレクトリを作成し、仮想環境の構築と起動を行いました。
次回からは、以下のコマンドのみを実行します。
```zsh
% cd django-reading
% source .venv/bin/activate
```

# Djangoのインストール
ターミナルで以下のコマンドを実行します。
```zsh
% python -m pip install Django
% python -m django --version
5.0
```

# アプリ作成
## Djangoプロジェクトの作成
ターミナルで以下のコマンドを実行します。
```zsh
% django-admin startproject config .
```
プロジェクトが動作するか確認します。
```zsh
% python manage.py runserver
```
[http://127.0.0.1:8000/](http://127.0.0.1:8000/)にアクセスすると、以下の画面が表示されます。

![スクリーンショット 2023-12-23 22.52.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3569835/6d4e30d8-37a5-9659-c4fe-1f0898b042f9.png)

## Todo読書メモアプリの作成
ターミナルで以下のコマンドを実行します。
```zsh
% python manage.py startapp reading
```

config/settings.pyを編集し、初期設定を行います。
```config/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'reading.apps.ReadingConfig' # 追加
]

# ...

# LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'ja'

# TIME_ZONE = 'UTC'
TIME_ZONE = 'Asia/Tokyo'
```

以下のコマンドを実行します。
```zsh
python manage.py runserver
```
[http://127.0.0.1:8000/](http://127.0.0.1:8000/)にアクセスすると、以下の画面が表示されます。

![スクリーンショット 2023-12-23 22.52.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3569835/269005f5-6915-ddff-8f17-9401d8ce1087.png)

英語から日本語に変わりました。

## モデルの作成と有効化
reading/models.pyを編集します。
```reading/models.py
from django.db import models


class Reading(models.Model):
    title = models.CharField("本のタイトル", max_length=30)
    writer = models.CharField("筆者", max_length=30)
    memo = models.TextField("メモ", blank=True)
    date = models.DateField("読了日")

    def __str__(self):
        return self.title
```
ターミナルで以下のコマンドを実行します。
```zsh
% python manage.py makemigrations
% python manage.py migrate
```

## 管理ユーザーの作成
ターミナルで以下のコマンドを実行します。
```zsh
% python manage.py createsuperuser
```
ユーザー名、メールアドレス、パスワードを入力します。
メールアドレスは必要ないようです。
以下のコマンドを実行し、サーバーを起動します。
```zsh
% python manage.py runserver
```
[http://127.0.0.1:8000/admin](http://127.0.0.1:8000/admin)にアクセスすると、以下の画面が表示されます。
![スクリーンショット 2023-12-23 23.15.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3569835/f52b852b-018c-92a0-88d7-65f6b799b136.png)

ユーザー名とパスワードを入力し、ログインすると、以下の画面が表示されます。

![スクリーンショット 2023-12-23 23.17.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3569835/78e6f561-a00c-87ea-7d57-d9714c0e424f.png)

reading/admin.pyを編集します。
```reading/admin.py
from django.contrib import admin
from .models import Reading


admin.site.register(Reading)
```
以下のコマンドを実行し、サーバーを起動します。
```zsh
% python manage.py runserver
```
[http://127.0.0.1:8000/admin](http://127.0.0.1:8000/admin)にアクセスします。

![スクリーンショット 2023-12-25 13.09.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3569835/d40f6a7f-17b9-c146-9e34-e2307efb1a40.png)

Readingテーブルが表示されました。
Readingを追加します。

![スクリーンショット 2023-12-26 0.01.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3569835/f176c7a5-c9b0-ec1e-69d0-d92b048f86c2.png)

## Readingの表示
config/urls.pyを編集します。
```config/urls.py
from django.contrib import admin
from django.urls import path, include # 追加


urlpatterns = [
    path('admin/', admin.site.urls),
    path("", include("reading.urls")) # 追加
]
```
reading/views.pyを編集します。
```reading/views.py
from django.shortcuts import render
from django.views.generic import ListView

from .models import Reading


class ReadingList(ListView):
    model = Reading
    context_object_name = "readings"
```

reading/urls.pyを作成します。
```reading/urls.py
from django.urls import path
from .views import ReadingList

urlpatterns = [
    path("", ReadingList.as_view(), name="list"),
]
```

reading/templates/reading/reading_list.htmlを作成します。
```reading/templates/reading/reading_list.html
<h1>Reading Memo</h1>

<table>
    <thead>
        <tr>
        <th scope="col">title</th>
        <th scope="col">writer</th>
        <th scope="col">date</th>
        </tr>
    </thead>
    <tbody>
    {% for reading in readings %}
    <tr>
        <td>{{ reading.title }}</td>
        <td>{{ reading.writer }}</td>
        <td>{{ reading.date }}</td>
    </tr>
    {% endfor %}
    </tbody>
</table>
```
サーバーを起動し、[http://127.0.0.1:8000/](http://127.0.0.1:8000/)にアクセスすると、以下の画面が表示されます。

![スクリーンショット 2023-12-26 0.04.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3569835/687dbe60-4a84-26f8-4dc7-924a13e27b7c.png)

読書メモアプリを起動することができ、作成したReadingを表示することができました。

## Readingの詳細の表示
reading/views.pyを編集します。
```reading/views.py
from django.shortcuts import render
from django.views.generic import ListView, DetailView

from .models import Reading


class ReadingList(ListView):
    model = Reading
    context_object_name = "readings"


class ReadingDetail(DetailView):
    model = Reading
    context_object_name = "reading"
```
reading/urls.pyを編集します。
```reading/urls.py
from django.urls import path
from .views import ReadingList, ReadingDetail # 追加

urlpatterns = [
    path("", ReadingList.as_view(), name="list"),
    path("detail/<int:pk>", ReadingDetail.as_view(), name="detail"), # 追加
]
```
reading/templates/reading/reading_list.htmlを編集します。
```reading/templates/reading/reading_list.html
<h1>Reading Memo</h1>

<table>
    <thead>
        <tr>
        <th scope="col">title</th>
        <th scope="col">writer</th>
        <th scope="col">date</th>
        </tr>
    </thead>
    <tbody>
    {% for reading in readings %}
    <tr>
        <td>{{ reading.title }}</td>
        <td>{{ reading.writer }}</td>
        <td>{{ reading.date }}</td>
        <td><a href="{% url 'detail' reading.pk %}">メモ</a></td>
    </tr>
    {% endfor %}
    </tbody>
</table>
```

reading/templates/reading/reading_detail.htmlを作成します。
```reading/templates/reading/reading_detail.html
<h1>Memo</h1>

<div>
    <h2>{{reading.title}}</h2>
    <p>{{reading.writer}}</p>
    <p>{{reading.date}}</p>
    <p>{{reading.memo}}</p>
</div>
```
サーバーを起動し、[http://127.0.0.1:8000/](http://127.0.0.1:8000/)にアクセスすると、以下の画面が表示されます。

![スクリーンショット 2023-12-26 0.06.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3569835/9dad4859-078f-8897-3351-4e2cdfb9950f.png)

メモをクリックすると、以下の画面が表示されます。

![スクリーンショット 2023-12-26 0.07.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3569835/4c8f6694-7e38-0c62-c1ef-1042085aa5a3.png)

Readingのメモを表示することができました。

## Readingの作成
reading/views.pyを編集します。
```reading/views.py
from django.shortcuts import render
from django.views.generic import ListView, DetailView, CreateView

from django.urls import reverse_lazy

from .models import Reading

# ...

class ReadingCreate(CreateView):
    model = Reading
    fields = "__all__"
    success_url = reverse_lazy("list")
```
reading/urls.pyを編集します。
```reading/urls.py
from django.urls import path
from .views import ReadingList, ReadingDetail, ReadingCreate # 追加

urlpatterns = [
    path("", ReadingList.as_view(), name="list"),
    path("detail/<int:pk>", ReadingDetail.as_view(), name="detail"),
    path("create/", ReadingCreate.as_view(), name="create"), # 追加
]
```
reading/templates/reading/reading_list.htmlを編集します。
```reading/templates/reading/reading_list.html
<h1>Reading Memo</h1>

<p><a href="{% url 'create' %}">新規作成</a></p>

<!-- ... -->
```
reading/templates/reading/reading_form.htmlを作成します。
```reading/templates/reading/reading_form.html
<form action="" method="POST">{% csrf_token %}
    <table>
        {{ form.as_table }}
    </table>
    <input type="submit" value="作成">
</form>
```
サーバーを起動し、[http://127.0.0.1:8000/](http://127.0.0.1:8000/)にアクセスすると、以下の画面が表示されます。

![スクリーンショット 2023-12-26 0.09.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3569835/7ca3e028-cc2c-bee8-f5e4-ba9a5a089244.png)

新規作成をクリックし、Readingを作成します。

![スクリーンショット 2023-12-26 0.11.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3569835/2490db00-7ec3-8462-970e-03ed57ecf14e.png)

作成をクリックすると、以下の画面が表示されます。

![スクリーンショット 2023-12-26 0.12.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3569835/2826b114-e775-8f44-c8c3-003096d1d4ba.png)

Readingを作成することができました。

## Readingの編集
reading/views.pyを編集します。
```reading/views.py
from django.shortcuts import render
from django.views.generic import ListView, DetailView, CreateView, UpdateView

from django.urls import reverse_lazy

from .models import Reading

# ...

class ReadingUpdate(UpdateView):
    model = Reading
    fields = "__all__"
    success_url = reverse_lazy("list")
```
reading/urls.pyを編集します。
```reading/urls.py
from django.urls import path
from .views import ReadingList, ReadingDetail, ReadingCreate, ReadingUpdate # 追加

urlpatterns = [
    path("", ReadingList.as_view(), name="list"),
    path("detail/<int:pk>", ReadingDetail.as_view(), name="detail"),
    path("create/", ReadingCreate.as_view(), name="create"),
    path("update/<int:pk>", ReadingUpdate.as_view(), name="update"), # 追加
]
```
reading/templates/reading/reading_list.htmlを編集します。
```reading/templates/reading/reading_list.html
<h1>Reading Memo</h1>

<p><a href="{% url 'create' %}">新規作成</a></p>

<table>
    <thead>
        <tr>
        <th scope="col">title</th>
        <th scope="col">writer</th>
        <th scope="col">date</th>
        </tr>
    </thead>
    <tbody>
    {% for reading in readings %}
    <tr>
        <td>{{ reading.title }}</td>
        <td>{{ reading.writer }}</td>
        <td>{{ reading.date }}</td>
        <td><a href="{% url 'detail' reading.pk %}">メモ</a></td>
        <td><a href="{% url 'update' reading.pk %}">編集</a></td>
    </tr>
    {% endfor %}
    </tbody>
</table>
```
サーバーを起動し、[http://127.0.0.1:8000/](http://127.0.0.1:8000/)にアクセスすると、以下の画面が表示されます。

![スクリーンショット 2023-12-26 0.16.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3569835/cdc423cf-f4cd-3ba1-ecde-72b0ecf9eb89.png)

Readingを編集することができるようになりました。

## Readingの削除
reading/views.pyを編集します。
```reading/views.py
from django.shortcuts import render
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView

from django.urls import reverse_lazy

from .models import Todo

# ...

class ReadingDelete(DeleteView):
    model = Reading
    context_object_name = "reading"
    success_url = reverse_lazy("list")
```
reading/urls.pyを編集します。
```reading/urls.py
from django.urls import path
from .views import ReadingList, ReadingDetail, ReadingCreate, ReadingUpdate, ReadingDelete # 追加

urlpatterns = [
    path("", ReadingList.as_view(), name="list"),
    path("detail/<int:pk>", ReadingDetail.as_view(), name="detail"),
    path("create/", ReadingCreate.as_view(), name="create"),
    path("update/<int:pk>", ReadingUpdate.as_view(), name="update"),
    path("delete/<int:pk>", ReadingDelete.as_view(), name="delete"), # 追加
]
```
reading/templates/reading/reading_list.htmlを編集します。
```reading/templates/reading/reading_list.html
<h1>Reading Memo</h1>

<p><a href="{% url 'create' %}">新規作成</a></p>

<table>
    <thead>
        <tr>
        <th scope="col">title</th>
        <th scope="col">writer</th>
        <th scope="col">date</th>
        </tr>
    </thead>
    <tbody>
    {% for reading in readings %}
    <tr>
        <td>{{ reading.title }}</td>
        <td>{{ reading.writer }}</td>
        <td>{{ reading.date }}</td>
        <td><a href="{% url 'detail' reading.pk %}">メモ</a></td>
        <td><a href="{% url 'update' reading.pk %}">編集</a></td>
        <td><a href="{% url 'delete' reading.pk %}">削除</a></td>
    </tr>
    {% endfor %}
    </tbody>
</table>
```
reading/templates/reading/reading_confirm_delete.htmlを作成します。
```reading/templates/reading/reading_confirm_delete.html
<h1>削除</h1>
<form action="" method="POST">{% csrf_token %}
    <p>{{reading}}</p>
    <input type="submit" value="delete">
</form>
```

サーバーを起動し、[http://127.0.0.1:8000/](http://127.0.0.1:8000/)にアクセスすると、以下の画面が表示されます。

![スクリーンショット 2023-12-26 0.21.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3569835/f267702b-164f-98ff-1bf8-d45d5eccfdb9.png)

削除をクリックすると、以下の画面が表示されます。

![スクリーンショット 2023-12-26 0.25.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3569835/9fea9209-be73-b0af-9548-71521610b0ba.png)

deleteをクリックします。

![スクリーンショット 2023-12-26 0.25.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3569835/cf897c9a-4395-867c-31b2-bcc4ccea0809.png)

Readingが削除されました。

# 見た目の変更
reading/templates/reading/base.htmlを作成します。
```reading/templates/reading/base.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Reading Memo</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP&display=swap" rel="stylesheet">
    <style>
        body{
            padding: 20px;
            font-family: 'Noto Sans JP', sans-serif;
        }
    </style>
</head>
<body>
    {% block header %}
    {% endblock header %}

    {% block content %}
    {% endblock content %}
</body>
</html>
```

reading/templates/reading/reading_list.htmlを編集します。
```reading/templates/reading/reading_list.html
{% extends 'reading/base.html' %}

{% block header %}
<h1>Reading Memo</h1>

<p><a href="{% url 'create' %}">新規作成</a></p>
{% endblock header %}

{% block content %}
<table>
    <thead>
        <tr>
        <th scope="col">title</th>
        <th scope="col">writer</th>
        <th scope="col">date</th>
        </tr>
    </thead>
    <tbody>
    {% for reading in readings %}
    <tr>
        <td>{{ reading.title }}</td>
        <td>{{ reading.writer }}</td>
        <td>{{ reading.date }}</td>
        <td><a href="{% url 'detail' reading.pk %}">メモ</a></td>
        <td><a href="{% url 'update' reading.pk %}">編集</a></td>
        <td><a href="{% url 'delete' reading.pk %}">削除</a></td>
    </tr>
    {% endfor %}
    </tbody>
</table>
{% endblock content %}
```

以下に変更前と変更後の見た目を示します。

変更前の見た目
![スクリーンショット 2023-12-26 0.21.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3569835/f267702b-164f-98ff-1bf8-d45d5eccfdb9.png)

変更後の見た目
![スクリーンショット 2023-12-27 0.55.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3569835/0ea3e6db-5dc9-be19-1c3e-a37c759b924a.png)

見た目が変わりました。

フォントを変えます。
reading/templates/reading/base.htmlを編集します。
```reading/templates/reading/base.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Reading Memo</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Kaisei+HarunoUmi&display=swap" rel="stylesheet">
    <style>
        body{
            padding: 20px;
            font-family: 'Kaisei HarunoUmi', serif;
        }
    </style>
</head>
<body>
    {% block header %}
    {% endblock header %}

    {% block content %}
    {% endblock content %}
</body>
</html>
```

以下に変更前と変更後の見た目を示します。

変更前の見た目
![スクリーンショット 2023-12-27 0.55.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3569835/0ea3e6db-5dc9-be19-1c3e-a37c759b924a.png)

変更後の見た目
![スクリーンショット 2023-12-28 1.01.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3569835/4eca1f27-cd05-7a4f-5067-da5986b1ad57.png)

フォントが変わりました。
[はやたすさんの記事](https://tech-diary.net/django-todo-tutorial/)を参考にしながら、Djangoで読書メモアプリを作ることができました。
また、BootstrapやGoogle Fontsを用いて、見た目を変えることができました。

# 参考文献
https://docs.djangoproject.com/ja/5.0/topics/install/
https://tech-diary.net/django-todo-tutorial/
https://qiita.com/white1107/items/5063d38cd5474b412e3e
https://getbootstrap.jp/docs/5.3/getting-started/introduction/
https://fonts.google.com/noto/specimen/Noto+Sans+JP
https://fonts.google.com/specimen/Kaisei+HarunoUmi