Django初心者がSNSアプリを作る　その5

前回 https://qiita.com/YuDachi/items/a3afd475a42c047ef558
元記事 https://note.com/saito_pythonista/n/n6550f5c2a07b

前回まで、HTMLファイルの準備と対応するview・URLの設定を行った。今回から、ツイートのモデル化に入っていく。また、モデル作成に伴ってListView等の汎用ビューが使えるようになるっぽいので、その実装もやっていく。

# モデルの作成

元記事のmodels.pyを参考にして、前に作成したツイートのデザインと合致するようなモデルを作成した。作ってみるとやることが多かったのでさきにまとめておくと、以下のような感じ。

- models.pyの作成
- マイグレーション
- 管理画面(django admin)にモデルを登録

## models.py

思ったよりも画像のアップロードがややこしかったので、一旦プロフィール写真はモデルに含めない方向で進め、一通り完成したら挑戦してみることにした。

```models.py
from django.db import models
from django.contrib.auth.models import User


class Post(models.Model):

    user = models.ForeignKey(User, on_delete=models.CASCADE)
    content = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)

    def __str__(self):
        return self.content

    # 投稿順にクエリを取得
    class Meta:
        ordering = ["-created_at"]
```

### def \_\_str__ について

この関数の戻り値に指定した文字列が、管理画面で一覧表示した際に、ラベルとして表示されるらしい。今回はツイートの内容自体を指定しておいた。

### class Meta について
この内部クラスを付与することで、モデルの取り扱い方(？)を変えれるらしい。この説明だけだとよくわからないが、とりあえずorderingを指定することで並び順を変更できるとのこと。他にも、

- db_table → データベースにおけるテーブル名を変更
- verbose_name→管理画面での表記方法の変更

等の種類があるようだ。
作成したモデルを、django admin から参照できるようにする。

## admin.py

```config/admin.py
from django.contrib import admin
from .models import Post

# django admin に model を登録
admin.site.register(Post)
```

これで django admin からインスタンス(？)の作成や削除を行うことができるようになった。適当にUserを作成し、適当なツイートを作成した。次はこの内容をviewに反映させることを目指す。

![スクリーンショット (156).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643756/9e7ef93d-8616-fd31-d496-8a107b70f3e2.png)

# モデルを反映させる

上で作ったモデルをviewに反映させる。現段階ではviewにTemplateViewを用いていたが、適宜用意されている汎用ビューに置き換えていく。まずはListViewから。

## views.py

```views.py
# 変更
class Home(ListView):
    model = Post
    template_name = 'list.html'
```

## list.html

```list.html
# 追加
{% for item in object_list %}
<div class="tweet">
    <div class="profile_image">
        <img src="../static/profile_sample.png" alt="#">
    </div>
    <div>
        <div class="profile_name">{{ item.user.username }}</div>
        <div class="submit_time">{{ item.created_at }}</div>
        <div class="content">{{ item.content }}</div>
        <a class="favorite">♡</a>
    </div>
</div>
{% endfor %}
```

インスタンスを適当にいくつか追加してrunserverすると、無事ツイートが表示された。このくらいならviewがめちゃ簡単なので、最初からモデルを作っても大丈夫かもしれない。
この調子で、detail,create,delete 画面もモデルと結び付けていく。

# detail画面の作成

ここでやることは、

- 各詳細画面に対応するURLを設定
- list.htmlに上のURLを記述
- 詳細画面にモデルの情報を反映する

```snsapp/urls.py
path('detail/<int:pk>', DetailView.as_view(), name='detail'), #変更
```

```templates/list.html
{% for item in object_list %}
<div class="tweet">
    <div class="profile_image">
        <img src="../static/profile_sample.png" alt="#">
    </div>
    <div>
        <div class="profile_name">{{ item.user.username }}</div>
        <div class="submit_time">{{ item.created_at }}</div>
        <div class="content">
            <a href="{% url 'detail' item.pk %}">{{ item.content }}</a> #変更
        </div>

        <a class="favorite">♡</a>
    </div>
</div>
{% endfor %}
```

