Djangoでクリック回数をカウントする

# Djangoでクリック回数をカウントしたい!!

　Djangoでブログサービスとかの開発を行う際記事がクリックされた回数を表示する機能が欲しいと思う方はいるんじゃないかと思います。オーソドックスな方法と言えば、JavaScriptのajax通信を用いてクリック回数を調べる手法がよく用いられていますが、ajax通信って何と言いますか、構文が複雑だったりJavaScriptをちょっとかじっただけでは理解が追い付かなかったりしますよね？私もそうでした。でもやっぱり開発しているアプリにそういう機能がどうしても欲しくてしょうがないという思いが捨てきれなかったからこそajaxに頼らないでクリック回数をカウントする機能をバックエンド側で作ってみました。同じ壁に当たってしまった方のために、また、自分自身の備忘録のために今回記事にさせていただきました。

#1.models.py
　クリック回数をカウントすることが目的ですのでモデルの定義もシンプルにします。

<pre>
# 記事
class Post(models.Model):
    user = models.ForeignKey(User, verbose_name='投稿者', on_delete=models.CASCADE)
    title = models.CharField(verbose_name='タイトル', max_length=40)
    content = models.TextField(verbose_name='本文')
    img = models.ImageField(blank=True, null=True)
    created_at = models.DateTimeField(verbose_name='作成日時', auto_now_add=True)

    # ここでクリック回数をカウント
    @property 
    def number_of_views(self):
        return View.objects.filter(post=self).count()

# クリック回数をカウントするために用いるクラス
class View(models.Model):
    article = models.ForeignKey(Post, verbose_name='表示記事', on_delete=models.CASCADE)
    count = models.IntegerField(default=0)
</pre>

　モデルの定義はこんなとこでしょうね。定義の中で重要になってくるのはPostクラスに記述されたnumber_of_views関数で、個々の記事毎の表示回数をこの関数内でカウントし、データベース内にて管理することになります。

　propertyの意味が分からない方はこちらをご参照ください。
　https://docs.python.org/3/library/functions.html#property

# 2.views.py
　次にviews.pyを編集することにしましょう。先ほど定義したモデルをまずはインポートします。

<pre>
from django.views import generic
from .models import Post, View
</pre>

　必要なクラスをインポートしましたら残りを書いていきます。とりあえず記事をリスト形式で表示し、記事をクリックした段階でカウント処理が行われるようにしましょう。

<pre>
# 記事の一覧表示
class PostListView(LoginRequiredMixin, generic.ListView):
    context_object_name = 'post_list'
    model = Post
    template_name = 'index.html'

    def get_queryset(self):
        posts = Post.objects.all().order_by('-created_at')
        return posts

# 記事の詳細へ移動
class PostDetailView(LoginRequiredMixin, generic.DetailView):
    model = Post
    template_name = 'post_detail.html'

# 記事の表示回数をカウント
def add_count(request, pk):
    post = get_object_or_404(Post, pk=pk)

    count = View()
    count.article = post
    count += 1
    count.save()
    return redirect('post-detail', pk=post.pk)
</pre>

　ポイントになってくるのはadd_countメソッドです。通常、リスト形式で一覧表示された記事の詳細を見るときには、PostDetailクラスで定義した処理を行わせるようにするわけですが、その前にadd_countメソッドを挟み込むことによってViewインスタンスを生成(本当はcountテーブル内の要素だけ変化させたい!!)し、データベースに保存、HTMLにインスタンスの数を出力することによってその記事がクリックされた回数を可視化するわけです。

# 3.urls.py
　次は、viewsで定義したクラスやメソッドのパスをつなげましょう。このあたりもDjangoの基礎的なところになってきますので説明不要でしょうけれどもパスをつなげた後の名前をどうしているかをわかりやすくするために一応用意させていただきました。

<pre>
from django.urls import path

from . import views

urlpatterns = [
    path('', views.PostListView.as_view(), name="index"),
    path('post_detail/<span><</span>int:pk>/', views.PostDetailView.as_view(), name='post_detail'),
    path('add_count/<span><</span>int:pk>/', views.add_count, name='add_count'),
]
</pre>

