How to pass object properties from view to template in Django? - 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>

Related

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

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.

checkbox django return all false

My checkbox always return False value
my model
ho_so_giu=models.BooleanField(default=False)
my form
report_ho_so_giu = forms.BooleanField(widget=forms.CheckboxInput(attrs={'class':'form-check-input'}),required=False)
in my html template I add more code in my html template
<div class="col-md-8">
<form method="POST" id = "form_save_report" name ="form_save_report" value="form_save_report">
<div style="color:red;background-color:white">THÔNG TIN XỬ LÝ</div>
<div class="row">
{% csrf_token %}
<div class="form-group row">
<div class="col-md-4" id="labelsohd" name="labelsohd" style="display:none">Ngày hẹn</div>
<div class="col-md-6" id="so_hd" name="so_hd" type="number" style="display:none">{{form.so_hd}}</div>
<div class="form-group row">
<div name={{form.report_ho_so_giu.name}} id ="form.report_ho_so_giu.auto_id"> {{form.report_ho_so_giu}} </div>
<label class="form-check-label" for="{{form.report_ho_so_giu.auto_id}}">{{form.report_ho_so_giu.label}}</label>
</div>
</div>
</div>
<!-- Dòng 3 -->
<div class="row">
<div class="col-md-4">
<div class="form-group row">
<div class="card-footer" >
<button type="submit" class="fa fa-save" style="font-size:20px; color:blue" class="btn btn-primary btn-block" id="submit" name="submit">Lưu báo cáo</button>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
in my view, I add more code in my view
if request.method=='POST':
#print("request POST")
form=AddReportForm(request.POST)
if form.is_valid():
#print("if form")
so_hd=form.cleaned_data["report_so_hd"]
print(request.POST.get("report_ho_so_giu")) # return None
print(request.POST.get("id_report_ho_so_giu")) # return None
print(request.GET.get("report_ho_so_giu")) # return None
print(request.GET.get("id_ho_so_giu")) # return None
print(form.cleaned_data["id_report_ho_so_giu"]) # return False with checked or unchecked
print(form.cleaned_data["report_ho_so_giu"]) # return KeyError: 'report_ho_so_giu'
daily_report=report(so_hd=so_hd, ho_so_giu=ho_so_giu)
daily_report.save()
report_response_data={}
report_response_data["so_hd"]=so_hd
return JsonResponse({"so_hd":report_response_data["so_hd"],
})
I am using ajax to return my value from form but it still return only on whenever I check or uncheck box
ho_so_giu =$('#ho_so_giu_text').val() //return on
I try to print the result it just return "None" or "False" in my view, If I use console log in html file it retunr only "on". I read some question in stackoverflow and use some answer but it just return None or False
Is there any solution to replace checkbox in this case if I cannot find out the issue?
I think the problem might be with your form, try this:
class MyForm(forms.ModelForm):
class Meta:
model = MyModel
fields = ['ho_so_giu']
widgets = {
'ho_so_giu': forms.CheckboxInput(
'class': 'form-check-input',
'id': 'ho_so_giu_text',
'name': 'ho_so_giu_text'
)
}
Notes:
I dont know why you have given a name to the form. I have never seen that before. Could that be creating issues with the post request to the backend?
Have you tried building the form on the frontend by youserlf? Using just html? It would be a good way to debug the issue.
Consider writing a hybrid form, mostly in html, with dynamic elements. This will allow you to monitor exactly what's happening. Check out the example here below:
<input name="{{ wc_user_form.last_name.name }}" type="text" class="form-control" id="{{ wc_user_form.last_name.auto_id }}" placeholder="Votre Nom" oninput="customValidation(this);" required>
<label for="{{ wc_user_form.last_name.auto_id }}">{{ wc_user_form.last_name.label }}</label>
Pretty cool eh? Pay particular attention to the {{ form.field.auto_id }} template tag. That - as you can imagine - generates the id of the field that the backend expects in order to perform a form.save()

