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
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.
You can mark the variables as safe, which again shaves off the rendering time, such as in
{% autoescape off %}
# body text
{% endautoescape %}
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
I have a form in which there are three fields, basically these fields contain drop-down lists - selections. These select lists are based on a data model that has fields associated with ForeynKey .
After completing this form.
I update information, code and do calculations in code.
Further after the calculations.
I am updating two elements on the template - a table and a graph.
I have these two elements in separate parts of the template.
Like two different pieces of HTML .
With (HTMX) I can only update one element on the table - this element is a chunk of the HTML template - which is updated by rendering that chunk of that template. How can I update another piece of the template?
How can two HTML template elements be updated using (HTMX) ?
I would be very grateful for any help.
--
<div class="row">
<div class="col-6">
<form method="POST" class="post-form">
{% csrf_token %} {{form_1.as_p}}
<button type="submit" class="save btn btn-light">Form</button>
</form>
</div>
</div>
<div class="row">
<div class="col">
{{ div_1|safe }}
{{ script_1|safe }}
</div>
</div>
<div class="row">
<div class="col">
{{ div_2|safe }}
{{ script_2|safe }}
</div>
</div>
--
class Form_1(forms.ModelForm):
class Meta:
model = Model_1
fields = "__all__"
--
class Model_1(models.Model):
name_1 = models.CharField(max_length=150, verbose_name="Name_1")
name_2 = models.CharField(max_length=150, verbose_name="Name_2")
def __str__(self):
return self.name_1, self.name_2
--
def form_1(request):
context = {}
form = Form_1(request.POST or None)
if form.is_valid():
form.save()
script_1, div_1 = components(data_table)
context['script_1'] = script_1
context['div_1'] = div_1
script_2, div_2 = components(fig)
context['script_2'] = script_2
context['div_2'] = div_2
return render(request, "data_table", "fig", context)
context['form_1'] = form
return render(request, "form_1.html", context)
added
def index_htmx(request):
context = {}
///code///
if request.htmx:
print("HTMX")
return render(request, 'index_htmx_added.html', context)
return render(request, "index_htmx.html", context)
index_htmx_added.html
<div id="table"
hx-swap="beforeend"
hx-swap-oob="true"
class="col-6">
{{ div|safe }}
{{ script|safe }}
</div>
<div id="figure"
hx-swap="beforeend"
hx-swap-oob="true"
class="col-6">
{{ div_2|safe }}
{{ script_2|safe }}
</div>
index_htmx.html
<div class="row">
<div class="col-4">
<select
id="select-name"
class="custom-select"
name="select"
autocomplete="off"
hx-get="{% url 'index_htmx' %}"
hx-target="#figure, #table"">
{% for select in selector %}
<option value="{{ select }}">{{ select }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="row">
<div id="table" class="col-6">
{{ div|safe }}
{{ script|safe }}
</div>
<div id="figure" class="col-6">
{{ div_2|safe }}
{{ script_2|safe }}
</div>
</div>
HTMX call this feature Out of Band Swap. In the response HTML you can have multiple HTML fragments with different targets and HTMX will swap the corresponding element on the page. Given your page looks like this:
<div id="tabular_data">...Tabular data...<div>
<div id="graph">...Graph...</div>
The response HTML should contain this (note the added hx-swap-oob="true"):
<div id="tabular_data" hx-swap-oob="true">...New tabular data...<div>
<div id="graph" hx-swap-oob="true">...New graph...</div>
HTMX will find id="tabular_data" element and swaps its content with the new content then do the same with id="graph" element.
These oob elements must be in the top level of the response, and not a children of an element.
At the backend you need to create a new template that contains all the element you want to swap together or just join the rendered HTML fragments when you return the response.
my checkbox html :
<form method="GET">
{% for subcategory in subcategories %}
<div class="filter-items filter-items-count">
<div class="filter-item">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" value="{{subcategory.id}}" name="subcategory" id="{{subcategory.id}}" {% if subcategory in subcategories1 %} checked {% endif %}>
<label class="custom-control-label" for="{{subcategory.id}}">{{subcategory}}</label>
</div><!-- End .custom-checkbox -->
<span class="item-count">{{subcategory.products.all|length}}</span>
</div><!-- End .filter-item -->
</div><!-- End .filter-items -->
{% endfor %}
<button type="submit" class="btn btn-primary w-100 mt-3">Filter!</button>
</form>
it work correctly to make filter.
views :
subcatid = request.GET.getlist('subcategory')
query string:
?subcategory=5&subcategory=6
it can be one or more than one, depends on number of subcategories.
but when I go next page i suppose it become like :
?page=2&subcategory=5&subcategory=6
but it remove earliest subcategory i choose and keep the last one, just one, like :
?page=2&subcategory=5
acutely when i put Manually ?page=2&subcategory=5&subcategory=6 in url field it works but not from pagination buttons.
so while all checkboxes in filter has same names, name="subcategory" i made them unique by changing to name="{{subcategory}}", now each checkbox has unique name, now after tapping next page, Everything is kept, and there is no problem like before,
but in views, I don't know how to get them with deafferents names
subcatid = request.GET.getlist('subcategory')
You can add a QueryDict to the object that does not contain any page parameter with:
def subcategory(request, category_url):
# …
reqget = request.GET.copy()
reqget.pop('page', None)
ctx = {
# …
'reqget': reqget
}
return render(request, 'products/subcategory.html', ctx)
Then in the template, the links to go to the previous/next page will urlencode the reqget:
<a href="?page={{ products.previous_page_number }}&{{ reqget.urlencode }}" aria-label="Previous" tabindex="-1" class="page-link page-link-prev" aria-disabled="true">
I want to render different queryset when user click radio button without submitting the radio button input.
My models are these:
class A(models.Model):
name = models.CharField(max_length=10)
def get_a_list(self):
return self.b_set.filter(name__endswith='lorem')
def get_b_list(self):
return self.b_set.filter(name='blah')
class B(models.Model):
dept = models.ForeignKey(A, on_delete=models.CASCADE)
In template I can do something like this:
<ul>
{% for a in a.objects.all %}
{% for b in a.b_set.all %} <!-- instead of returning all I can do a.get_a_list or b.get_b_list -->
<li></li>
{% endfor %}
{% endfor %}
</ul>
If I have a group of radio buttons in the same template like this:
<div>
<input type="radio" id="a-list" value="a-list" name="filter">
<label for="a-list">a_list</label>
</div>
<div>
<input type="radio" id="b-list" value="b-list" name="filter">
<label for="b-list">b_list</label>
</div>
<div>
<input type="radio" id="all" value="all" name="filter">
<label for="all">all</label>
</div>
When user select a-list button, I want to call get_a_list, and for b-list button, get_b_list. I want to display the result without changing url.
I managed to get those different list by putting custom method in models class, but I'm lost. And I know I might lose some reputation for this question, for it might be so easy for somebody.
Any suggestion would be so grateful. Thank you in advance.
Something like:
template.html:
<div id='a_set'>
{% for a in a.objects.all %}
...
{% endfor %}
</div>
<div id='b_set'>
{% for b in a.b_set.all %}
...
{% endfor %}
</div>
script.js:
function change_sets() {
if ($('#a-list').is(':checked')) {
$('#a_set').show();
$('#b_set').hide();
}
else if ($('#b-list').is(':checked')) {
$('#a_set').hide();
$('#b_set').show();
}
}
$('#a-list').click(change_sets);
$('#b-list').click(change_sets);
Im having some problem when I want to keep a checkbox checked. When I send the form all the checkbox are checked. so idk how to change the if statement for that :(
<div class="form-group">
<label >Marca</label>
{% for brand in q %}
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="{{brand.brand}}" name="test" value="{{brand.brand}}" {% if marca %} checked="checked" {%endif%}>
<label class="custom-control-label" for="{{brand.brand}}" style="cursor: pointer;">{{brand.brand}}</label>
</div>
{% endfor %}
</div>
And here is the view:
marca = request.GET.get('test')
if marca :
products = products.filter(brand__name__in=request.GET.getlist('test'))
All the other things are fine. It shows me the brands that I choose. So I just want to keep the checkbox that I checked :( and I think the problem is that If statement in the template
Just pass a set of the values in the values to the template:
marca_vals = set(request.GET.getlist('test'))
# …
context = {
'marca_vals': marca_vals,
# …
}
return render(request, 'some_template.html', context)
in the template you can then render it with checked in case the value is in the marca_vals:
<input type="checkbox" {% if brand.brand in marca_vals %}checked{% endif %} class="custom-control-input" id="{{brand.brand}}" name="test" value="{{brand.brand}}">
You might however want to consider using a form, or even django-filters [readthedocs] to both make filtering and rendering the form more convenient.
I am working with forms sets, I was wondering how one could use
<input type='hidden'
inside a form set. (Django formsets allow us to use multiple forms instances of a single form)
Its easy to do in normal single form where you just put the field with the type='hidden' and name='fieldname' e.g.
<input type='hidden' name='user' value='{{request.user.id}}'>
Dealing with formsets is a bit catchy, how to achieve the same behavior with the forms sets?
Views.py
PictureFormSet = modelformset_factory(Picture, form=UpdatePictureForm, extra=0)
formset_qset = Picture.objects.filter(id__in=[15, 16, 17, 18, 19, 20])
if request.method == POST:
ctx['form_set'] = PictureFormSet(request.POST, queryset=formset_qset)
ctx['form_set'].save()
ctx['form_set'] = PictureFormSet(queryset=formset_qset)
return render_to_response('temp tabs.html', context_instance=RequestContext(request, ctx))
Template
<form method="POST" action="" class="form-horizontal">
{% for form in form_set %}
{{form.id}}
<div class="form-group">
<label class="col-lg-2 control-label">
{% with form.meta_data.value|load_meta_data as meta %}
<div class="portfolio-item video-container">
<a class="" href="{% url 'view_image' form.id.value %}?in=pro">
<i style="background-image: url({{ meta.image_size.thumbnail_small }});"
class="ds-thumbnail-container"></i>
</a>
</div>
{% endwith %}
</label>
<div class="col-lg-8 ">
{{ form.name }}
</div>
</div>
{% endfor %}
{{ form_set.management_form }}
{% csrf_token %}
<input type="submit" value="Submit">
</form>
Explanation
Here in this code, We are rendering images from the database for editing there names. we have url information inside the meta_data, so we have selected
fields=['id', 'meta_data', 'name']
We want to change/Update the name, but not the meta_data
This code works fine for the most part, but how i want to keep one field unchanged for the modal?
I have meta_data field that I am using in the template, but i do not want that field to be modified, that value should be in the form like this
{{form.meta_data}}
This turns it into text area, with different name and id. and it expects it be changing. but i want to declare a hidden field and sets its value to the form.meta_data.value
If you have any questions regarding this please do not hesitate to ask.
Thanks.