sorl-thumbnail - Slow template rendering - django

I have serious performing problem with my templates. After some test, I found out that the slowest part from the rendering was the thumbnails, generated from sorl-thumbnail.
Below is the issued part from my template:
{% for listing in listings %}
<li class="pic_view">
<ul>
<li class="picture">
<a href="{% url 'listing' listing.id listing.title %}">
{% thumbnail listing.get_picture "240x143" crop="center" as im %}
<img src="{{ im.url }}">
{% empty %}
tc
{% endthumbnail %}
<div class="overlay"></div>
</a>
</li>
<li class="artist">
<a href="{% url 'profile_artist' listing.artist_id listing.artist.user.first_name %}">
{% thumbnail listing.artist.get_avatar "60x60" crop="center" as im %}
<img src="{{ im.url }}">
{% empty %}
tc
{% endthumbnail %}
</a>
</li>
</ul>
</li>
{% endfor %}
Currently I have about 6 listings and below is the measured time for rendering:
- 8512 ms on first rendering
- 4680 ms on reload
- 112 ms when thumbnail tags are removed
Can you please give me some advice on this point. The number above are measured with Debug=True, but there is no difference when the flag is False. Also accordingly to Django documentation, I have included the followings TEMPLATE_LOADERS:
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
('django.template.loaders.cached.Loader', (
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
)),
)

Sorl does this behind the scenes (the chapter in the docs are here, here is the TL;DR version)
Compute a hash value from the original file name and the options
given
Check in the database if that hash is already present, if so,
deliver associated thumbnail
If not, resize the image, store it in the media location, create a
database record with the hash/filename association, deliver
thumbnail
That being said, 4.5 seconds for a couple of database lookups is wayyyyyy too long. There is something else going on here.

Sorl Thumbnail generates thumbnails on the fly hence time consuming operation, what you need is to create the thumbnails using cronjob or celery (preferred) with the preferred sizes. There is package already called sorl-thumbnail-async which does what you are looking for and uses celery. So if the thumbnails are not available show dummy image as soon as the thumbnails are available they will be rendered. Also use memcached as a cache backend for django which is really good in terms of performance.

Related

Django: template loading slowly even using data from cache

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.

Overridding Django Admin's object-tools bar for one Model

I am looking for the solution. I think a lot of different examples have confused me a little. I want to override the object-tools template in the Django admin.
I have one button on my model's tools:
I need one other same type of button "Upload file". This should redirect me to the another view which I want to design myself in the same admin base template (with just one upload file control).
[A]
Move "django.contrib.admin" in your INSTALLED_APPS to end of INSTALLED_APPS.
You can make template file
<your_app>/templates/admin/<your_app>/<your_model>/change_list.html
source code:
{% extends "admin/change_list.html" %}
{% block object-tools-items %}
<li>
<a href="<your-action-url>" class="addlink">
Upload file
</a>
</li>
{{ block.super }}
{% endblock %}
[B]
Add change_list_template into your ModelAdmin.
class MyModelAdmin(admin.ModelAdmin):
change_list_template = '<path-to-my-template>.html'
And write template like [A] source code.
It has become lot easier to accomplish what you're looking for since this question has been asked the first time.
The Django documentation has a section on how to override admin templates. To add a button to the changelist object tools, follow these steps:
Copy Django's version of the change_list_object_tools.html template file into your project's or app's template folder: templates/admin/<app>/<model>/change_list_object_tools.html.
You can obtain the file form your virtual env's site-packages folder:
cp $VIRTUAL_ENV/lib/python3.8/site-packages/django/contrib/admin/templates/admin/change_list_tools.html templates/admin/$APP/$MODEL/
Note that you might need to adjust the path to site-packages for your Python version.
Now open the template file. It will look liek this:
{% load i18n admin_urls %}
{% block object-tools-items %}
{% if has_add_permission %}
<li>
{% url cl.opts|admin_urlname:'add' as add_url %}
<a href="{% add_preserved_filters add_url is_popup to_field %}" class="addlink">
{% blocktrans with cl.opts.verbose_name as name %}Add {{ name }}{% endblocktrans %}
</a>
</li>
{% endif %}
{% endblock %}
Add the link to your custom view:
…
{% if has_add_permission %}
<li><a class="addlink" href="{% url 'upload-file' %}">Upload file</a></li>
<li>
…
That's it! If you didn't know: you can add custom views and URLs to a ModelAdmin using ModelAdmin.get_urls() (docs). To not have to hardcode your custom admin URLs, you can of course reverse them (docs).
You can try to use https://github.com/texastribune/django-object-actions.
This will allow you to add buttons with custom logic. Though, I'm not sure whether you'll be able to a buttom for your list screen, as I did it only for a model-edit page.

