DjangoでSNSアプリの投稿機能を実装しよう!【CRUD】
DjangoでSNSアプリを開発する際に、投稿機能は最も基本的でありながら重要な要素です。ユーザーがテキストや画像を自由に投稿できる仕組みは、アプリケーションのインタラクティブ性や魅力を大きく左右します。しかし、投稿機能の実装には、**CRUD(Create, Read, Update, Delete)**と呼ばれる一連の処理を効率的に組み込む必要があり、これを正しく理解することで、柔軟なアプリケーション開発が可能になります。
本章では、Djangoを用いて、以下の4つの操作を学びながら投稿機能を実装していきます。
- 作成(Create)
ユーザーが画像やテキストを投稿するフォームを作成し、データベースに保存する方法を学びます。これにより、ユーザーは簡単に新しいコンテンツをアプリに追加できるようになります。 - 表示(Read)
ダッシュボードや投稿一覧ページで、すべての投稿を美しく並べて表示する方法を解説します。テンプレートとビューを連携し、データを動的に表示するテクニックを習得しましょう。 - 編集(Update)
ユーザーが自分の投稿を修正できる編集画面を実装します。既存の投稿を呼び出し、変更を適用して更新する仕組みを理解します。 - 削除(Delete)
ユーザーが不要になった投稿を削除する機能を追加します。これにより、ユーザーが自分のコンテンツを管理しやすくなります。
さらに、CRUD機能を用いた投稿機能を完成させた後には、アプリケーションのさらなる魅力を引き出すコメント機能の実装にも挑戦していきます。これらの機能を組み合わせることで、ユーザー同士の交流を促進し、より高度で本格的なSNSアプリを構築できるようになるでしょう。
さあ、Djangoの基礎を活用して、ユーザーが自由にコンテンツを作成・管理できるインタラクティブな投稿機能を一緒に学んでいきましょう!
目次
投稿機能
一覧表示
ダッシュボード
投稿を一覧表示するダッシュボードを作成します。ユーザーのアイコンや投稿数も表示されるようにします。
テンプレート
sns/templates/sns/dashboard.html
{% extends 'base.html' %}
{% block title %}
ダッシュボード
{% endblock %}
{% block main %}
<div class="max-w-5xl mx-auto">
<div class="bg-white p-4 flex mt-8">
{% if request.user.icon %}
<img class="block size-24 rounded-full aspect-[1/1] object-cover" src="{{ request.user.icon.path }}" />
{% endif %}
<div class="pl-4">
<p class="text-lg text-black font-semibold">{{ request.user.username }}</p>
<p class="font-medium whitespace-pre-wrap">{{ request.user.description }}</p>
<div class="flex mt-4">
<p class="text-sm text-black font-semibold">投稿{{ posts.count }}件</p>
<a href="/accounts/profile" class="ml-2 px-2 text-sm text-black font-semibold border rounded">プロフィールを編集</a>
</div>
</div>
</div>
<div class="grid grid-cols-3 gap-1 my-8 bg-white">
{% for post in posts %}
<a href="/posts/{{ post.id }}/edit"><img class="aspect-[1/1] w-full object-cover" src="{{ post.image.path }}" /></a>
{% endfor %}
</div>
</div>
{% endblock %}
解説: 上記のコードでは、ユーザーの情報とその投稿を表示するダッシュボードを作成しています。{% for post in posts %}
の部分で、投稿を動的に表示しています。
ビュー
sns/views.py
def dashboard(request):
posts = Post.objects.filter(user=request.user).order_by("-created_at")
return render(request, "sns/dashboard.html", {"posts": posts})
解説: dashboard
ビューでは、現在のユーザーが作成した投稿を取得し、作成日で降順に並べ替えています。取得した投稿は、テンプレートに渡されます。
URL登録
sns/urls.py
from django.urls import path
from . import views
urlpatterns = [
path("dashboard", views.dashboard, name="dashboard"),
]
myapp/urls.py
解説: このURL設定により、/dashboard
にアクセスすると、dashboard
ビューが呼び出されます。
※まだ投稿がないので、プロフィールのみ表示されます。
投稿一覧
テンプレート
sns/templates/sns/posts/index.html
{% extends 'base.html' %}
{% block title %}
投稿一覧
{% endblock %}
{% block main %}
<div>
<div class="max-w-5xl mx-auto">
<div class="py-8">
<div class="bg-white">
<div class="grid grid-cols-3 gap-1">
{% for post in posts %}
<a href="/posts/{{ post.id }}"><img class="aspect-[1/1] w-full object-cover" src="{{ post.image.path }}" /></a>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
{% endblock %}
解説: 投稿一覧ページでは、すべての投稿をグリッド形式で表示します。{% for post in posts %}
ループを使用して、投稿を動的に表示しています。
ビュー
sns/views.py
def posts_index(request):
if request.method == "GET":
posts = Post.objects.order_by("-created_at")
return render(request, "sns/posts/index.html", {"posts": posts})
解説: posts_index
ビューは、すべての投稿を取得し、作成日で降順に並べ替えた結果をテンプレートに渡します。GETリクエストがあった場合のみ、投稿を取得します。
URL登録
sns/urls.py
from django.urls import path
from . import views
urlpatterns = [
path("dashboard", views.dashboard, name="dashboard"),
path("posts", views.posts_index, name="posts_index"),
]
解説: /posts
にアクセスすることで、posts_index
ビューが実行されます。
※まだ投稿がないので、何も表示されません。
投稿作成
テンプレート
sns/templates/sns/posts/create.html
{% extends 'base.html' %}
{% block title %}
投稿作成
{% endblock %}
{% block main %}
<div class="max-w-5xl mx-auto">
<form method="POST" action="{% url 'posts' %}" enctype="multipart/form-data" class="bg-white p-4 mt-8">
{% csrf_token %}
<label class="mb-2 block font-medium text-sm text-gray-700">画像</label>
<input type="file" class="w-full text-gray-500 font-medium bg-gray-100 file:border-0 file:py-2.5 file:px-4 file:mr-4 file:bg-gray-800 file:text-white rounded" name="image" />
<label class="mt-4 mb-2 block font-medium text-sm text-gray-700">キャプション</label>
<textarea class="p-2.5 w-full rounded border border-gray-300 focus:ring-blue-500 focus:border-blue-500" name="caption" rows="8" placeholder="キャプションを入力してください..."></textarea>
<button type="submit" class="mt-4 inline-flex items-center px-4 py-2 bg-gray-800 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700 focus:bg-gray-700 active:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition ease-in-out duration-150">公開</button>
</form>
</div>
{% endblock %}
解説: 投稿作成フォームでは、画像とキャプションを入力できるフィールドを用意しています。POST
メソッドで送信するための設定がされています。
ビュー
sns/views.py
def posts_index(request):
if request.method == "GET":
posts = Post.objects.order_by("-created_at")
return render(request, "sns/posts/index.html", {"posts": posts})
elif request.method == "POST":
return _posts_store(request)
def _posts_store(request):
post = Post()
image = request.FILES.get("image")
caption = request.POST["caption"]
post.image = image
post.caption = caption
post.user = request.user
post.save()
return redirect("dashboard")
def posts_create(request):
return render(request, "sns/posts/create.html")
解説: posts_index
ビューは、GETリクエストで投稿一覧を表示し、POSTリクエストで新しい投稿を保存します。_posts_store
メソッドでは、フォームから送信された画像とキャプションを用いて新しい投稿を作成し、ダッシュボードにリダイレクトします。
URL登録
from django.urls import path
from . import views
urlpatterns = [
path("dashboard", views.dashboard, name="dashboard"),
path("posts", views.posts_index, name="posts"),
path("posts/create", views.posts_create, name="posts_create"),
]
投稿する際には、前回解説した画像アップロードの仕組みを使っています。
投稿画面は以下のようになります。実際に投稿してみましょう。
ダッシュボードで自身の投稿が見られるようになっています。
投稿詳細
テンプレート
sns/templates/sns/posts/detail.html
{% extends 'base.html' %}
{% block title %}
投稿詳細
{% endblock %}
{% block main %}
<div class="max-w-5xl mx-auto">
<div class="grid md:grid-cols-2 grid-cols-1 gap-1 bg-white mt-8">
<img class="aspect-[1/1] w-full object-cover" src="{{ post.image.path }}" />
<div class="p-2">
<div class="flex justify-between items-center">
<h3 class="font-semibold">オーナー</h3>
<div class="text-gray-500 text-xs">公開日: {{ post.created_at|date:'Y/m/d H:i' }}</div>
</div>
<a href="/users/{{ post.user.id }}">
<div class="bg-white flex border p-2 rounded mt-2">
{% if post.user.icon %}
<img class="block size-12 rounded-full aspect-[1/1] object-cover" src="{{ post.user.icon.path }}" />
{% endif %}
<div class="pl-4">
<p class="text-lg text-black font-semibold">{{ post.user.username }}</p>
<p class="text-sm font-medium whitespace-pre-wrap">{{ post.user.description }}</p>
</div>
</div>
</a>
<h3 class="font-semibold mt-2">キャプション</h3>
<p class="whitespace-pre-wrap py-2">{{ post.caption }}</p>
<h3 class="font-semibold mt-2">コメント{{ post.comment_set.count }}件</h3>
<div class="max-h-96 overflow-y-scroll">
{% for comment in post.comment_set.all %}
<div class="py-2 border-b">
<div class="flex items-center mb-1">
{% if comment.user.icon %}
<img src="{{ comment.user.icon.path }}" class="size-6 rounded-full" />
{% endif %}
<div class="text-sm font-medium text-gray-800 ml-1">{{ comment.user.username }}</div>
<div class="text-gray-500 text-xs ml-1">{{ comment.created_at|date:'Y/m/d H:i' }}</div>
</div>
<p class="text-sm">{{ comment.text }}</p>
</div>
{% endfor %}
</div>
<form method="POST" action="/posts/{{ post.id }}/comments">
{% csrf_token %}
<div class="flex mt-2">
<input name="text" class="mr-2 text-sm grow border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm" placeholder="コメントを追加..." />
<button type="submit" class="inline-flex items-center px-4 py-2 bg-gray-800 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700 focus:bg-gray-700 active:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition ease-in-out duration-150">送信</button>
</div>
</form>
</div>
</div>
</div>
{% endblock %}
解説: 投稿の詳細情報を表示するためのテンプレートです。投稿の画像とキャプションを表示しています。
ビュー
sns/views.py
def posts_detail(request, post_id):
if request.method == "GET":
post = Post.objects.get(id=post_id)
return render(request, "sns/posts/detail.html", {"post": post})
解説: 指定されたpost_id
に基づいて投稿を取得し、その詳細を表示します。
URL登録
sns/urls.py
from django.urls import path
from . import views
urlpatterns = [
path("dashboard", views.dashboard, name="dashboard"),
path("posts", views.posts_index, name="posts"),
path("posts/create", views.posts_create, name="posts_create"),
path("posts/<int:post_id>/", views.posts_detail, name="posts_detail"),
]
解説: /posts/<post_id>/
にアクセスすると、特定の投稿の詳細が表示されます。
投稿編集
テンプレート
sns/templates/sns/posts/edit.html
{% extends 'base.html' %}
{% block title %}
投稿編集
{% endblock %}
{% block main %}
<div class="max-w-5xl mx-auto">
<div class="grid md:grid-cols-2 grid-cols-1 gap-1 bg-white mt-8">
<img class="aspect-[1/1] w-full object-cover" src="{{ post.image.path }}" />
<div class="p-2">
<h3 class="font-semibold mb-2">オーナー</h3>
<div class="bg-white flex border p-2 rounded">
{% if post.user.icon %}
<img class="block size-12 rounded-full aspect-square object-cover" src="{{ post.user.icon.path }}" />
{% endif %}
<div class="pl-4">
<div>
<p class="text-lg text-black font-semibold">{{ post.user.username }}</p>
<p class="text-sm font-medium whitespace-pre-wrap">{{ post.user.description }}</p>
</div>
</div>
</div>
<form method="POST" action="/posts/{{ post.id }}/">
{% csrf_token %}
<h3 class="font-semibold mt-2">キャプション</h3>
<textarea name="caption" rows="8" class="mt-2 p-2.5 w-full rounded border border-gray-300 focus:ring-blue-500 focus:border-blue-500">{{ post.caption }}</textarea>
<div class="flex items-center mt-4">
<button class="inline-flex items-center px-4 py-2 bg-gray-800 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700 focus:bg-gray-700 active:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition ease-in-out duration-150" type="submit">更新</button>
<a href="/posts/{{ post.id }}" class="text-xs border p-2 rounded ml-4">投稿画面へ</a>
</div>
</form>
<form method="POST">
<button type="submit" class="mt-4 inline-flex items-center px-4 py-2 bg-white border border-gray-300 rounded-md font-semibold text-xs text-gray-700 uppercase tracking-widest shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 disabled:opacity-25 transition ease-in-out duration-150">削除</button>
</form>
</div>
</div>
</div>
{% endblock %}
解説: 投稿編集用のフォームを提供し、現在のキャプションがプリセットされています。
ビュー
sns/views.py
def posts_detail(request, post_id):
if request.method == "GET":
post = Post.objects.get(id=post_id)
return render(request, "sns/posts/detail.html", {"post": post})
elif request.method == "POST":
return _posts_update(request, post_id)
def _posts_update(request, post_id):
post = Post.objects.get(id=post_id, user=request.user)
if post is None:
raise PermissionDenied()
post.caption = request.POST["caption"]
post.save()
return redirect("dashboard")
def posts_edit(request, post_id):
post = Post.objects.get(id=post_id)
return render(request, "sns/posts/edit.html", {"post": post})
解説: posts_edit
ビューでは、GETリクエストで既存の投稿を表示し、PUTリクエストでその投稿を更新します。
URL登録
from django.urls import path
from . import views
urlpatterns = [
path("dashboard", views.dashboard, name="dashboard"),
path("posts", views.posts_index, name="posts"),
path("posts/create", views.posts_create, name="posts_create"),
path("posts/<int:post_id>/", views.posts_detail, name="posts_detail"),
path("posts/<int:post_id>/edit", views.posts_edit, name="posts_edit"),
]
解説: /posts/<post_id>/edit
にアクセスすると、投稿編集画面が表示されます。
コメント機能
ビュー
from django.core.exceptions import PermissionDenied
from django.shortcuts import redirect, render
from .models import Comment, Post
# ...
def comments_create(request, post_id):
post = Post.objects.get(id=post_id)
comment = Comment()
comment.text = request.POST["text"]
comment.user = request.user
comment.post = post
comment.save()
return redirect("posts_edit", post.id)
解説: comments_create
関数は、指定されたpost_id
に関連する投稿に新しいコメントを追加します。request.POST
からコメントのテキストを取得し、ユーザーと投稿を設定してから保存します。処理が完了したら、該当する投稿の編集ページにリダイレクトします。
URL登録
from django.urls import path
from . import views
urlpatterns = [
path("dashboard", views.dashboard, name="dashboard"),
path("posts", views.posts_index, name="posts"),
path("posts/create", views.posts_create, name="posts_create"),
path("posts/<int:post_id>/", views.posts_detail, name="posts_detail"),
path("posts/<int:post_id>/edit", views.posts_edit, name="posts_edit"),
path("posts/<int:post_id>/comments", views.comments_create, name="comments_create"),
]
解説: 上記のURL設定により、/posts/<post_id>/comments
にアクセスすることで、コメントを作成するビューが呼び出されます。
コメント機能が追加され、ユーザーが投稿に対して意見や感想を共有できるようになりました。
オーナー機能
オーナー一覧
ビュー
from django.contrib.auth import get_user_model
from django.core.exceptions import PermissionDenied
from django.shortcuts import redirect, render
from .models import Comment, Post
User = get_user_model()
# ...省略 ...
def users_index(request):
users = User.objects.exclude(is_superuser=True).order_by("-created_at")
return render(request, "sns/users/index.html", {"users": users})
解説: users_index
関数では、スーパーユーザーを除いたすべてのユーザーを取得し、作成日で降順に並べ替えています。取得したユーザー情報は、ユーザー一覧テンプレートに渡されます。
テンプレート
sns/templates/users/index.html
{% extends 'base.html' %}
{% block title %}
オーナー一覧
{% endblock %}
{% block main %}
<div class="max-w-5xl mx-auto my-8 bg-white shadow-sm">
<div class="grid grid-cols-2 gap-1">
{% for user in users %}
<a href="/users/{{ user.id }}">
<div class="bg-white p-4 flex">
{% if user.icon %}
<img class="block h-24 rounded-full aspect-[1/1] object-cover" src="{{ user.icon.path }}" />
{% endif %}
<div class="pl-4">
<div>
<p class="text-lg text-black font-semibold">{{ user.username }}</p>
<p class="font-medium whitespace-pre-wrap">{{ user.description }}</p>
<div class="flex mt-4">
<p class="text-sm text-black font-semibold">投稿{{ user.post_set.count }}件</p>
</div>
</div>
</div>
</div>
</a>
{% endfor %}
</div>
</div>
{% endblock %}
解説: ユーザー一覧テンプレートでは、各ユーザーのアイコン、ユーザー名、説明文、投稿数を表示します。ユーザー名をクリックすると、そのユーザーの詳細ページに遷移します。
URL登録
sns/urls.py
from django.urls import path
from . import views
urlpatterns = [
path("dashboard", views.dashboard, name="dashboard"),
path("posts", views.posts_index, name="posts"),
path("posts/create", views.posts_create, name="posts_create"),
path("posts/<int:post_id>/", views.posts_detail, name="posts_detail"),
path("posts/<int:post_id>/edit", views.posts_edit, name="posts_edit"),
path("posts/<int:post_id>/comments", views.comments_create, name="comments_create"),
path("users", views.uses_index, name="users_index"),
]
解説: /users
にアクセスすることで、users_index
ビューが呼び出され、ユーザー一覧が表示されます。
オーナー詳細
ビュー
sns/views.py
def users_detail(request, user_id):
user = User.objects.get(id=user_id)
posts = user.post_set.order_by("-created_at")
return render(request, "sns/users/detail.html", {"user": user, "posts": posts})
解説: users_detail
関数は、指定されたユーザーIDに基づいてユーザー情報を取得し、そのユーザーが作成した投稿を降順に並べて表示します。
テンプレート
sns/templates/users/detail.html
{% extends 'base.html' %}
{% block title %}
オーナー詳細
{% endblock %}
{% block main %}
<div class="max-w-5xl mx-auto">
<div class="bg-white p-4 flex mt-8">
{% if user.icon %}
<img class="block h-24 rounded-full aspect-[1/1] object-cover" src="{{ user.icon.path }}" />
{% endif %}
<div class="pl-4">
<p class="text-lg text-black font-semibold">{{ user.username }}</p>
<p class="font-medium whitespace-pre-wrap">{{ user.description }}</p>
<div class="flex mt-4">
<p class="text-sm text-black font-semibold">投稿{{ user.post_set.count }}件</p>
</div>
</div>
</div>
<div class="my-8 bg-white">
<div class="grid grid-cols-3 gap-1">
{% for post in posts %}
<a href="/posts/{{ post.id }}"><img class="aspect-[1/1] w-full object-cover" src="{{ post.image.path }}" /></a>
{% endfor %}
</div>
</div>
</div>
{% endblock %}
解説: ユーザー詳細ページでは、ユーザーのアイコン、ユーザー名、説明文、およびそのユーザーが作成した投稿を表示します。投稿は、画像としてサムネイル形式で表示され、クリックすることで詳細ページに遷移します。
URL登録
sns/urls.py
from django.urls import path
from . import views
urlpatterns = [
path("dashboard", views.dashboard, name="dashboard"),
path("posts", views.posts_index, name="posts"),
path("posts/create", views.posts_create, name="posts_create"),
path("posts/<int:post_id>/", views.posts_detail, name="posts_detail"),
path("posts/<int:post_id>/edit", views.posts_edit, name="posts_edit"),
path("posts/<int:post_id>/comments", views.comments_create, name="comments_create"),
path("users", views.users_index, name="users_index"),
path("users/<int:user_id>/", views.users_detail, name="users_detail"),
]
解説: /users/<user_id>/
にアクセスすることで、特定のユーザーの詳細情報が表示されます。
これにより、コメント機能とオーナー機能が実装され、ユーザー同士の交流や情報を表示する仕組みが整いました。ユーザーが自分や他のユーザーの投稿にコメントを残したり、他のユーザーのプロフィールを閲覧できることで、よりインタラクティブなSNSアプリが実現できます。
まとめ
今回の章では、Djangoを用いた基本的な投稿機能の実装を通して、CRUD(作成・表示・編集・削除)操作を効果的に学びました。これらの機能は、ユーザーがアプリケーションを自由に使いこなし、自分のコンテンツを管理するための基礎となる重要な要素です。それぞれの操作を以下の流れで整理し、どのように実装すればよいかを深掘りしました。
- 作成(Create): 投稿作成フォームを通じて、ユーザーがテキストと画像を送信し、データベースに登録する手順を学びました。特に、画像ファイルの取り扱い方法や
POST
リクエストでのデータ送信における注意点を押さえたことで、前章で学んだ画像アップロードの知識を応用できました。 - 表示(Read): ダッシュボードや投稿一覧ページで、ユーザーの投稿をどのようにテンプレートに表示するかを学びました。Djangoのクエリセットを使ってデータベースから情報を取得し、
for
ループを用いてHTML上に動的に表示する方法は、他のデータ表示機能にも応用可能です。 - 編集(Update): 既存の投稿を呼び出し、フォームに反映させて内容を変更する編集画面を実装しました。この際、データベースとの連携を行い、更新された内容を適用するための
POST
リクエスト処理を組み込み、ユーザーが投稿を管理しやすい仕組みを作り上げました。 - 削除(Delete): 投稿を安全に削除する機能を追加しました。不要なコンテンツを削除することで、ユーザーがアプリ内の情報を整理できるため、投稿の管理をより効率的に行えます。
本章で学んだことを実際のアプリ開発に役立てていきましょう!
この記事へのコメントはありません。