Add row dynamically in django formset - javascript

In my django app I have two models i.e Player and Team which are connected by many to many relationship. To add the data dynamically in my tables I want to use javascript to add Add row or Remove Row button in my forms but unable to do so.
Here are the details:
Models.py
class Player(models.Model):
pname = models.CharField(max_length=50)
hscore = models.IntegerField()
age = models.IntegerField()
def __str__(self):
return self.pname
class Team(models.Model):
tname = models.CharField(max_length=100)
player= models.ManyToManyField(Player)
def __str__(self):
return self.tname
Forms.py
class PlayerForm(forms.Form):
pname = forms.CharField()
hscore= forms.IntegerField()
age = forms.IntegerField()
PlayerFormset= formset_factory(PlayerForm)
class TeamForm(forms.Form):
tname= forms.CharField()
player= PlayerFormset()
Views.py
def post(request):
if request.POST:
form = TeamForm(request.POST)
form.player_instances = PlayerFormset(request.POST)
if form.is_valid():
team= Team()
team.tname= form.cleaned_data['tname']
team.save()
if form.player_instances.cleaned_data is not None:
for item in form.player_instances.cleaned_data:
player = Player()
player.pname= item['pname']
player.hscore= item['hscore']
player.age= item['age']
player.save()
team.player.add(player)
team.save()
else:
form = TeamForm()
return render(request, 'new.html', {'form':form})
new.html
<html>
<head>
<title>gffdfdf</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="/static/jquery.formset.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
<form id="myForm" action="" method="post" class="">
{% csrf_token %}
<h2> Team</h2>
{% for field in form %}
{{ field.errors }}
{{ field.label_tag }} : {{ field }}
{% endfor %}
{{ form.player.management_form }}
<h3> Product Instance(s)</h3>
<table id="table-product" class="table">
<thead>
<tr>
<th>player name</th>
<th>highest score</th>
<th>age</th>
</tr>
</thead>
{% for player in form.player %}
<tbody class="player-instances">
<tr>
<td>{{ player.pname }}</td>
<td>{{ player.hscore }}</td>
<td>{{ player.age }}</td>
</tr>
</tbody>
{% endfor %}
</table>
<button type="submit" class="btn btn-primary">save</button>
</form>
</div>
<script>
$(function () {
$('#myForm tbody tr').formset();
})
</script>
</body>
</html>
How can I use the javascript to add or delete the rows connected by many to many relationship ?
The above code gives us the following:

