How to add comments on django

Viewed 593

I am trying to add a comment system to my project, all the code looks fine but I am getting this error "ValueError at / The QuerySet value for an exact lookup must be limited to one result using slicing". I dont know what is wrong but the error might be on the views.py file.

views.py

    def imagelist(request):
        images = Post.objects.all()
        post = get_object_or_404(Post)
        comments = Comment.objects.filter(post=images)
        if request.method == 'POST':
          comment_form = CommentForm(request.POST or None)
          if comment_form.is_valid():
            contentt = request.POST.get('content')
            comment = Comment.objects.create(post=images, user=request.user, content=content)
            comment.save()
            return HttpResponseRedirect(post.get_absolute_url())
        else:
          comment_form = CommentForm()
        context2 = {
            "images": images,
            "comments": comments,
            "comment_form": comment_form,
        }
return render(request, 'imagelist.html', context2)

models.py

    class Comment(models.Model):
        post = models.ForeignKey(Post, on_delete=models.CASCADE)
        user = models.ForeignKey(User, on_delete=models.CASCADE)
        content = models.TextField(max_length=160)
        timestamp = models.DateTimeField(auto_now_add=True)

        def __str__(self):
            return '{}-{}'.format(self.post.title.str(self.user.username))

    class Post(models.Model):
        text = models.CharField(max_length=200)
        posti = models.ImageField(upload_to='media/images', null=True, blank="True")
        video = models.FileField(upload_to='media/images', null=True, blank="True")
        user = models.ForeignKey(User, related_name='imageuser', on_delete=models.CASCADE, default='username')
        liked = models.ManyToManyField(User, default=None, blank=True, related_name='liked')
        updated = models.DateTimeField(auto_now=True)
        created =models.DateTimeField(auto_now_add=True)

        def __str__(self):
            return str(self.tittle)

forms.py

    class CommentForm(forms.ModelForm):
        class Meta:
            model = Comment
            fields = ('content',)
3 Answers

You need to pass to the create mathod of the comments a single Post since the corresponding fields is a ForeignKey and you're passing an entire queryset (Post.objects.all())

You need get just the post where the comment should live.

single_post = Post.objects.get(pk=the_post_pk)
comment = Comment.objects.create(post=single_post, user=request.user, content=content)

The problem is that you write:

images = Post.objects.all()
comments = Comment.objects.filter(post=images)

Here images is a set of Post objects, not a single Post object, hence you can not filter on that. But you actually do not need to do this anyway.

Furthermore there is also a small mistake in the __str__ of your Comment model:

class Comment(models.Model):
    # …

    def __str__(self):
        return '{}-{}'.format(self.post.text, self.user.username)

But the view itself looks "odd", since you here write a list of Posts, but if you make a POST request, you will somehow need to know to what post you want to submit the Comment, therefore at least the view that accepts the POST request, will need to know the primary key of the Post to comment on. You can for example encode that in the urls.py:

# appname/urls.py

from django.urls import path
from app import views

urlpatterns = [
    path('post/<int:pk>/', views.post_detail, name='post_detail'),
    path('post/', views.post_list, name='post_detail')
]

In that view, we can then fetch the item, for example with get_object_or_404, and in case of a POST set the post and user objects in the view:

from django.shortcuts import redirect, get_object_or_404
from django.contrib.auth.decorators import login_required

@login_required
def post_detail(request, pk):
    image = get_object_or_404(Post, pk=pk)
    if request.method == 'POST':
          comment_form = CommentForm(request.POST, request.FILES)
          if comment_form.is_valid():
            comment_form.instance.user = request.user
            comment_form.instance.post = image
            comment_form.save()
            return redirect(post)
    else:
        comment_form = CommentForm()
    context = {
        'post': image,
        'comment_form': comment_form,
    }
    return render(request, 'imagedetail.html', context)

In your template, you can render the comments of the post with:

{{ post }}
{% for comment in post.comment_set.all %}
    {{ comment }}
{% endfor %}
<form method="post" action="{% url 'post_detail' pk=post.pk %}">
    {% csrf_token %}
    {{ comment_form }}
</form>

you can also make a view that renders a list:

@login_required
def post_list(request, pk):
    images = Post.objects.prefetch_related('comment_set')
    comment_form = CommentForm()
    context = {
        'image': images,
        'comment_form': comment_form,
    }
    return render(request, 'imagelist.html', context)

in the template for the list, you can render it with:

{% for post in images %}
    {{ post }}
    {% for comment in post.comment_set.all %}
        {{ comment }}
    {% endfor %}
    <form method="post" action="{% url 'post-detail' pk=post.pk %}">
        {% csrf_token %}
        {{ comment_form }}
    </form>
{% endfor %}

here we thus make a POST request to the post-detail view.

After 4 hours of searching for answers, this is how I achieve it. All I did was add this new view, method, url and html. Hope this helps!

views.py

     def imagedetail(request, pk):
       post = get_object_or_404(Post, pk=pk)
       comments = Comment.objects.filter(post=post)
       if request.method == 'POST':
         comment_form = CommentForm(request.POST or None)
         if comment_form.is_valid():
           content = request.POST.get('content')
           comment = Comment.objects.create(post=post, user=request.user, content=content)
           comment.save()
           return HttpResponseRedirect(post.get_absolute_url())

       else:
         comment_form = CommentForm()
       context2 = {
          "comments": comments,
          "comment_form": comment_form,
       }
       return render(request, 'imagedetail.html', context2)

models.py (on Post model)

    def get_absolute_url(self):
        return reverse('imagedetail', args=[self.id])

urls.py (for new view which is imagedetail)

    path('imagedetail/<int:pk>/', views.imagedetail, name='imagedetail'),

imagelist.html (to redirect to imagedetail, btw it is a bootstrap button)

    <a role="button" class="btn btn-primary" href="{% url 'imagedetail' pk=image.pk %}"></a>

imagedetail.html (it only shows comments)

    <form method="post">
      {% csrf_token %}
      {{comment_form.as_p}}
      {% if request.user.is_authenticated %}
        <input type="submit" value="Submit" class="btn btn-outline-succes">
      {% else %}
        <input type="submit" value="Submit" class="btn btn-outline-succes" disabled>
      {% endif %}
    </form>
    <div class="main-comment-section">
      {{ comments.count }}
      {% for comment in comments %}
        <blockquote class="blockquote">
            <p class="mb-0">{{ comment.content }}</p>
            <footer class="blockquote-footer">by <cite title="Source Title">{{ comment.user }}</cite></footer>
        </blockquote>
      {% endfor %}
    </div>
Related