I have a comments section on some pages on my site that I build with a {% for ... %}
loop (and another nested loop for comment replies. The section was hacked together, and I am still learning web development and Django, so please forgive any frustrating sloppiness or weirdness. I am not concerned with efficiency at the moment, only efficacy, and right now it is not working quite right.
For each comment I have a Bootstrap dropdown button that will bring up the options Edit
and Delete
. Edit
will open a modal to edit the comment. The modals are rendered with an {% include %}
tag. Below I have included part of my code unmodified, rather than trying to simplify my example and risk leaving something crucial out:
<div class="panel panel-default">
{% for comment in spot.ordered_comments %}
<div class="panel-heading row">
<div class="col-sm-10">
<strong>{{ comment.poster.username }}</strong>
<em style="margin-left: 2em">{{ comment.created|date:'M d \'y \a\t H:i' }}</em>
</div>
<div class="btn-group col-sm-2" role="group">
{% if comment.poster == user %}
<form id="delete-comment-form" class="form"
method="post" action="{% url 'delete_comment' spot.id comment.id %}">
{% csrf_token %}
</form>
{% include 'topspots/editmodal.html' with edit_type='comment' %}
<div class="btn-group">
<button type="button" class="btn btn-default dropdown-toggle"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="fa fa-edit"></i> <span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li><a href="#" data-toggle="modal" data-target="#editModal">Edit</a></li>
<li role="separator" class="divider"></li>
<li>
<a href="javascript:;" onclick="$('#delete-comment-form').submit();">Delete</a>
</li>
</ul>
</div>
{% endif %}
{% if user.is_authenticated %}
{% include 'topspots/replymodal.html' %}
<button type="button" class="btn btn-default" data-toggle="modal"
data-target="#replyModal">
Reply
</button>
{% endif %}
</div>
</div>
<div class="panel-body">
<div class ="row">
<div class="col-sm-8">
{{ comment.comment_text }}
</div>
</div>
<br/>
<!-- Comment replies -->
{% if comment.commentreply_set %}
{% for reply in comment.commentreply_set.all %}
<div class="row" style="padding-left: 1em">
<div class="col-sm-8 well">
<p>{{ reply.reply_text }}</p>
<div class="row">
<div class="col-sm-4">
<p>
<strong>{{ reply.poster.username }}</strong>
<em style="margin-left: 2em">{{ comment.created|date:'M d \'y \a\t H:i' }}</em>
</p>
</div>
{% if reply.poster == user %}
{% include 'topspots/editmodal.html' with edit_type='reply' %}
<form id="delete-reply-form" class="form"
method="post" action="{% url 'delete_reply' spot.id reply.id %}">
{% csrf_token %}
</form>
<div class="col-sm-2">
<div class="btn-group">
<button type="button" class="btn btn-default dropdown-toggle"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="fa fa-edit"></i> <span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li><a href="#" data-toggle="modal"
data-target="#editModal">Edit</a></li>
<li role="separator" class="divider"></li>
<li>
<a href="javascript:;"
onclick="$('#delete-reply-form').submit();">Delete</a>
</li>
</ul>
</div>
</div>
{% endif %}
</div>
</div>
</div>
{% endfor %}
{% endif %}
</div>
{% endfor %}
</div>
Here is the edit modal:
<!-- editmodal.html -->
{% load static %}
<div class="modal fade" id="editModal" tabindex="-1" role="dialog" aria-labelledby="editModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span></button>
<h2 class="modal-title" id="editModalLabel">
Edit {{ edit_type }}:
</h2>
</div>
<form action="{% url 'edit_comment' spot.id comment.id %}" method="post">
<div class="modal-body">
<input class="form-control" name="text" value="{{ comment.comment_text }}" autofocus>
<input type="hidden" name="edit_type" value="{{ edit_type }}">
{% csrf_token %}
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-default">Finish editing</button>
</div>
</form>
</div>
</div>
</div>
<script>
$('.modal').on('shown.bs.modal', function() {
$(this).find('[autofocus]').focus();
});
</script>
and the reply modal:
<!-- replymodal.html -->
{% load static %}
<div class="modal fade" id="replyModal" tabindex="-1" role="dialog" aria-labelledby="replyModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span></button>
<h2 class="modal-title" id="replyModaLabel">
Reply to <strong>{{ comment.poster.username }}'s</strong> comment
</h2>
</div>
<div class="modal-body">
<form action="{% url 'reply_comment' spot.id comment.id %}" method="post">
<input class="form-control" name="reply_text" placeholder="Write a reply..." autofocus>
{% csrf_token %}
</form>
</div>
</div>
</div>
</div>
<script>
$('.modal').on('shown.bs.modal', function() {
$(this).find('[autofocus]').focus();
});
</script>
The issue I am having is that my reply and edit modals (e.g. {% include 'topspots/editmodal.html' with edit_type='reply' %}
or {% include 'topspots/replymodal.html' %}
seem to be only rendered once with the context of the first iteration of my for loop. So even though all the questions are correctly rendered on the page, when I click reply, edit or delete, regardless of which button I click (i.e., whether I click the button for the first comment, or the fifth comment, etc.) I can only reply to, edit, or delete the very first comment. I have a feeling that this has something to do with closures and scope in a way I am not quite understanding (I have gotten into trouble in the past with unexpected results using lambda
in Python loops because of this or this), but I am not sure.
I did a test with the following view:
def test(request):
spots = Spot.objects.all()
return render(request, 'test.html', {'spots': spots})
and templates:
<!-- test.html -->
<h1>Hello world</h1>
{% for spot in spots %}
{% include 'testinclude.html' %}
{% endfor %}
and
<!-- testinclude.html -->
<h3>{{ spot.name }}</h3>
And it printed out a list of unique spot names, so why the difference with the modals?