Homework: create comment model

    Let’s open and append this piece of code to the end of file:

    You can go back to the Django models chapter in the tutorial if you need a refresher on what each of the field types mean.

    In this tutorial extension we have a new type of field:

    • models.BooleanField - this is true/false field.

    The related_name option in models.ForeignKey allows us to have access to comments from within the Post model.

    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:

    1. (myvenv) ~/djangogirls$ python manage.py makemigrations blog
    2. Migrations for 'blog':
    3. 0002_comment.py:
    4. - 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:

    1. (myvenv) ~/djangogirls$ python manage.py migrate blog
    2. Operations to perform:
    3. Apply all migrations: blog
    4. Running migrations:
    5. Rendering model states... DONE
    6. 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?

    To register the Comment model in the admin panel, go to blog/admin.py and add this line:

    1. admin.site.register(Comment)

    directly under this line:

    1. admin.site.register(Post)

    Remember to import the Comment model at the top of the file, too, like this:

    1. from django.contrib import admin
    2. from .models import Post, Comment
    3. admin.site.register(Post)
    4. admin.site.register(Comment)

    If you type python manage.py runserver on the command line and go to in your browser, you should have access to the list of comments, and also the capability to add and remove comments. Play around with the new comments feature!

    Make our comments visible

    Go to the blog/templates/blog/post_detail.html file and add the following lines before the {% endblock %} tag:

    1. <hr>
    2. {% for comment in post.comments.all %}
    3. <div class="comment">
    4. <div class="date">{{ comment.created_date }}</div>
    5. <strong>{{ comment.author }}</strong>
    6. <p>{{ comment.text|linebreaks }}</p>
    7. </div>
    8. {% empty %}
    9. <p>No comments here yet :(</p>
    10. {% endfor %}

    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 static/css/blog.css file:

    1. .comment {
    2. margin: 20px 0px 20px 20px;
    3. }

    After that our template should look like this:

    1. {% extends 'blog/base.html' %}
    2. {% block content %}
    3. <div class="post">
    4. <div class="date">
    5. </div>
    6. <h1><a href="{% url 'post_detail' pk=post.pk %}">{{ post.title }}</a></h1>
    7. <p>{{ post.text|linebreaksbr }}</p>
    8. <a href="{% url 'post_detail' pk=post.pk %}">Comments: {{ post.comments.count }}</a>
    9. </div>
    10. {% endfor %}
    11. {% endblock content %}

    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 end of the file:

    1. class CommentForm(forms.ModelForm):
    2. class Meta:
    3. model = Comment
    4. fields = ('author', 'text',)

    Remember to import the Comment model, changing the line:

    1. from .models import Post

    into:

    1. from .models import Post, Comment

    Now, go to blog/templates/blog/post_detail.html and before the line {% for comment in post.comments.all %}, add:

    1. <a class="btn btn-default" href="{% url 'add_comment_to_post' pk=post.pk %}">Add comment</a>

    If you go to the post detail page you should see this error:

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

    1. url(r'^post/(?P<pk>\d+)/comment/$', views.add_comment_to_post, name='add_comment_to_post'),

    Refresh the page, and we get a different error!

    AttributeError

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

    1. def add_comment_to_post(request, pk):
    2. post = get_object_or_404(Post, pk=pk)
    3. if request.method == "POST":
    4. form = CommentForm(request.POST)
    5. if form.is_valid():
    6. comment = form.save(commit=False)
    7. comment.post = post
    8. comment.save()
    9. return redirect('post_detail', pk=post.pk)
    10. else:
    11. form = CommentForm()
    12. return render(request, 'blog/add_comment_to_post.html', {'form': form})

    Remember to import CommentForm at the beginning of the file:

    Now, on the post detail page, you should see the “Add Comment” button.

    However, when you click that button, you’ll see:

    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:

    1. {% extends 'blog/base.html' %}
    2. {% block content %}
    3. <h1>New comment</h1>
    4. <form method="POST" class="post-form">{% csrf_token %}
    5. {{ form.as_p }}
    6. <button type="submit" class="save btn btn-default">Send</button>
    7. </form>
    8. {% endblock %}

    Yay! 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/post_detail.html and change lines:

    1. <div class="comment">
    2. <strong>{{ comment.author }}</strong>
    3. <p>{{ comment.text|linebreaks }}</p>
    4. </div>
    5. {% empty %}
    6. <p>No comments here yet :(</p>
    7. {% endfor %}

    to:

    1. {% for comment in post.comments.all %}
    2. {% if user.is_authenticated or comment.approved_comment %}
    3. <div class="comment">
    4. <div class="date">
    5. {{ comment.created_date }}
    6. {% if not comment.approved_comment %}
    7. <a class="btn btn-default" href="{% url 'comment_remove' pk=comment.pk %}"><span class="glyphicon glyphicon-remove"></span></a>
    8. <a class="btn btn-default" href="{% url 'comment_approve' pk=comment.pk %}"><span class="glyphicon glyphicon-ok"></span></a>
    9. {% endif %}
    10. </div>
    11. <strong>{{ comment.author }}</strong>
    12. <p>{{ comment.text|linebreaks }}</p>
    13. </div>
    14. {% endif %}
    15. {% empty %}
    16. <p>No comments here yet :(</p>
    17. {% 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:

    1. url(r'^comment/(?P<pk>\d+)/approve/$', views.comment_approve, name='comment_approve'),
    2. url(r'^comment/(?P<pk>\d+)/remove/$', views.comment_remove, name='comment_remove'),

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

    1. @login_required
    2. def comment_approve(request, pk):
    3. comment = get_object_or_404(Comment, pk=pk)
    4. comment.approve()
    5. return redirect('post_detail', pk=comment.post.pk)
    6. @login_required
    7. def comment_remove(request, pk):
    8. comment = get_object_or_404(Comment, pk=pk)
    9. comment.delete()
    10. return redirect('post_detail', pk=comment.post.pk)

    You’ll need to import Comment at the top of the file:

    1. from .models import Post, Comment

    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/post_list.html and change the line:

    1. <a href="{% url 'post_detail' pk=post.pk %}">Comments: {{ post.comments.count }}</a>

    to:

    1. return self.comments.filter(approved_comment=True)

    Now your comment feature is finished! Congrats! :-)