Comment not showing properly

Hi there, I am having trouble in debugging my code. In my website, I want to create a comments section where authenticated users can make comments, and everyone else can view the comments.

The current issue is that the viewing of comments is not available, even though authenticated users can submit comments (and I can see the comments in the admin page). I’m not able to point out exactly which part of my code is off. I had to manually test each portion and I figured that there is some issues in my views/urls/html template.

I appreciate if anyone can point me in the right direction!

# import django modules
from django.shortcuts import render, redirect, get_object_or_404
from django.urls import reverse_lazy
from django.http import HttpResponse
from django.urls import reverse
from django.views import View
from django.contrib.auth.mixins import LoginRequiredMixin
# import respective classes from files in ad
from .forms import CreateForm, CommentForm
from .models import Ads, Comment
from .owner import OwnerListView, OwnerDetailView, OwnerCreateView, OwnerUpdateView, OwnerDeleteView

class AdsListView(OwnerListView):
    model = Ads
    # By convention:
    template_name = "ads/ads_list.html"


class AdsDetailView(OwnerDetailView):
    model = Ads
    template_name = "ads/ads_detail.html"
    #not sure if this get request is ok?
    def get(self, request, pk) :
        context = Ads.objects.get(id=pk)
        comments = Comment.objects.filter(text=context).order_by('-updated_at')
        comment_form = CommentForm()
        # want to show the detailed view of the ad details, comment header, and comment form
        context = { 'ads' : context, 'comments': comments, 'comment_form': comment_form }
        return render(request, self.template_name, context)

# Class for the ad creation
class AdsCreateView(OwnerCreateView):
    model = Ads
    # define the fields in the ad create view
    fields = ['title', 'text', 'price', 'picture']
    #add from pics
    template_name = 'ads/ads_form.html'
    success_url = reverse_lazy('ads:all')

    def get(self, request, pk=None):
        form = CreateForm()
        context = {'form': form}
        return render(request, self.template_name, context)

    def post(self, request, pk=None):
        form = CreateForm(request.POST, request.FILES or None)

        if not form.is_valid():
            context = {'form': form}
            return render(request, self.template_name, context)

        # Add owner to the model before saving
        pic = form.save(commit=False)
        pic.owner = self.request.user
        pic.save()
        return redirect(self.success_url)


class AdsUpdateView(OwnerUpdateView):
    model = Ads
    fields = ['title', 'text', 'price', 'picture']

    #add from pics
    template_name = 'ads/ads_form.html'
    success_url = reverse_lazy('ads:all')

    def get(self, request, pk):
        pic = get_object_or_404(Ads, id=pk, owner=self.request.user)
        form = CreateForm(instance=pic)
        context = {'form': form}
        return render(request, self.template_name, context)

    def post(self, request, pk=None):
        pic = get_object_or_404(Ads, id=pk, owner=self.request.user)
        form = CreateForm(request.POST, request.FILES or None, instance=pic)

        if not form.is_valid():
            context = {'form': form}
            return render(request, self.template_name, context)

        pic = form.save(commit=False)
        pic.save()

        return redirect(self.success_url)

class AdsDeleteView(OwnerDeleteView):
    model = Ads

def stream_file(request, pk):
    pic = get_object_or_404(Ads, id=pk)
    print(pic)
    response = HttpResponse()
    response['Content-Type'] = pic.content_type
    response['Content-Length'] = len(pic.picture)
    response.write(pic.picture)
    return response

# comment related views
class CommentCreateView(LoginRequiredMixin, View):
    def post(self, request, pk) :
        f = get_object_or_404(Ads, id=pk)
        # ads is the foreign key in Comment class under models.py
        comment = Comment(text=request.POST['comment'], owner=request.user, ads=f)
        print(comment.text)
        comment.save()
        return redirect(reverse('ads:ads_detail', args=[pk]))

class CommentDeleteView(OwnerDeleteView):
    model = Comment
    template_name = "ads/ads_comment_delete.html"

    # https://stackoverflow.com/questions/26290415/deleteview-with-a-dynamic-success-url-dependent-on-id
    def get_success_url(self):
        ads = self.object.ads
        return reverse('ads:ads_detail', args=[ads.id])
# import django module
from django.urls import path, reverse_lazy
# from current folder, import views
from . import views
# define app name
app_name='ads'
# define urlpatterns
urlpatterns = [
    path('', views.AdsListView.as_view(), name='all'),
    path('ads/<int:pk>', views.AdsDetailView.as_view(), name='ads_detail'),
    path('ads_picture/<int:pk>', views.stream_file, name='ads_picture'),
    path('ads/create',
        views.AdsCreateView.as_view(success_url=reverse_lazy('ads:all')), name='ads_create'),
    path('ads/<int:pk>/update',
        views.AdsUpdateView.as_view(success_url=reverse_lazy('ads:all')), name='ads_update'),
    path('ads/<int:pk>/delete',
        views.AdsDeleteView.as_view(success_url=reverse_lazy('ads:all')), name='ads_delete'),
    #path for comments. this has issue, need to fix it
    path('comment/<int:pk>',
        views.CommentCreateView.as_view(), name='ads_comment_create'),
    path('comment/<int:pk>/delete',
        views.CommentDeleteView.as_view(success_url=reverse_lazy('ads:all')), name='ads_comment_delete'),
]

