Возможность ставить лайк/голосовать за сообщения из списка сообщений

У меня проблема. Я ставил лайки постам на видео с YouTube. Поставить лайк можно только из вида post_detail и это работает правильно! Как сделать так, чтобы можно было ставить лайки постам в post_list (в моем случае это 'feed')? Пожалуйста, помогите!

МОЙ КОД:

utils.py

def is_ajax(request):
    return request.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest'

views.py

from .utils import is_ajax

def feed(request):
    comments = Comment.objects.all()
    posts = Post.objects.all().order_by('-created_at')
    return render(request, 'main/feed.html', {'posts': posts,
                                              'comments': comments})


def post_detail(request, slug):
    post = get_object_or_404(Post, slug=slug)
    comments = Comment.objects.filter(post=post, reply=None).order_by('-created_at')
    is_voted = False
    if post.votes.filter(id=request.user.id).exists():
        is_voted = True

    if request.method == 'POST':
        comment_form = CommentForm(request.POST or None)
        if comment_form.is_valid():
            content = request.POST.get('content')
            reply_id = request.POST.get('comment_id')
            comment_qs = None
            if reply_id:
                comment_qs = Comment.objects.get(id=reply_id)
            comment = Comment.objects.create(
                post = post,
                author = request.user,
                content = content,
                reply = comment_qs
            )
            comment.save()
            return redirect(post.get_absolute_url())
        else:
            for error in list(comment_form.errors.items()):
                messages.error(request, error)
    else:
        comment_form = CommentForm()
    return render(request, 'main/post_detail.html', {'post':post,
                                                     'comment_form':comment_form,
                                                     'comments':comments,
                                                     'is_voted':is_voted})


@login_required
def post_vote(request, slug):
    post = get_object_or_404(Post, id=request.POST.get('id'), slug=slug)
    is_voted = False
    if post.votes.filter(id=request.user.id).exists():
        post.votes.remove(request.user)
        is_voted = False
    else:
        post.votes.add(request.user)
        is_voted = True
    context = {
        'post': post,
        'is_voted': is_voted,
    }
    if is_ajax(request):
        html = render_to_string('main/votes.html', context, request)
        return JsonResponse({'form':html})

votes.html

<form action="{% url 'main:post-vote' slug=post.slug %}" method="post">
    {% csrf_token %}
    {% if is_voted %}
        <button type="submit" id="vote" name="post_id" value="{{ post.id }}" btn="btn btn-outline">Unvote</button>
    {% else %}
        <button type="submit" id="vote" name="post_id" value="{{ post.id }}" btn="btn btn-outline">Vote</button>
    {% endif %}
</form>
{{post.votes_count}}

post_detail.html


<div id="vote-section">
    {% include 'main/votes.html' %}
</div>

<script>
    $(document).ready(function(event){
        $(document).on('click','#vote', function(event){
            event.preventDefault();
            var pk = $(this).attr('value');
            $.ajax({
                type: 'POST',
                url: "{% url 'main:post-vote' slug=post.slug %}",
                data: {'id':pk, 'csrfmiddlewaretoken': '{{ csrf_token }}'},
                dataType: 'json',
                success: function(resoponse){
                    $('#vote-section').html(resoponse['form'])
                    console.log($('#vote-section').html(resoponse['form'])); 
                },
                error: function(rs, e){
                    console.log(rs.responseText);
                }
            })
        });
    });
</script>

feed.html

...
{% for post in posts %}
<div id="vote-section">
    {% include 'main/votes.html' %}
</div>
...
{% endfor %}

<script>
        $(document).ready(function(event){
            $(document).on('click','#vote', function(event){
                event.preventDefault();
                var pk = $(this).attr('value');
                $.ajax({
                    type: 'POST',
                    url: "{% url 'main:post-vote' slug=post.slug %}",
                    data: {'id':pk, 'csrfmiddlewaretoken': '{{ csrf_token }}'},
                    dataType: 'json',
                    success: function(resoponse){
                        $('#vote-section').html(resoponse['form'])
                        console.log($('#vote-section').html(resoponse['form'])); 
                    },
                    error: function(rs, e){
                        console.log(rs.responseText);
                    }
                })
            });
        });
</script>

models.py

class Post(models.Model):
    ...
    votes = models.ManyToManyField(get_user_model(), related_name='votes', blank=True)

    @property
    def votes_count(self):
        return self.votes.count()

urls.py

