jinja2 template takes over 10 secs to render, how to optimize? - flask

In my flask web app, there is a route that display either all the member with their first name starting with 'A, B, C, etc..' or the Show All button that shows all the members registered . Right now it sum up to about 750 users. The problem is that displaying the 'show all' list takes over 10 seconds. The request to the DB itself is fast, it's the rendering that takes all those seconds. I was wondering if there is a way to speed this up? I'm pretty new to python, flask and jinja2 so I don't know the optimiztion paths yet.
Here is the views.py, when I click the 'Show all" button on the webpage, it calls the url with the letter argument set to '0':
#main.route('/all-members')
#login_required
#admin_required
def all_members():
alphabet = list(string.ascii_lowercase)
first_letter = request.args.get('letter')
if first_letter == '0':
user_ = User.query.order_by(User.fullname.asc()).all()
else:
user_ = User.query.filter(User.username.startswith(first_letter)).order_by(User.fullname.asc()).all()
return render_template('all_members.html', all_members = user_, alphabet = alphabet)
It return 750 user objects. I then pass that result to the template:
return render_template('all_members.html', all_members = user_, alphabet = alphabet)
And the template:
{% extends "base.html" %}
{% block title %}Zata - members list{% endblock %}
{% block page_content %}
<div class="page-header" xmlns="http://www.w3.org/1999/html">
<h1>Tribe members list</h1>
<p></p>
{% for letter in alphabet %}
<a class="btn btn-success" href="{{ url_for('.all_members', letter = letter) }}">{{ letter }}</a>
{% endfor %}
<a class="btn btn-info" href="{{ url_for('.all_members', letter = 0) }}">Show All</a>
</div>
{% for member in all_members %}
<div class="row">
<p>
<div class="col-md-2">
{{ member.fullname }}
</div>
<div class="col-md-1">
<a class="btn btn-warning btn-xs" href="{{ url_for('.edit_profile_admin', id=member.id) }}"> Edit Profile</a>
</div>
<div class="col-md-3">
<a class="btn btn-info btn-xs" href="{{ url_for('.add_punchcard', id=member.id, next=request.url) }}"> Manage Punchcard</a>
{% if member.punchcard_gold %}<span class="btn btn-xs" style="background-color:gold">G:{{ member.punchcard_gold }}</span>{% endif %}
{% if member.punchcard_normal %}<span class="btn btn-xs" style="background-color:#c0c0c0">R:{{ member.punchcard_normal }}</span>{% endif %}
</div>
<div class="col-md-4">
<a class="btn btn-info btn-xs" href="{{ url_for('.add_seasonpass', id=member.id, next=request.url) }}"> Manage Season pass</a>
{% if member.season_pass_gold %}<span class="btn btn-xs" style="background-color:gold">G:{{ member.season_pass_gold }}</span>{% endif %}
{% if member.season_pass_normal %}<span class="btn btn-xs" style="background-color:#c0c0c0">R:{{ member.season_pass_normal }}</span>{% endif %}
</div>
<div class="col-md-2">
<span class="btn btn-xs" style="background-color:yellowgreen">Class attended: {{ member.class_attended|length }}
</div>
</p>
</div>
{% endfor %}
{% endblock %}
The slow part is obviously the {% for member in all_members %} but is there another way to go through the list of user and build the page?

The problem isn't with your Jinja template but your Python code. You're returning all the records at the same time which slows down the execution. You need to perform some sort of pagination/lazy loading. If you're using Flask-SQLAlchemy, then it has a paginate method which will do all the work for you. For example, your query will look something like this
user_ = User.query.order_by(User.fullname.asc()).paginate(1, 10, True)
You can find more information here

Related

How can I reset a forloop.counter0 in django?

