Djangoでクリック数(view数)カウント

##はじめに
クリック数のカウント、具体的には投稿した記事に対するView数を表示する機能を、DjangoとJavascriptのajax通信を使って実装していきます。
[こちらのサイト](https://blog.ikappio.com/post-on-ajax-also-add-csrf_token-with-django/)を参考にしています。

##カウントする部分

```post_list.html
{% for post in posts %}
    <table>
    　　<div class="post">
       　　<div class="date">
              <p>{{ post.published_date }}</p>
           </div>
             　<a class="post_{{ post.pk }}" href="{% url 'post_detail' pk=post.pk %}">{{ post.title }}</a>
　　　　　　　　　{{ new.view_number }}views
        </div>
    </table>
{% endfor %}
```
Listviewでcontext_object_name ="posts"　と宣言して、templateにPostmodelを渡しています。
そして、aタグに　class="post_{{ post.pk }}"　として、objectごとにpkを振ったクラス名を付けます。DetailViewを用いた各objectのpost_detailに遷移した回数をカウントしていきます。


ここで登場するPostmodelは

```models.py
class Post(models.Model):
    author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    title = models.CharField(max_length=50)
    text = MDTextField() 
    view_number = models.IntegerField(blank=False, default=0)
    created_date = models.DateTimeField(default=timezone.now)
    published_date = models.DateTimeField(blank=True, null=True)
```
の一般的な形です。
##カウント処理

```count.js
$(function() {
//csrf_tokenの取得に使う
function getCookie(name) {
    var cookieValue = null;
    if (document.cookie && document.cookie !== '') {
        var cookies = document.cookie.split(';');
        for (var i = 0; i < cookies.length; i++) {
            var cookie = jQuery.trim(cookies[i]);
            
            if (cookie.substring(0, name.length + 1) === (name + '=')) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
    return cookieValue;
}

function csrfSafeMethod(method) {
    return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}

for(let number =1;number<=1000;number ++){
    //post_1~post_1000まで、クリックした時の動作
    $('.post_'+number).click(function() {
        var csrf_token = getCookie("csrftoken");

        $.ajax({
            type: 'POST',
            url: './post_list',
            //渡したいdataを辞書形式で挿入
            data: {
                view_number: 1,
                post_number: number,  
            },
            contentType: "application/json",
            //csrftokenを付与
            beforeSend: function(xhr, settings) {
                if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
                    xhr.setRequestHeader("X-CSRFToken", csrf_token);}
            },
            });
              
    });

};
});
```
Djangoでformを作るときに使う{% csrf_token %}をつけるための処理をする必要があるため、少しコードが長くなっています。上半分のコードの意味はよくわかりませんが、ここでは大丈夫そうです。
view_numberはrequestされるごとに1を足すだけなので、送るデータはpost_numberだけでも十分です。

```views.py
from django.http import QueryDict

    if request.method == 'POST':
        # 送ったdataはrequest.bodyに入っている。
        dic = QueryDict(request.body, encoding='utf-8')
        number = int(dic.get('view_number'))
        post_number = int(dic.get('post_number'))
        
        viewed_post = get_object_or_404(Post, pk=post_number)
        viewed_post.view_number += number
        viewed_news.save()
```
request.bodyには　view_number=1&post_number=(count.jsで送ったnumber)　が入っています。QueryDictに関しては[Django公式](https://djangoproject.jp/doc/ja/1.0/ref/request-response.html)を見ると理解できそうです。QueryDict(request.body)で辞書形式に変換してgetでkeyに対応する値を取得できます。
後はget_object_or_404(Post, pk=post_number)で遷移先のPostオブジェクトを取得し、view_numberに1を加算して、保存。

##おわりに
csrf_tokenの問題で混乱した箇所だったので残しておこうと思いました。