I\'m making my own blog with Django and I already made a Comments system.. I want to add the replies for each comment (like a normal comment\'s box) and I don\'t know what to do
I had the same problem and resolved it as follows:
1.
For admin site as mentioned above just set blank=True
for parent field. My comment model:
class Comment(models.Model):
post = models.ForeignKey(Post, related_name='comments')
name = models.CharField(max_length=80)
email = models.EmailField(max_length=200, blank=True)
body = models.TextField()
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
# manually deactivate inappropriate comments from admin site
active = models.BooleanField(default=True)
parent = models.ForeignKey('self', null=True, blank=True, related_name='replies')
class Meta:
# sort comments in chronological order by default
ordering = ('created',)
def __str__(self):
return 'Comment by {}'.format(self.name)
makemigrations
and migrate
2.Let's start with views. I'm using the post_detail
view to display the post and its comments. We add a QuerySet to retrieve all parent active comments for this post. After this, we validate the submitted data using the form's is_valid()
. If the form is valid we check if submitted data comes from hidden input in replay button form. Next if parent_id
exits we create parent object(parent_obj
) for replay comment and replay_comment
object, then we assign parent_obj
to replay_comment
.
If parent_obj
is equal to None
we just proceed with normal comment by creating new_comment
object and saving it to the database.
def post_detail(request, post):
# get post object
post = get_object_or_404(Post, slug=post)
# list of active parent comments
comments = post.comments.filter(active=True, parent__isnull=True)
if request.method == 'POST':
# comment has been added
comment_form = CommentForm(data=request.POST)
if comment_form.is_valid():
parent_obj = None
# get parent comment id from hidden input
try:
# id integer e.g. 15
parent_id = int(request.POST.get('parent_id'))
except:
parent_id = None
# if parent_id has been submitted get parent_obj id
if parent_id:
parent_obj = Comment.objects.get(id=parent_id)
# if parent object exist
if parent_obj:
# create replay comment object
replay_comment = comment_form.save(commit=False)
# assign parent_obj to replay comment
replay_comment.parent = parent_obj
# normal comment
# create comment object but do not save to database
new_comment = comment_form.save(commit=False)
# assign ship to the comment
new_comment.post = post
# save
new_comment.save()
return HttpResponseRedirect(post.get_absolute_url())
else:
comment_form = CommentForm()
return render(request,
'core/detail.html',
{'post': post,
'comments': comments,
'comment_form': comment_form})
Simple comment form:
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ('name', 'email', 'body')
* More about ModelForm
And lastly template. We need to create two forms. One form for comments and the second one for replays. Here you have simple template:
<!-- Comments Form -->
<h2>Add a new comment</h2>
<form action="." method="post">
{{ comment_form.as_p }}
{% csrf_token %}
<button type="submit">Add comment</button>
</form>
<!-- Comment with nested comments -->
{% for comment in comments %}
<div class="comment" style="background-color: powderblue">
<p class="info">{{ comment.name }} | {{ comment.created }}</p>
{{ comment.body|linebreaks }}
{% for replay in comment.replies.all %}
<p class="info">{{ replay.name }} | {{ replay.created }}</p>
<li>{{ replay.body }}</li>
{% endfor %}
<h5>Replay</h5>
<form action="." method="post">
{{ comment_form.as_p }}
{% csrf_token %}
<!-- Hidden input for parent comment.id -->
<input type="hidden" name="parent_id" value="{{ comment.id }}">
<input class="btn btn-primary" type="submit" value="Replay">
</form>
</div>
{% empty %}
<h4>There are no comments yet.</h4>
{% endfor %}
just add some nice css and maybe jquery to have fade in reply comments and that's all.
first Question:parent must be set in admin.
parent = models.ForeignKey('self', null=True, blank=True, related_name='replies')
blank=True
can let you don't set parent in admin.
second Question:add comment dynamicly.
<form id="comment-form" method="post" role="form">
{% csrf_token %}
<textarea id="comment" name="comment" class="form-control" rows="4" placeholder="input comment!"></textarea>
<button type="submit" class="btn btn-raised btn-primary pull-right">submit</button>
</form>
$('#comment-form').submit(function(){
$.ajax({
type:"POST",
url:"{% url 'article_comments' article.en_title %}",
data:{"comment":$("#comment").val()},
beforeSend:function(xhr){
xhr.setRequestHeader("X-CSRFToken", $.cookie('csrftoken'));
},
success:function(data,textStatus){
$("#comment").val("");
$(".comment ul").prepend(data);
},
error:function(XMLHttpRequest, textStatus, errorThrown){
alert(XMLHttpRequest.responseText);
}
});
return false;
});
view.py:
print_comment = u"<p>comment:{}</p>".format(text)
if parent:
print_comment = u"<div class=\"comment-quote\">\
<p>\
<a>@{}</a>\
{}\
</p>\
</div>".format(
parent.user.username,
parent.text
) + print_comment
# current comment
html = u"<li>\
<div class=\"comment-tx\">\
<img src={} width=\"40\"></img>\
</div>\
<div class=\"comment-content\">\
<a><h1>{}</h1></a>\
{}\
<p>{}</p>\
</div>\
</li>".format(
img,
comment.user.username,
print_comment,
datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
)
return HttpResponse(html)