I have a code in django that creates a carousel for each card item. As I am looping through each image for each specific card, I realized the forloop.counter will keep continuing until the initial for loop has finished. So in my example, i is my initial loop and p is my secondary loop. i loops through cards and p loops through images within the carousel of each card. I need to reset the counter after one i so that the carousel counter starts from the beginning so that first image of every carousel is active.
I'd really appreciate any help
{% for i in inspections %}
<div class="card - mb-3" style="width: 40 rem;">
<div id="myCarousel" class="carousel slide" data-bs-ride="carousel">
<div class="carousel-indicators">
{% for p in photos.all %}
{% if p.inspection_id == i.id %}
<button type="button" data-bs-target="#myCarousel"
data-bs-slide-to="{{ forloop.counter0 }}"
class="{% if forloop.counter0 == 0 %} active {% endif %}"
aria-current="true"
aria-label="Slide {{forloop.counter0}}"></button>
{% endif %}
{% endfor %}
</div>
<div class="carousel-inner">
{% for p in photos.all %}
{% if p.inspection_id == i.id %}
<div class="carousel-item {% if forloop.counter0 == 0 %} active {% endif %}">
<img src="{{ p.InspImages.url }}"
class="img-responsive mx-auto d-block w-80" height="300"
alt="...">
</div>
{% endif %}
{% endfor %}
</div>
<button class="carousel-control-prev" type="button"
data-bs-target="#myCarousel" data-bs-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="True"></span>
<span class="visually-hidden">Previous</span>
</button>
<button class="carousel-control-next" type="button"
data-bs-target="#myCarousel" data-bs-slide="next">
<span class="carousel-control-next-icon" aria-hidden="True"></span>
<span class="visually-hidden">Next</span>
</button>
</div>
<div class="card-body">
# body codes #
{% endfor %}

Image ids apparently not being created

