I have a list of banking transactions that I'm listing. I'm currently using bootstrap for my HTML/CSS. I found some template code that works well enough for what I want to do, but I've run into a problem: there's no way to know the id of the collapsing div element for the parent element. It's easier to see the html than to explain:
{% for key, value in transactions.items %}
<div id="accordion">
<div class="card">
<div class="card-header" id="heading-1">
<h5 class="mb-0">
<a role="button" data-toggle="collapse" href="#whatid" aria-expanded="true" aria-controls="whatid">
{{ key }}
</a>
</h5>
</div>
{% for key2, value2 in value.items %}
<div id="whatid" class="collapse show" data-parent="#accordion" aria-labelledby="heading-1">
<div class="card-body">
<div id="accordion-1">
<div class="card">
<div class="card-header" id="heading-1-1">
<h5 class="mb-0">
<a class="collapsed" role="button" data-toggle="collapse" href="#collapse-1-1" aria-expanded="false" aria-controls="collapse-1-1">
{{ key2 }}
</a>
</h5>
</div>
{% for tr in value2 %}
<div id="collapse-1-1">{{ tr.description }}</div>
{% endfor %}
</div>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
{% endfor %}
The attributes href, id and most importantly aria-controls of the child div element can't be known ahead of time and therefore won't be correctly associated with the button click. Is it possible to do this? It's a bit of a chicken before the egg problem, so I don't really know what there is to do.
Related
I'm trying to make a for loop to build an accordion in Bootstrap, but every time I click the button to expand/ collapse a card, every card expands/ collapses. I think this is due to a shared id across all cards, so I want to use the post title as the id selector for each card. Here is the code I currently have:
{% for post in blog_posts %}
<div id="accordion">
<div class="card">
<h3 class="card-header" id="header">
<button class="btn btn-info" data-toggle="collapse"
data-target="#body" aria-expanded="false"
aria-controls="post: ">
{{ post.title }}
<small><p>Posted by {{ post.author }} on
{{ post.date_added|date:'D M d, Y H:i' }}</p></small>
</button>
</h3>
<div id="body" class="collapse show" aria-labelledby="header"
data-parent="#accordion">
<div class="card-body">
{{ post.text|linebreaks }}
<small>
<a class="text-dark" href="{% url 'blogs:edit_post' post.id %}">
edit post</a>
</small>
</div>
</div>
</div>
{% empty %}
<p>There are no posts to display.</p>
{% endfor %}
I've tried changing the h3 id to {{ post.title }}, but that doesn't seem to work. Any help is much appreciated.
Example code:
https://getbootstrap.com/docs/4.0/components/collapse/#accordion-example
You need the id's to be unique for each accordion section (otherwise they will all open at once), you could use the post.id for that.
{% if blog_posts %}
<div id="accordion">
{% for post in blog_posts %}
<div class="card">
<h3 class="card-header" id="header-{{post.id}}">
<button class="btn btn-info" data-toggle="collapse"
data-target="#post-{{post.id}}" aria-expanded="false"
aria-controls="post-{{post.id}}">
{{ post.title }}
<small><p>Posted by {{ post.author }} on
{{ post.date_added|date:'D M d, Y H:i' }}</p></small>
</button>
</h3>
<div id="post-{{post.id}}" class="collapse show" aria-labelledby="header-{{post.id}}"
data-parent="#accordion">
<div class="card-body">
{{ post.text|linebreaks }}
<small>
<a class="text-dark" href="{% url 'blogs:edit_post' post.id %}">
edit post</a>
</small>
</div>
</div>
{% endfor %}
</div>
{% else %}
<p>There are no posts to display.</p>
{% endif %}
So I am developing a website and for the life of me I can't figure out how to align the description, price, stock and add cart button in multiple versions of the same <div>. I know it is to do with the size of the image I am using but I'm not sure how to fix this.
Here is a diagram of how I want it to look:
But when I apply a 'h-100' class to the card <div> this is what happens:
I want the images to keep their positions but for the descriptions, add cart button and price/stock to all be horizontally aligned, as well as the height of the overall cards to be the same.
Here is my Django template code:
{% extends 'base.html' %}
{% block content %}
<div class="container-fluid">
<div class="jumbotron">
<h2>Welcome to MyTea</h4>
<p>Here we have teas of all varieties from all around the globe</p>
</div>
<div class="row">
<div class="col-sm-3">
<h4>Categories</h4>
<ul class="list-group">
All Categories
{% for c in countcat %}
<a href="{{ c.get_absolute_url }}" class="list-group-item catheight">{{c.name}}
<span class="badge badge-light">{{c.num_products}}</span>
</a>
{% endfor %}
</ul>
</div>
<div class="col-sm-9">
{% for product in products %}
{% if forloop.first %}<div class="row">{% endif %}
<div class="col-sm-6">
<div class="card border-primary mt-3 h-100">
<div class="card-header"><h3>{{product.name}}</h3></div>
<div class="card-body">
{% if product.image %}
<div class="h">
<img src="{{product.image.url}}" class="img-fluid">
</div>
{% endif %}
<p class="bg-light font-weight-light ">{{product.description}}</p>
{% if product.stock > 0 %}
<a href="{% url 'add_cart' product.id %}" type="button" class="btn btn-primary btn-sm mb-2">
<p class="m-0">Add to cart</p>
</a>
{% else %}
<a href="#" type="button "class="btn btn-danger btn-sm mb-2">
<p class="m-0">Out of stock</p>
</a>
{% endif %}
<div class="card-footer">
<p>Price: €{{product.price}}</p>
<p>Stock left: {{product.stock}}</p>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
{% endblock content %}
Thanks for any help
The code can be corrected with a simple re-alignment of the content inside .card and correctly closing </div> statements.
Remove {% if forloop.first %}<div class="row">{% endif %} statement and place <div class="row"> above the for loop.
Add to cart and Out of stock buttons should be placed inside .card-footer and .card-body should be closed appropriately. This will leave the image and description within .card-body
Make sure h-100 class is added to `.card'.
Might I suggest adding end of div comments to all the div statements. The code is readable better in this way and helps in mitigating missing or misplaced </div> statements.
{% extends 'base.html' %}
{% block content %}
<div class="container-fluid">
<div class="jumbotron">
<h2>Welcome to MyTea</h4>
<p>Here we have teas of all varieties from all around the globe</p>
</div>
<!-- .jumbotron -->
<div class="row">
<div class="col-sm-3">
<h4>Categories</h4>
<ul class="list-group">
All Categories
{% for c in countcat %}
<a href="{{ c.get_absolute_url }}" class="list-group-item catheight">{{c.name}}
<span class="badge badge-light">{{c.num_products}}</span>
</a>
{% endfor %}
</ul>
</div>
<!-- .col-sm-3 -->
<div class="col-sm-9">
<div class="row">
{% for product in products %}
<div class="col-sm-6">
<div class="card border-primary mt-3 h-100">
<div class="card-header">
<h3>{{product.name}}</h3>
</div>
<!-- .card-header -->
<div class="card-body">
{% if product.image %}
<div class="h">
<img src="{{product.image.url}}" class="img-fluid">
</div>
<!-- .h -->
{% endif %}
<p class="bg-light font-weight-light ">{{product.description}}</p>
</div>
<!-- .card-body -->
<div class="card-footer">
{% if product.stock > 0 %}
<a href="{% url 'add_cart' product.id %}" type="button" class="btn btn-primary btn-sm mb-2">
<p class="m-0">Add to cart</p>
</a>
{% else %}
<a href="#" type="button " class="btn btn-danger btn-sm mb-2">
<p class="m-0">Out of stock</p>
</a>
{% endif %}
<p>Price: €{{product.price}}</p>
<p>Stock left: {{product.stock}}</p>
</div>
<!-- .card-footer -->
</div>
<!-- .card -->
</div>
<!-- . col-sm-6 -->
{% endfor %}
</div>
<!-- .row -->
</div>
<!-- .col-sm-9 -->
</div>
<!-- .row -->
</div>
<!-- .container-fluid -->
{% endblock content %}
I am trying to render a block of HTML code repeatedly using for loop. But Django throws a TemplateSyntaxError when I reload my browser
<div class="carousel-item active">
{% for number in range(3) %}
<!--Slide {{ number + 1 }}-->
<div class="row">
{% for number in range(6) %}
<!--Slide 1 Col {{ number + 1 }}-->
<div class="col-lg-2">
<div class="card" style="width: 100%;">
<img class="card-img-top" src="..." alt="Card image cap">
<div class="card-body">
<h5 class="card-title">Card title</h5>
<p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
Go somewhere
</div>
</div>
</div>
{% endfor %}
</div>
{% endfor %}
</div>
I expected a repeated rendering of the blocks inside the for loop but got "TemplateSyntaxError at /
Could not parse the remainder: '(3)' from 'range(3)'" instead
Django does not allow to make function calls (with parameters) as well as subscripting in the templates. The rationale is that business logic should not be part of the template. You thus can pass the range(3) and range(6) object through the context to the template. The {{ number + 1 }} will not work either, since such operators are not supported either.
An alternative is using Jinja, which is a template engine that does allow to use such Python syntax in the templates.
Since the numbers are quite small, a third options is to use string literals instead:
<div class="carousel-item active">
{% for row in '123' %}
<!--Slide {{ row }}-->
<div class="row">
{% for col in '123456' %}
<!--Slide 1 Col {{ col }}-->
<div class="col-lg-2">
<div class="card" style="width: 100%;">
<img class="card-img-top" src="..." alt="Card image cap">
<div class="card-body">
<h5 class="card-title">Card title</h5>
<p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
Go somewhere
</div>
</div>
</div>
{% endfor %}
</div>
{% endfor %}
</div>
What It Looks Like Now(Image)
What I would like if for the episodes to append under each season. There are 7 lists in the episodes list, and 7 seasons in total, right now the whole episode list is printing into each season (can be seen in the picture provided), but I would like each list within the episode list of go under each season. Right now there are two episodes for each season so I would like there to be two episodes under each season, instead of all episodes under each season. This is done using the Bootstrap accordion. Thanks
<div id="accordion" role="tablist">
{% for season in tvdata[currentSeries]["seasons"] %}
<div class="card">
<div class="card-header" role="tab" id="heading">
<h5 class="mb-0">
<a data-toggle="collapse" href="#collapse" role="button" aria-expanded="false" aria-controls="collapse">
<p>Season {{ season }}</p>
</a>
</h5>
</div>
{% for season in tvdata[currentSeries]["episodes"] %}
{% for items in season %}
<div id="collapse" class="collapse show" role="tabpanel" aria-labelledby="heading" data-parent="#accordion">
<div class="card-body">
{{ items }}
</div>
</div>
{% endfor %}
{% endfor %}
</div>
{% endfor %}
</div>
JSON FILE:
"seasons": [1, 2, 3, 4, 5, 6, 7],
"episodes": [ ["S1-ep1","S1-ep2"],
["S2-ep1","S2-ep2"],
["S3-ep1","S3-ep2"],
["S4-ep1","S4-ep2"],
["S5-ep1","S5-ep2"],
["S6-ep1","S6-ep2"],
["S7-ep1","S7-ep2"]]
}
ISSUE Fixed (Thanks To Below):
<div id="collapse" class="collapse show" role="tabpanel" aria-labelledby="heading" data-parent="#accordion">
<div class="card-body">
{% for seasoneps in tvdata[currentSeries]["episodes"][season-1] %}
{% for eps in seasoneps %}
{{ eps }}
{% endfor %}
{% endfor %}
</div>
</div>
I havent used jinja - but used some knockout templating as well as looked into mustache and asp.net razor views - yours feels off to me:
You use two for-loops with the same variable seasons with the inner one overwriting the outer ones value - just guesstemating.
Please try:
<div id="accordion" role="tablist">
{% for season in tvdata[currentSeries]["seasons"] %}
<div class="card">
<div class="card-header" role="tab" id="heading">
<h5 class="mb-0">
<a data-toggle="collapse" href="#collapse" role="button" aria-expanded="false" aria-controls="collapse">
<p>Season {{ season }}</p>
</a>
</h5>
</div>
{% for items in tvdata[currentSeries]["episodes"][season-1] %}
<div id="collapse" class="collapse show" role="tabpanel" aria-labelledby="heading" data-parent="#accordion">
<div class="card-body">
{{ items }}
</div>
</div>
{% endfor %}
</div>
{% endfor %}
</div>
I am unsure about the [season-1] - part - might be you need to use [{{season}}-1] or whatever syntax lets you select the part of the json thats specific to that season (the 2 items inner list)
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>Edit</li>
<li role="separator" class="divider"></li>
<li>
Delete
</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?
As emulbreh postulates, a modal is in fact rendered for every comment. However, all of the modals have the same ID, so regardless of which comment’s edit button was clicked, the first modal gets triggered every time. IDs are supposed to be unique across an HTML document.
How can you fix this? You can make the IDs of the modals unique to each comment. You can get a unique identifier by writing id="editModal-{{ comment.id }}" or just id="editModal-{{ forloop.counter }} (documentation here).
But then your editModal.html template is coupled very tightly with your ‘master’ template. A better solution would be to use classes instead of IDs and put the identification where it belongs: the container of each comment. You can try:
adding an ID to each comment’s container:
<div class="panel panel-default">
{% for comment in spot.ordered_comments %}
<div class="panel-heading row" id="comment-{{ comment.id }}">
...
using classes instead of IDs in your modal templates as so:
<!-- editmodal.html -->
{% load static %}
<div class="modal fade editModal" tabindex="-1" ...>
...
changing data-target in your buttons from:
<li>Edit</li>
to:
<li>Edit</li>
It looks like all your edit modals will have the same id id="editModal" as well as your reply modals id="replyModal" if you are showing them based on id probably you will always open the first DOM element with that id. You could try appending a unique identifier like forloop.counter
https://docs.djangoproject.com/en/1.9/ref/templates/builtins/#for