keyerror in formset form field - javascript

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.

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>

Django Form Disabled Select Option Values Not Submitted Even When They are Enabled with JavaScript Before Submission

My HTML displays about 20 or more forms via modelformset_factory and each having a preselected staff name (so this is disabled in forms.py). Staff name is primary key to Staff model, so I don't want all their names be available for selection. Logged in user have some staff assigned and so they are only required to enter phone number and address. The disabled fields are enabled with Javascript before the form is submitted but I still get {'staff_name': ['This field is required.']} error when the form is submitted.
Here are my codes:
forms.py
class StaffForm(ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['staff_name'].disabled = True
views.py
class StaffModelFormSet(BaseModelFormSet):
def clean(self):
super().clean()
print(self.errors)
class StaffEntryView(LoginRequiredMixin, FormView):
def post(self, request, *args, **kwargs):
StaffFormSet = modelformset_factory(Staff, form=StaffForm, formset=StaffModelFormSet)
submitted_form = StaffFormSet(request.POST)
if submitted_form.is_valid():
self.form_valid(submitted_form)
print("This form is valid, Sir!")
else:
self.form_invalid(submitted_form)
print("This is not a valid form.")
template.html
<form method="post" class="form-control" id="staff_form" name="staff-form" onsubmit="enablePath();">
<!-- form details goes here -->
<tbody>
{% for form in formset %}
<tr class="table-striped">
<td>{{ form.staff_name }}</td>
<td>{{ form.phone }}</td>
<td>{{ form.address }}</td>
</tr>
{% endfor %}
</tbody>
script.js
function enablePath(e) {
var options = document.getElementsByTagName('select');
for (var i=0, iLen=options.length; i<iLen; i++) {
options[i].disabled = false;
}

Add row dynamically in django formset

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.

How to "load" dependent drop down upon page load?

I have a form with a dependent drop-down. This secondary drop-down is hidden whenever the primary option selected does not have any secondary options, and when the page first loads. Whenever the form is submitted, only the first field gets cleared out, since most of the time the drop-downs remain the same, however, since the script works whenever there is a change in the primary drop-down, since the load upon does not constitute a change, it just keeps the selected/submitted option on the primary drop-down, and will just display an empty secondary drop-down, even when the primary option selected does have secondary options. I got most of the JS from the drop-down from a tutorial, as I am not very familiar with it. For a more visual understanding:
This is the form when the page first loads
When you select an option that has secondary options, the other dropdown appears
After you select a Station and submit, the Employee # clears, but the other two are supposed to remain, however, when the page reloads upon submission, it looks like this, and the station has been cleared according to the debugger since there are none technically. I don't care so much about the station clearing, but more about not having an empty drop-down that should not be empty.
And when I look at the data that remained in the form, only the work area stayed, because the dependent dropdown does not load until you select another option from the drop down, and if you wanted to be able to see the Box Assembly options again, you'd have to click another option and then go back to Box Assembly (for example)
How could I fix this issue? Is there a way to force the javascript to attempt to load first so that it checks if the option that remained does have the secondary options, whether it has been triggered or not?
forms.py
class WarehouseForm(AppsModelForm):
class Meta:
model = EmployeeWorkAreaLog
widgets = {
'employee_number': ForeignKeyRawIdWidget(EmployeeWorkAreaLog._meta.get_field('employee_number').remote_field, site, attrs={'id':'employee_number_field'}),
}
fields = ('employee_number', 'work_area', 'station_number')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['station_number'].queryset = Station.objects.none()
if 'work_area' in self.data:
try:
work_area_id = int(self.data.get('work_area'))
self.fields['station_number'].queryset = Station.objects.filter(work_area_id=work_area_id).order_by('name')
except (ValueError, TypeError):
pass
elif self.instance.pk:
self.fields['station_number'].queryset = self.instance.work_area.stations.order_by('name')
views.py
def enter_exit_area(request):
enter_without_exit = None
exit_without_enter = None
if request.method == 'POST':
form = WarehouseForm(request.POST)
if form.is_valid():
emp_num = form.cleaned_data['employee_number']
area = form.cleaned_data['work_area']
station = form.cleaned_data['station_number']
# Submission logic
form = WarehouseForm(initial={'employee_number': '', 'work_area': area, 'station_number': station})
else:
form = WarehouseForm()
return render(request, "operations/enter_exit_area.html", {
'form': form,
'enter_without_exit': enter_without_exit,
'exit_without_enter': exit_without_enter,
})
urls.py
urlpatterns = [
url(r'enter-exit-area/$', views.enter_exit_area, name='enter_exit_area'),
path('ajax/load-stations/', views.load_stations, name='ajax_load_stations'),
]
At the end of this html is the script that handles the dependent drop-down
enter_exit_area.html
{% extends "operations/base.html" %}
{% block main %}
<form id="warehouseForm" action="" method="POST" data-stations-url="{% url 'operations:ajax_load_stations' %}" novalidate >
{% csrf_token %}
<div>
<div>
<label>Employee #</label>
{{ form.employee_number }}
</div>
<div>
<label>Work Area</label>
{{ form.work_area }}
</div>
<div class="col-xs-8" id="my-hidden-div">
<label>Station</label>
{{ form.station_number }}
</div>
</div>
</form>
<script>
function loadStations() {
var url = $("#warehouseForm").attr("data-stations-url");
var workAreaId = $(this).val();
var $stationNumberField = $("#{{ form.station_number.id_for_label }}");
$.ajax({
url: url,
data: {
'work_area': workAreaId
},
success: function (data) {
$("#my-hidden-div").show(); // show it
$stationNumberField.html(data);
// Check the length of the options child elements of the select
if ($stationNumberField.find("option").length === 1) {
$stationNumberField.parent().hide(); // Hide parent of the select node
} else {
// If any option, ensure the select is shown
$stationNumberField.parent().show();
}
}
});
}
$("#id_work_area").change(loadStations);
$(document).ready(loadStations);
</script>
{% endblock main %}
station_number_dropdown_options.html
<option value="">---------</option>
{% for station in stations %}
<option value="{{ station.pk }}">{{ station.name }}</option>
{% endfor %}
I see that you have $(document).ready(loadStations);.
But the problem is that in loadStations, you do var workAreaId = $(this).val();.
this will be document, and $(document).val() is an empty string.
Either hardcode the selector in loadStations:
// var workAreaId = $(this).val();
var workAreaId = $("#id_work_area").val();
Or trigger the change from the element instead:
$("#id_work_area").change(loadStations);
// $(document).ready(loadStations);
$("#id_work_area").change();

How to select document Ids that are being populated via Flask on page load

I am using a submit button to pass form values into flask, which then populate the ids and values of an html table on another page.
I then want to select those values by their ids and use them to filter a dataset. However, I'm finding that when I try to use d3.select(#thing-id).value, I am getting back undefined, I think because the file which is selecting the ids is running before they are populated in the html.
I've tried to use a variety of selection methods including d3 and get Document by Id.
The Flask:
#app.route("/prediction", methods=["GET", "POST"])
def predict():
if request.method == 'POST':
new_film = [request.form["adults"], request.form["years"],
request.form["film_length"], \
request.form["genre_1"], request.form["genre_2"], request.form["genre_3"]]
def commentary(rating):
if rating <= 3:
comment = "This sucks worse than what Rachel said"
elif rating <= 6:
comment = "Mediocre at best"
else:
comment = "The best thing since sliced bread"
return comment
prediction = {
"adults_entered1": new_film[0],
"years_entered1": new_film[1],
"film_length_entered1": new_film[2],
"genre1_entered1" : new_film[3],
"genre2_entered1" : new_film[4],
"genre3_entered1" : new_film[5],
"prediction": commentary(rating[0]),
"rating": rating[0]
}
for key, value in prediction.items():
print(f"{key}: {value}")
return render_template('prediction.html', result=prediction)
The html:
<tbody id="filter-table"></tbody>
<tr>
{% if result %}
{% for key, value in result.items() %}
<td id={{key}}>
{{value}}
</td>
{% endfor %}
{% endif %}
</tr>
The Javascript:
filter_val = d3.select(#adults_entered1).value
console.log(filter_val)
I would expect to be returned the {{value}} for the adults_entered1. Instead it is undefined.

Categories

Resources