I have created a template for displaying a photo gallery and giving users the ability to add photos to that gallery:
{% extends 'nowandthen/base.html' %}
{% block body_block %}
<br>
<br>
{% if pictures %}
<ul>
{% for p in pictures %}
<div class="container">
<div class="row">
<div class="col-md-8 card mb-4 mt-3 ">
<!-- Card -->
<!-- Card content -->
<div class="card-body d-flex flex-row">
<!-- Content -->
<div>
<!-- Title -->
<h4 class="card-title font-weight-bold mb-2">{{ p.title }}</h4>
<!-- Subtitle -->
<p class="card-text"><i class="far fa-clock pr-2"></i>{{ p.when_added }}</p>
</div>
</div>
<!-- Card image -->
<div class="view overlay">
<img class="card-img-top rounded-0" src="{{ p.image.url }}" alt="Card image cap">
<a href="#!">
<div class="mask rgba-white-slight"></div>
</a>
</div>
<!-- Card content -->
<div class="card-body">
<div class="collapse-content">
<!-- Text -->
<p class="card-text collapse" id="collapseContent">{{ p.description }}</p>
<!-- Button -->
<a class="btn btn-flat red-text p-1 my-1 mr-0 mml-1 collapsed" data-toggle="collapse" href="#collapseContent" aria-expanded="false" aria-controls="collapseContent">Click for description</a>
<i class="fas fa-share-alt text-muted float-right p-1 my-1" data-toggle="tooltip" data-placement="top" title="Share this post"></i>
<i class="fas fa-heart text-muted float-right p-1 my-1 mr-3" data-toggle="tooltip" data-placement="top" title="I like it"></i>
</div>
</div>
<div class="card-body">
<!-- comments -->
<h2>comments</h2>
{% if not p.comments %}
No comments
{% endif %}
{% for x in p.comment %}
<div class="comments" style="padding: 10px;">
<p class="font-weight-bold">
<h4>Comment by</h4> {{ x.user }}
<span class=" text-muted font-weight-normal">
{{ x.created_on }}
</span>
</p>
{{ x.body | linebreaks }}
</div>
{% endfor %}
</div>
<div class="card-body">
{% if new_comment %}
<h2>Your comment has been posted.</h2>
{% else %}
<h3>Leave a comment</h3>
<form action="{% url 'nowandthen:add_comment' p.image_id %}" method="POST">
{{ comment_form.as_p }}
{% csrf_token %}
<button type="submit" class="btn btn-primary btn-lg">Submit</button>
{% endif %}
</div>
</div>
</div>
</div>
<!-- Card -->
{% endfor %}
</ul>
{% else %}
<li><strong>There are no photographs present.</strong></li>
{% endif %}
{% endblock %}
The idea is that there is an image associated with each comment - there is a for loop of {% for p in pictures %} at the start of the page, and I use variations of p. (e.g. p.image_id) to associate particular pictures with particular comments.
In addition, the urls.py contains the following:
path('add_comment/<int:p.image_id>', views.add_comment, name='add_comment')
However, when I run the code, I get an error message that suggests that image ids aren't being created (even though image is a field in he Pictures model I created):
Reverse for 'add_comment' with arguments '('',)' not found. 1 pattern(s) tried: ['add_comment/$']
What do you suggest, please?
EDIT: This is my view:
#login_required
def add_comment(request, image_id):
new_comment = None
template_name = 'add_comment.html'
image = get_object_or_404(Picture, id=image_id)
comment = image.comments.filter(active=True)
new_comment = None
# Comment posted
if request.method == 'POST':
comment_form = CommentForm(data=request.POST)
if comment_form.is_valid():
# Create Comment object and don't save to database yet
new_comment = comment_form.save(commit=False)
# Assign the current post to the comment
new_comment.post = post
# Save the comment to the database
new_comment.save()
else:
comment_form = CommentForm()
context = {'image': image,'comment': comment, 'new_comment': new_comment,'comment_form': comment_form}
return render(request, template_name, context)
And this is my add_comment.html template:
{% extends 'nowandthen/base.html' %}
{% load staticfiles %}
{% block title_block %}
Add self
{% endblock %}
{% block body_block %}
<h1>Add a Comment</h1>
<div>
<form id="comment_form" method="post" action="{% url 'nowandthen:add_comment' comment_id%}" enctype="multipart/form-data" >
{% csrf_token %}
{{ form.as_p }}
<input type="submit" name="submit" value="Add Comment" />
</form>
</div>
{% endblock %}
Since you pictures variable is a Queryset, when you loop through it in your template, to access its ID, you just need to do it that way. instance.id
Based on your case, you will have:
{% url 'nowandthen:add_comment' p.id %}
And you will be able to access the id in your view with image_id

Django page navigation- home list not visible