Django custom widget: how to access field error?

I try to have more control over form rendrering and template.
Initially, I used django-crispy but event if Layout give huge range of customization I thought it became confused...
So I truned out to widget customization following django documentation and this site https://www.webforefront.com/.
I first face issue with renderding field label. I fix that by passing label argument in attrs of CustomWidget but not sure it is a good practice.
But my main issue is rendering erros...
How can I manage this?
template for input CustumoInput.html
<div class="row mb-0">
<div class="input-group input-group-sm col-6 rounded">
<label>{{ widget.label }}</label>
</div>
<div class="input-group input-group-sm mb-1 col-2">
<input
type="{{ widget.type }}"
name="{{ widget.name }}"
id="id_{{ widget.name }}"
class = "textinput textInput form-control"
{% if widget.value != None %}
value="{{ widget.value }}"
{% endif %}
{% include "django/forms/widgets/attrs.html" %}
/>
{% for error in widget.errors %}
<div class="invalid-feedback">{{ error }}</div>
{% endfor %}
</div>
</div>
widgets.py
from django import forms
class custom_input(forms.widgets.Input):
template_name = 'ecrf/CustomInput.html'
input_type = 'text'
def __init__(self, attrs={}):
super(custom_input, self).__init__(attrs)
def get_context(self, name, value, attrs):
context = super(custom_input, self).get_context(name, value, attrs)
context['widget']['label'] = self.attrs['label']
context['widget']['attrs']['placeholder'] = self.attrs['placeholder']
context['widget']['attrs']['autocomplete'] = self.attrs['autocomplete']
context['widget']['attrs']['data-mask'] = self.attrs['data-mask']
return context
forms.py
self.fields['inc_tai'] = forms.IntegerField(label = 'Taille (mesurée ou déclarée, en cm)',widget=custom_input(attrs={'label':'Taille (mesurée ou déclarée, en cm)','autocomplete': 'off','placeholder': '000','data-mask':'000'}),required=False,disabled=True)
template.htlm (usage)
{{ form.inc_tai }}

Django: unable to make a calculation inside a template

