質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.71%

<python,django>投稿されたコメントにカウンターボタン(いいねボタン)を設置したい

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 826

sr2460

score 37

前提・実現したいこと

ボタンを押すと数値が増えるカウンターボタンを投稿されたコメントに紐づけたいです。
投稿ページを別に作ってそこに遷移させればもっと簡単だとは思うのですが、今回は1つのページ内で行いたいと思っています。

該当のソースコード

ここに投稿フォームの表示。投稿されたコメントの表示。いいねボタンの設置の3つを1ページで実現しようとしています。
board.html

#ここがコメントの投稿フォームです
<form action="" method="POST" enctype="multipart/form-data">
{{ form.as_p }}
<button type="submit">送信</button>
{% csrf_token %}
</form>

#ここがコメントの表示部分です
{% for post in post_list %}

<p>{{ post.name }}</p>
<p>{{ post.text | linebreaksbr }}</p>
{% if post.file %}
<p><img src="{{ post.file.url }}" ></p>
{% endif %}

<p>{{ post.date }}</p>

#ここにいいねボタンを設置。postのidと紐つけていいねボタンを押すとカウントが増えていく
<h3>"いいね"ボタンを押してください</h3>
<form action="{% url 'board:good' question.id %}" method="post">
    {% csrf_token %}
    <input type="submit" name="good" value="いいね">({{ good_count }} いいね)
</form>



{% endfor %}

関数でgoodを機能させるURLであるgood/<int:pk>を設置しています。
urls.py

    #!/usr/bin/env python
# -*- coding: utf-8 -*-

from django.urls import path

from . import views

app_name = 'board'
urlpatterns = [
    path('', views.FormAndListView.as_view(), name='board'),
    path('good/<int:pk>/', views.good, name='good'),
]

class Goodはclass Postに紐つけています。

models.py

from django.db import models
from django.utils import timezone

class Post(models.Model):
    class Meta:
        verbose_name = '投稿'
        verbose_name_plural = '投稿リスト'

    name = models.CharField('名前', max_length=20)
    text = models.TextField('本文')
    date = models.DateTimeField('日付', default=timezone.now)
    file = models.FileField('ファイル', blank=True, null=True)


    def __str__(self):
        return self.text


class Good(models.Model):

    """いいね."""

    created_at = models.DateTimeField(default=timezone.now)
    good = models.ForeignKey(Post, on_delete=models.CASCADE, null=True)


    class Meta:
        db_table = 'good'
        verbose_name = verbose_name_plural = 'いいね'

フォームと投稿されたコメントといいねボタンの表示。

class FormAndListView(FormView, ListView, TemplateResponseMixin):
    def get(self, request, *args, **kwargs):
        formset =  PostForm(request.POST or None, files=request.FILES or None)
        formView = FormView.get(self, request, *args, **kwargs)
        listView = ListView.get(self, request, *args, **kwargs)
        formData = formView.context_data['form']
        listData = listView.context_data['object_list']
        good_count = Good.objects.count()
        context = {'form' : formData, 'post_list' : listData, 'good_count' : good_count}
        return render(request, 'board/board.html', context)

いいねボタンをクリックすると数値が増える。

def good(request, pk):
    """いいねボタンをクリック."""
    pk = get_object_or_404(Post, pk=pk)
    if request.method == 'POST':
        # データの新規追加
        Good.objects.create(pk=request.POST['good'])
        context = {'pk' : pk}
    return redirect('board:board')

エラーメッセージ

django.urls.exceptions.NoReverseMatch: Reverse for 'good' with arguments '('',)'
 not found. 1 pattern(s) tried: ['board/good/(?P<pk>[0-9]+)/$']


pk関連のコードを削除した場合いいねボタンで数値が増加することは確認しております。ただしその場合コメントへの紐づけが行われません。

試したこと

現在はurlからプライマリーキーを紐つけるコードの書き方なのでこのままでは実現が難しいことは理解しているのですが・・・。
urlに情報が無い状態でPostのプライマリーキーを特定して紐つけることがdjangoで果たしてかのうなのでしょうか。

補足情報(FW/ツールのバージョンなど)

python=3.7.0
django=(2, 0, 2, 'final', 0)

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+1

ご認識の課題がいくつかあるようですので、できる範囲でひとつひとつお答えさせていただきますね。

1) エラー

django.urls.exceptions.NoReverseMatch: Reverse for 'good' with arguments '('',)'

このエラーは、変数 question が存在しないために {% url 'board:good' question.id %} の行で発生しているようです。最後の引数は question.id ではなく post.id ではないでしょうか。

2) good の post へのひもづけ

1 のポイントを直すと、いいねフォームの送信時に関数 good() に post.pk (= pk )が渡されるはずなので、それで解決となるのではないかと思います。ただし good() は例えば次のようにした方がよい気がします(細かな説明は割愛しますが、コードをご覧いただくと自明かと思いますので、読み取ってください)。