I am super new to Dango; pardon me for a rookie question :)
I am learning Django by creating a "Note taking app". This is how the application home page looks.
When I click on any of the notes from the note list, it opens the details on the right-side page. But the problem is it wipes-out the left-hand side note list. To reload the list I need to click on the Home link again. The expected behavior is, it should retain the note-list on the left side as well as show the details on the right frame.
urls.py
from django.urls import path
from .views import NoteListView, NoteDetailView, NoteCreateView, NoteUpdateView, NoteDeleteView
from . import views
urlpatterns = [
path('', NoteListView.as_view(), name='lekha-home'),
path('note/<int:pk>/', NoteDetailView.as_view(), name='note-detail'),
path('note/new/', NoteCreateView.as_view(), name='note-create'),
path('note/<int:pk>/update', NoteUpdateView.as_view(), name='note-update'),
path('note/<int:pk>/delete', NoteDeleteView.as_view(), name='note-delete'),
path('about/', views.about, name='lekha-about'),
]
enter code here
views.py
class NoteListView(ListView):
model = Note
template_name = 'lekha/home.html'
context_object_name = 'notes'
ordering = ['-date_created']
class NoteDetailView(DetailView):
model = Note
# success_url = 'lekha/note_detail.html'
class NoteCreateView(LoginRequiredMixin, CreateView):
model = Note
fields = ['title', 'content']
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
home.html
{% extends "lekha/base.html" %}
{% block content %}
{% for note in notes %}
<div class="list-group">
{{ note.title }}
</div>
{% endfor %}
{% endblock content %}
note_detail.html
{% extends "lekha/base.html" %}
{% block content2 %}
<article class="media content-section">
<div class="media-body">
<div class="article-metadata">
<a class="mr-8" href="#">{{ object.author }}</a>
<small class="text-muted">{{ object.date_created|date:"F d, Y" }}</small>
{% if object.author == user %}
<a class="btn float-right btn-secondary ml-1 btn-sm" href="{% url 'note-update' object.id %} ">Update</a>
<a class="btn float-right btn-danger ml-1 btn-sm" href="{% url 'note-delete' object.id %} ">Delete</a>
{% endif %}
</div>
<h4 class="article-title">{{ object.title }}</h4>
<hr>
<p class="article-content">{{ object.content }}</p>
</div>
</article>
{% endblock content2 %}
And this is how I am calling it in base.html
<main role="main" class="container-fluid px-2">
<div class="row">
<div class="col-md-3">
<div class="content-section">
<h4>Notes</h4>
{% block content %}{% endblock %}
</div>
</div>
<div class="col-md-8">
{% block content2 %}{% endblock %}
</div>
</div>
</main>
Sorry for the detailed post. I would appreciate any pointers. Thanks!
Welcome to Django!
Your template note_detail.html extends base.html, which doesn't contain the HTML for the list of notes, and note_detail.html doesn't add the list, so that's why it's not showing up - you haven't added it!
To do this, you need the same {% block content %} from home.html in note_detail.html, and you also need to manually pass a notes context variable to the template. You get that for free with the ListView class.
First, the change to the template:
note_detail.html
{% extends "lekha/base.html" %}
{% block content %}
{% for note in notes %}
<div class="list-group">
{{ note.title }}
</div>
{% endfor %}
{% endblock content %}
{% block content2 %}
<article class="media content-section">
<div class="media-body">
<div class="article-metadata">
<a class="mr-8" href="#">{{ object.author }}</a>
<small class="text-muted">{{ object.date_created|date:"F d, Y" }}</small>
{% if object.author == user %}
<a class="btn float-right btn-secondary ml-1 btn-sm" href="{% url 'note-update' object.id %} ">Update</a>
<a class="btn float-right btn-danger ml-1 btn-sm" href="{% url 'note-delete' object.id %} ">Delete</a>
{% endif %}
</div>
<h4 class="article-title">{{ object.title }}</h4>
<hr>
<p class="article-content">{{ object.content }}</p>
</div>
</article>
{% endblock content2 %}
And the change to the view:
views.py
class NoteListView(ListView):
model = Note
template_name = 'lekha/home.html'
context_object_name = 'notes'
ordering = ['-date_created']
class NoteDetailView(DetailView):
model = Note
def get_context_data(self):
data = super().get_context_data
data['notes'] = Note.objects.all().order_by('-date_created')
One last tip: to keep your HTML templates "DRY," you should really extract the list of notes into a separate html template (often called a partial) that you can plug into multiple other templates. Your template setup would look like this:
partials/all_notes.html
{% for note in notes %}
<div class="list-group">
{{ note.title }}
</div>
{% endfor %}
home.html
{% extends "lekha/base.html" %}
{% block content %}
{% include 'lekha/partials/all_notes.html' %}
{% endblock content %}
note_detail.html
{% extends "lekha/base.html" %}
{% block content %}
{% include 'lekha/partials/all_notes.html' %}
{% endblock content %}
{% block content2 %}
<article class="media content-section">
<div class="media-body">
<div class="article-metadata">
<a class="mr-8" href="#">{{ object.author }}</a>
<small class="text-muted">{{ object.date_created|date:"F d, Y" }}</small>
{% if object.author == user %}
<a class="btn float-right btn-secondary ml-1 btn-sm" href="{% url 'note-update' object.id %} ">Update</a>
<a class="btn float-right btn-danger ml-1 btn-sm" href="{% url 'note-delete' object.id %} ">Delete</a>
{% endif %}
</div>
<h4 class="article-title">{{ object.title }}</h4>
<hr>
<p class="article-content">{{ object.content }}</p>
</div>
</article>
{% endblock content2 %}
base.html
<main role="main" class="container-fluid px-2">
<div class="row">
<div class="col-md-3">
<div class="content-section">
<h4>Notes</h4>
{% block content %}{% endblock %}
</div>
</div>
<div class="col-md-8">
{% block content2 %}{% endblock %}
</div>
</div>
</main>