my ads_details.html

{% extends "base_menu.html" %}
{% load humanize %} <!-- https://docs.djangoproject.com/en/3.0/ref/contrib/humanize -->

<!-- block head from pics-->
{% block head %}
<style>
.overlay{
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 10;
  display: none;
  background-color: rgba(0,0,0,0.5); /*dim the background*/
}
</style>
{% endblock %}

{% block content %}

<!--Show the ads update&delete if authenticated-->
{% if ads.owner == user %}
<span style="float: right;">
<!-- i class refers to icon, so update = pencil icon, delete = trash icon-->
<a href="{% url 'ads:ads_update' ads.id %}"><i class="fa fa-pencil"></i></a>
<a href="{% url 'ads:ads_delete' ads.id %}"><i class="fa fa-trash"></i></a>
</span>
{% endif %}

<!--want to show the title portion of the ads-->
<h1>{{ ads.title }}</h1>

<!-- this displays the image -->
{% if ads.content_type %}
<img style="float:right; max-width:50%;" src="{% url 'ads:ads_picture' ads.id %}"
    onclick="document.getElementById('overlay').style.display = 'block';">
{% endif %}


<!--want to show the text portion of the ads-->
<p>
{{ ads.text }}
</p>
<!--show time of update - refer to humanize -->
<p>
({{ ads.updated_at|naturaltime }})
</p>

<!-- allow user to comment if they are logged in-->
{% if user.is_authenticated %}
<br clear="all"/>
<p>
{% load crispy_forms_tags %}
<form method="post" action="{% url 'ads:ads_comment_create' ads.id %}">
   {% csrf_token %}
   {{ comment_form|crispy }}
<input type="submit" value="Submit Comment" onclick="window.location.href='{% url 'ads:all' %}';return false;">
<input type="submit" value="All Ads" onclick="window.location.href='{% url 'ads:all' %}';return false;">
</form>
</p>
{% endif %}

<!-- show comments to everyone. there is an issue here, perhaps in naming?-->
{% for comment in comments %}
<p> {{ comment.text }} 
({{ comment.updated_at|naturaltime }})
<!-- if user is comment owner, they can delete the comment -->
{% if user == comment.owner %}
<a href="{% url 'ads:ads_comment_delete' comment.id %}"><i class="fa fa-trash"></i></a>
{% endif %}
</p>
{% endfor %}
{% endblock %}

here are the files associated with this app.
image

Hi @Zinc,

Have you considered installing the Django Debug Toolbar? That can help you investigate what the template is being rendered with and what view is being used. It’s nice to have those assumptions about what should be working validated.

For this particular issue, it seems like the view and template are properly configured, so then my assumption would be that either the data doesn’t exist or the queryset is being filtered to None. Since you said there is data in the admin, that means it’s likely with the filtering on the comments.

It looks like the issue is here: Comment.objects.filter(text=context). Looking at CommentCreateView, this should be Comment.objects.filter(ads=context) or ads.comments if you wanted to use the related_name.

Hi Tim,

Thank you for the suggestion! I have installed the debug toolbar.

I tried changing to Comment.objects.filter(ads=context) but the comments still didn’t show on the page. I used the debug toolbar and I manually went to the url linked to path('comment/<int:pk>', views.CommentCreateView.as_view(), name='ads_comment_create'), which gave a 405 error, and the url linked to path('comment/<int:pk>/delete', views.CommentDeleteView.as_view(success_url=reverse_lazy('ads:all')), name='ads_comment_delete'), gave me a 404 page not found, with the comment “No comment found matching the query”.

Other than that, everything seems ok. I have no idea which part of the comment text is off…

edit: It’s this part in the ads_details.html that is not showing -

<!-- show comments to everyone. there is an issue here, perhaps in naming?-->
{% for comment in comments %}
<p> {{ comment.text }} 
({{ comment.updated_at|naturaltime }})
<!-- if user is comment owner, they can delete the comment -->
{% if user == comment.owner %}
<a href="{% url 'ads:ads_comment_delete' comment.id %}"><i class="fa fa-trash"></i></a>
{% endif %}
</p>
{% endfor %}
{% endblock %}

Or could it be something named wrongly in my models.py?

from django.db import models
from django.core.validators import MinLengthValidator
from django.conf import settings

