Django template rendering slowly

Viewed 281

I'm rendering a template and it's taking anywhere between 2-3 seconds to do it. There are quite a few elements on the page (10k checkboxes which change every query) and I'm wondering if anyone knows any helpful tricks to cut down on load time.

Link to site: http://18.207.127.123/search/

FYI: there are no Django Forms or Formsets in this application! :-) I'm working with objects called Lemmas which have Forms as their children... sorry if this causes confusion.

Update #1

Whatever is taking so long, it's here:

<li class="form-item" data-lemma="{{lemma.id}}" data-group="{{group}}" style="display:none">
    <input type="checkbox" name="{{lemma.name}}@{{lemma.latin}}@{{lemma.homonym_id}}@{{group}}@{{form}}" onchange="countCheckboxes(this)" id="{{lemma.id}}@{{group}}@{{form}}" checked>
    <label for="{{lemma.id}}@{{group}}@{{form}}">{{ form }}</label>
</li>

are the dot methods what's slowing this down?

Update #2 - Found the bottleneck

Apparently all the {{ var }} calls for each of the 10k items is what's taking so long. Each one costs ~50-200ms.

Update #3 - Remove unnecessary tags and autoescape off

<input id="x"...
<label for="x">

can be replaced with

<label>
<input ...>
</label>

saving six variable calls.

Adding

{% autoescape off %}
# body text
{% endautoescape %}

removes the need to make each variable safe.

These two changes bring the rendering time down about 50%.

Is it database hits?

After inspecting my django connections and profiling, I'm only making two database calls as far as I can tell.

My datastructure is a dict which looks like:

results_dict = { LemmaObj1 : [form11, form12, form13, ...],
                 LemmaObj2 : [form21, form22, form33, ...],
                 ...

where the LemmaObj are Queryset objects. Generating this dict only takes 0.5 seconds. I force evaluation of the lazy queryset by putting the formij into a list comprehension.

views.py

lemma_qs = Lemma.objects.prefetch_related('form_set')

# ...
# some logic goes here
# ...

for lemma in lemma_qs:

    form_qs = lemma.form_set.all()
    form_list = [form.name for form in form_qs if some_condition(form)]

    if form_list:
        results_dict[lemma] = form_list

context['results_dict'] = results_dict

return render(request, "query.html", context)

All this is to say that I'm pretty sure the slowdown isn't coming from database hits.

The template in question

In my query.html I have two for loops.

query.html

<div class="lemma-box">
    <ol>
        <li class="lemma-title">
            <div class="lemma-col-1">
                lemma [{{results_dict|length}}] (group {{group}})
            </div>
            <div class="lemma-col-2">
                latin
            </div>
            <div class="lemma-col-3">
                homonym id.
            </div>
            <div class="lemma-col-4">
                <input type="button" class="pushable" value="all" onclick="checkAllLemmas('{{group}}', true)"></input>
                <input type="button" class="pushable" value="none" onclick="checkAllLemmas('{{group}}', false)"></input>
            </div>
        </li>
        {% for lemma, form_list in results_dict.items %}
        <li class="lemma-item" data-lemma="{{lemma.id}}" data-group="{{group}}" onclick="activateLemma(this)">
            <div class="lemma-col-1">
                <input type="checkbox" onchange="countCheckboxes(this)" onclick="lemmaToggleAll(this)" id="{{lemma.id}}@{{group}}" checked></input>
                <label for="{{lemma.id}}@{{group}}">
                {{ lemma.name }}
                </label>
            </div>
            <div class="lemma-col-2">
                {{ lemma.latin }}
            </div>
            <div class="lemma-col-3">
                {{ lemma.homonym_id }}
            </div>
            <div class="lemma-col-4">
                {% with form_list|length as total %}
                <span class="counter">(<span class="total">{{ total }}</span>/<span>{{ total }}</span>)</span>
                {% endwith %}
            </div>
        </li>
        {% endfor %}
        {% for item in not_found_items_set %}
        <li class="lemma-item-not-found">
            {{ item }} not found
        </li>
        {% endfor %}
    </ol>
</div>
<div class="form-box">
    <ol>
        <li class="form-title">
            <div class="form-col-1">
                forms (group {{group}})
            </div>
            <div class="form-col-2" data-group="{{group}}">
                <input type="button" class="pushable" value="all" onclick="checkAllForms('{{group}}', true)"></input>
                <input type="button" class="pushable" value="none" onclick="checkAllForms('{{group}}', false)"></input>
            </div>
        </li>   
        {% for lemma, form_list in results_dict.items %}
            {% for form in form_list %}
            <li class="form-item" data-lemma="{{lemma.id}}" data-group="{{group}}" style="display:none">
                <input type="checkbox" name="{{lemma.name}}@{{lemma.latin}}@{{lemma.homonym_id}}@{{group}}@{{form}}" onchange="countCheckboxes(this)" id="{{lemma.id}}@{{group}}@{{form}}" checked>
                <label for="{{lemma.id}}@{{group}}@{{form}}">{{ form }}</label>
            </li>
            {% endfor %}
        {% endfor %}
    </ol>
</div>

Is this about as fast as I'm going to get?

Jinja2 the answer?

I tried jinja2 briefly by converting query.html to query.jinja and it seemed to make almost no difference. Do I need extra steps to leverage jinja2?

Models.py

Finally, for inspection, my models.py.

models.py

class Form(models.Model):
    name = models.CharField(max_length=100, db_index = True)
    lemma = models.ForeignKey("Lemma", on_delete = models.CASCADE)

    def __str__(self):
        return self.name

class Lemma(models.Model):
    name = models.CharField(max_length=100, db_index = True)
    latin = models.CharField(max_length=100, db_index = True, blank = True, null = True)
    homonym_id = models.IntegerField(null = True)

    def __str__(self):
        return self.name
1 Answers
  1. One thing to do is remove as many template tags as possible by thinking clearly about what your code needs to do. That will cut down on the rendering time substantially. Such as
<input id="x"...
<label for="x">

being replaced with

<label>
<input ...>
</label>

and saving a series of template tags.

  1. You can mark the variables as safe, which again shaves off the rendering time, such as in
{% autoescape off %}
# body text
{% endautoescape %}
  1. The absolute best way (if you need speed and have a ton of data) is to send the JSON to the client and have the JS render it. This is a little messy, but it's much faster (2-3x faster maybe) and gives you more flexibility to manipulate the DOM piecewise.

I'm going for solution (3) for now.

Related