I've created a e-commerce Django application and in the back office of this application, I have a page that is supposed to show some statisctics.
I'm trying to display benefits or losses.
For the costs, I've created a #property in the model as following:
class Statistics(models.Model):
"""
The Statistics model represents the statistics that can be calculated
"""
costs_infra = models.FloatField(verbose_name="Costs Infrastructure")
costs_salary = models.FloatField(verbose_name="Costs Salary")
class Meta:
verbose_name_plural = "Statistics"
def __str__(self):
"""Unicode representation of Statistics"""
return " Infra costs: {}, Salary costs: {}".format(
self.costs_infra,
self.costs_salary
)
#property
def calculate_costs(self):
return self.costs_infra + self.costs_salary
For the total income, I've calculated it inside a view as following:
#group_required('Administrator', 'Manager')
def stats_home(request):
total_users = User.objects.all().count()
costs = Statistics.objects.all()
subscriptions_1month = Subscription.objects.get(plan_name='1 Month')
subscriptions_1year = Subscription.objects.get(plan_name='1 Year')
subscriptions_3year = Subscription.objects.get(plan_name='3 Years')
user_subscriptions_1month = UserSubscription.objects.filter(subscription=subscriptions_1month).annotate(Count('user', distinct=True)).count()
user_subscriptions_1year = UserSubscription.objects.filter(subscription=subscriptions_1year).annotate(Count('user', distinct=True)).count()
user_subscriptions_3years = UserSubscription.objects.filter(subscription=subscriptions_3year).annotate(Count('user', distinct=True)).count()
income_per_subscription_1month = Subscription.objects.get(plan_name='1 Month').price * UserSubscription.objects.filter(subscription=subscriptions_1month).count()
income_per_subscription_1year = Subscription.objects.get(plan_name='1 Year').price * UserSubscription.objects.filter(subscription=subscriptions_1year).count()
income_per_subscription_3years = Subscription.objects.get(plan_name='3 Years').price * UserSubscription.objects.filter(subscription=subscriptions_3year).count()
total_income = income_per_subscription_1month + income_per_subscription_1year + income_per_subscription_3years
return render (request, "stats_home.html", locals())
An finally, I'm trying to make a simple calculation (total income - total costs) but I'm unable to do this inside the template, as far as I could see and my research led me.
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% load has_group %}
{% block title %} Statistics Home {% endblock %}
{% block content %}
<div class="card border-dark mb-4" id="profil">
<h5 class="card-header bg-dark text-white">Total number of users subscribed to the site:</h5>
<div class="card-body">
{{total_users}}
</div>
</div>
<div class="card border-dark mb-4" id="profil">
<h5 class="card-header bg-dark text-white">Total number of users per subscription type:</h5>
<div class="card-body">
<br>1 Month: {{user_subscriptions_1month}}
<br>1 Year: {{user_subscriptions_1year}}
<br>3 Years: {{user_subscriptions_3years}}
</div>
</div>
<div class="card border-dark mb-4" id="profil">
<h5 class="card-header bg-dark text-white">Costs:</h5>
<div class="card-body">
{% for cost in costs %}
<p>Infrastructure Costs: {{cost.costs_infra}}</p>
<p>Salary Costs: {{cost.costs_salary}}</p>
<p>Total Costs: {{cost.calculate_costs}}</p>
{% endfor %}
</div>
</div>
<div class="card border-dark mb-4" id="profil">
<h5 class="card-header bg-dark text-white"> Total Income: </h5>
<div class="card-body">
{{total_income}}
</div>
</div>
<div class="card border-dark mb-4" id="profil">
<h5 class="card-header bg-dark text-white"> Benefits/Loss: </h5>
<div class="card-body">
Benefits/Loss: {{total_income - cost.calculate_costs}}
</div>
</div>
By doing {{total_income - cost.calculate_costs}} i get an error
Could not parse the remainder: ' - cost.calculate_costs' from 'total_income - cost.calculate_costs'
The thing is that i can get the total costs with {{cost.calculate_costs}} and the total income with {{total_income}}, but somehow i'm unable to make a simple substraction inside the template.
What would be the best way to achieve this?
Your issue is that you are trying to access cost.calculate_costs outside of loop {% for cost in costs %}. Since cost is not declared, you are getting this error. On top of that, django doesn't natively support calculation in template - reason is that it's best to keep logic entirely in views and leave only presentation in templates. There is however addition filter, but you still need to calculate total_costs.
Your solution is calculate total_profit in view:
def stats_home(request):
...
total_costs = sum(cost.calculate_costs for cost in costs)
total_profit = total_income - total_costs
return render (request, "stats_home.html", locals())
<div class="card border-dark mb-4" id="profil">
<h5 class="card-header bg-dark text-white"> Benefits/Loss: </h5>
<div class="card-body">
Benefits/Loss: {{ total_profit }}
</div>
</div>
The best thing you can do is, rather than doing calculation in templates do it in views as it is recommended not to do this in templates as it will decrease the performance of the website do this instead:
views.py
variable = total_income - total_costs
context = {"variable":variable}
return render(request, "stats_home.html", locals(), context)

Django form rendering isn't passing along original HTTP request object