#create class "Ads"
class Ads(models.Model) :
    title = models.CharField(
            max_length=200,
            validators=[MinLengthValidator(2, "Title must be greater than 2 characters")]
    )
    price = models.DecimalField(max_digits=7, decimal_places=2, null=True)
    text = models.TextField()
    owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='ads_owned')
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    #comments for the ads
    comments = models.ManyToManyField(settings.AUTH_USER_MODEL,
        through='Comment', related_name='comments_owned')

    # Picture for the ad
    picture = models.BinaryField(null=True, blank=True, editable=True)
    content_type = models.CharField(max_length=256, null=True, blank=True,
                                    help_text='The MIMEType of the file')

    # Shows up in the admin list
    def __str__(self):
        return self.title

#create class "Comment" for the ads
class Comment(models.Model) :
    text = models.TextField(
        validators=[MinLengthValidator(3, "Comment must be greater than 3 characters")]
    )

    ads = models.ForeignKey(Ads, on_delete=models.CASCADE)
    owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)

    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    # Shows up in the admin list
    def __str__(self):
        if len(self.text) < 15 : return self.text
        return self.text[:11] + ' ...'

It looks like your AdsDetailView inherits from OwnerDetailView, but I don’t see that in what you’ve posted so far - can you post that?

Sure - here is the owner.py code

# import modules from django
from django.views.generic import CreateView, UpdateView, DeleteView, ListView, DetailView

from django.contrib.auth.mixins import LoginRequiredMixin

# create classes as extension of original django class
class OwnerListView(ListView):
    """
    Sub-class the ListView to pass the request to the form.
    """
class OwnerDetailView(DetailView):
    """
    Sub-class the DetailView to pass the request to the form.
    """
# require authentication for createview
class OwnerCreateView(LoginRequiredMixin, CreateView):
    """
    Sub-class of the CreateView to automatically pass the Request to the Form
    and add the owner to the saved object.
    """

    # Saves the form instance, sets the current object for the view, and redirects to get_success_url().
    def form_valid(self, form):
        print('form_valid called')
        object = form.save(commit=False)
        object.owner = self.request.user
        object.save()
        return super(OwnerCreateView, self).form_valid(form)


class OwnerUpdateView(LoginRequiredMixin, UpdateView):
    """
    Sub-class the UpdateView to pass the request to the form and limit the
    queryset to the requesting user.
    """

    def get_queryset(self):
        print('update get_queryset called')
        """ Limit a User to only modifying their own data. """
        qs = super(OwnerUpdateView, self).get_queryset()
        return qs.filter(owner=self.request.user)


class OwnerDeleteView(LoginRequiredMixin, DeleteView):
    """
    Sub-class the DeleteView to restrict a User from deleting other
    user's data.
    """

    def get_queryset(self):
        print('delete get_queryset called')
        qs = super(OwnerDeleteView, self).get_queryset()
        return qs.filter(owner=self.request.user)

# References

# https://docs.djangoproject.com/en/3.0/ref/class-based-views/mixins-editing/#django.views.generic.edit.ModelFormMixin.form_valid

# https://stackoverflow.com/questions/862522/django-populate-user-id-when-saving-a-model

# https://stackoverflow.com/a/15540149

# https://stackoverflow.com/questions/5531258/example-of-django-class-based-deleteview

Ok, so OwnerDetailView is just an implementation of a GCBV DetailView.

First observation is that you’re not working with the GCBV the way they’re normally structured. On top of that, you’ve supplied a form in a view not intended to display a form.

First thing I’d suggest is to change the base class from DetailView to View.

You might want to spend some time looking at how the GCBVs are put together and work to know how to use them. I suggest both the Classy Class-Based View site and the Class Based View diagrams page. Understanding what methods to override and what methods and attributes to supply is key to really taking advantage of them.

Beyond that, you have:

context = Ads.objects.get(id=pk)
comments = Comment.objects.filter(text=context).order_by('-updated_at')

So context is an Ads object. Your filter in the second statement is looking for text = context, which is probably never going to be true. You want to check for ads = context to find all Comment objects related to that particular ads object.

In the Django Debug Toolbar, you can look at the context being supplied to the template being rendered. You can examine that to determine whether or not the ‘comments’ key has the list of comments you’re expecting to see rendered.

Hi Ken, thank you very much for the links - they are very helpful for a Django learner. It’s funny, because I changed to ads = context as what Tim recommended before and it didn’t work, but now after trying it again, it works for most of the ads! Coding is a funny thing really.

I am also aware that my Django foundations is still rather weak, and I guess this coursera course on Django that I’m taking might not be suited for me, since I am struggling to understand things. Do you have any recommendations for a newbie Django learner looking at developing web applications? I have some basic knowledge in MATLAB, R and Python.

I always recommend people start with working their way through either (or both!) of the Official Django Tutorial and the Django Girls Tutorial .

Great! Thank you very much!