How can two HTML template elements be updated using (HTMX)? - django

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.

Related

Accessing dictionary inside Django template

I am customizing my item query result and marking these items as added in the cart based on DB values, i have succeeded in making this dictionary but the issue is how to access its values now inside the template
my view
menu_items =[]
menus = MenuItem.objects.all().order_by('-date_added')
for item in menus:
print(item.id)
menus_added['item']=item
if item.id in cartitem:
menus_added['is_added']=True
else:
menus_added['is_added']=False
menu_items.append(menus_added)
menus_added = {}
print(menu_items)
restaurants = Restaurants.objects.all()
categories= Category.objects.all()
return render(request,'store/store.html',context={
'product':menu_items, <-- i want access this dictionary
'restaurants':restaurants,
'categories':categories
})
this approach is not working
template
{% for item in product.item %}
<div class="col-md-4">
<figure class="card card-product-grid">
<div class="img-wrap">
<img src="{{item.image_url}}">
</div> <!-- img-wrap.// -->
<figcaption class="info-wrap">
<div class="fix-height">
{{item.item_name}}
<div class="price-wrap mt-2">
<span class="price">{{item.price}}</span>
<del class="price-old">${{item.price}}</del>
</div> <!-- price-wrap.// -->
</div>
{% if is_added %}
Added !
{% else %}
Add to cart
{% endif %}
</figcaption>
</figure>
</div> <!-- col.// -->
{% endfor %}
You don't need for loop here({% for item in product.item %}), just try {{product.image_url}}, not {{item.image_url}}
or {{ product.price }}, not {{item.price}}

Django template rendering slowly

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.

How to pass object properties from view to template in Django?

I must be missing something really basic here. I have created an object in my view to pass to my template. All the properties of my object are passed successfully but the last property, an integer, is not shown when rendering my template. Why?
Here is my code:
Views.py:
def courseListView(request):
object_list = {}
courses = Course.objects.all()
for course in courses:
object_list[course] = {
'title': course.title,
'slug': course.slug,
'thumbnail': course.thumbnail,
'get_absolute_url': '/' + course.slug,
'progress': int(lessonsCompleted.objects.filter(user=request.user.id, course=course.id).count() / course.lessons.count() * 100)
}
print(object_list)
context = {
'object_list': object_list,
}
return render(request, "courses/course_list.html", context)
This way I am creating an object that looks like this when I print it:
{<Course: Mezclar música chill y ambient>: {'title': 'Mezclar música chill y ambient', 'slug': 'mezcla-chill-ambient', 'thumbnail': <ImageFieldFile: mixing.jpeg>, 'get_absolute_url': '/mezcla-chill-ambient', 'progress': 66}, <Course: Componer bases electrónicas>: {'title': 'Componer bases electrónicas', 'slug': 'componer-bases-electronicas', 'thumbnail': <ImageFieldFile: beats.jpeg>, 'get_absolute_url': '/componer-bases-electronicas', 'progress': 75}, <Course: Melodías ultrasónicas>: {'title': 'Melodías ultrasónicas', 'slug': 'melodias-ultrasonicas', 'thumbnail': <ImageFieldFile: melodies.jpeg>, 'get_absolute_url': '/melodias-ultrasonicas', 'progress': 50}}
Ultimately I want to pass to the template the progress for each course, for the currently logged user, so I can show this information for each course in my web page.
To show that, in my template I am using this code:
{% for object in object_list %}
<div class="row justify-content-center">
<div class="col-auto mb-3">
<div class="card" style="width: 18rem;">
<img class="card-img-top" src="/static/{{ object.thumbnail }}" alt="Card image cap">
<div class="card-body">
<h5 class="card-title">{{ object.title }}</h5>
<div class="w3-light-grey w3-round-xlarge">
<div class="w3-container w3-blue w3-round-xlarge" id="progress-{{ object.slug }}" style="width:{{ object.progress }}%">{{ object.progress }}%</div>
</div>
<div class="card-footer">
Ver curso
</div>
</div>
</div>
</div>
</div>
{% endfor %}
All other properties of my object are rendered correctly: title, slug, thumbnail and get_absolute_url. However, progress, eventhough it is printed correctly in my terminal where I am running Django, when my template is rendered, this bit {{ object.progress }} is not rendering the desired result. It should be rendering the integer that represents the percentage completed for each course. Instead, it renders nothing:
https://imgur.com/a/SOIvJ5U
I can not see what I am missing. Any help would be really appreciated, I am stuck, really close to my aim of being able to show the progress for each course for the logged user.
Finally, thanks to another user I have understood the problem. The problem I had is I am not passing an object, actually it is a dictionary, it's not the same thing. If I want to access the properties of my dictionary here is what I need to do:
{% for object, props in object_list.items %} instead of {% for object in object_list %}.
In this way I can access in my for loop the property 'prog' as I wanted.
Here is how it looks like now that it is working:
views.py:
def courseListView(request):
object_list = {}
courses = Course.objects.all()
for course in courses:
object_list[course] = {
'prog': int(lessonsCompleted.objects.filter(user=request.user.id, course=course.id).count() / course.lessons.count() * 100),
}
print(courses)
print(request.user)
print(object_list)
context = {
'object_list': object_list,
}
return render(request, "courses/course_list.html", context)
Note that now I am only passing the 'prog' kay in my dictionary. I don't need the others as they are in the object anyways.
template:
<div class="container d-flex justify-content-center">
<div class="row">
<h1>Cursos</h1>
<div class="container-fluid mt-4 d-flex card-deck justify-content-center">
{% for object, props in object_list.items %}
<div class="row justify-content-center">
<div class="col-auto mb-3">
<div class="card" style="width: 18rem;">
<img class="card-img-top" src="/static/{{ object.thumbnail }}" alt="Card image cap">
<div class="card-body">
<h5 class="card-title">{{ object.title }}</h5>
<div class="w3-light-grey w3-round-xlarge">
<div class="w3-container w3-blue w3-round-xlarge" id="progress-{{ object.slug }}" style="width:{{ props.prog }}%;">{{ props.prog }}%</div>
</div>
<div class="card-footer">
Ver curso
</div>
<script>console.log({{ object_list }});</script>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>