# 4.HTMLファイルの編集
　最後に、HTMLファイルの編集をしていきましょう。こちらも最低限の機能しか追加しないようにしていきます。

　とりあえずbase.htmlから作っていきましょう。

<pre>
<p><!DOCTYPE html></p>
{% load static %}

<span><</span>html lang='ja'>

    <span class="na"><</span>head>
        <span><</span>meta charset="utf-8">
        <span><</span>meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <span><</span>meta name="description" content="">
        <span><</span>meta name="author" content="">

        <span><</span>title>{% block title %}{% endblock %}<span><</span>/title>

    <span><</span>/head>

    <span><</span>body>
        {% block header %}{% endblock %}

        {% block contents %}{% endblock %}

        <span><</span>footer>
            <span><</span>p>Copyright &copy; hogehoge 2021<span><</span>/p>
        <span><</span>/footer>

    <span><</span>/body>
</pre>

　ではこれを元としてindex.htmlも編集しましょう。

<pre>
{% extends base.html %}

{% load static %}

{% block title %}記事{% endblock %}

{% block contents %}
{% for items in post_list %}

<span><</span>!-- ここでpost_detailではなくadd_countのパスを指定 -->
<span><</span>a href="{% url 'add_count', items.pk %}">
    {% if items.img %}
    <span><</span>img src="{{ items.img.url }}" width=100 height=100 style="float:right;">
    {% endif %}

    <span><</span>h3>記事タイトル:{{ items.title }}<span><</span>/h3>
    <span><</span>p>記事本文:{{ items.content }}<span><</span>/p>
    <span><</span>p>投稿日時:{{ items.created_at }}<span><</span>/p>
<span><</span>/a>
{% empty %}
<span><</span>p>記事がありません。<span><</span>/p>
{% endfor %}

{% endblock %}
</pre>

　ポイントになってくるのはindex.htmlで通常post_detailを指定するはずの箇所をadd_countにしていることです。こうすることによって詳細画面に移動する前にデータベース内にViewインスタンスが生成され、保存されます。生成されたインスタンスの数をデータベース内でカウントし、インスタンスの数でクリック回数を示すわけです。もしクリック回数を表示したい場合ですと、先ほどのコードを以下のように修正するとクリック回数、正確には記事が表示された回数が反映されます。

<pre>
{% extends base.html %}

{% load static %}

{% block title %}記事{% endblock %}

{% block contents %}
{% for items in post_list %}

<span><</span>!-- ここでpost_detailではなくadd_countのパスを指定 -->
<span><</span>a href="{% url 'add_count', items.pk %}">
    {% if items.img %}
    <span><</span>img src="{{ items.img.url }}" width=100 height=100 style="float:right;">
    {% endif %}

    <span><</span>h3>記事タイトル:{{ items.title }}<span><</span>/h3>
    <span><</span>p>記事本文:{{ items.content }}<span><</span>/p>
    <span><</span>p>投稿日時:{{ items.created_at }}<span><</span>/p>

    <span><</span>!-- 追加箇所 -->
    <span><</span>p>表示回数:{{ items.number_of_views }}<span><</span>/p>

<span><</span>/a>
{% empty %}
<span><</span>p>記事がありません。<span><</span>/p>
{% endfor %}

{% endblock %}
</pre>

　これで記事の表示回数をバックエンドで処理してフロントエンド側に出力させることが可能になります。私としてはもう少しスマートな書き方ができればなあと思いますが現状これ以上いいものは思いつきません。もっといい書き方をご存知の方は是非とも教えていただきたいものです。あるいは記事作ってほしいです。

　より効率良い書き方見つけましたのでよろしければどうぞ→<a href="https://qiita.com/hexanitrobenzen/items/c622f8f36ab1f06f6f6c">https://qiita.com/hexanitrobenzen/items/c622f8f36ab1f06f6f6c</a>