Dynamically updating values of a field depending on the choice selected in another field in Django
Question:
I have two tables.
Inventory
and Invoice
.
InventoryModel:
from django.db import models
class Inventory(models.Model):
product_number = models.IntegerField(primary_key=True)
product = models.TextField(max_length=3000, default='', blank=True, null=True)
title = models.CharField('Title', max_length=120, default='', blank=True, unique=True)
amount = models.IntegerField('Unit Price', default=0, blank=True, null=True)
def __str__(self):
return self.title
InvoiceModel:
from django.db import models
from inventory.models import Inventory
class Invoice(models.Model):
invoice_number = models.IntegerField(blank=True, primary_key=True)
line_one = models.ForeignKey(Inventory, on_delete=models.CASCADE, related_name='+', verbose_name="Line 1", blank=True, default='', null=True)
line_one_quantity = models.IntegerField('Quantity', default=0, blank=True, null=True)
line_one_unit_price = models.IntegerField('Unit Price(₹)', default=0, blank=True, null=True)
line_one_total_price = models.IntegerField('Line Total(₹)', default=0, blank=True, null=True)
line_two = models.ForeignKey(Inventory, on_delete=models.CASCADE, related_name='+', verbose_name="Line 2", blank=True, default='', null=True)
line_two_quantity = models.IntegerField('Quantity', default=0, blank=True, null=True)
line_two_unit_price = models.IntegerField('Unit Price(₹)', default=0, blank=True, null=True)
line_two_total_price = models.IntegerField('Line Total(₹)', default=0, blank=True, null=True)
line_three = models.ForeignKey(Inventory, on_delete=models.CASCADE, related_name='+', verbose_name="Line 3", blank=True, default='', null=True)
line_three_quantity = models.IntegerField('Quantity', default=0, blank=True, null=True)
line_three_unit_price = models.IntegerField('Unit Price(₹)', default=0, blank=True, null=True)
line_three_total_price = models.IntegerField('Line Total(₹)', default=0, blank=True, null=True)
line_four = models.ForeignKey(Inventory, on_delete=models.CASCADE,related_name='+', verbose_name="Line 4", blank=True, default='', null=True)
line_four_quantity = models.IntegerField('Quantity', default=0, blank=True, null=True)
line_four_unit_price = models.IntegerField('Unit Price(₹)', default=0, blank=True, null=True)
line_four_total_price = models.IntegerField('Line Total(₹)', default=0, blank=True, null=True)
line_five = models.ForeignKey(Inventory, on_delete=models.CASCADE, related_name='+', verbose_name="Line 5", blank=True, default='', null=True)
line_five_quantity = models.IntegerField('Quantity', default=0, blank=True, null=True)
line_five_unit_price = models.IntegerField('Unit Price(₹)', default=0, blank=True, null=True)
line_five_total_price = models.IntegerField('Line Total(₹)', default=0, blank=True, null=True)
line_six = models.ForeignKey(Inventory, on_delete=models.CASCADE, related_name='+', verbose_name="Line 6", blank=True, default='', null=True)
line_six_quantity = models.IntegerField('Quantity', default=0, blank=True, null=True)
line_six_unit_price = models.IntegerField('Unit Price(₹)', default=0, blank=True, null=True)
line_six_total_price = models.IntegerField('Line Total(₹)', default=0, blank=True, null=True)
line_seven = models.ForeignKey(Inventory, on_delete=models.CASCADE, related_name='+', verbose_name="Line 7", blank=True, default='', null=True)
line_seven_quantity = models.IntegerField('Quantity', default=0, blank=True, null=True)
line_seven_unit_price = models.IntegerField('Unit Price(₹)', default=0, blank=True, null=True)
line_seven_total_price = models.IntegerField('Line Total(₹)', default=0, blank=True, null=True)
line_eight = models.ForeignKey(Inventory, on_delete=models.CASCADE, related_name='+', verbose_name="Line 8", blank=True, default='', null=True)
line_eight_quantity = models.IntegerField('Quantity', default=0, blank=True, null=True)
line_eight_unit_price = models.IntegerField('Unit Price(₹)', default=0, blank=True, null=True)
line_eight_total_price = models.IntegerField('Line Total(₹)', default=0, blank=True, null=True)
line_nine = models.ForeignKey(Inventory, on_delete=models.CASCADE, related_name='+', verbose_name="Line 9", blank=True, default='', null=True)
line_nine_quantity = models.IntegerField('Quantity', default=0, blank=True, null=True)
line_nine_unit_price = models.IntegerField('Unit Price(₹)', default=0, blank=True, null=True)
line_nine_total_price = models.IntegerField('Line Total(₹)', default=0, blank=True, null=True)
line_ten = models.ForeignKey(Inventory, on_delete=models.CASCADE, related_name='+', verbose_name="Line 10", blank=True, default='', null=True)
line_ten_quantity = models.IntegerField('Quantity', default=0, blank=True, null=True)
line_ten_unit_price = models.IntegerField('Unit Price(₹)', default=0, blank=True, null=True)
line_ten_total_price = models.IntegerField('Line Total(₹)', default=0, blank=True, null=True)
def __unicode__(self):
return self.invoice_number
I have a form where the person selects an Inventory.title
in the line_one
. I want the line_one_unit_price
to be automatically filled according to the option selected in the line_one
. That is, I want the amount of that product to be displayed.
I tried using a JSON object and then sending it to the template.
views.py:
def add_invoice(request):
form = InvoiceForm(request.POST or None)
data = Inventory.objects.all()
dict_obj = model_to_dict(data)
serialized = json.dumps(dict_obj)
total_invoices = Invoice.objects.count()
queryset = Invoice.objects.order_by('-invoice_date')[:6]
if form.is_valid():
form.save()
messages.success(request, 'Successfully Saved')
return redirect('/invoice/list_invoice')
context = {
"form": form,
"title": "New Invoice",
"total_invoices": total_invoices,
"queryset": queryset,
"serialized":serialized,
}
return render(request, "entry.html", context)
I am not sure how I can access that JSON file in javascript? what will be the name of that JSON object? Is it possible to access that in JavaScript? How can I use it to update the price of an item in the forms dynamically?
Thanks.
EDIT:
I added this in the HTML file:
{{ data|json_script:"hello-data" }}
<script type="text/javascript">
const data = JSON.parse(document.getElementById('hello-data').textContent);
document.getElementById('id_line_one').onchange = function(){
document.getElementById('id_line_one_unit_price').value = data[this.value];
};
</script>
views.py
:
def add_invoice(request):
form = InvoiceForm(request.POST or None)
data = serializers.serialize("json", Inventory.objects.all())
total_invoices = Invoice.objects.count()
queryset = Invoice.objects.order_by('-invoice_date')[:6]
if form.is_valid():
form.save()
messages.success(request, 'Successfully Saved')
return redirect('/invoice/list_invoice')
context = {
"form": form,
"title": "New Invoice",
"total_invoices": total_invoices,
"queryset": queryset,
"data": data,
}
return render(request, "entry.html", context)
The output I get when I select an item is {
?
I don’t know why?
I guess it’s because the logic I used in the javascript is incorrect?
JSON File:
[{"model": "inventory.inventory", "pk": 1, "fields": {"product": "nice", "title": "Asus", "amount": 56000}}, {"model": "inventory.inventory", "pk": 2, "fields": {"product": "nice", "title": "Lenovo", "amount": 55000}}]
Edit2:
I added this in the script
:
<script type="text/javascript">
const data = JSON.parse(document.getElementById('hello-data').textContent);
document.getElementById('id_line_one').onchange = function(){
var line_one=document.getElementById('id_line_one').value;
document.getElementById('id_line_one_unit_price').value = data.fields[line_one].amount;
};
</script>
And now I am getting an Uncaught TypeError: Cannot read properties of undefined (reading '1')
error. Whenever I select the object from the dropdown, It’s value is stored from 1, etc. So the first object in the list returns it’s primary key, i.e., 1.
I think I am accessing the fields in a wrong way.
Answers:
when you pass the dict_obj to your template you can get the json with
{{ dict_obj|json_script:"hello-data" }}
const value = JSON.parse(document.getElementById('hello-data').textContent);
https://docs.djangoproject.com/en/4.0/ref/templates/builtins/#json-script
change the value depending on the selected item
const data = {
"apple": 2.3,
"orange": 1.7,
}
document.getElementById('inventory_item').onchange = function() {
document.getElementById('price').value = data[this.value];
};
<select name="InventoryDropdown" id="inventory_item">
<option value="" selected disabled>---</option>
<option value="apple">apple</option>
<option value="orange">orange</option>
</select>
<input type="text" id="price" >
if i get it right you get the pk
of inventory in this line
var line_one=document.getElementById('id_line_one').value
so you need to iterate over the json, to find it – i tested it with your printed JSON File
function getDictEntryByPk(dict_list, pk) {
for (let i=0; i < dict_list.length; i++) {
if (dict_list[i].pk == pk) {
return dict_list[i]
}
}
}
and use it like that:
var line_one=document.getElementById('id_line_one').value;
var line_one_data = getDictEntryByPk(data, line_one);
document.getElementById('id_line_one_unit_price').value = line_one_data.fields.amount
EDIT:
same with less code:
function getDictEntryByPk(dict_list, pk) {
return dict_list.find(element => element.pk == pk);
}
An easy alternative would be to redefine your Inventory __str__
method to include the unit price, so the drop down becomes e.g. ‘Inventory #1 – ₹500’. You don’t then need to worry about JS, JSON, or updating the amount. The total amount can be refreshed whenever the form is submitted with the quantity. Otherwise you need JS to get the unit price and to calculate the total price with the quantity.
As an aside, your models have lines manually added to the Invoice – i.e. line_one, line_two, etc. – this isn’t a good way of structuring your models or data. You should add an intermediary model called ‘InvoiceLine’ or something similar that can handle any numbers of invoice lines and connects with FKs between your Inventory and Invoice models. The reason why this is particularly important is because at the moment all of your lines have models.CASCADE
as the on_delete option, meaning that an Invoice with ten lines could be completely deleted even if just one of your FK-relation Inventory models is deleted!
I figured out how to achieve this functionality. I wish I could give the credits to a single person but it’s really a combination of many people’s answers.
in the views.py
which returns the HTML page where I want this functionality, I wrote this code, It returns the Product objects to the HTML file:
model_data=Inventory.objects.values_list('product_number','amount')
data=[model for model in model_data.values()]
context = {
"data": data,
}
return render(request, "entry.html", context)
In the entry.html
, I access the data using: {{ data|json_script:"hello-data" }}
Then the data I get is:
[{"product_number": 1, "product": "Laptop", "title": "Lenovo", "amount": 50000}, {"product_number": 2, "product": "Single Table Tennis Ball", "title": "Table Tennis Ball Share", "amount": 4}]
In the entry.html
I used some javascript:
<script type="text/javascript">
var data = JSON.parse(document.getElementById('hello-data').textContent);
document.getElementById('id_line_one').onchange = function(event){
var data1 = data.find(({product_number}) => product_number == event.target.value);
document.getElementById('id_line_one_unit_price').value = data1 && data1.amount ? data1.amount : 0;
};
So, I parse the JSON data, and then when the user clicks the drop-down menu whose id is id_line_one
, I check if the returned number(i.e., product_number) matches with any product_number
present in the variable data
. and then I change the amount of that respective field.
Referrence: https://stackoverflow.com/a/72923853/9350154
I have two tables.
Inventory
and Invoice
.
InventoryModel:
from django.db import models
class Inventory(models.Model):
product_number = models.IntegerField(primary_key=True)
product = models.TextField(max_length=3000, default='', blank=True, null=True)
title = models.CharField('Title', max_length=120, default='', blank=True, unique=True)
amount = models.IntegerField('Unit Price', default=0, blank=True, null=True)
def __str__(self):
return self.title
InvoiceModel:
from django.db import models
from inventory.models import Inventory
class Invoice(models.Model):
invoice_number = models.IntegerField(blank=True, primary_key=True)
line_one = models.ForeignKey(Inventory, on_delete=models.CASCADE, related_name='+', verbose_name="Line 1", blank=True, default='', null=True)
line_one_quantity = models.IntegerField('Quantity', default=0, blank=True, null=True)
line_one_unit_price = models.IntegerField('Unit Price(₹)', default=0, blank=True, null=True)
line_one_total_price = models.IntegerField('Line Total(₹)', default=0, blank=True, null=True)
line_two = models.ForeignKey(Inventory, on_delete=models.CASCADE, related_name='+', verbose_name="Line 2", blank=True, default='', null=True)
line_two_quantity = models.IntegerField('Quantity', default=0, blank=True, null=True)
line_two_unit_price = models.IntegerField('Unit Price(₹)', default=0, blank=True, null=True)
line_two_total_price = models.IntegerField('Line Total(₹)', default=0, blank=True, null=True)
line_three = models.ForeignKey(Inventory, on_delete=models.CASCADE, related_name='+', verbose_name="Line 3", blank=True, default='', null=True)
line_three_quantity = models.IntegerField('Quantity', default=0, blank=True, null=True)
line_three_unit_price = models.IntegerField('Unit Price(₹)', default=0, blank=True, null=True)
line_three_total_price = models.IntegerField('Line Total(₹)', default=0, blank=True, null=True)
line_four = models.ForeignKey(Inventory, on_delete=models.CASCADE,related_name='+', verbose_name="Line 4", blank=True, default='', null=True)
line_four_quantity = models.IntegerField('Quantity', default=0, blank=True, null=True)
line_four_unit_price = models.IntegerField('Unit Price(₹)', default=0, blank=True, null=True)
line_four_total_price = models.IntegerField('Line Total(₹)', default=0, blank=True, null=True)
line_five = models.ForeignKey(Inventory, on_delete=models.CASCADE, related_name='+', verbose_name="Line 5", blank=True, default='', null=True)
line_five_quantity = models.IntegerField('Quantity', default=0, blank=True, null=True)
line_five_unit_price = models.IntegerField('Unit Price(₹)', default=0, blank=True, null=True)
line_five_total_price = models.IntegerField('Line Total(₹)', default=0, blank=True, null=True)
line_six = models.ForeignKey(Inventory, on_delete=models.CASCADE, related_name='+', verbose_name="Line 6", blank=True, default='', null=True)
line_six_quantity = models.IntegerField('Quantity', default=0, blank=True, null=True)
line_six_unit_price = models.IntegerField('Unit Price(₹)', default=0, blank=True, null=True)
line_six_total_price = models.IntegerField('Line Total(₹)', default=0, blank=True, null=True)
line_seven = models.ForeignKey(Inventory, on_delete=models.CASCADE, related_name='+', verbose_name="Line 7", blank=True, default='', null=True)
line_seven_quantity = models.IntegerField('Quantity', default=0, blank=True, null=True)
line_seven_unit_price = models.IntegerField('Unit Price(₹)', default=0, blank=True, null=True)
line_seven_total_price = models.IntegerField('Line Total(₹)', default=0, blank=True, null=True)
line_eight = models.ForeignKey(Inventory, on_delete=models.CASCADE, related_name='+', verbose_name="Line 8", blank=True, default='', null=True)
line_eight_quantity = models.IntegerField('Quantity', default=0, blank=True, null=True)
line_eight_unit_price = models.IntegerField('Unit Price(₹)', default=0, blank=True, null=True)
line_eight_total_price = models.IntegerField('Line Total(₹)', default=0, blank=True, null=True)
line_nine = models.ForeignKey(Inventory, on_delete=models.CASCADE, related_name='+', verbose_name="Line 9", blank=True, default='', null=True)
line_nine_quantity = models.IntegerField('Quantity', default=0, blank=True, null=True)
line_nine_unit_price = models.IntegerField('Unit Price(₹)', default=0, blank=True, null=True)
line_nine_total_price = models.IntegerField('Line Total(₹)', default=0, blank=True, null=True)
line_ten = models.ForeignKey(Inventory, on_delete=models.CASCADE, related_name='+', verbose_name="Line 10", blank=True, default='', null=True)
line_ten_quantity = models.IntegerField('Quantity', default=0, blank=True, null=True)
line_ten_unit_price = models.IntegerField('Unit Price(₹)', default=0, blank=True, null=True)
line_ten_total_price = models.IntegerField('Line Total(₹)', default=0, blank=True, null=True)
def __unicode__(self):
return self.invoice_number
I have a form where the person selects an Inventory.title
in the line_one
. I want the line_one_unit_price
to be automatically filled according to the option selected in the line_one
. That is, I want the amount of that product to be displayed.
I tried using a JSON object and then sending it to the template.
views.py:
def add_invoice(request):
form = InvoiceForm(request.POST or None)
data = Inventory.objects.all()
dict_obj = model_to_dict(data)
serialized = json.dumps(dict_obj)
total_invoices = Invoice.objects.count()
queryset = Invoice.objects.order_by('-invoice_date')[:6]
if form.is_valid():
form.save()
messages.success(request, 'Successfully Saved')
return redirect('/invoice/list_invoice')
context = {
"form": form,
"title": "New Invoice",
"total_invoices": total_invoices,
"queryset": queryset,
"serialized":serialized,
}
return render(request, "entry.html", context)
I am not sure how I can access that JSON file in javascript? what will be the name of that JSON object? Is it possible to access that in JavaScript? How can I use it to update the price of an item in the forms dynamically?
Thanks.
EDIT:
I added this in the HTML file:
{{ data|json_script:"hello-data" }}
<script type="text/javascript">
const data = JSON.parse(document.getElementById('hello-data').textContent);
document.getElementById('id_line_one').onchange = function(){
document.getElementById('id_line_one_unit_price').value = data[this.value];
};
</script>
views.py
:
def add_invoice(request):
form = InvoiceForm(request.POST or None)
data = serializers.serialize("json", Inventory.objects.all())
total_invoices = Invoice.objects.count()
queryset = Invoice.objects.order_by('-invoice_date')[:6]
if form.is_valid():
form.save()
messages.success(request, 'Successfully Saved')
return redirect('/invoice/list_invoice')
context = {
"form": form,
"title": "New Invoice",
"total_invoices": total_invoices,
"queryset": queryset,
"data": data,
}
return render(request, "entry.html", context)
The output I get when I select an item is {
?
I don’t know why?
I guess it’s because the logic I used in the javascript is incorrect?
JSON File:
[{"model": "inventory.inventory", "pk": 1, "fields": {"product": "nice", "title": "Asus", "amount": 56000}}, {"model": "inventory.inventory", "pk": 2, "fields": {"product": "nice", "title": "Lenovo", "amount": 55000}}]
Edit2:
I added this in the script
:
<script type="text/javascript">
const data = JSON.parse(document.getElementById('hello-data').textContent);
document.getElementById('id_line_one').onchange = function(){
var line_one=document.getElementById('id_line_one').value;
document.getElementById('id_line_one_unit_price').value = data.fields[line_one].amount;
};
</script>
And now I am getting an Uncaught TypeError: Cannot read properties of undefined (reading '1')
error. Whenever I select the object from the dropdown, It’s value is stored from 1, etc. So the first object in the list returns it’s primary key, i.e., 1.
I think I am accessing the fields in a wrong way.
when you pass the dict_obj to your template you can get the json with
{{ dict_obj|json_script:"hello-data" }}
const value = JSON.parse(document.getElementById('hello-data').textContent);
https://docs.djangoproject.com/en/4.0/ref/templates/builtins/#json-script
change the value depending on the selected item
const data = {
"apple": 2.3,
"orange": 1.7,
}
document.getElementById('inventory_item').onchange = function() {
document.getElementById('price').value = data[this.value];
};
<select name="InventoryDropdown" id="inventory_item">
<option value="" selected disabled>---</option>
<option value="apple">apple</option>
<option value="orange">orange</option>
</select>
<input type="text" id="price" >
if i get it right you get the pk
of inventory in this line
var line_one=document.getElementById('id_line_one').value
so you need to iterate over the json, to find it – i tested it with your printed JSON File
function getDictEntryByPk(dict_list, pk) {
for (let i=0; i < dict_list.length; i++) {
if (dict_list[i].pk == pk) {
return dict_list[i]
}
}
}
and use it like that:
var line_one=document.getElementById('id_line_one').value;
var line_one_data = getDictEntryByPk(data, line_one);
document.getElementById('id_line_one_unit_price').value = line_one_data.fields.amount
EDIT:
same with less code:
function getDictEntryByPk(dict_list, pk) {
return dict_list.find(element => element.pk == pk);
}
An easy alternative would be to redefine your Inventory __str__
method to include the unit price, so the drop down becomes e.g. ‘Inventory #1 – ₹500’. You don’t then need to worry about JS, JSON, or updating the amount. The total amount can be refreshed whenever the form is submitted with the quantity. Otherwise you need JS to get the unit price and to calculate the total price with the quantity.
As an aside, your models have lines manually added to the Invoice – i.e. line_one, line_two, etc. – this isn’t a good way of structuring your models or data. You should add an intermediary model called ‘InvoiceLine’ or something similar that can handle any numbers of invoice lines and connects with FKs between your Inventory and Invoice models. The reason why this is particularly important is because at the moment all of your lines have models.CASCADE
as the on_delete option, meaning that an Invoice with ten lines could be completely deleted even if just one of your FK-relation Inventory models is deleted!
I figured out how to achieve this functionality. I wish I could give the credits to a single person but it’s really a combination of many people’s answers.
in the views.py
which returns the HTML page where I want this functionality, I wrote this code, It returns the Product objects to the HTML file:
model_data=Inventory.objects.values_list('product_number','amount')
data=[model for model in model_data.values()]
context = {
"data": data,
}
return render(request, "entry.html", context)
In the entry.html
, I access the data using: {{ data|json_script:"hello-data" }}
Then the data I get is:
[{"product_number": 1, "product": "Laptop", "title": "Lenovo", "amount": 50000}, {"product_number": 2, "product": "Single Table Tennis Ball", "title": "Table Tennis Ball Share", "amount": 4}]
In the entry.html
I used some javascript:
<script type="text/javascript">
var data = JSON.parse(document.getElementById('hello-data').textContent);
document.getElementById('id_line_one').onchange = function(event){
var data1 = data.find(({product_number}) => product_number == event.target.value);
document.getElementById('id_line_one_unit_price').value = data1 && data1.amount ? data1.amount : 0;
};
So, I parse the JSON data, and then when the user clicks the drop-down menu whose id is id_line_one
, I check if the returned number(i.e., product_number) matches with any product_number
present in the variable data
. and then I change the amount of that respective field.
Referrence: https://stackoverflow.com/a/72923853/9350154