New to Django/Python/stackexchange...
I have a Jquery Datatable and I'm having difficulty passing along the value of a cell (called email_id) of the table as an HTTP parm to be used when handling a new form page.
The view is initially receiving the request correctly per the debug output:
WSGIRequest: GET '/main_app/makeTask/?csrfmiddlewaretoken=gDeTwaNfGNLO7cdMk1
B9gsdpcGYpKAyL&email_id=14d2a002852e1738'
It successfully extracts email_id with request.GET.get() on the first call (then proceeds to construct the form and render it) but it gets dropped when the form is being rendered. I.e., email_msg_id is extracted correctly on the GET but not on the subsequent POST.
Here is the extraction code along with the render statement. The problem is the 'email_id' is not propagating when I render the form so it cannot be used by view when it processes the form input.
email_msg_id = request.GET.get('email_id', "GET - no email_id")
...
return render(request, 'main_app/makeTask.html', {'form': form, 'email_id': email_msg_id})
Debug message:
<WSGIRequest: GET '/main_app/makeTask.html'>
Here are relevant sections of urls.py:
url(r'^makeTask', views.make_task, name='makeTask'),
My Jquery call (which appears to be working/passing along the email_ID correctly:
$('#make_task').click( function () {
alert( table.cell('.selected',4).data()+' Converted to Task');
// alert(table.cell('.selected',0).data()+' Make Task selected:');
$.ajax({
type: "GET",
url: "/main_app/makeTask/",
data: {
'csrfmiddlewaretoken': '{{ csrf_token }}',
'email_id' : table.cell('.selected',4).data(),
},
success: makeTaskSuccess,
dataType: 'html'
});
function makeTaskSuccess(data)
{
alert('Convert email to task ');
}
} );
Here is the view (with unrelated stuff removed):
def make_task(request):
if request.method == "GET":
email_msg_id = request.GET.get('email_id', "GET - no email_id") # from the post dictionary of the request object passed in
else:
email_msg_id = request.POST.get('email_id', "POST - no email_id")
print "EMAIL_MSG_ID: ", email_msg_id, "METHOD: ", request.method
if request.method == 'POST':
form = MakeTaskForm(request.POST or None) # Create a form instance and populate it with the data
if form.is_valid():
# process and save to db
#...
return HttpResponseRedirect(reverse('main_app.views.index'))
else:
print form.errors
return
else:
form = MakeTaskForm()
return render(request, 'main_app/makeTask.html', {'form': form, 'email_id': email_msg_id})
#return render(request, 'main_app/makeTask.html', context_dict)
UPDATE: added relevant template code:
<form id="make_task_form" method="post" action="/main_app/makeTask/", method="Post">
{% csrf_token %}
<div class="row">
<div class="col-lg-8 col-xs-12">
<div class="form-group">
<label>Due Date: &nbsp</label>
<label class="radio-inline">
<input type="radio" name="due_selection" id="optionsRadiosInline1" value="TODAY" {{ form.due_selection }} Today
</label>
</div>
</div>
<div class="col-lg-4 col-xs-12">
<p>Specific Date: <input type="text" id="datepicker" {{ form.due_date }}</p>
</div>
</div>
<div class="row">
<div class="col-lg-4 col-xs-12">
<button type="submit" class="btn btn-info btn-med" id="btn-make-task">
Make Task
</button>
<i class="fa fa-trash-o fa-2x pull-right"></i>
</div>
</div>
</form>
From the code you posted I assume you're using GET for AJAX and POST in your form.
The form code you posted is wrong, access your form fields in your template like this (also get rid of the duplicated method attribute in <form>):
<form id="make_task_form" method="post" action="/main_app/makeTask/">
{% csrf_token %}
<div class="row">
<div class="col-lg-8 col-xs-12">
{{ form.due_selection }}
</div>
<div class="col-lg-4 col-xs-12">
{{ form.due_date }}
</div>
</div>
<div class="row">
<div class="col-lg-4 col-xs-12">
<button type="submit" class="btn btn-info btn-med" id="btn-make-task">
Make Task
</button>
<i class="fa fa-trash-o fa-2x pull-right"></i>
</div>
</div>
</form>
To use <input type="radio"> for the field due_selection you should specify the RadioSelect widget in your ModelForm class:
CHOICES = (('1', 'First',), ('2', 'Second',))
due_selection = forms.ChoiceField(widget=forms.RadioSelect, choices=CHOICES)