Django - Displaying a user list takes over 14 seconds, how to speed it up?

I have a view that will display members based on the letter someone select(ex: clicking L on the webpage will display all the members whose firstname start with L, ordered by lastname)
The problem is that it can take over 14 seconds to display the page(like"M", around 180 members to list, 17 seconds or so to do so.) . The SQL query doesn't seems the problem since Debug Toolbar reports that it takes less than a second for the query.
Here is the view:
def show_all_members(request, letter):
members = MyUsers.objects.filter(firstname__istartswith=letter).order_by('lastname')
alphabet = list(string.ascii_lowercase)
request.session['url'] = request.get_full_path()
context_dict = {'all_members': members, 'alphabet': alphabet}
return render(request, "users/show_all_members.html", context_dict)
Programming is a hobby so I'm a bit lost on what is happening here and how to optimize it. Any help and pointer is appreciated.
Using Django 2.1.1
Edit: Here is the template. It shows if the member has bought punchcards or access passes and also a link to his profile.
{% extends "base.html" %}
{% load staticfiles %}
{% load i18n %}
{% block title %}Members{% endblock %}
{% block content %}
<div class="container">
<div class="page-header" xmlns="http://www.w3.org/1999/html">
<h2>Tribe members list</h2>
{% for l in alphabet %}
<a href="{% url 'users:show_all_members' l %}" class="btn btn-success">
{{ l }}
</a>
{% endfor %}
</div>
<br>
{% for user in all_members %}
<div class="row">
<p>
<div class="col-md-2">
<a href="{% url 'users:member_info' user.id %}" >
<h7>{{ user.fullname }}</h7>
</a>
</div>
<div class="col-md-1">
<a class="btn btn-warning btn-sm" href="{% url 'users:edit_profile_full' user.id %}"> Edit Profile</a>
</div>
<div class="col-md-6">
<a class="btn btn-success btn-sm" href="{% url 'classes:buy_pass' user.id %}"> Current PC/SP</a>
{%if user.punchcardbyuser_set.all %}
{%for pc in user.punchcardbyuser_set.all %}
<span class="badge badge-primary" >{{ pc.cardclasstype.name }}:{{ pc.classes }}</span>
{%endfor%}
{% endif %}
{%if user.allaccesspassbyuser_set.all %}
{%for aap in user.allaccesspassbyuser_set.all %}
<span class="badge badge-danger">AAP:{{ aap.classes }}</span>
{%endfor%}
{% endif %}
{%if user.seasonpass_set.all %}
/
{%for sp in user.seasonpass_set.all %}
<span class="badge badge-primary">{{ sp.label }}</span>
{%endfor%}
{% endif %}
</div>
<div class="col-md-3">
<a class="btn btn-success btn-sm" href="{% url 'classes:buy_pass' user.id %}"> Buy passes</a>
<a class="btn btn-primary btn-sm" href="{% url 'users:detail' user.username %}">Class attended: {{user.classes_set.all|length}}</a>
</div>
</p>
</div>
{% endfor %}
</div>
{% endblock content %}
Thanks to #Willem Van Onsem tips, I read on Django N+1 and it was indeed the problem. I had 747 queries in total...(!) (The page does display alot of informations from different M2M relations.) By reading on "prefetch_related" and some fiddling, it is now down to 7 and the page now loads in 1.2 seconds max. I'm sure I could get it lower by learning more about optimizing queries.
members = MyUsers.objects.filter(firstname__istartswith=letter).order_by('lastname').prefetch_related("punchcardbyuser_set").prefetch_related("classes_set").\
prefetch_related("allaccesspassbyuser_set").prefetch_related("seasonpass_set")
Thanks!