To keep it simple and generic, I reduced the OP's example to a single model and a basic formset, without the Team-Player many-to-many relation. The principle of the JavaScript part remains the same. If you do want to implement the many-to-many relation, you could use e.g. an inline formset, as explained here.
So, suppose we have a simple model:
class Player(models.Model):
name = models.CharField(max_length=50)
age = models.IntegerField()
Our view could look like this (based on the example in the docs):
def my_formset_view(request):
response = None
formset_class = modelformset_factory(
model=Player, fields=('name', 'age'), extra=0, can_delete=True)
if request.method == 'POST':
formset = formset_class(data=request.POST)
if formset.is_valid():
formset.save()
response = redirect(to='my_success_view')
else:
formset = formset_class()
if response is None:
response = render(
request, 'myapp/my_formset_template.html', dict(formset=formset))
return response
The my_formset_template.html django template below (skipping the boilerplate) enables us to add and remove formset-forms:
...
<template id="id_formset_empty_form">{{ formset.empty_form }}</template>
<form method="post" id="id_html_form" autocomplete="off">
{% csrf_token %}
<table id="id_formset_container">
{{ formset }}
</table>
<div id="id_formset_add_button" style="text-decoration: underline; cursor: pointer;">Add</div>
<input id="id_formset_submit_button" type="submit" value="Submit">
</form>
...
The HTML <template> element makes it easy to copy the content from formset.empty_form.
Side note: If we don't set autocomplete="off", the browser will cache the TOTAL_FORMS value on the management form, even after reloading the page.
Now, the following JavaScript does the job for me (no attempt was made to optimize, I just tried to make it easy to read):
window.addEventListener('load', (event) => {
// get form template and total number of forms from management form
const templateForm = document.getElementById('id_formset_empty_form');
const inputTotalForms = document.querySelector('input[id$="-TOTAL_FORMS"]');
const inputInitialForms = document.querySelector('input[id$="-INITIAL_FORMS"]');
// get our container (e.g. <table>, <ul>, or <div>) and "Add" button
const containerFormSet = document.getElementById('id_formset_container');
const buttonAdd = document.getElementById('id_formset_add_button');
const buttonSubmit = document.getElementById('id_formset_submit_button');
// event handlers
buttonAdd.onclick = addForm;
buttonSubmit.onclick = updateNameAttributes;
// form counters (note: proper form index bookkeeping is necessary
// because django's formset will create empty forms for any missing
// indices, and will discard forms with indices >= TOTAL_FORMS, which can
// lead to funny behavior in some edge cases)
const initialForms = Number(inputInitialForms.value);
let extraFormIndices = [];
let nextFormIndex = initialForms;
function addForm () {
// create DocumentFragment from template
const formFragment = templateForm.content.cloneNode(true);
// a django form is rendered as_table (default), as_ul, or as_p, so
// the fragment will contain one or more <tr>, <li>, or <p> elements,
// respectively.
for (let element of formFragment.children) {
// replace the __prefix__ placeholders from the empty form by the
// actual form index
element.innerHTML = element.innerHTML.replace(
/(?<=\w+-)(__prefix__|\d+)(?=-\w+)/g,
nextFormIndex.toString());
// add a custom attribute to simplify bookkeeping
element.dataset.formIndex = nextFormIndex.toString();
// add a delete click handler (if formset can_delete)
setDeleteHandler(element);
}
// move the fragment's children onto the DOM
// (the fragment is empty afterwards)
containerFormSet.appendChild(formFragment);
// keep track of form indices
extraFormIndices.push(nextFormIndex++);
}
function removeForm (event) {
// remove all elements with form-index matching that of the delete-input
const formIndex = event.target.dataset.formIndex;
for (let element of getFormElements(formIndex)) {
element.remove();
}
// remove form index from array
let indexIndex = extraFormIndices.indexOf(Number(formIndex));
if (indexIndex > -1) {
extraFormIndices.splice(indexIndex, 1);
}
}
function setDeleteHandler (containerElement) {
// modify DELETE checkbox in containerElement, if the checkbox exists
// (these checboxes are added by formset if can_delete)
const inputDelete = containerElement.querySelector('input[id$="-DELETE"]');
if (inputDelete) {
// duplicate the form index instead of relying on parentElement (more robust)
inputDelete.dataset.formIndex = containerElement.dataset.formIndex;
inputDelete.onclick = removeForm;
}
}
function getFormElements(index) {
// the data-form-index attribute is available as dataset.formIndex
// https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes#javascript_access
return containerFormSet.querySelectorAll('[data-form-index="' + index + '"]');
}
function updateNameAttributes (event) {
// make sure the name indices are consecutive and smaller than
// TOTAL_FORMS (the name attributes end up as dict keys on the server)
// note we do not need to update the indices in the id attributes etc.
for (let [consecutiveIndex, formIndex] of extraFormIndices.entries()) {
for (let formElement of getFormElements(formIndex)){
for (let element of formElement.querySelectorAll('input, select')) {
if ('name' in element) {
element.name = element.name.replace(
/(?<=\w+-)(__prefix__|\d+)(?=-\w+)/g,
(initialForms + consecutiveIndex).toString());
}
}
}
}
updateTotalFormCount();
}
function updateTotalFormCount (event) {
// note we could simply do initialForms + extraFormIndices.length
// to get the total form count, but that does not work if we have
// validation errors on forms that were added dynamically
const firstElement = templateForm.content.querySelector('input, select');
// select the first input or select element, then count how many ids
// with the same suffix occur in the formset container
if (firstElement) {
let suffix = firstElement.id.split('__prefix__')[1];
let selector = firstElement.tagName.toLowerCase() + '[id$="' + suffix + '"]';
let allElementsForId = containerFormSet.querySelectorAll(selector);
// update total form count
inputTotalForms.value = allElementsForId.length;
}
}
}, false);
Note that simply adding and removing formset forms is not that complicated, until something goes wrong: Approximately half the lines above have to do with handling edge cases, such as failed validation on forms that were added dynamically.

