Create a comment model in Django

Create comment model Django Python
Create comment model Django Python

I decide create a comment model in Django after Create my first open source project – Pyxtract and after learning Django. I wrote code for some Django apps, e.i. my Blog app.

But how about receiving some feedbacks from your readers and letting them comment in your Django web site or blog?

Creating comment blog model

Let’s open blog/models.py and append this code to the end of the file:

class Comment(models.Model):
    post = models.ForeignKey('BlogPost', on_delete=models.CASCADE, related_name='comments', null=True)
    author = models.CharField(max_length=50)
    email = models.EmailField(max_length=50, unique=True, verbose_name="email address")
    body = models.TextField(max_length=500, verbose_name="body")
    created_on = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)
    approved_comment = models.BooleanField(default=False)

    class Meta:
        ordering = ['created_on']
        verbose_name = 'Post Comment'
        verbose_name_plural = 'Post Comments'

    def __str__(self):
        return 'Comment {} by {}'.format(self.body, self.author)

    def approve(self):
        self.approved_comment = True
        self.save()

The related_name option in models.ForeignKey allows us to have access to comments from within the Post model. We use a name ‘BlogPost’, if the model ‘BlogPost’ will create below this code, or use name BlogPost if we created it before.

Create tables for models in your database

Now it’s time to add our comment model to the database. To do this we have to tell Django that we made changes to our model. Type python manage.py makemigrations blog in your command line. You should see output like this:

(venv)  C:\src\djangos\locallibrary> python manage.py makemigrations blog
Migrations for 'blog':
  0002_comment.py:
    - Create model Comment

You can see that this command created another migration file for us in the blog/migrations/ directory. Now we need to apply those changes by typing python manage.py migrate blog in the command line. The output should look like this:

(venv)  C:\src\djangos\locallibrary> python manage.py migrate blog
    Operations to perform:
      Apply all migrations: blog
    Running migrations:
      Rendering model states... DONE
      Applying blog.0002_comment... OK

Our Comment model exists in the database now! Wouldn’t it be nice if we had access to it in our admin panel?

Register Comment model in admin panel

Remember to import the Comment model at the top of the file and register the Comment model in the admin panel, go to blog/admin.py and add these lines:

from django.contrib import admin
from .models import Comment


# Register your models here.
class CategoryAdmin(admin.ModelAdmin):
    list_display = ('name', 'description')

admin.site.register(Category, CategoryAdmin)

If you type python manage.py runserver on the command line and go to http://127.0.0.1:8000/admin/ in your browser, you should have access to the list of comments, and also the capability to add and remove comments.

Make our comments visible

Go to the blog/templates/blog/blogpost_detail.html file and add the following lines:

    {% if blogpost.category %}
        Category: <a href="{{ blogpost.category.get_absolute_url }}"><em> {{ blogpost.category.name }}</em></a> <br><br><br>
    {% else %}
        <small>Category: nothing</small><br><br><br>
    {% endif %}

Now we can see the comments section on pages with post details.

But it could look a little bit better, so let’s add some CSS to the bottom of the blog/static/css/styles.css file:

.comment {
    margin: 20px 0px 20px 20px;
}

We can also let visitors know about comments on the post list page. Go to the blog/templates/blog/blogpost_list.html file and add the line:

<a href="{{ post.get_absolute_url }}">{{ post.title }}</a> with <a href="{% url 'blogpost-detail' pk=post.pk %}">{{ post.approved_comments.count }}</a> comments - <a href="{{ post.blogger.get_absolute_url }}">{{post.blogger}}</a> (<small>{{ post.created_on }})</small><br>

After that our template should look like this:

{% extends "blog/base_generic.html" %}

{% block content %}
  <h1>Posts</h1>
  {% if blogpost_list %}
  <ul>
    {% for post in blogpost_list %}
      <li>
          {% if post.category %}
          <small>Category: <i> {{ post.category.name }}</i></small> <br>
          {% else %}
              <small>Without category:</small><br>
          {% endif %}
          <a href="{{ post.get_absolute_url }}">{{ post.title }}</a> with <a href="{% url 'blogpost-detail' pk=post.pk %}">{{ post.approved_comments.count }}</a> comments - <a href="{{ post.blogger.get_absolute_url }}">{{post.blogger}}</a> (<small>{{ post.created_on }})</small><br>


      </li>
    {% endfor %}
  </ul>
  {% else %}
    <p>There are no posts in the blog.</p>
  {% endif %}       
{% endblock %}

Let your readers write comments

Right now we can see comments on our blog, but we can’t add them. Let’s change that!

Go to blog/forms.py and add the following lines to the file:

from django import forms

from blog.models import Comment


class CommentForm(forms.ModelForm):

    class Meta:
        model = Comment
        fields = ('author', 'email', 'body')

Now, go to blog/templates/blog/blogpost_detail.html and add:

            {% if user.is_authenticated %}

                <h6><a class="btn btn-default" href="{% url 'add_comment_to_post' pk=blogpost.pk %}">Add a new comment</a></h6>
           {% else %}
             <a href="{% url 'login'%}?next={{request.path}}">Login to add a new comment</a>
           {% endif %}

If you go to the post detail page you should see the error ‘NoReverseMatch’.

We know how to fix that! Go to blog/urls.py and add this pattern to urlpatterns:

from django.urls import path, re_path
from . import views


urlpatterns = [
    path('', views.index, name='index'),
    path('blogs/', views.BlogPostListView.as_view(), name='blogposts'),
    re_path(r'^post/(?P<pk>\d+)$', views.BlogPostDetailView.as_view(), name='blogpost-detail'),
]

Refresh the page, and we get a different error – ‘AtributeError’.

To fix this error, add this view to blog/views.py:

def add_comment_to_post(request, pk):
    post = get_object_or_404(BlogPost, pk=pk)
    if request.method == "POST":
        form = CommentForm(request.POST)
        if form.is_valid():
            comment = form.save(commit=False)
            comment.post = post
            comment.save()
            return redirect('blogpost-detail', pk=post.pk)
    else:
        form = CommentForm()
    return render(request, 'blog/add_comment_to_post.html', {'form': form, 'post': post})

Now, on the post detail page, you should see the “Add a new comment” link.

However, when you click that button, you’ll see an error ‘TemplateDoesNotExist’.

Like the error tells us, the template doesn’t exist yet. So, let’s create a new one at blog/templates/blog/add_comment_to_post.html and add the following code:

{% extends 'blog/base_generic.html' %}

{% block content %}
    <h1>New comment</h1>

    <p>Post your comment for: <a href="{% url 'blogger-detail' post.pk %}">{{ post.title }}</a></p>

    <form method="POST" class="post-form">{% csrf_token %}
        <div class="form-group">
        {{ form.as_p }}
        <button type="submit" class="save btn btn-default">Send</button>
        </div>
    </form>
{% endblock %}

Wow! Now your readers can let you know what they think of your blog posts!

Moderating your comments

Not all of the comments should be displayed. As the blog owner, you probably want the option to approve or delete comments. Let’s do something about it.

Go to blog/templates/blog/blogpost_detail.html and add the following code:

{% for comment in blogpost.comments.all %}
                {% if user.is_authenticated or comment.approved_comment %}
                    <span class="be-comment-name">
                    {{ comment.author }}
                    </span>
                    <span class="be-comment-time"><strong class="fa fa-clock-o"> {{ comment.created_on }}</strong></span>

                <br>
                    <p class="be-comment-text">
                    {{ comment.body|linebreaks }}
                    </p>

                {% if not comment.approved_comment %}
                <a class="btn btn-default" href="{% url 'comment_remove' pk=comment.pk %}"><span class="glyphicon glyphicon-remove"></span></a>
                <a class="btn btn-default" href="{% url 'comment_approve' pk=comment.pk %}"><span class="glyphicon glyphicon-ok"></span></a>
                {% endif %}
                {% endif %}
            {% endfor %}

You should see NoReverseMatch, because no URL matches the comment_remove and comment_approve patterns… yet!

To fix the error, add these URL patterns to blog/urls.py:

    path('comment/<int:pk>/approve/', views.comment_approve, name='comment_approve'),
    path('comment/<int:pk>/remove/', views.comment_remove, name='comment_remove'),

Now, you should see AttributeError. To fix this error, add these views in blog/views.py:

@login_required
def comment_approve(request, pk):
    comment = get_object_or_404(Comment, pk=pk)
    comment.approve()
    return redirect('blogpost-detail', pk=comment.post.pk)

@login_required
def comment_remove(request, pk):
    comment = get_object_or_404(Comment, pk=pk)
    comment.delete()
    return redirect('blogpost-detail', pk=comment.post.pk)

Everything works! There is one small tweak we can make. In our post list page — under posts — we currently see the number of all the comments the blog post has received. Let’s change that to show the number of approved comments there.

To fix this, go to blog/templates/blog/blogpost_list.html and change the line to:

<a href="{{ post.get_absolute_url }}">{{ post.title }}</a> with <a href="{% url 'blogpost-detail' pk=post.pk %}">{{ post.approved_comments.count }}</a> comments - <a href="{{ post.blogger.get_absolute_url }}">{{post.blogger}}</a> (<small>{{ post.created_on }})</small><br>

Finally, add this method to the BlogPost model in blog/models.py:

    def approved_comments(self):
        return self.comments.filter(approved_comment=True)

Now everything works! You are the best! You create a comment model in Django!

Автор Serhii Kupriienko

I have a multifaceted experience, accomplished several successful projects, including national-scale, modernized a museum, engaged in online commerce, wholesale and retail sales, was an entrepreneur, publisher, and editor. I headed the development department. I gave lectures and training. I organized events and press conferences. I was the secretary of the collegial boards. And I also did the planning and reporting in an organization. My Ph.D. thesis was “The social and economic system of the Inca Empire Tawantinsuyu“.

KUPRIENKO