Python+DjangoでSNSを作る ~Day7 forms.pyとDBからの抽出・表示
前回は、DBモデルの設計とテーブル間の連携について検討・記載しました。
今回から、実際にテンプレートファイル(HTMLファイル)上でのDB更新・表示に取組んでいきます。
1. 前回からDBを変えた点 (models.py)
前回はDB間の連携等を簡易的にシェルで確認しましたが、今回Djangoユーザー認証機能を活かしたいのでUserモデルは作成しません。
models.py
from django.db import models
from django.contrib.auth.models import User
from datetime import date
# Create your models here.
class Follow(models.Model):
owner = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name = 'do_follow_user'
)
follow_target = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name = 'accept_follow_user'
)
class Post(models.Model):
title = models.CharField(
max_length = 50
)
content = models.TextField(
max_length = 1000
)
owner = models.ForeignKey(
User,
on_delete=models.CASCADE
)
publish_date = models.DateField()
def __str__(self):
return self.title
class Like(models.Model):
owner = models.ForeignKey(
User,
on_delete=models.CASCADE
)
target_post = models.ForeignKey(
Post,
on_delete=models.CASCADE
)
class Repost(models.Model):
owner = models.ForeignKey(
User,
on_delete=models.CASCADE
)
target_post = models.ForeignKey(
Post,
on_delete=models.CASCADE
)
from django.contrib.auth.models import Userとしてるので、その他のモデルは変わらず、Userを外部キーとして設定しています。
2. 今日の目標(登録フォーム・投稿内容を表示)
今日の目標は以下3点です。
① 投稿・いいね・フォローできるようフォームを作る
➁ 記事を表示できるようにする
➂ フォローしたユーザーの投稿のみ表示できるようにする
では、書いていきます!
3. forms.py を作成する
アプリケーションフォルダにforms.pyを作成します。
そして、以下のようにコードを記載しました。
from django import forms
from .models import Follow, Post, Like, Repost
from django.contrib.auth.models import User
class PostForm(forms.Form):
title = forms.CharField(max_length = 50, label = "タイトル")
content = forms.CharField(max_length=1000, label="本文")
class FollowForm(forms.Form):
class Meta:
model = Follow
fields = ('owner', 'follow_target')
class LikeForm(forms.Form):
class Meta:
model = Like
fields = ('owner', 'target_post')
PostFormが投稿用、FollowFormが他ユーザーのフォロー用、LikeFormが「いいね」用です。
models.pyのPost classと比較すると・・・項目が足りないように思われるかもしれません。
class Post(models.Model):
title = models.CharField(
max_length = 50
)
content = models.TextField(
max_length = 1000
)
owner = models.ForeignKey(
User,
on_delete=models.CASCADE
)
publish_date = models.DateField()
def __str__(self):
return self.title
理由は、ownerはログインしているユーザー、publish_dateは投稿日に必然的になるため、入力してもらう必要がないからです。
そのため、2項目のみをあえて記載しているものです。(後程なぜか分かります)
また、Formの書き方が、Postとそれ以外で違うと気づくかもしれませんが、2つ書き方があります。(どっちでもOK)
3. views.py で投稿時の処理を記載する
次に、views.pyファイルにフォームでの登録時の挙動を記載していきます。
まずは、投稿用のpost関数を記載します。
def post(request):
params = {
'form': PostForm(),
}
if (request.method == 'POST'):
title=request.POST['title']
content=request.POST['content']
owner=request.user
publish_date = date.today()
post = Post(title = title, content = content, owner = owner,\
publish_date = publish_date
)
post.save()
return redirect(to = '/sns')
return render(request, 'sns/post.html', params)
ざっくりいうと、HTMLリクエストがPOST (登録) の場合に、HTTPリクエスト情報からから title(記事) や content(投稿内容) ・ owner(投稿者) ・ publish_date(投稿日) を各変数に格納する。
Postクラスの引数として、各変数を渡して、Postクラスのインスタンスを作成しています。作成したインスタンスをDB上に保存(post.save)しています。
前回、シェルで行っていた事とDBへの処理は同様ですが、各項目の値をフォームに投稿された内容から一部取得しているという点がポイントです。
同様にフォローをした際のfollow関数、いいねをした際のlike関数も書いていきます。(ちなみに関数名に決まりはありませんので、好きなように命名してOKです)
from django.shortcuts import render,redirect,get_object_or_404
def follow(request, user_id):
params = {
'form' : FollowForm(),
}
follow_user = get_object_or_404(User, pk=user_id)
if (request.method == 'POST'):
owner = request.user
follow_target = follow_user
follow = Follow(owner = owner, follow_target = follow_target)
follow.save()
return redirect(to = '/sns')
return render(request, 'sns/index.html', params)
def like(request, post_id):
params = {
'form' : LikeForm(),
}
target_post = get_object_or_404(Post, pk=post_id)
if (request.method == 'POST'):
owner = request.user
target_post = target_post
like = Like(owner = owner, target_post = target_post)
like.save()
return redirect(to = '/sns')
return render(request, 'sns/index.html', params)
postと異なっているのは、引数が2つという点と、get_object_or_404関数を利用している点、redirect関数を使って、トップページに戻っている点です。
フォローするのも、いいねをするのも、対象は特定する必要があります。
引数で、「誰」に対するフォローなのか、「どの記事」に対するいいねなのか明示しないといけないためです。
get_object_or_404関数は、対象のオブジェクトから1件取得し、取得できない場合はPage NotFoundエラー(404)を返すためのものです。
リダイレクトせずに、「フォローできました!」「いいねしました!」みたいな表示ページを別途作成して、そこから戻るボタンをおしてトップページに遷移するやり方もありだと思います。
今回、それぞれのテンプレートファイル(htmlファイル)を書く手間を省くためにそうしました(笑)
4. テンプレートファイル (htmlファイル)の更新
では、テンプレートファイルを更新します。
まずは投稿ページとしてpost.htmlファイルを用意します。
post.html
{% extends 'sns/base.html' %}
{% load static %}
{% block title %}投稿ページ{% endblock %}
{% block header %}
投稿しましょう
{% endblock %}
{% block body %}
<table class="post_table">
<form action="{% url 'post' %}" method="post">
{% csrf_token %}
{{ form.as_table }}
<tr><td><input type="submit" value="投稿" class="btn-submit"></td></tr>
</form>
</table>
{% endblock %}
以下の記述のうち、actionは「urls.pyファイルに記述された、views.pyファイルのpost関数を使うね」という挙動を指示、methodは先ほど書いた、「HTTPリクエストのPOSTでやるね」ということを書いています。
(つまりurls.pyも更新が必要です)
<form action="{% ulr 'post'%}" method="post">
以下の記述は、過去記事でも記載したクロスサイトリクエストフォージェリという攻撃を防ぐものです。
{% csrf_token %}
そして、以下の記載のみでformを用意してくれるのです。(便利~)
なお、inputタグは投稿内容を確定させるためのパーツです。(class属性はCSSでボタンのデザインを整えるために記載してます)
{{ form.as_table }}
<tr><td><input type="submit" value="投稿" class="btn-submit"></td></tr>
5. urls.pyファイルの更新
では、フォーム投稿した際にviews.pyファイルの post関数で処理を行うようurls.pyファイルにも記述をします。
urlpatternsリストに以下を追記します。
path('sns/post/', views.post, name='post'),
views.postの部分で、views.pyファイルのpost関数で処理してねと書いています。また、名前空間としてname='post'と定義することで、htmlファイルにテンプレートで記載した{% url 'post' %}という記述ができるようにしています。
では、ここまでできたらDjangoプロジェクトを起動して試してみましょう。
6. Djangoプロジェクト起動とテスト投稿
では、既におなじみのpython manage.py runserverでプロジェクトを起動します。
では、http://127.0.0.1:8000/sns/post/ にアクセスしてみましょう。
まあまあCSSで整えられてはいますが(笑)
フォームが表示されました。
投稿して、アドミンページでPostモデルに投稿されたオブジェクトを確認します。
はい、test_user3で投稿しましたが、きちんとオブジェクトが作成されていて、DBにも登録されています。
7. 投稿された記事をページに表示する
では、今度はviews.pyファイルのindex関数と、index.htmlファイルを修正して、投稿内容を表示させます。
views.py
def index(request):
user = request.user
posts = Post.objects.all()
params = {
'data' : posts,
'user' : user,
}
return render(request, 'sns/index.html',params)
user変数にはアクセスしているユーザーを、posts変数には、Postオブジェクト全て(Post.objects.all())を格納して、render関数でindex.htmlファイルに渡しています。
index.html
{% extends 'sns/base.html' %}
{% load static %}
{% block title %}トップページ{% endblock %}
{% block header %}
<h3>こんにちわ、{{user.username}}さん</h3>
{% endblock %}
{% block body %}
<h3>タイムライン</h3>
{% for item in data %}
<table class="show_table">
<tr>
<td>
<form action="{% url 'follow' item.owner.id %}" method="post">
{% csrf_token %}
{{item.owner}}
<input type="submit" value="フォロー" class="btn-submit">
</form>
</td>
<td class="pub_date">{{item.publish_date}}</td>
<tr/>
<tr>
<td class="title">{{item.title}}</td>
</tr>
<tr>
<td>{{item.content}}</td>
</tr>
<tr>
<td>
<form action="{% url 'like' item.id %}" method="post">
{% csrf_token %}
<input type="submit" value="すき" class="btn-submit2">
</form>
</td>
</tr>
</table>
{% endfor %}
{% endblock %}
dataとして渡した、Postオブジェクト達をテンプレートタグ{% for item in data %}で順番に取り出して、owner・publish_date・title・contentの順番にtabelタグ内で記載しています。
そして、ownerのところにはフォローをするためのFollowForm、ページ下部には下部にいいね(すき)をするためのLikeFormを表示させてます。
また、form actionにはそれぞれ用の名前空間もしています。
これらは、それぞれViews.pyファイルに以下のように関数を追記しています。
follow関数 (views.py)
def follow(request, user_id):
params = {
'form' : FollowForm(),
}
follow_user = get_object_or_404(User, pk=user_id)
if (request.method == 'POST'):
owner = request.user
follow_target = follow_user
follow = Follow(owner = owner, follow_target = follow_target)
follow.save()
return redirect(to = '/sns')
return render(request, 'sns/index.html', params)
like関数 (views.py)
def like(request, post_id):
params = {
'form' : LikeForm(),
}
target_post = get_object_or_404(Post, pk=post_id)
if (request.method == 'POST'):
owner = request.user
target_post = target_post
like = Like(owner = owner, target_post = target_post)
like.save()
return redirect(to = '/sns')
return render(request, 'sns/index.html', params)
そして、urls.pyファイルのurlpatternsにも以下パスを追記しています。
path('<int:user_id>/follow/', views.follow, name='follow'),
path('<int:post_id>/like/', views.like, name='like'),
followについては、<int:user_id>。likeについては、<int:post_id>とパスの先頭に記載しているのは、先ほどviews.pyファイルでも解説した「どの」ユーザーをフォロー、投稿にいいねするかを特定するためのものです。
では、ページを表示してみましょう。
これまたCSSでレイアウトは整えられていますが、表示としては意図した通りにできています。
また、フォローボタン・すき(いいね)も機能することをアドミンサイトで確認しました。
folloオブジェクト
likeオブジェクト
8. フォローしているユーザーの投稿のみ表示する
では、今回の最後に、ログインユーザーがフォローしているユーザーの投稿のみ表示させてみます。
index_follow関数 (views.py)
def index_follow(request):
follow = Follow.objects.filter(owner = request.user.id)
posts = Post.objects.filter(
owner__in = [f.follow_target for f in follow]
)
params = {
'data' : posts,
}
return render(request, 'sns/index_follow.html', params)
ポイントは、以下の部分。
まずは、ログインユーザー(request.user)がownerとなっているfollowオブジェクトを抽出しています。
follow = Follow.objects.filter(owner = request.user.id)
それから、Postオブジェクトから、follow変数に格納されたフォローオブジェクトのフォロー対象がownerとなっているPostオブジェクト全てをposts変数に格納してindex_follow.htmlに渡しています。
posts = Post.objects.filter(
owner__in = [f.follow_target for f in follow]
)
個人的に、リスト内包表記が有効活用できたなという感じです。
index_follow.html
{% extends 'sns/base.html' %}
{% load static %}
{% block title %}トップページ{% endblock %}
{% block header %}
<h3>フォローユーザーの投稿のみ表示</h3>
{% endblock %}
{% block body %}
{% for item in data %}
<table class="show_table">
<tr>
<td>
<form action="{% url 'follow' item.owner.id %}" method="post">
{% csrf_token %}
{{item.owner}}
<input type="submit" value="フォロー" class="btn-submit">
</form>
</td>
<td class="pub_date">{{item.publish_date}}</td>
<tr/>
<tr>
<td class="title">{{item.title}}</td>
</tr>
<tr>
<td>{{item.content}}</td>
</tr>
<tr>
<td>
<form action="{% url 'like' item.id %}" method="post">
{% csrf_token %}
<input type="submit" value="すき" class="btn-submit2">
</form>
</td>
</tr>
</table>
{% endfor %}
{% endblock %}
対象絞って渡しているので、index.htmlとほぼ同一内容です。
では、urls.pyファイルのurlpatternsに以下を追記して、ページを表示してみます。
path('sns/index/', views.index_follow, name='index_follow'),
・・・の前に、きちんと対象が絞られて表示されるか、まずはnonstop_iidaというユーザーがフォローしているユーザーを確認します。
前回同様、シェルを使ってみます。
nonstop_iidaがフォローしているのは、test_user2とtest_user1なので、二人の投稿のみが表示されればOKです。
ではページを表示してみましょう。(python manage.py runserver忘れずに)
はい、test_user2とtest_user1の分だけ表示されました!
9. まとめ
今回は、Webページ上からDB更新するためのforms.pyや、urls.pyでのパスのコントロール、views.pyファイルでの処理記述方法、最後にhtmlファイルの記述の仕方を解説しました。
次回は、いいね数やリポスト等、ブラッシュアップしていきたいと思います。
最後に、参考としてフォルダ構成や各ファイルのコード全文を載せておきます。(CSSもおまけで載せておきます)
これはさすがに有料にさせていただきます・・・が、安心価格¥100にしておきます(笑)
10. フォルダ構成・コード全文
(1) フォルダ構成
プロジェクトフォルダ階層
accountsフォルダ (ログイン等の機能をこちらで実装)
なお、ログイン認証等はこちらのサイトを参考にしてますので、気になった方はみてください。(記事の趣旨と異なるので説明は割愛します。)
settings.py
ここから先は
¥ 100
この記事が気に入ったらチップで応援してみませんか?