def good(request, pk):
    """いいねボタンをクリック."""
-    pk = get_object_or_404(Post, pk=pk)
+    post = get_object_or_404(Post, pk=pk)
    if request.method == 'POST':
        # データの新規追加
-        Good.objects.create(pk=request.POST['good'])
+        Good.objects.create(good=post)
-        context = {'pk' : pk}
    return redirect('board:board')

(蛇足ですが、個人的には Good から Post を参照する ForeignKey に good という名前をつけるといろいろと混乱しそうなので、素直に post 等の名前に方がよいかな、という気がします)

3) その他

余談です。 DateTimeField で default=timezone.now とするのはあまりよくないので、代わりに auto_now を使う方がよいかと思います。

-    created_at = models.DateTimeField(default=timezone.now)
+    created_at = models.DateTimeField(auto_now=True)

理由は次のページ等でわかりやすく説明されているのでそちらをご覧になってみてください。

4) URL 以外での外部キーひもづけ 

urlに情報が無い状態でPostのプライマリーキーを特定して紐つけることがdjangoで果たしてかのうなのでしょうか。

可能かと思います。一般的な方法は、対象のプライマリーキーを格納した hidden フィールドをフォームに追加しておいてそれを取得する、というやり方でしょうか。

ご参考になれば幸いです。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/01/26 16:19 編集

    いつもありがとうございます!!
    エラーについてですが見落としでした。これは気がつけるミスですし気を付けていきたいなと思います。

    patch部分についても基本的な部分は何とか僕でも読めていると思います。
    確かにこれで投稿にいいねが紐つけられましたしそれは管理画面でも確認できました。

    auto_now=Trueの部分については英語サイトなこともあり苦戦していますが、理解できるように努めます。


    現在これによりエラーは出ておりません。本当にありがとうございました。

    現在まだhtmlですべてのいいねの総数がそのままカウントされているので、現在そこを検討しています。
    アドバイスしていただいた

    対象のプライマリーキーを格納した hidden フィールドをフォームに追加しておいてそれを取得する

    good_count = Good.objects.count() の段階でなにかフィルターをかけてhtmlに表示される際はコメントにつけられたいいねの数ごとに表示ができないか?

    html部分の{{ good_count }}のところを改造してプライマリキーごとに表示できないか?

    などを検討して調べております。

    リストでテンプレートに渡しているので
    {% for x, y in data.items %}のような形でhtmlを書いて、そのうえでプライマリーキーをhtmlの方に渡せればできるような気はしているのですが。

    すぐにベストアンサーを付けたいのですがいましばらく待っていただけると幸いです。

    キャンセル

  • 2019/01/26 22:25

    そうですか。前進されたようでよかったです :D

    > 現在まだhtmlですべてのいいねの総数がそのままカウントされているので、現在そこを検討しています。

    > html部分の{{ good_count }}のところを改造してプライマリキーごとに表示できないか?

    `post` ごとにいいねの数をカウントされたい感じですかね。こうなる理由はご自身でお調べになってみていただければと思いますが、 `{{ good_count }}` を `{{ post.good_set.count }}` と書き換えると、 `post` ごとのいいねの数が表示できるのではないかと思います。お試しになってみてください。

    キャンセル

  • 2019/01/27 18:54 編集

    ありがとうございます!!!
    class postにForeignkeyで紐づいたgoodをあらわすときに_setを使うという部分で似たようなことを確か以前にも教えて頂いた記憶があります。
    また仮に_set.allですと一般的にすべてが表示されるような表現ですが、後ろに.countがつくとpostに紐づいたgoodを数えられるのでしょうね。

    一度教えていただいたことですからできれば自分から応用できると一番良いのですが、大変お手数をおかけいたします。
    また自分でも頂いたアドバイスを元に試行錯誤しまして

    class Post(models.Model):
    class Meta:
    verbose_name = '投稿'
    verbose_name_plural = '投稿リスト'

    name = models.CharField('名前', max_length=20)
    text = models.TextField('本文')
    date = models.DateTimeField('日付', default=timezone.now)
    file = models.FileField('ファイル', blank=True, null=True)
    good = models.IntegerField(default=0)

    def __str__(self):
    return self.text



    def good(request, pk):
    """いいねボタンをクリック."""
    post = get_object_or_404(Post, pk=pk)
    if request.method == 'POST':
    # データの新規追加
    post.good += 1
    post.save()
    return redirect('board:board')



    <form action="{% url 'board:good' post.id %}" method="post">
    {% csrf_token %}
    <input type="submit" name="good" value="いいね">({{ post.good }} いいね)
    </form>

    という方法でも同様のことが実現できることが分かりました。
    現在2つの方法があるので今後実現したいことに合わせて(いいねボタンを押した時に画面遷移をせずにカウントを増やす。他のページとのリンクの兼ね合いなど)どちらの方法が良いか検討していきます。
    本当にありがとうございます!

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.71%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる