How to create a Comment Section for your Django Blog!
Tags: django, python, blog
Published: 2020-09-28 11:53:16

Hi!

This mini-tutorial will focus on creating a Comment Section for **adding** (without update/delete) comments to each blog post. We will implement this feature while using a *class-based* view, namely our ***BlogPost DetailView***, mine's looks like this:
```python
# MainApp/models.py
class BlogPost(models.Model):
    title = models.CharField(max_length=100)
    subtitle = models.CharField(max_length=200, blank=True, null=True)
    content = models.TextField()
    date_posted = models.DateTimeField(default=timezone.now)
    author = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
    image = models.ImageField(upload_to='blog_images',
                              storage=gd_storage, null=True, blank=True)

    def __str__(self):
        return self.author.username + ', ' + self.title[:40]

    def get_absolute_url(self):
        return reverse('blogpost-detail', kwargs={'pk': self.pk})

# MainApp/views.py
class BlogPostDetailView(DetailView):
    model = BlogPost
    # template_name = MainApp/BlogPost_detail.html
    # context_object_name = 'object'
```

<hr>

First things first: we need to create our BlogComment model in our `models.py`. We can personalize our model in any way we want, I'll stick to the basics of a comment and will add the following fields: *author*, *content/body*, and *a date* (when the comment was posted):
```python
# models.py
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User

class BlogComment(models.Model):
    blogpost_connected = models.ForeignKey(
        BlogPost, related_name='comments', on_delete=models.CASCADE)
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    content = TextField()
    date_posted = models.DateTimeField(default=timezone.now)

    def __str__(self):
        return str(self.author) + ', ' + self.blogpost_connected.title[:40]
```
- Every BlogComment will have an id (foreign key) of its BlogPost (a BlogPost can have multiple BlogComments), and if a BlogPost is deleted, then all the BlogComments that were linked to that BlogPost will be deleted (on_delete == CASCADE).
- The author will also be a foreign key to the whole User object (that has its User id, username, email, etc), therefore a User can have multiple comments.
- We will also add the magic method __str__() to view the comments in a more readable way (instead of viewing the object type) when we are making queries from our CLI/Admin panel).

![Database Diagram for our comment](https://lh3.googleusercontent.com/69xf-pQkvH4LVRLar9gbwykflgcAa7tNOctrErrlH4NMmiPbwWy8XpgApwU7SbtB2ZyxUVCoo0-GKFknOmkHUQ52pUInuZF57UWJbZrQ9JJCWJsIGtXZJ_Qxiaz4uXe2ZV0W2gXLa-I=w2400)

In `models.py`, in our BlogPost model, we can also write a function that will return the number of comments of a blog post:
```python
# models.py
class BlogPost(models.Model):
    title = models.CharField(max_length=100)
    ...

    @property
    def number_of_comments(self):
        return BlogComment.objects.filter(blogpost_connected=self).count()
```

After every change in the models.py file, we need to open our terminal and make the migrations to our database:
```bash
# CLI/Terminal
>> cd C:\Projects\...\YourDjangoAppMainFolder
>> python manage.py makemigrations
>> python manage.py migrate
```

<hr>

Let's create a new form class: In the same folder as our models.py, create a new file named `forms.py`, where we'll write the following:
```python
# forms.py
from django import forms
from .models import BlogComment

class NewCommentForm(forms.ModelForm):
    class Meta:
        model = BlogComment
        fields = ['content']
```
Très bien, now we can add to our [get_context_data](https://docs.djangoproject.com/en/3.1/topics/class-based-views/generic-display/#adding-extra-context) function within our class-based view BlogPost DetailView, in views.py, the following:
```python
# views.py
from .models import BlogPost, BlogComment
from .forms import NewCommentForm

class BlogPostDetailView(DetailView):
    model = BlogPost
    # template_name = MainApp/BlogPost_detail.html
    # context_object_name = 'object'

    def get_context_data(self, **kwargs):
        data = super().get_context_data(**kwargs)

        comments_connected = BlogComment.objects.filter(
            blogpost_connected=self.get_object()).order_by('-date_posted')
        data['comments'] = comments_connected
        if self.request.user.is_authenticated:
            data['comment_form'] = NewCommentForm(instance=self.request.user)

        return data
```
Here we will retrieve all the comments from our current BlogPost object, store them (the query) in a local variable *comments_connected*, then send it further as a context to our HTML-based blogpost_detail.

However, in order to post comments directly from our class-based BlogPost DetailView, we also need to define a **post method** to receive the context from our form (situated in this view/html). Therefore, in the same class, we need to add:
```python
# views.py
class BlogPostDetailView(DetailView):
    ... 

    def get_context_data(self, **kwargs):
        ...

    def post(self, request, *args, **kwargs):
        new_comment = BlogComment(content=request.POST.get('content'),
                                  author=self.request.user,
                                  blogpost_connected=self.get_object())
        new_comment.save()
        return self.get(self, request, *args, **kwargs)
```

<hr>

Finally, in our `blogpost_detail.html` let's write in the DjangoTemplateLanguage the following:
```html
<!-- COMMENTS  -->
<h2>Leave your comment!</h2>
<div id="comments_section">

  {% if user.is_authenticated %}
  <form method="POST">
	{% csrf_token %}
	<div class="form-group">
	  {{ comment_form }}
	  <button class="btn btn-info" type="submit">Add comment <i class="fas fa-comments"></i></button>
	</div>
  </form>
  {% else %}
  <a class="btn btn-outline-info" href="{% url 'login' %}?next={{request.path}}">Log in to add a comment!</a><br>
  {% endif %}

  {% if comments %}
  <strong class="text-secondary">{{ object.number_of_comments }} Comment{{ object.number_of_comments|pluralize }}</strong>
  <hr>
  <ul>
    {% for comment in comments %}
    <li>           
     <div>
	    <span>
	      <strong class="text-info">{{ comment.author }} </strong>
	      <small class="text-muted">{{ comment.date_posted }}</small>
	    </span>
	    <p>
	      {{ comment.content|safe }}
	    </p>
      </div>
    </li>
	{% endfor %}
  </ul>
  {% else %}
    <strong class="text-secondary">No comments yet...</strong>
  {% endif %}

</div>
```
Sooo, there's a lot of code there, let's go through some parts of it step by step:
- The first thing that we do is to check if the user is authenticated: if True, then show the user a form where he can write the content of his new comment. if False, then show the user a button that redirects him to the Login page. Also, it's important that after a user logs into his account to redirect him to the earlier blog post that he wanted to post a comment, so we'll add to our redirect link `"?next={{request.path}}"` where request.path is the current page path (e.g. localhost/blogpost/7)
- Then we check if our current blogpost has any comments, if not, we'll put in our HTML "No comments yet..", but if we have any comments, we will then write the number of comments and then we will loop through each of them and show its author, date, and content.

<hr>

Perfect, so we are almost done! However, this comment section will look rather dull... Therefore we need to create some styling, that we can find on Google or... [here](https://www.bootdey.com/snippets/view/Simple-Comment-panel#html) (thanks to [bootdey.com](https://www.bootdey.com/))! To integrate it, we just need to add the CSS code in our `blogpost_detail.html` and the corresponding tags in our `<div> and <ul>` (list) sections, following their example.

Nice, now we are done! Hope you will find this useful. 😁
You can see a live example of this comment section on my blog: [codingtranquillity.herokuapp.com](https://codingtranquillity.herokuapp.com/)... where you can also find more articles like this! 

Have a nice day and... Happy coding!
R.B.