'I am having trouble getting an ajax request data to show up in each dynamically created form in Django
My Django project has an app that registers sales which needs to have multiple forms saved all at once using Django formsets, I have managed to get the dynamic forms working but it needs improvement so that with a product selected in each form, the product price may appear on the price field dynamically and the total price (product_price * quantity) may also appear in the its respective field.
My code only shows the first form's price but when I add new forms (dynamically) they all appear with the the price of the first form and not the price of the product selected in in each form.
My sale models...
class Product(models.Model):
slug = models.SlugField(editable=False, unique=True, verbose_name=_("product id"))
is_active = models.BooleanField(default=True, verbose_name=_("is active"))
product_name = models.CharField(max_length=50, verbose_name=_("product name"))
cost_price = models.DecimalField(max_digits=19, decimal_places=2, verbose_name=_("cost price"))
retail_price = models.DecimalField(max_digits=19, decimal_places=2, verbose_name=_("retail price"))
class ProductSale(models.Model):
slug = models.SlugField(editable=False, unique=True, verbose_name=_("product sale id"))
product = models.ForeignKey(Product, on_delete=models.PROTECT, verbose_name=_("product"))
retail_price = models.DecimalField(max_digits=19, decimal_places=2, blank=True, null=True, verbose_name=_("retail price"))
quantity = models.SmallIntegerField(verbose_name=_("quantity"))
total = models.DecimalField(max_digits=19, decimal_places=2, blank=True, null=True, verbose_name=_("total"))
The form...
ProductSaleFormset = modelformset_factory(
ProductSale,
fields=("product", "quantity"),
extra=1,
localized_fields="__all__",
widgets={
"product": forms.Select(
attrs={
"id": "id_product",
"class": "form-control",
"style": "width:350px",
}
),
"quantity": forms.NumberInput(
attrs={
"id": "id_quantity",
"class": "form-control",
"style": "width:180px",
},
),
},
)
The html...
<form method="POST" action="">{% csrf_token %}
<div class="row">
<div class="col-sm-6 col-md-3">
<div class="form-group">
<label>{{ pos_sale_form.client.label }}</label>
{{ pos_sale_form.client }}
</div>
</div>
<div class="col-sm-6 col-md-3">
<div class="form-group">
<label>{{ pos_sale_form.payment_method.label }}</label>
{{ pos_sale_form.payment_method }}
</div>
</div>
</div>
<div class="row">
<div class="col-md-12 col-sm-12">
<div class="table-responsive">
<table class="table table-hover table-white">
<thead>
<tr>
<th style="width: 20px">#</th>
<th class="col-sm-2">{% trans "Item" %}</th>
<th style="width:100px;">{% trans "Retail Price" %}</th>
<th style="width:80px;">{% trans "Qty" %}</th>
<th>{% trans "Amount" %}</th>
<th></th>
</tr>
</thead>
<tbody>
{{ formset.management_form }}
{% for form in formset.forms %}
<tr class="col form-col spacer">
<td>{{ form.id }}</td>
<td>
{{ form.product }}
{{ form.product.help_text }}
<p class="text text-danger">{{ form.product.errors|striptags }}</p>
<!-- <input class="form-control" type="text" style="min-width:150px">-->
</td>
<td data-unit-cost="{{ form.product.retail_price }}">
<input class="form-control" id="id_product_price" style="width:100px" type="text">
</td>
<td>
{{ form.quantity }}
{{ form.quantity.help_text }}
<p class="text text-danger">{{ form.quantity.errors|striptags }}</p>
<!-- <input class="form-control" style="width:80px" type="text">-->
</td>
<td data-total-price="{{ form.total }}">
<input class="form-control" id="id_total_price" style="width:180px" type="text" placeholder="0">
</td>
<td class="input-group-append">
<button class="btn btn-success add-form-col">+</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="table-responsive">
<table class="table table-hover table-white">
<tbody>
<tr>
<td colspan="5" class="text-right">
{% trans "Discount" %} %
</td>
<td style="text-align: right; padding-right: 30px;width: 230px">
{{ pos_sale_form.discount }}
</td>
</tr>
<tr>
<td colspan="5" style="text-align: right; font-weight: bold">
{% trans "Grand Total" %}
</td>
<td id="grand_total" style="text-align: right; padding-right: 30px; font-weight: bold; font-size: 16px;width: 230px">
0.00
</td>
</tr>
</tbody>
</table>
</div>
<br>
</div>
</div>
<div class="text-center m-t-20">
<button class="btn btn-primary btn-lg " type="submit">{% trans "Sell" %}</button>
</div>
</form>
The script...
<script type="text/javascript">
function getProductPrice() {
$('#id_product').change(function(){
var product = $(this).val();
console.log(product);
$.ajax({
url : "{% url 'stock:product-retail-price' %}",
type : "GET",
dataType: "json",
data : {
"product" : product,
},
success: function(json) {
document.getElementById('id_product_price').value=json.retail_price;
document.getElementById('id_total_price').value=json.total_price;
},
failure: function(json) {
alert('Got an error dude');
}
});
});
};
function updateElementIndex(el, prefix, ndx) {
var id_regex = new RegExp('(' + prefix + '-\\d+)');
var replacement = prefix + '-' + ndx;
if ($(el).attr("for")) $(el).attr("for", $(el).attr("for").replace(id_regex, replacement));
if (el.id) el.id = el.id.replace(id_regex, replacement);
if (el.name) el.name = el.name.replace(id_regex, replacement);
}
function cloneMore(selector, prefix) {
var newElement = $(selector).clone(true);
var total = $('#id_' + prefix + '-TOTAL_FORMS').val();
newElement.find(':input:not([type=button]):not([type=submit]):not([type=reset])').each(function() {
var name = $(this).attr('name')
if(name) {
name = name.replace('-' + (total-1) + '-', '-' + total + '-');
var id = 'id_' + name;
$(this).attr({'name': name, 'id': id}).val('').removeAttr('checked');
}
});
newElement.find('label').each(function() {
var forValue = $(this).attr('for');
if (forValue) {
forValue = forValue.replace('-' + (total-1) + '-', '-' + total + '-');
$(this).attr({'for': forValue});
}
});
total++;
$('#id_' + prefix + '-TOTAL_FORMS').val(total);
$(selector).after(newElement);
var conditionRow = $('.form-col:not(:last)');
conditionRow.find('.btn.add-form-col')
.removeClass('btn-success').addClass('btn-danger')
.removeClass('add-form-col').addClass('remove-form-col')
.html('-');
return false;
}
function deleteForm(prefix, btn) {
var total = parseInt($('#id_' + prefix + '-TOTAL_FORMS').val());
if (total > 1){
btn.closest('.form-col').remove();
var forms = $('.form-col');
$('#id_' + prefix + '-TOTAL_FORMS').val(forms.length);
for (var i=0, formCount=forms.length; i<formCount; i++) {
$(forms.get(i)).find(':input').each(function() {
updateElementIndex(this, prefix, i);
});
}
}
return false;
}
$(document).on('click', '.add-form-col', function(e){
e.preventDefault();
cloneMore('.form-col:last', 'form');
return false;
});
$(document).on('click', '.remove-form-col', function(e){
e.preventDefault();
deleteForm('form', $(this));
return false;
});
$(document).ready(
getProductPrice()
);
</script>
The ajax view...
from django.contrib.auth.decorators import login_required
from django.http import JsonResponse
from django.views.decorators.http import require_GET
from .models import Product
@login_required
@require_GET
def get_product_retail_price(request):
product_id = request.GET["product"]
product_quantity = request.GET.get("id_quantity")
if product_id:
try:
retail_price = Product.objects.get(id=product_id).retail_price
total_price = retail_price * product_quantity if product_quantity else 0
response_data = {"retail_price": retail_price, "total_price": total_price}
return JsonResponse(response_data)
except Product.DoesNotExist:
pass
return JsonResponse({"status": "error"})
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|