urlpatterns = [
    path('', views.feed, name='feed'),
    path('post_create/', views.post_create, name='post-create'),
    path('post/<slug>/', views.post_detail, name='post-detail'),
    path('post/<slug>/delete', views.post_delete, name='post-delete'),
    path('post/<slug>/update', views.post_update, name='post-update'),
    path('post/<slug>/vote', views.post_vote, name='post-vote'),
    path('comment/<slug>/delete', views.comment_delete, name='comment-delete'),
    path('comment/<slug>/update', views.comment_update, name='comment-update'),
    path('comment_reply/<slug>/delete', views.reply_comment_delete, name='comment-reply-delete')
]

Я понимаю, что мне нужно сделать то же самое, что и в посте подробнее. Я пытался это сделать, но получил ошибки

def post_detail(request, slug):
    post = get_object_or_404(Post, slug=slug)
    comments = Comment.objects.filter(post=post, reply=None).order_by('-created_at')
    is_voted = False
    if post.votes.filter(id=request.user.id).exists():
        is_voted = True
        ...

Попытка сделать:

def feed(request, slug=None):
    comments = Comment.objects.all()
    posts = Post.objects.all().order_by('-created_at')
    v_post = get_object_or_404(Post, slug=slug)
    is_voted = False
    if v_post.votes.filter(id=request.user.id).exists():
        is_voted = True
    return render(request, 'main/feed.html', {'posts': posts,
                                              'comments': comments,
                                              'is_voted': is_voted})

гот

Page not found (404)

Также пробовал

def feed(request, slug):
    comments = Comment.objects.all()
    count_filter = Q(votes=request.user)
    vote_case = Count('votes', filter=count_filter, output_field=BooleanField())
    posts = Post.objects \
    .annotate(is_voted=vote_case) \
    .all().order_by('-created_at')
    return render(request, 'main/feed.html', {'posts': posts,
                                              'comments': comments})

гот

TypeError at /
feed() missing 1 required positional argument: 'slug'

OK - здесь есть что распаковать:

Во-первых, вообще говоря, похоже, что вы хотите, чтобы Ajax выполнял основную часть работы. Причина (или, по крайней мере, начальная причина), почему это не работает, кроется в том, как вы воспроизводите формы

Для хорошо построенного HTML только один элемент на странице должен иметь определенный ID

Ваша форма генерируется следующим образом

{% for post in posts %}
<div id="vote-section"> #This will repeat for every post
    {% include 'main/votes.html' %}
</div>

Это означает, что каждая форма будет иметь один и тот же id, раздел голосования, так что

success: function(resoponse){
                        $('#vote-section').html(resoponse['form'])

не будет знать, куда писать.

Аналогично, в формах списка каждая кнопка имеет id="vote"

На странице подробностей это не проблема, потому что там только одна форма и одна кнопка голосования/отмены голосования. На странице списка с несколькими формами это так.

Так что же с этим делать?

Прежде всего, добавьте класс к вашей кнопке.

votes.html

<button type="submit" id="vote" class="vote-button" name="post_id" value="{{ post.id }}" btn="btn btn-outline">Unvote</button>

Далее настройте html вашего фида так, чтобы он правильно использовал идентификаторы. Мы будем использовать {{[forloop.counter][1]}} для генерации уникальных идентификаторов.

Мы также перенесем ajax в цикл, так как slug для каждого поста разный в разделе генерации url:. Мы добавим ID нового div в селектор и функцию success, чтобы сделать его уникальным.

feed.html

{% for post in posts %}
<div id="vote-section{{forloop.counter}}">
    {% include 'main/votes.html' %}
</div>
...

<script>
        $(document).ready(function(event){
            $(document).on('click','#vote-section{{forloop.counter}} .vote-button', function(event){
                event.preventDefault();
                var pk = $(this).attr('value');
                $.ajax({
                    type: 'POST',
                    //this needs to be in the for post in posts loop to get post.slug
                    url: "{% url 'main:post-vote' slug=post.slug %}",
                    data: {'id':pk, 'csrfmiddlewaretoken': '{{ csrf_token }}'},
                    dataType: 'json',
                    success: function(resoponse){
                        $('#vote-section{{forloop.counter').html(resoponse['form'])
                        console.log($('#vote-section{{forloop.counter}}').html(resoponse['form'])); 
                    },
                    error: function(rs, e){
                        console.log(rs.responseText);
                    }
                })
            });
        });
</script>


{% endfor %}

Сейчас, вероятно, есть способы сделать это более эффективным, но, надеюсь, это поможет вам преодолеть горб и добиться того, чтобы это работало.

Вернуться на верх