How to properly create recursive comments in templates django?

Question:

I want to create a tree like comment section in my site, but I can’t handle to work it as expected. I just ended up with a mess there, but it seems to me that model works perfectly.. Maybe I am wrong, but I assume there is a template logic problems. I’ve attached a screenshot of how it looks now. As you can see, comments are duplicating each other and do not construct a tree like stuff. Can somebody point me in the right direction?

Views.py

It is the main View that rendering social network profile page.

class ProfileDetail(DetailView):
    context_object_name = 'profile'
    template_name = 'user_detail.html'

    def get(self, request, *args, **kwargs):
        user_profile = User.objects.get(slug=kwargs.get('slug'))
        settings = Settings.objects.get(user=user_profile.id)
        posts = Post.objects.filter(author=user_profile.id).order_by('-post_date')
        subscriptions = Subscription.objects.filter(author=user_profile.id).order_by('price')
        comment_form = CommentForm()
        return render(request, self.template_name,
                      context={
                          'profile': user_profile,
                          'posts': posts,
                          'settings': settings,
                          'subscriptions': subscriptions,
                          'form': comment_form
                      })

    def post(self, request, *args, **kwargs):
        user_profile = User.objects.get(slug=kwargs.get('slug'))

        if 'subscruption_action' in request.POST:
            action = request.POST['subscruption_action']
            if action == 'unfollow':
                request.user.follows.remove(user_profile.id)
            else:
                request.user.follows.add(user_profile.id)
            request.user.save()
            return HttpResponseRedirect(reverse('profile_detail', kwargs={'slug': user_profile.slug}))


    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        self.object = self.get_object()
        context['posts'] = Post.objects.filter(author=self.object.id)
        context['settings'] = Settings.objects.get(user=self.object.id)
        return context

View func that register new comment:

def add_comment(request, slug, pk):
    form = CommentForm(request.POST)
    post = get_object_or_404(Post, pk=pk)
    if form.is_valid():
        cleaned_data = form.cleaned_data
        try:
            answered_comment = Comment.objects.get(pk=cleaned_data.get('parent_comment'))
        except ObjectDoesNotExist:
            new_comment = Comment(
                user=request.user,
                post=post,
                body=cleaned_data.get('body'),
            )
            new_comment.save()
        else:
            new_comment = Comment(
                user=request.user,
                post=post,
                body=cleaned_data.get('body'),
                comment_to_reply=answered_comment
            )
            new_comment.save()
    else:
        messages.error(request, 'Form is not valid!')
    return redirect('index')

Models.py

class Comment(models.Model):
    user = models.ForeignKey(User, related_name='user', on_delete=models.CASCADE)
    post = models.ForeignKey(Post, related_name='comments', on_delete=models.CASCADE)
    body = models.TextField(max_length=255)
    comment_to_reply = models.ForeignKey("self", on_delete=models.CASCADE, null=True, blank=True, related_name='replies')
    created_at = models.DateTimeField(auto_now_add=True)

comments_template.html

{% if post.comments %}
    {% for comment in post.comments.all %}
        <a name="comment-{{ comment.id }}"></a>
        <div class="row" id="{{ comment.id }}">
            <div class="shadow-sm mb-3 card border-0 p-3 bg-light">
                <div class="panel-heading">
                    <strong>{{ comment.user.username }}</strong>
                    <a href="#comment-{{ comment.id }}"></a>
                    <small class="text-muted">{{ comment.created_at }}</small>
                </div>
                {% if form %}
                <div class="row">
                    <div class="col-10">{{ comment.body }}</div>
                    <div class="col text-end">
                        <a class="btn btn-primary btn-sm" onclick="return show_comments_form({{ comment.id }})">Reply</a>
                    </div>
                </div>
                {% else %}
                <div>{{ comment.body }}</div>
                {% endif %}
            </div>
        </div>
        {% if comment.replies %}
            <div class="offset-md-3">
            {% for comment in comment.replies.all %}
            <div class="row">
                <div class="col offset-md-1">
                    <div class="shadow-sm mb-3 border-0 p-3 bg-light" id="{{ comment.id }}">
                        {% if form %}
                            <div class="row">
                                <strong><a href="{% url 'profile_detail' comment.user.slug %}">{{ comment.user.username }}</a></strong>
                                <small class="text-muted">{{ comment.created_at }}</small>
                                <p>{{ comment.body }}</p>
                                <div class="col">
                                    <small class="text-muted">Reply to - {{ comment.comment_to_reply.user }}</small>
                                </div>
                                <div class="col-2 text-end">
                                    <a class="btn btn-primary btn-sm" onclick="return show_comments_form({{ comment.id }})">Reply</a>
                                </div>
                            </div>
                        {% else %}
                            <strong><a href="{% url 'profile_detail' reply.user.slug %}">{{ comment.user.username }}</a></strong>
                            <small class="text-muted">{{ reply.created_at }}</small>
                            <p>{{ reply.body }}</p>
                            <small class="text-muted">Reply to - {{ comment.comment_to_reply.user }}</small>
                        {% endif %}
                    </div>
                </div>
            </div>
        {% endfor %}
            </div>
        {% endif %}
    {% endfor %}

how it looks now

Asked By: jmur

||

Answers:

You could structure a recursive template in the following manner:
(This is just a bare metal example to illustrate the concept)

comments_template.html – render all comments under a post

{% if post.comments %}
    {% for comment in post.comments.all %}
        {% include 'recursive_comment.html' with comment=comment %}
    {% endfor %}
{% endif %}

recursive_comment.html – render a comment and include its replies (if any)

{% comment %} insert comment markup here {% endcomment %}

{% if comment.replies %}
    {% for reply in comment.replies.all %}
        {% include 'recursive_comment.html' with comment=reply %}
    {% endfor %}
{% endif %}
Answered By: Fractalism