Related

Django smart selects doesn't work with JS cloning

I'm trying to create a page with ability to add any amount of form-copy.
I use django-smart-selects to make my form's field chained. It works fine if I have only 1 form on page.
Then I'm using javascript to make a function to clone form instance by pressing button and addind new form's ids to these clones.
The problem is when I press "+" button I get new cloned form on page, but chained-selection doesn't work anymore(only first one still works), and it seems that this clone copying all my choices from the firs form and remembering this state.
I see in terminal this response every time I choose any selection in chained fields:
[31/Oct/2022 16:42:27] "GET /chaining/filter/cash_table/LevelOne/flowtype/cash_table/CashFlowPattern/level_one/1/ HTTP/1.1" 200 115
[31/Oct/2022 16:42:29] "GET /chaining/filter/cash_table/LevelOne/flowtype/cash_table/CashFlowPattern/level_one/2/ HTTP/1.1" 200 105
But in cloned forms it doesn't happen.
My Formset is:
forms.py
from django import forms
from .models import CashFlowPattern
from django.forms import modelformset_factory
class CashFlowForm(forms.ModelForm):
class Meta:
model = CashFlowPattern
fields = '__all__'
CashFowFormSet = modelformset_factory(
CashFlowPattern,
fields=(
'flow_type',
'level_one',
'level_two',
'eom',
'amount',
'comment'
),
extra=1
)
views.py
class FormAddView(TemplateView):
template_name = 'cash_table/index.html'
def get(self, *args, **kwargs):
formset = CashFowFormSet(queryset=CashFlowPattern.objects.none())
return self.render_to_response({'formset': formset})
def post(self, *args, **kwargs):
end_of_month = (datetime.datetime.now() + relativedelta(day=31)).strftime('%Y-%m-%d')
formset = CashFowFormSet(data=self.request.POST)
if formset.is_valid():
forms = formset.save(commit=False)
for form in forms:
form.eom = end_of_month
form.user = self.request.user
form.save()
# return redirect(reverse_lazy("bird_list"))
return self.render_to_response({'formset': formset})
template:
<form id="form-container" method="POST">
{% csrf_token %}
{{ formset.management_form }}
<div class="empty-form">
{% for form in formset %}
{{ form.media.js }}
<p>{{ form }}</p>
{% endfor %}
</div>
<button id="add-form" type="button">+</button>
<button type="submit" class="btn btn-primary">Отправить</button>
</form>
<script>
let emptyForm = document.querySelectorAll(".empty-form")
let container = document.querySelector("#form-container")
let addButton = document.querySelector("#add-form")
let totalForms = document.querySelector("#id_form-TOTAL_FORMS")
let formNum = emptyForm.length-1
addButton.addEventListener('click', addForm)
function addForm(e) {
e.preventDefault()
let newForm = emptyForm[0].cloneNode(true) //Clone the form
let formRegex = RegExp(`form-(\\d){1}-`,'g') //Regex to find all instances of the form number
formNum++ //Increment the form number
newForm.innerHTML = newForm.innerHTML.replace(formRegex, `form-${formNum}-`) //Update the new form to have the correct form number
container.insertBefore(newForm, addButton) //Insert the new form at the end of the list of forms
totalForms.setAttribute('value', `${formNum+1}`) //Increment the number of total forms in the management form
}
</script>

dynamic form in django using choiceField

