DjangoでSessionを使っていいねを実装する

Django
DjangoPython
0

本記事では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.pymysite/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ください。

スポンサーリンク
H-MEMO

コメント

  1. めう より:

    はじめまして。

    わかりやすい記事を書いてくださり、ありがとうございます。
    大変、勉強になりました。

    一点、ミス記載かと思われる文を発見しましたので、ご連絡致します。

    views.pyの10行目
    誤:for articles in articles:
    正:for article in articles:

    以上です。宜しくお願い致します。

    0