list.htmlの{% url 第1引数 第2引数 %}は、

- 第1引数にはurls.pyで定義されている名前(name='detail'の部分)
- 第2引数にurlのパラメータ(ここではpk)

という形式で記述する。pkについては、記事の番号ということしかあまりよくわかってないが今度詳しく調べてみる。

上記の2つの設定を行ったことで、detail.htmlにモデルの情報を反映できるようになった。

ここで、viewをTemplateViewからDetailViewに変更するのを忘れていたので変更する。本来はpk等の情報からオブジェクトを特定して情報を表示するらしいが、DetailViewを用いることでその記述を省略できるようだ。モデルの追加も忘れずに行っておく。

```snsapp/views.py
# 変更：TemplateView → DetailView
class DetailView(DetailView):
    model = Post #追加
    template_name = "detail.html"
```

次にdetail.htmlにモデルを反映する。渡されるモデルの情報(コンテキスト)はデフォルトでobjectという変数に格納されているようだ。この変数名はview.pyから変更できるらしい(ここでは変更しない)。

```detail.html
<div class="container">
    <h1>THIS IS DETAIL</h1>
    <div class="tweet">
        <div class="profile_image">
            <img src="../static/profile_sample.png" alt="#">
        </div>
        <div>
            <div class="profile_name">
                {{ object.user }} #変更
            </div>
            <div class="submit_time">
                2022/09/02 15:19
            </div>
            <div class="content">
                {{ object.content }} #変更
            </div>
            <a class="favorite">♡</a>
        </div>

    </div>
    <a href="./delete" class="delete">削除</a>
</div>
```

これでツイートごとに詳細画面の内容が変わるようになった。

# Create画面の作成

viewの変更の注意点は以下の3つ。

- modelを追加する
- fieldsにフォーム入力したい項目を追加
- 投稿後に遷移するURLを指定

```views.py
# TemplateView → CreateView
class CreateView(CreateView):
    model = Post #追加
    template_name = "create.html"
    fields = ['user', 'content'] #追加
    success_url = reverse_lazy('home') #追加
```

次にcreate.htmlだが、CreateViewのデフォルト機能で「form」や「form.as_p」と記述するだけで勝手にフォームを実装してくれるようで、大変便利。

```create.html
{% block content %}

<div class="container">
    <h1>THIS IS CREATE</h1>
</div>

#追加
<form method="post">{% csrf_token %}
    {{ form.as_p }}
    <input type="submit" value="投稿">
</form>

{% endblock content %}
```

これで、ブラウザからツイートを投稿できるようになった。

# Delete画面の作成

URLをdetail配下に置く必要があったのでそこだけ注意。

```urls.py
urlpatterns = [
    path('', Home.as_view(), name='home'),
    path('detail/<int:pk>', DetailView.as_view(), name='detail'),
    path('create', CreateView.as_view(), name='create'),
    path('detail/<int:pk>/delete', DeleteView.as_view(), name='delete'), #変更
]
```
```detail.html
<div class="container">
    <h1>THIS IS DETAIL</h1>
    <div class="tweet">
        ...(中略)...
    </div>
    <a href="{% url 'delete' object.pk %}" class="delete">削除</a> #変更
</div>
```

他は大体同じ。

```views.py
#TemplateView → DeleteView
class DeleteView(DeleteView):
    model = Post #追加
    template_name = "delete.html"
    success_url = reverse_lazy('home') #追加
```

```delete.html
<div class="container">
    <h1>THIS IS DELETE</h1>
</div>

<form method="post">{% csrf_token %}
    <p>本当に削除してもよろしいですか?</p>
    <input type="submit" value="削除する">
</form>
```

これでブラウザからツイート削除ができるようになった。

長くなったので今回はここまで。