how to load image in the size specified in django template?

Suppose, I have 20 images of size 1600 X 900 in a page. How do I load the images in the size that I specify on a template? In the css I can do it. But I want to change the actual size of the image, so that when clicked on the particular image, it will load the original image with its original size. Is there any way that I can do it? I tried using easy_thumbnails and it was great, until it gave me problems when I deployed it using the apache server. Any help will be deeply appreciated. Thank you!
EDIT:
.html:
{% for Status in status %}
<p class="user">{{ Status.creator.get_full_name }}</p>
{% if Status.image %}
<div class="image_image">
<center>
<img src="{{ MEDIA_URL }}{{Status.image}}" width=300px />
</center>
</div>
<p class="status_image">{{Status}}</p>
<span class="clear"></span>
<hr>
{% else %}
<p class="status_status">{{Status}}</p>
<span class="clear_right"></span>
<hr>
{% endif %}
{% endfor %}
Try to use sorl thumbnail, it even supports Amazon S3 Server.
pip install sorl-thumbnail
To Installed apps:
'sorl.thumbnail',
add {% load thumbnail %} to your template.
{% thumbnail firma.firma_logo "130x110" format="PNG" as im %}
<div class="logo" style="background-image:url('{{ im.url }}')"></div>
{% endthumbnail %}
The make image format PNG is important, it converts into JPEG by the default. And if the user uploads file in transparent format, it sucks.

How to use local default image for django-gravatar?

I am using django-gravatar and I am wondering how to display a local image to use as a default image. Is there a way to do so?
I have read in the docs to use GRAVATAR_DEFAULT_IMAGE, but it does not seem to work specifying the path to the image within the static/ directory. Is it only for online images from other websites?
Thanks!
EDIT
I am using the following configuration:
STATIC_URL = '/static/'
GRAVATAR_DEFAULT_IMAGE = STATIC_URL + 'img/StartMyProjects_100.png'
Hack finally used to solve the problem:
<img class="media-object" src="
{% if profile.gravatar_email %}
{% gravatar_for_email profile.gravatar_email %}
{% else %}
/static/img/StartMyProjects_100.png
{% endif %}"
alt="{{ profile.full_name }}">
To solve the problem, as stated in the edited question, I actually used an if-else (in Django). Maybe someone knows a better way to do so, but this worked for me!
<img class="media-object" src="
{% if profile.gravatar_email %}
{% gravatar_for_email profile.gravatar_email %}
{% else %}
/static/img/StartMyProjects_100.png
{% endif %}"
alt="{{ profile.full_name }}">

Django TemplateSyntax Error with Sorl-thumbnail

I'm trying to get Sorl-thumbnail running on my staging server, but I'm running into a TemplateSyntaxError which is throwing me since the app works fine on localhost.
The error is coming in at {% endthumbnail %}
TemplateSyntaxError at /home/
Invalid block tag: 'endthumbnail', expected 'endif'
Any help would be greatly appreciated. Thanks!
{% load thumbnail %}
{% if picture.photo_medium %}
<img src="{{AWS_URL}}{{picture.photo_medium}}" class="imagepage" width="400" height="300">
{% else %}
{% if picture.photo_large|is_portrait %}
<div class="portrait">
{% thumbnail picture.photo_large "400" crop="center" as im %}
<img src="{{AWS_URL}}{{ im }}">
</div>
{% else %}
<div class="landscape">
{% thumbnail picture.photo_large "400" crop="center" as im %}
<img src="{{AWS_URL}}{{ im }}">
</div>
{% endif %}
{% endif %}
It is likely that you have an older version of sorl-thumbnail installed on your localhost than is installed on your staging server. The endthumbnail tag was added relatively recently as part of a major rewrite.
If you find that you need to upgrade you may find the setting THUMBNAIL-DEBUG helpful for tracking down other problems.
I might be wrong, but I don't think you need the {% endthumbnail %} tag.
The problem can also be with loading template tags.
I was doing {% load thumbnail %} in base html.
When I call below code in inherited html, got same error.
{% thumbnail service_type.pic.image "100x100" crop="center" as im %}
<img .....>
{% endthumbnail %}
See this discussion about loading template tags in base.html
I just ran into this problem in using SORL Thumbnail in Mezzanine. Apparently Mezzanine loads it's own thumbnailer, so if you {% load thumbnail mezzanine_tags %}, mezzanine's thumbnail takes over from SORL's Thumbnail tag. However, if you reverse it {% load mezzanine_tags thumbnail %}, it works fine.
Lesson Learned: Make sure other libraries you're using aren't inadvertently taking over, and just to be safe maybe load thumbnail last.