I'm building my first web app with django and am having a hard time figuring out how to use dynamic form in order to have different output for different selected choice.
for example, for my measure choice qualitative, I want the form be the same(no extra fields) but if the quantitative value is selected, I want my template to show two more fields(value_min and value_max)
the first option when qualitative value is selected
the second option when quantitative value is selected
thank you for your help...
You can't use the django tags for conditions, because it only renders from the backend, so this is a frontend issue. In my implementations I normally use javascript with the following idea:
Start with the values min and max not displayed (style.display = "None")
AddEventListener (onChange type) to the selector (in your case, Mesure)
Check if the condition is met with javascript and change the style.display to block, for example
Forms are rendered on the template before the page load. So, django variables cannot e manipulated by the user.
While rendering your form, django allows you to set classes to the form fields. use them to hide the extra fields.
example value_min = forms.CharField(widget=forms.TextInput(attrs={'class':'hide'}))
you may add a form check while clean
class MYform(forms.Form):
#....
def clean_min_value(self):
#check here for choice field value
#return value or blank accordingly
similarly you can add validators to the form to ensure this value is only set if choice is set to quantitative
value_min = forms.CharField(widget=forms.TextInput(attrs={'class':'hide'}), validators=[check_choice])
def check_choice(Value):
# validate Value
thanks #videap and #Rhea for your help... so I figured out how to resolve my problem using the guidance of videap and the post Show and hide dynamically fields in Django form
So the solution was:
for the form :
class NewCriterioForm(forms.ModelForm):
parent = TreeNodeChoiceField(queryset=Criteria.objects.all())
def __init__(self,*args,**kwargs):
super().__init__(*args,**kwargs)
self.criteria_CHOICES = [('FunCriteria','FunCriteria'),('nonFunCriteria','nonFunCriteria')]
self.mesure_CHOICES = (('Quantitative','Quantitative'),('Qualitative','Qualitative'))
self.fields['parent'].label=''
self.fields['parent'].required=False
self.fields['type']= forms.CharField(widget=forms.Select(choices=self.criteria_CHOICES))
self.fields['mesure']= forms.ChoiceField(choices=self.mesure_CHOICES)
class Meta:
model = Criteria
fields = ('name', 'parent', 'type','slug','description','mesure','value_min','value_max')
}
for the view :
......
criterion = NewCriterioForm()
return render(request, 'addCriteria.html', {'criterion': criterion})
and finaly, in the template , I put this code:
<script>
function Hide() {
if(document.getElementById('id_mesure').options[document.getElementById('id_mesure').selectedIndex].value == "Qualitative") {
document.getElementById('id_value_min').style.display = 'none';
document.getElementById('id_value_max').style.display = 'none';
} else {
document.getElementById('id_value_min').style.display = '';
document.getElementById('id_value_max').style.display = '';
}
}
window.onload = function() {
document.getElementById('id_mesure').onchange = Hide;
};
</script>
<div>
{{ criterion.name.label_tag }}{{ criterion.name }}
</div>
<tr></tr>
<div>
{{ criterion.parent.label_tag }}{{ criterion.parent }}
</div>
<div>
{{ criterion.type.label_tag }}{{ criterion.type }}
</div>
<div>
{{ criterion.slug.label_tag }}{{ criterion.slug }}
</div>
<div>
{{ criterion.description.label_tag }}{{ criterion.description }}
</div>
<div>
{{ criterion.mesure.label_tag }}{{ criterion.mesure }}
</div>
<div id="id_value_min">
{{ criterion.value_min.label_tag }}{{ criterion.value_min }}
</div>
<div id="id_value_max">
{{ criterion.value_max.label_tag }}{{ criterion.value_max }}
</div>

How to insert JavaScript object into Django JSONField

