Juan Martin Zabala
Juan Martin Zabala

Reputation: 801

How to add comments on django

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',)

Upvotes: 2

Views: 1528

Answers (4)

codecomment
codecomment

Reputation: 1

2 - forms.py icine elave ele:

from django import forms
from .models import Comment
class CommentForm(forms.ModelForm):
class Meta:
    model = Comment   
fields = ('name', 'body')

# overriding default form setting and adding bootstrap class
def __init__(self, *args, **kwargs):
    super(CommentForm, self).__init__(*args, **kwargs)
    self.fields['name'].widget.attrs = {'placeholder': 'Enter name', 'class': 'form-control'}
    self.fields['body'].widget.attrs = {'placeholder': 'Comment here...', 'class': 'form-control', 'rows': '5'}

3 - models.py elave ele :

class Comment(models.Model):
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name="comments")
name = models.CharField(max_length=50)
parent = models.ForeignKey("self", null=True, blank=True, on_delete=models.CASCADE)
body = models.TextField()

created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
active = models.BooleanField(default=True)

class Meta:
    ordering = ('created',)

def __str__(self):
    return self.name

def get_comments(self):
    return Comment.objects.filter(parent=self).filter(active=True)

4 - views.py post bele gorunmelidi :

def post (request,slug):
post = Post.objects.get(slug = slug)
latest = Post.objects.order_by('-timestamp')[:3]
comments = post.comments.filter(active=True)
new_comment = None
comment_form = CommentForm()


comment_form = CommentForm(data=request.POST)
if comment_form.is_valid():
    new_comment = comment_form.save(commit=False)
    new_comment.post = post
    new_comment.save()
    comment_form = CommentForm()

context = {
    'post': post,
    'latest': latest,
    'comments': comments,
    'comment_form': comment_form
}
return render(request, 'post.html', context)

5 - post.html de author altina divider den sonra elave ele :

    <hr/>
    <h3>Add Comment</h3>
    <form method="post" action="">
        {% csrf_token %}
        {{ comment_form.as_p }}
        <button type="submit" class="btn btn-primary">Comment</button>
    </form>

    {% with comments.count as total_comments %}
        <h3 class="mt-5">
            {{ total_comments }} comment{{ total_comments|pluralize }}
        </h3>
    {% endwith %}
    {% if not post.comments.all %}
        No comments yet

    {% else %}
        {% for comment in post.get_comments %}
            {% include 'comment.html' with comment=comment %}
        {% endfor %}
    {% endif %}
</div>

6 - admin.py elave ele :

@admin.register(Comment)
class CommentAdmin(admin.ModelAdmin):
list_display=('name', 'post', 'created', 'active')
list_filter = ('active', 'created', 'updated')
search_fields = ('name', 'body')

7 - templates de comment.html yarat ve icine elave et :

<div class="border-0 border-start border-2 ps-2" id="{{comment.id}}">

    <div class="mt-3">
        <strong>{{comment.name}}</strong>
        {% if  comment.parent.name%} to <strong>{{comment.parent.name}}</strong>{% endif %}
        <small class="text-muted">On {{ comment.created.date }}</small>
    </div>
    <div class="border p-2 rounded">
        <p>{{comment.body}}</p>
        <button class="btn btn-primary btn-sm" onclick="handleReply({{comment.id}})">Reply</button>

        <div id="reply-form-container-{{comment.id}}" style="display:none">

           
        </div>
    </div>
    {% for comment in comment.get_comments %}
        {% include 'comment.html' with comment=comment %}
    {% endfor %}

8 - models de post un altina elave ele

def get_comments(self):
    return self.comments.filter(parent=None).filter(active=True)

Upvotes: 0

Juan Martin Zabala
Juan Martin Zabala

Reputation: 801

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>

Upvotes: 1

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 477824

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.

Upvotes: 1

Yasiel Cabrera
Yasiel Cabrera

Reputation: 618

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)

Upvotes: 1

Related Questions