Django: template loading slowly even using data from cache - django

I have a django app hosted on heroku, and I have a 'family album' page that loads a bunch of image thumbnails which link to a larger image detail page. (And the set of pictures can be different per user).
Each of these thumbnails uses an image_link.html template. At the most there can be up to ~1100 of these thumbnails on the family album page, and it loads peachy in production.
The problem: now I've added a little hover overlay (grabbed from w3schools here), so that when you mouse over a thumbnail you can see an overlay of who the picture contains. This list of is returned by a method on the Image class- it's not an attribute, so it's not returned with the Image objects.
Adding this has made the template really slow to load: 4-5 seconds locally, and ~22 seconds in production (Heroku, using 1 professional Dyno). I think usually this sort of thing would be made better by pagination, but in this case I like having one long page. (Though I'd be fine with having the top part load first and then the rest fill in afterward).
So I've done a few things:
added a 'get_image_index_data' function in views.py to loop through, get the pictured_list results, make an array with what I'll need, and cache it. I'm calling this in a receiver function (listening for signal that user logged in) so it'll be set by the time the user clicks the Family Album link, and views.py gets it from the cache
I added template fragment caching on the family album page
Issues: even with the data cached (and I confirmed that there wasn't a miss), this can still be slow-loading (until the template fragment caching kicks in? investigating...)
Here's the code:
I have a receiver function that calls this code to save the data:
def get_image_index_data(accessible_branches, profile):
image_list = Image.objects.none()
for branch in accessible_branches:
name = branch.display_name
image_list = image_list.union(Image.objects.filter(branches__display_name__contains=name).order_by('year'))
sorted_list = image_list.order_by('year')
# Save time on Family album page (aka image_index) by calling ahead for pictured_list. Elsewhere the template will retrieve it
family_album_data = []
for image in sorted_list:
family_album_data.append([image, image.pictured_list])
image_cache_name = 'images_' + str(profile.user)
cache.set(image_cache_name, family_album_data, 60 * 30) # save this for 30 minutes
return family_album_data
Then here's the function to render the family album:
#login_required(login_url=login_url)
def image_index(request):
profile = get_display_profile(request).first()
accessible_branches = get_valid_branches(request)
image_cache_name = 'images_' + str(profile.user)
family_album_data = cache.get(image_cache_name)
if not family_album_data:
family_album_data = get_image_index_data(accessible_branches, profile)
context = {'image_list': family_album_data, 'accessible_branches': accessible_branches, 'branch2_name': branch2_name,
'profile': profile, 'user_person': profile.person, 'media_server': media_server, 'user': profile.user}
return render(request, 'familytree/image_index.html', context)
And here's the family album template (image_index.html):
{% extends 'familytree/base.html' %}
{% block title %} - family album{% endblock title %}
{% block content %}
{% load cache %}
{% cache 1800 album profile %}
<h1>Family Album</h1>
{% if image_list %}
{% for image in image_list %}
{% include "familytree/image_link.html" with image=image height=150 show_hover=True pictured_list=pictured_list%}
{% endfor %}
{% else %}
<p>No images are available.</p>
{% endif %}
{% endcache %}
{% endblock content %}
image_link.html (the new bit is the overlay span in the 'if show_hover' block):
<div style="display: inline-block; margin-bottom:10px"; class="image_container">
<a href="{% url 'image_detail' image.id %}">
{% if image.little_name %}
<img src="{{ media_server }}/image/upload/h_{{ height }},r_20/{{ image.little_name }}" class="image"/>
{% else %}
<img src="{{ media_server }}/image/upload/h_{{ height }},r_20/{{ image.big_name }}" class="image"/>
{% endif %}
{% if show_hover %}
<span class="overlay">
<span class="text">Pictured: {{pictured_list}}</span>
</span>
{% endif %}
<div style="padding-left: 8px">
{% if image.year %}
({{ image.year }})
{% endif %}
</div>
</a>
</div>

Some suggestions:
Delegate the cache building on a background process handler like celery
If you want to display a long page, have a look at doing a facebook-like infinite scroll pagination, to give the illusion that you have a long page but still paginating in the background.

Related

django-tables2 permissions in TemplateColumn

I feel like I have read about this a hundred times but I still can't figure out how to use permissions within a django-tables2 TemplateColumn.
My goal is to be able to render buttons in a column based on permissions that a user may have or may not have on a given model. That does not sound complicated to me and from what I have read I should be able to use something like {% if perms.myapp.delete_mymodel %} to achieve what I'd like to do.
Here is the code I'm trying to get to work as I expect:
import django_tables2 as tables
MY_MODEL_ACTIONS = """
{% if perms.myapp.change_mymodel %}
<i class="fas fa-edit"></i>
{% endif %}
{% if perms.myapp.delete_mymodel %}
<i class="fas fa-trash"></i>
{% endif %}
"""
class MyModelTable(tables.Table):
# some columns
actions = tables.TemplateColumn(
verbose_name="",
template_code=MY_MODEL_ACTIONS,
)
class Meta(BaseTable.Meta):
model = MyModel
fields = (
# some columns
"actions",
)
When rendering the table no issues are triggered but the column just do not display any buttons (yes I do have the permissions for them to show up). Removing the {% if … %} clauses, thus removing the permission checks, allows the buttons to be seen of course.
The issue was a bit tricky. I defined my own template to render the table and did not used the {% render_table table %} tag inside it. Due to this the context was not reachable from the TemplateColumn code.
To fix this, I changed my template a bit and move my table rendering custom code to another template file. After that I used the render_table tag like this {% render_table table 'includes/table.html' %}
After this, the code that I mentioned above in the column works just fine, permissions are honored like expected.
What adds perms to your context?TemplateColumns do not have the same context as the template {{ render_table table }} is called from, so you must be a little more explicit.
The documentation for render_table mentions it will attach the context of the calling template to table.context, so this should fix your problem:
MY_MODEL_ACTIONS = """
{% if table.context.perms.myapp.change_mymodel %}
<i class="fas fa-edit"></i>
{% endif %}
{% if table.context.perms.myapp.delete_mymodel %}
<i class="fas fa-trash"></i>
{% endif %}
"""

Creating Django Wagtail Sidebar with Index Page Child Links

I'm building a site using Django Wagtail and am having trouble figuring out how to add a sidebar menu that will list all the child pages of the parent index page. For example, I have a standard_index_page.html that I created a parent page with in Admin, then I added child pages of that using standard_page.html template.
In my standard_index_page.html template, I have the following code
{% standard_index_listing calling_page=self %}
and it displays all the child pages with links, but I would also like to display a list of all the child links on the child pages as well.
I hope this is making sense and someone can lend a hand. Thank you.
In essence you traverse the tree structure of your page hierarchy that is provided to Wagtail by Django-Treebeard.
Many front-end frameworks do not allow for multiple levels of menus as some consider it outside of best practices. However, with a library such as SmartMenus you can display this structure with a little elbow grease.
For my needs, there was no easy solution to this. So, while I want to share an example of how I went about this, it may be missing explanation. If you have any questions, I'd be happy to answer them.
I struggled with this for awhile and while there may be easier methods to traverse the tree, I built the following method as my needs expanded. It allows us to traverse all live pages in our site, check when the current page is being rendered in the menu, and allows for fine-grained control over rendering.
Here's what we're going to do:
Create a few template tags that will get the site root of the
current site, loop through direct children of the site root, and loop through any lower levels of children, while looping through children of the current menuitem when discovered at each level.
In your base template, this means we need to:
{% load demo_tags %} to import our custom template tags
Call {% top_menu calling_page=self %} to get and render all
direct children of the site root. These are items that would be shown across a standard menu bar.
Call {% top_menu_children parent=menuitem %} within the template
rendered by {% top_menu %} to get and render all second- and
lower-level children pages. This encompasses all menu items to be shown when hovering on the parents menu item.
Here's the custom demo_tags.py file I created to traverse all levels of the page hierarchy. The beauty of this is that it does not require any custom context data to be supplied; it works out of the box with Wagtail!
#register.assignment_tag(takes_context=True)
def get_site_root(context):
'''
Returns a core.Page, not the implementation-specific model used
so object-comparison to self will return false as objects would differ
'''
return context['request'].site.root_page
def has_menu_children(page):
'''
Returns boolean of whether children pages exist to the page supplied
'''
return page.get_children().live().in_menu().exists()
#register.inclusion_tag('info_site/tags/top_menu.html', takes_context=True)
def top_menu(context, parent, calling_page=None):
'''
Retrieves the top menu items - the immediate children of the parent page
The has_menu_children method is necessary in many cases. For example, a bootstrap menu requires
a dropdown class to be applied to a parent
'''
root = get_site_root(context)
try:
is_root_page = (root.id == calling_page.id)
except:
is_root_page = False
menuitems = parent.get_children().filter(
live=True,
show_in_menus=True
).order_by('title')
for menuitem in menuitems:
menuitem.show_dropdown = has_menu_children(menuitem)
return {
'calling_page': calling_page,
'menuitems': menuitems,
'is_root_page':is_root_page,
# required by the pageurl tag that we want to use within this template
'request': context['request'],
}
#register.inclusion_tag('my_site/tags/top_menu_children.html', takes_context=True)
def top_menu_children(context, parent, sub=False, level=0):
''' Retrieves the children of the top menu items for the drop downs '''
menuitems_children = parent.get_children().order_by('title')
menuitems_children = menuitems_children.live().in_menu()
for menuitem in menuitems_children:
menuitem.show_dropdown = has_menu_children(menuitem)
levelstr= "".join('a' for i in range(level)) # for indentation
level += 1
return {
'parent': parent,
'menuitems_children': menuitems_children,
'sub': sub,
'level':level,
'levelstr':levelstr,
# required by the pageurl tag that we want to use within this template
'request': context['request'],
}
In essence, there are three levels of pages rendered:
The site root is called by {% get_site_root %}
First-level children are called by {% top_menu %}
Second- and lower-level children are called by {% top_menu_children %}, which is called any time a page shown in the menu has children while rendering this tag.
In order to do this, we need to create the templates to be rendered by our top_menu and top_menu_children template tags.
Please note - these all are built for Bootstrap 3's navbar class and customized for my needs. Just customize these for your needs. The whole menu building process is called by {% top_menu_children %}, so place this tag in your base template where you want the menus rendered. Change top_menu.html to reflect the overall structure of the menu and how to render each menuitem. Change children_items.html to reflect how you want children of all top-menu items, at any depth, rendered.
my_site/tags/top_menu.html
{% load demo_tags wagtailcore_tags static %}
{% get_site_root as site_root %}
{# FOR TOP-LEVEL CHILDREN OF SITE ROOT; In a nav or sidebar, these are the menu items we'd generally show before hovering. #}
<div class="container">
<div class="collapse navbar-collapse" id="navbar-collapse-3">
<ul class="nav navbar-nav navbar-left">
{% for menuitem in menuitems %}
<li class="{% if menuitem.active %}active{% endif %}">
{% if menuitem.show_dropdown %}
<a href="{{ menuitem.url }}">{{ menuitem.title }}
<span class="hidden-lg hidden-md hidden-sm visible-xs-inline">
<span class="glyphicon glyphicon-chevron-right"></span>
</span>
</a>
{% top_menu_children parent=menuitem %}
{% else %}
{{ menuitem.title }}
{% endif %}
</li>
{% endfor %}
</ul>
</div>
</div>
my_site/tags/children_items.html
{% load demo_tags wagtailcore_tags %}
{# For second- and lower-level decendents of site root; These are items not shown prior to hovering on their parent menuitem, hence the separate templates (and template tags) #}
<ul class="dropdown-menu">
{% for child in menuitems_children %}
{% if child.show_dropdown %}
<li>
<a href="{% pageurl child %}">
{% for i in levelstr %}&nbsp&nbsp{% endfor %}
{{ child.title }}
<span class="glyphicon glyphicon-chevron-right"></span>
</a>
{# On the next line, we're calling the same template tag we're rendering. We only do this when there are child pages of the menu item being rendered. #}
{% top_menu_children parent=child sub=True level=level %}
{# ^^^^ SmartMenus is made to render menus with as many levels as we like. Bootstrap considers this outside of best practices and, with version 3, has deprecated the ability to do so. Best practices are made to be broken, right :] #}
</li>
{% else %}
<li>
<a href="{% pageurl child %}">
<!-- Allows for indentation based on depth of page in the site structure -->
{% for i in levelstr %}&nbsp&nbsp{% endfor %}
{{ child.title }}
</a>
</li>
{% endif %}
{% endfor %}
</ul>
Now, in your base level template (let's assume you are using one; if not, get to it :) ) you can traverse the menu while keeping clutter cleared away to the templates used by your inclusion_tags.
my_site/base.html
<ul class="nav navbar-nav navbar-left">
{% for menuitem in menuitems %}
<li class="{% if menuitem.active %}active{% endif %}">
{% if menuitem.show_dropdown %}
<a href="{{ menuitem.url }}">{{ menuitem.title }}
<span class="hidden-lg hidden-md hidden-sm visible-xs-inline">
<span class="glyphicon glyphicon-chevron-right"></span>
</span>
</a>
{% top_menu_children parent=menuitem %}
{% else %}
{{ menuitem.title }}
{% endif %}
</li>
{% endfor %}
</ul>
I wrote a blog post about this - check it out for more details. Or, head over to Thermaline.com to see it in action, though I think there's not multiple levels of depth right now. IF THERE WERE, they'd be rendered automatically :)
Now, this example is for a navbar, but it could easily be adapted for a sidebar.
All you need to do is:
Include demo_tags in your base template
Call {% top_menu %} where you wish to render your menus.
Customize top_menu.html and children_items.html to render the
first and then all subsequent levels of pages.
Shout out to Tivix for their post on two-level menus that was a great starting point for me!

Django - troubleshooting slow page load times for a comments page

I have a page on my site that is dedicated to group comments. Users can create posts (i.e. questions or comments), and other users can comment on them. Comment hierarchy is identical to Facebook; there is no comment threading like reddit. Using Heroku.
I'm using django-debug-toolbar to optimize this page. Right now the page loads in 5-6 seconds; that's just the time that my browser waits for a response from the server (doesn't include JS/CSS/IMG load times). There are 30 SQL queries take between 80-100ms to load (am using prefetch_related on relevant m2m fields). The size of the page returned is 344KB, so not a monster page at all. The comments are paginated and I'm only returning 10 posts + all comments (in test each post only has 3-4 comments).
I can't figure out why it takes such a long time to load the page when the SQL queries are only taking up to 100ms to complete.
Below is the relevant code. I am getting back the 'posts' object and using a for loop to render each object. Within that 'posts' for loop I'm doing another for loop for the 'comments'.
What else can I do to further optimize this/reduce page load time?
# views.py
def group_app(request, course_slug):
# get the group & plan objects
group = Group.objects.get(slug=course_slug)
# load the page with a new Post and comment form
group_post_form = newGroupPost()
post_comment_form = newPostComment(auto_id=False)
# gather all the Posts that have already been created
group_posts = GroupPost.objects.filter(group=group).prefetch_related('comments', 'member', 'group')
paginator = Paginator(group_posts, 10)
page = request.GET.get('page')
try:
posts = paginator.page(page)
except PageNotAnInteger:
page = 1
posts = paginator.page(page)
except EmptyPage:
posts = paginator.page(paginator.num_pages)
variables = RequestContext(request, {
'group': group,
'group_post_form': group_post_form,
'posts': posts,
'next_page': int(page) + 1,
'has_next': posts.has_next(),
'post_comment_form': post_comment_form
})
if request.is_ajax():
return render_to_response('group-post.html', variables)
else:
return render_to_response('group-app.html', variables)
# group-app.html
{% extends 'base.html' %}
{% block canvas %}
<div id="group-body" class="span8">
{% include "group-body.html" %}
</div>
{% endblock canvas %}
# group-body.html
{% for post in posts %}
{% include 'group-post-item.html' %}
{% endfor %}
# group-post-item.html
{% with post_avatar=post.member.get_profile.medium_image.url %}
<div class="groupPost" id="{{ post.id }}">
<div class="avatarContainer">
{% if post_avatar %}
<img class="memberAvatar" src="{{ post_avatar }}" alt="">
{% else %}
<img class="memberAvatar" src="{{ STATIC_URL }}img/generic-avatar.png">
{% endif %}
</div>
<div class="postContents">
<div class="groupPostComment">
<h6 class="comment-header">SHOW/ADD COMMENTS</h6>
<div class="comments">
{% for comment in post.comments.all %}
{% with comment_avatar=comment.member.get_profile.medium_image.url %}
<div class="commentText">
<p class="comment-p">{{ comment.comment }}</p>
</div>
{% endwith %}
{% endfor %}
</div>
</div>
</div>
</div>
{% endwith %}
As I understand this is related to avatar image and not SQL queries. Working with remote storages like S3 is a bit slow. But it should not hamper the user experience as you have faced above.
I didn't get what you are using to get 'avatar' image, but I would suggest to use some third party packages like.
Easy_thumbnails - It is really nice and has lots of options. I recently shifted to this you. In case of remote storages, you could pregenerate avatars with celery and serve saved thumbnail
sorl-thumbnail - I used this for my previous project. It doesn't have option to pregenerate, so first request is slow. But once thumbnail is created, next request will be fast. It has smart caching. I tried to fork it here to use with celery https://github.com/neokya/sorl-thumbnail-async
I hope it helps.

Factorizing a header menu in Django template

I'm building a website using django with a header on top of every page, which basically is a menu with a few links, constant throughout the pages.
However, depending on the page you're on I'd like to highlight the corresponding link on the menu by adding the class "active". To do so, I am currently doing as follow: each page has a full menu block that integrates within a general layout, which does NOT contain the menu. For exemple, page2 would look like this:
{% extends "layout.html" %}
{% block menu %}
<li>Home</li>
<li>page1</li>
<li class="active">page2</li>
<li>page3</li>
{% endblock %}
The problem is that, beside from that solution being not so pretty, every time I want to add a link to the header menu I have to modify each and every page I have. Since this is far from optimal, I was wondering if any of you would know about a better way of doing so.
Thanks in advance!
You can create a custom templatetag:
from django import template
from django.core.urlresolvers import reverse, NoReverseMatch, resolve
register = template.Library()
#register.simple_tag
def active(request, view_name):
url = resolve(request.path)
if url.view_name == view_name:
return 'active'
try:
uri = reverse(view_name)
except NoReverseMatch:
uri = view_name
if request.path.startswith(uri):
return 'active'
return ''
And use it in the template to recognize which page is loaded by URL
<li class="{% active request 'car_edit' %}">Edit</li>
If you have a "page" object at every view, you could compare a navigation item's slug to the object's slug
navigation.html
<ul>
{% for page in navigation %}
<li{% ifequal object.slug page.slug %} class="active"{% endifequal %}>
{{ page.title }}
</li>
{% endfor %}
</ul>
base.html
<html>
<head />
<body>
{% include "navigation.html" %}
{% block content %}
Welcome Earthling.
{% endblock %}
</body>
</html>
page.html
{% extends "base.html" %}
{% block content %}
{{ object }}
{% endblock %}
Where navigation is perhaps a context_processor variable holding all the pages, and object is the current PageDetailView object variable
Disclaimer
There are many solutions for your problem as noted by Paulo. Of course this solution assumes that every view holds a page object, a concept usually implemented by a CMS. If you have views that do not derive from the Page app you would have to inject page pretenders within the navigation (atleast holding a get_absolute_url and title attribute).
This might be a very nice learning experience, but you'll probably save loads time installing feinCMS or django-cms which both define an ApplicationContent principle also.
You may use the include tag and pass it a value which is the current page.
For example, this may be a separate file for declaring the menu template only:
menu.html
{% if active = "a" %}
<li>Home</li>
{% if active = "b" %}
<li>page1</li>
{% if active = "c" %}
<li class="active">page2</li>
{% if active = "d" %}
<li>page3</li>
And call this from within your template like this:
{% include 'path/to/menu.html' with active="b"%} # or a or c or d.
Hope it helps!

django applying a style class based on a conditional

I am displaying a list of images.
If the user has uploaded an image, I want to keep its opacity 0.5 and in the list of images, the images uploaded by others should have full opacity.
I have done it as follows, is there a better way to do it??
{% if request.user == obj.shared_by %}
<div class="item-image" style="opacity:0.5;filter:alpha(opacity=50);">
{% else %}
<div class="item-image">
{% endif %}
......Some code here....
</div>
Thanks!
I normally go for:
<div class="item-image{% if foo %} own-image{% endif %}">...</div>
but switching out the entire div tag may be more readable.
Either way I'd do the styling with another class, not with inline css.
I have added class on if condition by this way....
<li class="nav-item {% if app_url == '/' %} active{% endif %}">