In my django web app, I have two pages- One is a form for the user to fill out the name, size, image, and specify the names of some points of interest on the image. The next page displays that image and allows the user to place some SVG circles corresponding to the points of interest on top, and when the user submits, I want the form from the first page and a JSONField for the locations of the circles (points of interest) on the image to all get saved into one model.
Currently, my solution is a page with the first form, then pass that entire form into the next page. Since I don't want the user to see that form anymore, I put it in a hidden div. I render the image from the form and using JavaScript, I draw the circles where the user clicks. When the submit button is pressed, it runs a function in the script that submits the form for a second time but updates the JSONField with the locations of all the circles before submitting.
The code below works up until the form is submitted, but when I get to the view2 function, the form.is_valid() always returns false. I suspect that the issue is here:
document.getElementById("id_spots").innerHTML = spots
I thought that since the Django form field is a JSONField, I could assign the innerHTML to be the spots object that I created in JavaScript. If I take a look at how this affects the HTML, [object Object] is inserted in the spots textarea box. I also tried JSON.stringify(spots) but to no avail.
Can anyone spot the issue? And is there a better way to assign the value of the JSONFiled to match a variable in django?
views.py
def view1(request):
if request.method == 'POST':
form = ImageForm(request.POST, request.FILES)
if form.is_valid():
b64Img = str(b64encode(form.files['img'].file.read()))[2:-1]
return render(request, 'my-app/view2.html', { 'form': form, 'base': base, 'my_img': b64Img })
else:
form = ImageForm()
return render(request, 'my-app/page1.html', { 'form':form, "base":base })
def view2(request):
if request.method == 'POST':
form = ImageForm(request.POST, request.FILES)
if form.is_valid():
form.save()
else:
form = DocumentsForm()
return render(request, 'my-app/page1.html', {'form':form, 'base':base})
models.py
class MyImage(models.Model):
name = models.CharField(max_length=50)
width = models.CharField(max_length=10)
height = models.CharField(max_length=10)
spot_names = models.CharField(max_length=1000)
img = models.ImageField(default=None, upload_to='media/')
spots = JSONField(blank=True)
def __str__(self):
return self.name
forms.py
class ImageForm(ModelForm):
class Meta:
model = MyImage
fields = '__all__'
html/js
{% extends 'main/base.html' %}
{% block content %}
<div class="my-div">
<div>
<h1>Draw Dots!</h1>
<h3 id="spot-selection">Click Spot: </h3>
</div>
<form id="drawForm" enctype="multipart/form-data" method="POST" action="/my-app/view2/">
{% csrf_token %}
<div style="display: none;">
{{ form }}
</div>
<svg id="svg">
<image id="my-img" href="data:image/png;base64,{{ my_img }}"/>
</svg>
<button typr="button" id="submit-img" style="width: 100px;">Submit</button>
</form>
</div>
{% endblock %}
{% block script_content %}
const spotNames = "{{ form.spot_names.value }}".split(",")
const width = {{ form.width.value }}
const height = {{ form.height.value }}
var spots = {}
this.window.addEventListener('DOMContentLoaded', (event) => {
const svgElem = document.getElementsByTagName("svg")
if (svgElem != undefined && svgElem.length != 0) {
const svg = svgElem[0]
const image = svg.firstChild
var i = 0
document.getElementById("spot-selection").innerHTML = "Click Spot: " + spotNames[i]
svg.onclick = function(event) {
if (i < spotNames.length) {
var newCircle = document.createElementNS('http://www.w3.org/2000/svg', 'circle')
let m = mousePositionSVG(event)
newCircle.setAttribute('cx', m.x)
newCircle.setAttribute('cy', m.y)
newCircle.setAttribute('r', '10')
newCircle.setAttribute('fill', 'red')
svg.append(newCircle)
spots[spotNames[i]] = [width*(m.x / document.getElementById("my-img").getBoundingClientRect().width), height*(m.y / document.getElementById("my-img").getBoundingClientRect().height)]
i++
if (spotNames[i] != undefined){
document.getElementById("spot-selection").innerHTML = "Click Spot: " + spotNames[i]
}else{
document.getElementById("spot-selection").innerHTML = "All Spots Clicked!"
}
}
}
document.addEventListener("keydown", function(event){
if (event.key == "Backspace" && i > 0){
const circles = document.querySelectorAll("circle")
circles[circles.length - 1].remove()
i--
document.getElementById("spot-selection").innerHTML = "Click Spot: " + spotNames[i]
}
})
}
document.getElementById("submit-img").onclick = function() {
if (document.getElementById("spot-selection").innerHTML == "All Spots Clicked!"){
document.getElementById("id_spots").innerHTML = spots
console.log({{ form.spots.value }})
document.getElementById("spotForm").submit()
}
}
})
function mousePositionSVG(e) {
let svg = document.querySelector('svg')
var p = svg.createSVGPoint()
p.x = e.clientX
p.y = e.clientY
var ctm = svg.getScreenCTM().inverse();
var p = p.matrixTransform(ctm);
return p;
}
{% endblock %}
Using JSON.stringify() is actually the correct method:
document.getElementById("id_spots").innerHTML = JSON.stringify(spots)
I thought this part was the error because I was getting errors on the form submission, and the only part of the form that changes is the JSONField. In reality, the error seems to be coming from the ImageField. It turns out that if you pass a form as context to another page, ImageFields will not be preserved.
I have made a more relevant post about the ImageField issue, so any new answers should be redirected to this post:
How to pass image file from one page to another in multi-page form (Django)