Django: How save partial filled form state

I don't know exactly how to ask this question.
The thing is that I have a main view to create new entries in a model. This model has some 1-many relations, so I added a + button to add new entries of this fields (secondary model) in case they did not exist. When I submit this new data I redirect to the previous page (main view), and if you already filled some fields in the main view, that information is lost.
Can someone suggest me what the best way to deal with this would be?
Thanks in advance!
UPDATE:
'main model view'
class OrganismCreate(LoginRequiredMixin,CreateView):
"""Template: //catalog/templates/catalog/organism_form.html"""
model = Organism
fields = '__all__'
'main model template' (part)
<form action="" method="post">
{% csrf_token %}
<div class="panel-group">
<div class="panel panel-default">
<div class="panel-heading">Add a new entry: </div>
<div class="panel-body">
<hr>
<div class="row">
<div class="form-group col-sm-4 col-md-3">
<div class="form-group col-sm-4 col-md-3">
<label for="id_inst_own">Owner:</label>
{% render_field form.inst_own class="form-control" %}
<i class="fa fa-plus-circle "></i> Add new
</div>
<div class="panel panel-default">
<div class="panel-body">
...........................................
<button type="submit" class="btn btn-primary"> <span class="glyphicon glyphicon-filter"></span> submit </button>
</div>
</div>
</div>
Then the related model view:
def test_f(request):
if request.method == "GET":
Form = InstitutionForm()
render(request, 'catalog/institution_form.html')
if request.method == "POST":
Form = InstitutionForm(request.POST)
if Form.is_valid():
Form.save()
next = request.POST.get('next', '/')
return redirect(next)
pre=request.META.get('HTTP_REFERER')
return render(request, 'catalog/institution_form.html',{"form" : Form, "pre": pre})
And the related model template
{% block content %}
<form action="" method="post">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<input type="hidden" name="next" value="{{ pre }}">
<input type="submit" class="btn btn-success" value="Submit" />
</form>
{% endblock %}
There are a number of ways to solve this problem. Here's some suggestions. Not an exclusive list:
Save the incomplete model data in the database as as a 'draft' version of the final data. This could be a totally different model or else using the same model (assuming the related fields are nullable) and giving it a 'draft' flag or similar.
Use an inline formset to create the related objects in the same view. Django Extra Views has some useful tools for this (https://github.com/AndrewIngram/django-extra-views).
Using JavaScript, save the unfinished form data to local storage and then recover it when the original form is loaded again.
I have implemented a draft system to do this along the lines of 1. in #ChidG's answer.
In models I have something like
class AbstractThing(models.Model):
field = models.CharField()
class Meta:
abstract = True
class CompleteThing(AbstractThing):
class Meta:
managed = True
db_table = 'complete_thing'
class IncompleteThing(AbstractThing):
fields_to_not_blank = [AbstractThing._meta.get_field(x) for x in []] #if you don't want to change some fields
for f in AbstractThing._meta.fields:
if f not in fields_to_not_blank:
f.blank = True
f.null = True
class Meta:
managed = True
db_table = 'incomplete_thing'
Then you can use model forms and handle the cases in your views.

Using modelForm hidden field with Formsets without using Crispyform.

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.