Django+Ajaxでいいねボタンの実装


# 1. はじめに
記念すべきQiita初投稿です。

Twitter大好き人間なのですが、業務中にTwitterは触れない。ということで、思考やメモを書き殴るためのTwitter風アプリを作り始めたのですが、いいねボタンの実装がなかなか難しく。。。 
formでやるとページの遷移を伴い、Twitter感が薄くなってしまうので他の方法を模索。
そんな時、Ajaxというものを知り、なんとか実装することが出来たので投稿してみようと思った次第です。

![dislike.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213641/33d78653-49d4-acfc-7068-6b9f15352156.png)　![like.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/213641/8cc32d48-6113-e4b7-6731-e4115810b647.png)

WebフレームワークやDBについて勉強し始めたばかりですので、間違い等ありましたらご教授頂けると幸いです。


# 2. 実装 & 解説
今回はformから入力したデータをDBに保存する部分の説明は省きますので、予めご了承ください。

```python:urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('', views.timeline, name='index'),
    path('like/', views.like, name='like'),
]
```
ルーティングの設定。httpresponseを返すページを設定しておきます。

```python:models.py
from django.db import models
from django.utils import timezone

class PostTweet(models.Model):
    memo = models.TextField(max_length=140)
    published = models.DateTimeField(default=timezone.now)
    star = models.BooleanField(default=False)
```
models.pyでは、メモの内容に加え、「いいね」の状態を示すブール値を定義しておきます。

```python:forms.py
class PostForm(forms.ModelForm):
    class Meta:
        model = PostTweet
        fields = ['memo', 'published', 'star']
```
forms.pyはこんな感じ。特筆することはありませんが一応載せます。


```python:views.py
from django.http import HttpResponse
from django.shortcuts import render
from .models import PostTweet
from .forms import PostForm, SearchForm

def index(request):
    posts = PostTweet.objects.all().order_by('-published')
    form = PostForm()
    d = {
        'form': form,
        'posts': posts
    }
    return render(request, 'index.html', d)

def like(request):
    if request.method == 'GET':
        if 'star' in request.GET:
            like = PostTweet.objects.get(id=int(request.GET['star']))
            like.star = not like.star
            like.save()
            return HttpResponse('OK')
    else:
        return HttpResponse("NG")
```
like()については後程説明します。

```html:index.html
{% extends 'base.html' %}
{% block body %}
    {% for post in posts %}
        {{ post.memo | linebreaker }}
        <br>
        {% if post.star == False %}
            <input type="checkbox" class="like" id="like_{{ post.id }}" 
                   data_id="{{ post.id }}">
        {% else %}
            <input type="checkbox" class="like" checked="checked" id="like_{{ post.id }}"
                   data_id="{{ post.id }}">
        {% endif %}
        <label for="like_{{ post.id }}" class="like_label">★</label>
    
    <script src="https://code.jquery.com/jquery-3.5.0.js" 
            integrity="sha256-r/AaFHrszJtwpe+tHyNi/XCfMxYpbsRg2Uqn0x3s2zc=" 
            crossorigin="anonymous"></script>
    <script type="text/javascript">
        $('.like').click(function(){
            var id;
            id = $(this).attr("data_id");
            $.ajax(
                {
                    type:"GET",
                    url: "like",
                    data:{
                        star: id
                        }
                })
            });
    </script>
{% endblock %}
```

チェックボックスとラベルを使って、いいねボタンを表現することにしました。
いいねの状態を表すstarがTrueなら、チェックボックスをあらかじめcheckedにしておきます。  

base.htmlについては省略。

```CSS:style.css
.like{
    display: none;
}
.like_label{
    margin-left: 10px;
    cursor: pointer;
    color: #cccccc;
    border: none;  /* 枠線を消す */
    outline: none; /* クリックしたときに表示される枠線を消す */
    background: transparent; /* 背景の灰色を消す */
}
input:checked + .like_label{
    color: gold;
}
```
### 見た目の更新
ボタンの表示を消し、ラベルのみ表示するようにします。
デフォルトのボタンラベルの色はグレーにします。
チェックボックスがcheckedになっているときは、ボタンラベルの色をgoldに変更します。

こうすることで、ボタンを押すたびに色が変わるようになります。

### DBの更新
jQueryをインポートします。
Ajax（javascriptの非同期処理）を使って、押されたボタンのレコードidをviews.pyのlikeメソッドに返します。（idはDBに追加した際に自動的に追加される。）
そして、views.pyのlike()で、取得したidのstarをnot starにして更新します。

こうすることで、ボタンを押すたびにDBの内容が更新されます。



# まとめ
Ajaxとチェックボックスを使うことで、良い感じのいいねボタンを実装できました。
今回のアプリは自分しか見ない前提なのでオンオフしかありませんが、いいね数をカウントするタイプも同じように作ることが出来るのではないでしょうか。

ここまで読んでくださってありがとうございました！