Live Form Field Calculations in Django Templates

I am creating a stock market site and would like to show the total purchase value live as the user is typing rather than as an error during form clean. The calculation involves multiplying two fields. The first is a quantity field which is just a number the user inputs. The second is the stock which is a drop down for a foreign key. The user selects a stock and an attribute of the stock is the price. I would like to multiply these fields and display the result each time a user alters a field. I would also like to do this with a model form if possible.
I have tried using {{ form.field.value }} within the template to get one the fields but I cannot get it to update for a change. For the stock field, I think my best bet is to create a matching array in javascript and once I can get a live updating form value match it to the stock price from the array. Another possibility may be using getElementById with the field id but I have been unable to get that to work so far as well.
Here is an example not made by me that is close to what I want to do Real Time Javascript Calculation Based On Form Inputs
Any help is much appreciated!
Models.py
class Stock(models.Model):
name = models.CharField(max_length=30, primary_key=True, blank=True)
#property
def price():
do some calculations
class Transaction(models.Model):
transaction_types = (
('buy', 'Buy'),
('sell', 'Sell'),
)
user = models.ForeignKey(User, on_delete=models.CASCADE)
stock = models.ForeignKey(Stock, on_delete=models.CASCADE)
transaction_type = models.CharField(max_length=4, choices=transaction_types)
quantity = models.BigIntegerField()
purchase_price = models.DecimalField(decimal_places=2, max_digits=30)
time = models.DateTimeField()
Forms.py
class TransactionForm(forms.ModelForm):
class Meta:
model = Transaction
fields = ['transaction_type', 'stock', 'quantity']
widgets = {
'quantity': forms.NumberInput(attrs={'id': 'quant', 'name': 'quant'})
}
def clean(self):
super(TransactionForm, self).clean()
quantity = self.cleaned_data.get('quantity')
stock= self.cleaned_data.get('stock')
transaction_type = self.cleaned_data.get('transaction_type')
purchase_price = stock.price
if quantity < 1:
self._errors['quantity'] = self.error_class(['You must purchase at least one share.'])
elif quantity * purchase_price > self.user.profile.buying_power and transaction_type == 'buy':
self._errors['quantity'] = self.error_class([
f'Your purchase value (${round(quantity * purchase_price, 2)}) must be less than or equal to your buying power '
f'(${round(self.user.profile.buying_power, 2)}). You can afford a maximum of '
f'{floor(self.user.profile.buying_power / purchase_price)} share(s).'])
Trade.html
{% extends "base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<div>
<form method="POST" id="trade_form">
{% csrf_token %}
<fieldset class="form-group">
<legend class="border-bottom mb-4">Trade</legend>
{{ form|crispy }}
</fieldset>
<div class="form-group">
<button class="btn btn-outline-success" type="submit">Submit</button>
</div>
</form>
<p>Quantity: <span id="quantity"></span></p>
<script>
$('input').keyup(function () { // run anytime the value changes
let quant = Number($('#quant').val());
$('#quantity').html(quant);
});
</script>
</div>
{% endblock %}
Failed attempt
<script>
var stock_prices = new Array();
{% for stock in stocks %}
stock_prices ["{{ stock .name }}"] = {{ stock .price }};
{% endfor %}
function getStockPrice() {
var price = 0;
var selectedStock = document.getElementById("id_stock");
for (var i = 0; i < selectedStock.length; i++) {
if (selectedStock[i].checked) {
price = stock_prices [selectedStock[i].value];
break;
}
}
return price;
}
function getQuantity() {
var quantity = document.getElementById("quant");
var howmany = 0;
howmany = parseInt(quantity.value);
return howmany;
}
function getTotal() {
var total = getStockPrice() * getQuantity();
document.getElementById('totalPrice').innerHTML =
"Total Price: $" + total;
}
</script>
I figured out how to do this. Basically, I made an api using Django Rest Framework. From there, I used Vue to make my form a v-model. I used watch to call the api every time the v-model changed.

keyerror in formset form field

I am making a delivery note transaction form, I have created a formset for which I want Django to ignore item transactions where the item is not selected and is empty.
forms.py
class Delivery_note_transiction_form(forms.Form):
item = forms.CharField(widget=Select2Widget(attrs={"class" : "item"}),label=False,required=False)
description = forms.CharField(widget=forms.TextInput(attrs={ 'placeholder' : 'optionall','class' : 'description'}),label=False,required=False)
quantity = forms.IntegerField(widget=forms.NumberInput(attrs={'class' : 'quantity'}),label=False,min_value=1)
id = forms.CharField(widget=forms.HiddenInput,required=False)
Delivery_note_transiction_form_formset = forms.formset_factory(Delivery_note_transiction_form,extra=1)
views.py
def feedback(request):
if request.method == "POST" and request.is_ajax():
form = Deliver_Note_Main_Modelform(request.POST)
formset = Delivery_note_transiction_form_formset(request.POST,request.FILES)
if form.is_valid() and formset.is_valid():
ins = form.save(commit=False)
ins.author = request.user
result = Customer_data.objects.get(pk=form.cleaned_data['customer'])
ins.customer_list = result
ins.save()
max_invoice = Invoice_max.objects.get(invoice_name='delivery')
max_invoice.invoice_no = max_invoice.invoice_no + 1
max_invoice.save()
print(formset)
for instant in formset:
if instant.cleaned_data['item']:
item = Product.objects.get(pk=instant.cleaned_data['item'])
description = instant.cleaned_data['description']
quantity = instant.cleaned_data['quantity']
Delivery_Note_Transiction.objects.create(
item=item,
description=description,
quantity=quantity,
delivery_invoice_no=ins
)
return JsonResponse({'success':True, 'next' : reverse_lazy('delivery note:delivery note home page')})
else:
return render(request,"delivery_note/ajax/delivery note error message.html",{"error" : form, "transiction_error": formset})
return HttpResponse("Hello from feedback!")
template.html
{% for delivery in delivery_transiction %}
<tr class=" delivery_form ">
<td class="col-sm-4">{{ delivery.item|as_crispy_field }}</td>
<td class="col-sm-4">{{ delivery.description|as_crispy_field }}</td>
<td class="col-sm-4">{{ delivery.quantity|as_crispy_field }}</td>
</tr>
{% endfor %}
The post data is sent by Ajax and the selected option is created on the template. When it is loaded, a new row is added by Ajax. The problem is I want to it ignore transaction entry if the item is not selected or is empty, but when I run it, it gives this error:
"KeyError: 'item'"
It should ignore empty or not selected items. This only happens when the item is not selected in the transaction. I want to fix this error so that it will simply ignore rows in which the item is not selected.
You have to use a try except when searching a dictionary for a key.
for instant in formset:
try:
item = Product.objects.get(pk=instant.cleaned_data['item'])
Except KeyError:
# What to do if no 'item'.
You will have to figure out where to put the rest of your code, but this will get you past the KeyError.

Categories

Resources