本記事ではDjangoでいいね機能を実装する。閲覧しているユーザーでいいねの履歴を管理するのが一般的であるが、ログインしていない(アカウントを作っていない)ユーザーでもいいねすることができるようにSessionを使って実装する。
はじめに
この記事は続きであり、ユーザーだけを管理する基本的ないいねの実装については次の記事を参照されたい。
また、この記事はDjangoの初めてのアプリ作成(pollsのチュートリアル)くらいの知識は前提としているので、ファイル名の説明とかは省略している。
さらにAjax通信用のjQueryコードの説明は前記事で行っているため、本記事では簡単にする。
Sessionでいいねを管理する意味
これまででいいねが実装されているのが確認できるが、一般に公開すると誰もいいねすることができないし、そもそも記事一覧ページを開くこともできない。
これはログインしていないユーザーはAnonymousUserでIDを持たず、いいねをすることができるのはアカウントを作成したユーザーのみであるからである。
前の記事だとsuperuserに登録した本人しかいいねできない。
TwitterなどのSNSではアカウントを持っている人だけがいいねできるためこれで良いかもしれない。実際、世の中のほとんどのサービスがアカウントを持っている人だけがいいねできるようになっていて、それ以外の人にはアカウント作成に誘導するという形をとっている。
ただ、アカウントを持っていない人でもいいねできるサービスも少なからずあるため、これを実装してみたい。
そこでSessionを使って管理してみる。
環境
- Python 3.7.9
- Django 3.1.3
DjangoのSessionについて
Djangoはデフォルトでセッションを記録するようになっている。したがって、とりあえずセッションを使ってみるという場合はsettings.py
などはいじらなくても良い気がする。
触りたい場合はこちらをどうぞ。
セッションには期限があり、期限内であれば同じユーザーがサイトを訪れてもいいねの履歴が保持される。
期限切れになったり、ユーザーが明示的にキャッシュ消去などを行うとセッションが新しいものに切り替わるため、再び新しいセッションが発行される。
セッション期限はデフォルトで2週間になっており、settings.py
に次を追加することで変更することができる。
SESSION_COOKIE_AGE = 60 * 60 * 24 * 365
単位は秒である。
期限切れになったセッションは自動ではパージされないので、定期的に
python manage.py clearsessions
する必要がある。
これでいいねの数が減るのではないかと心配になるかもしれないが、消されても残るような設定がある。詳しくは後述する。
データベースの定義
テーブルの作成
models.py
に以下のように記事といいねのテーブルをそれぞれ作る。
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User
from django.contrib.sessions.models import Session
class Article(models.Model):
title = models.CharField(max_length=100, verbose_name='タイトル')
text = models.TextField(verbose_name='本文')
timestamp = models.DateTimeField(default=timezone.now)
class Like(models.Model):
article = models.ForeignKey(Article, on_delete=models.CASCADE)
user = models.ForeignKey(User, blank=True, null=True, on_delete=models.CASCADE)
session = models.ForeignKey(Session, blank=True, null=True, on_delete=models.SET_NULL)
timestamp = models.DateTimeField(default=timezone.now)
Like
は扱いやすいため別テーブルで作った。誰が、どの記事に、いつ、いいねしたのかを記録する。
ログインしていないユーザーはsession
を記録し、user
はblankにする。ログインしているユーザーはその逆である。on_delete
はセッションが消されたときにいいねを消すかどうかを指定するのだが、必ずしもSET_NULL
である必要はないため、次の中から好きなものを指定すれば良い。
- CASCADE:関連付けられているいいねを削除する。
- PROTECT:関連付けられているいいねがあると削除できない。
- SET_NULL:NULLをセット。
- SET_DEFAULT:デフォルト値をセット。
- SET():削除時の処理を自由に設定する。
- DO_NOTHING:何もしない。
次にmigrateする。
python manage.py makemigrations
python manage.py migrate
Adminページで編集できるようにする
Admin.pyを次のように編集する。
from django.contrib import admin
from .models import Article, Like
admin.site.register(Article)
admin.site.register(Like)
保存して、superuserを作成し、サーバーを立ち上げる。
python manage.py createsuperuser
...
python manage.py runserver
http://127.0.0.1:8000/admin
に行き、作成したsuperuserでログインすると記事やいいねを操作するUIがあるので適当に記事を増やしておく。
記事一覧のページを作成
mysite/{自分のapp名}/templates
を作成する。
- templates
articles.html
like.html
という2つのhtmlファイルを作成する。articles.html
は記事一覧を表示し、like.html
はいいねボタンを表示する。
urls.pyの編集
まず、mysite/{自分のapp名}/urls.py
とmysite/mysite/urls.py
を編集する。
mysite/{自分のapp名}/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.ArticlesView, name='articles'),
path('like', views.LikeView, name='like'),
]
mysite/mysite/urls.py
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path('', include('mural.urls')),
path('admin', admin.site.urls),
]
views.pyの編集
次に、mysite/{自分のapp名}/views.py
を編集する。
from django.shortcuts import get_object_or_404, render
from .models import Article, Like
from django.http import JsonResponse
def ArticlesView(request):
articles = Article.objects.all()
liked_list = []
user = request.user
if user.is_authenticated:
for article in articles:
liked = article.like_set.filter(user=user)
if liked.exists():
liked_list.append(article.id)
else:
try:
session = Session.objects.get(pk=request.session.session_key)
except Session.DoesNotExist:
session = request.session.create()
for article in articles:
liked = article.like_set.filter(session=session)
if liked.exists():
liked_list.append(article.id)
context = {
'articles': articles,
'liked_list': liked_list,
}
return render(request, '{自分のapp名}/articles.html', context)
def LikeView(request):
if request.method =="POST":
article = get_object_or_404(Article, pk=request.POST.get('article_id'))
user = request.user
liked = False
if user.is_authenticated:
like = Like.objects.filter(article=article, user=user)
if like.exists():
like.delete()
else:
like.create(article=article, user=user, session=None)
liked = True
else:
session = get_object_or_404(Session, pk=request.session.session_key)
like = Like.objects.filter(article=article, session=session)
if like.exists():
like.delete()
else:
like.create(article=article, user=None, session=session)
liked = True
context={
'article_id': article.id,
'liked': liked,
'count': article.like_set.count(),
}
if request.is_ajax():
return JsonResponse(context)
ArticlesViewについて
urls.py
で定義したようにarticles.html
が呼ばれたときに実行される。
ユーザーがAnonymousUser
だった場合セッションを取得するのだが、初めての閲覧の場合はまだセッションが発行されていないため、次のようにしている
try:
session = Session.objects.get(pk=request.session.session_key)
except Session.DoesNotExist:
session = request.session.create()
liked_list
というものを用意し、閲覧しているユーザー(request.user
で取得)または、セッションが過去にどの記事をいいねしたかを格納する。
次の部分はlike_set
でarticleに紐づく全てのいいねを取得し、閲覧しているユーザーまたはセッションででフィルターをかけている。
liked = article.like_set.filter(user=user)
# または
liked = article.like_set.filter(session=session)
LikeViewについて
urls.py
で定義したようにlike.html
が呼ばれたときに実行される。
最初にif request.method =="POST":
としているように、POST
つまり「いいねの押下」で呼ばれなければ何もしない。
また、最後にif request.is_ajax():
で返しているようにAjax通信でなければ何も返さない。
like.html
が呼ばれるときは、すでにユーザーのセッションはあるため、try/except
の部分は必要ない。
「いいねの押下」によって呼ばれると、ユーザーがすでにその記事をいいねしているかどうかを調べ、
- いいねしていたら、いいねテーブルから削除する。
- いいねしていなければ、いいねテーブルに追加する。
return JsonResponse(context)
はAjax通信でJson形式で必要な次の値を返している。
- 記事のID
- いいねしたのか(True)、取り消したのか(False)
- 記事のいいね総数
HTMLを書く
HTML部分は以下の記事と全く同じなので、以下の記事を参照されたい。
おわりに
これでセッションでいいねの実装は完了した。
ユーザーはキャッシュを消去し続ければいいねを増やし続けることができるので、システムとしてどうかという部分はあるがとりあえずは実装できた。
何かミスや誤植があればCommentください。
コメント
はじめまして。
わかりやすい記事を書いてくださり、ありがとうございます。
大変、勉強になりました。
一点、ミス記載かと思われる文を発見しましたので、ご連絡致します。
views.pyの10行目
誤:for articles in articles:
正:for article in articles:
以上です。宜しくお願い致します。
ご指摘ありがとうございます。修正致しました。