Django with Bootstrap 2nd Modal (or subsequent modals) not working

Using the Django for loop It am generating a paginated list from the database. This works. The generated list has a delete button to the right of each entry.
At the same page load I an using the the django for loop to generate individual Modal windows that match each delete button. So the button will call modal ID=### and there is a modal ### that matches. The HTML appears to render perfectly.
When I delete the TOP(First entry on the page) it works like a charm, I can do that all day long, with entries moving up and being deleted.
THE PROBLEM: When I choose a 2nd position or lower button the screen goes grey and freezes it needs a reload to respond again. This pattern is repeatable.
HTML:
{% load static %}
{% block content %}
<link rel="stylesheet" href="{% static "css/cabinet_table.css"%}">
<div class="col-lg-2">
</div>
<div class="col-lg-8">
<div class="table-responsive">
<table class="table table-hover">
<th colspan="3"style="text-align: center;"><h2>{{ user.username|capfirst }} Notes</h2></th>
{% for note in note_list %}
<tr>
<td >{{ note.title }}</td>
<td>{{ note.text|truncatewords:15 }}</td>
<td><button type="button" class="btn btn-info btn-success" data-toggle="modal" data-target="#deleteModal{{ note.id }}">Delete</button></td>
</tr>
{% endfor %}
</table>
</div>
<!-- Pagination below -->
{% if note_list.has_other_pages %}
<ul class="pagination">
{% if note_list.has_previous %}
<li>«</li>
{% else %}
<li class="disabled"><span>«</span></li>
{% endif %}
{% for i in note_list.paginator.page_range %}
{% if note_list.number == i %}
<li class="active"><span>{{ i }} <span class="sr-only">(current)</span></span></li>
{% else %}
<li>{{ i }}</li>
{% endif %}
{% endfor %}
{% if note_list.has_next %}
<li>»</li>
{% else %}
<li class="disabled"><span>»</span></li>
{% endif %}
</ul>
{% endif %}
</div>
<div class="col-lg-2">
</div>
{% include 'cabinet/_note_list_modal.html' %}
{% endblock %}
Included HTML(The modal generation):
{% for note in note_list %}
<!-- Modal {{ note.id }} -->
<div class="modal fade" id="deleteModal{{ note.id }}" tabindex="-1" role="dialog" aria-labelledby="myModalLabel{{ note.id }}">
<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>
<h4 class="modal-title" id="myModalLabel{{ note.id }}">Delete Note</h4>
</div>
<div class="modal-body">
<h4>Title :</h4> {{ note.title }}<br>
<h4>Idea:</h4> {{ note.text|truncatewords:25 }}
...
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-danger" onclick="settleUp{{ note.id }}()" >Delete</button>
</div>
</div>
</div>
</div>
<div id="hiddenForm{{ note.id }}" style="display: none" class="visibility=hidden">
<form class="" action="/fc/delete/{{ note.id }}" name="hiddenForm{{ note.id }}" method="post">
{% csrf_token %}
<input type="hidden" name="deleteNote{{ note.id }}" id="deleteNote{{ note.id }}" value="{{ note.id }}">
<!-- <button type="submit" name="betBalance">Update Balance</button> -->
</form>
<script type="text/javascript">
function settleUp{{ note.id }}(){
document.forms.hiddenForm{{ note.id }}.submit()
}
</script>
{% endfor %}
OBJECTIVE: Click on any delete button have its modal pop up and work.
Thanks for any help.
PS using inspect, which I don't know how to use well, I see no errors in the console.
The problem with this, which was solved in another question, is there is a missing DIV tag. Thanks for everyone who looked.