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>
Related
Edit: I seem to have figured out the next step in fixing this, as the actual name for each variable in the payload needs to be how I set it to be in forms. Will update if I solve it. Additionally, if I change it to a GET request, it works fine, but I have no idea why Django does not work with the POST request.
Need some help with this. If I pass a formset to a html template and request its payload with a POST request, I get this error
Field Error: primary <ul class="errorlist"><li>This field is required.</li></ul> Field Error: symbol <ul class="errorlist"><li>This field is required.</li></ul> Field Error: secondary <ul class="errorlist"><li>This field is required.</li></ul>
For the formset, the forms are dynamically added or deleted onto the page, but there will always be a single form on the page when the page is loaded. And for the other dynamically added forms, they get the same error as well.
But when I pass a single form to the html template, I get the POST payload just fine.
views.py
def advanced(request):
form = formset_factory(Search)
if request.method == 'POST':
formset = Search(request.POST)
for field in formset:
print("Field Error:", field.name, field.errors)
return render(request,"advancedsearch.html", {"formset":form})
forms.py
indicator = [
('', ''),
('high', 'Estimated high'),
('low', 'Estimated low'),
('median', 'Estimated median'),
('realprice', 'Real Price'),
]
symbol= [
('', ''),
('>', 'higher than'),
('<', 'less than'),
('=', 'equal to'),
]
class Search(forms.Form):
primary = forms.CharField(label='a', widget=forms.Select(choices=indicator))
symbol = forms.CharField(label='b', widget=forms.Select(choices=symbol))
secondary = forms.CharField(label='c', widget=forms.Select(choices=indicator))
advancedsearch.html
<form method="POST" action="">{% csrf_token %}
{% for x in formset %}
<div class = "d-flex flex-row justify-content-center bd-highlight mb-5">
{{ x.as_p }}
</div>
{% endfor %}
<button type="submit" class="btn btn-primary" >Search</button>
</form>
Form Data example
csrfmiddlewaretoken: Sc2bMfDJr2qQ9rqeOxd3YnVpB37d36ZkQ85OfGaUL7vD61IyGzNiVDn6c5vydKSX
form-0-primary: low
form-0-symbol: >
form-0-secondary: low
Two ways came up in mind for this.
Switch form.CharField to form.ChoiceField
# forms.py
...
class Search(forms.Form):
primary = forms.CharField(label='a', widget=forms.Select(choices=indicator))
symbol = forms.CharField(label='b', widget=forms.Select(choices=symbol))
secondary = forms.CharField(label='c', widget=forms.Select(choices=indicator))
Ture form field required to False, check this question
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();
I can't get how to fetch data from HTML-element that contains data generated by django-autocomplete-light.
Here is a code of the form:
class ThreadForm(forms.Form):
topic = forms.CharField(label="Topic", max_length=255)
body = forms.CharField(label="Body", widget=forms.Textarea(attrs={'rows': '12', 'cols':'100'}))
tags = autocomplete_light.fields.MultipleChoiceField(choices=(tuple((tag.name, tag.name) for tag in Tag.objects.all())),
label='Tags',
widget=autocomplete_light.widgets.MultipleChoiceWidget('TagAutocomplete',
attrs={'class':'form-control',
'placeholder':'Tag'}
)
)
def save(self, author, created):
topic = self.cleaned_data['topic']
body = self.cleaned_data['body']
tags = self.cleaned_data['tags']
th = Thread(author = author,
topic = topic,
body = body,
created = created,
)
rtags = []
for tag in tags:
sr = Tag.objects.get(tag)
rtags.append(sr.name)
th.save()
Tag.objects.update_tags(th, tags)
And autocomplete_light_registry.py:
from threads.models import Thread
import autocomplete_light
from tagging.models import Tag
class TagAutocomplete(autocomplete_light.AutocompleteModelBase):
search_fields = ['^name']
autocomplete_light.register(Tag, TagAutocomplete, attrs={
'data-autocomplete-minimum-characters': 1,
},)
As you see I've changed the django-autocomplete app. In the base.py I found a variable choice_html_format = '<span data-value="%s" name="choice">%s</span>'
Attribute name was added by me to get data like that:
tags = request.POST.get('name')
But this doesn't work. I'm getting an error like "NoneType in not callable"
Next thing I've tried is change choice_html from base.py:
def choice_html(self, choice):
"""
Format a choice using :py:attr:`choice_html_format`.
"""
return self.choice_html_format % (
escape(self.choice_value(choice)),
escape(self.choice_label(choice)))
It is original function, I've changed choice_value(choice) to choice_label(choice). And got an error "invalid literal for int() with base 10: <tag_name_here>". Looks like data-value attribute is only for int() type (but I can't get where I can change it, maybe in js-function, I don't know).
And the last, I'm trying to get the pk of each tag, and then get the name via manager. But I'm getting error Cannot resolve keyword '4' into field. Choices are: id, items, name.
I absolutely sure that there is an easy way to perform the task I need.
autocomplete-light has a template called widget.html that is rendered in the template:
...
{% block select %}
{# a hidden select, that contains the actual selected values #}
<select style="display:none" class="value-select" name="{{ name }}" id="{{ widget.html_id }}" multiple="multiple">
{% for value in values %}
<option value="{{ value|unlocalize }}" selected="selected">{{ value }}</option>
{% endfor %}
</select>
{% endblock %}
...
as you can see, this <select> element contains all selected choices for the autocomplete widget.
Its name (we are going to identify it by its name attribute later in the view) is simply the autocomplete's name ('tags').
So now you need to make sure your autocomplete field in the template is wrapped in <form> tags so the values get submitted (if you haven't already).
The next step is to retrieve the data in the view:
request.POST.getlist('tags')
That's it. You now have a list of primary keys of the selected values:
>>> print(str(request.POST.getlist('tags'))
['1', '3', '4', '7', ...]
I'm trying to get the first "Order Name" field in a form to automatically fill in other "Order Name" fields on other pages of the form using Blade Templating inside Laravel. The form will be split up to different people depending on the order so the contact info needs to be on every page.
I wanted to give the first "Order Name" input field a class of "general_order_name" and the other name fields throughout the form the class "order_name". The idea being that the text in "general_order_name" would be copied to a variable and then placed inside all the inputs with the class of "order_name" but for some reason it is not working.
If anyone can point out where I went wrong or explain how to fix this it would be very much appreciated! Thanks!
HTML:
{{ Form::label('order_name', 'Order Name:') }}
{{ Form::text('order_name', null,
array('class' => 'general_order_name')) }}
{{ Form::label('page_name', 'Order Name:') }}
{{ Form::text('page_name', null,
array('class' => 'order_name')) }}
JS:
$(document).on("change", ".general_order_name", function(){
var order_name = $(this).value();
$('.order_name').value(order_name);
});
jQuery uses val() not value
$(document).on("change", ".general_order_name", function(){
var order_name = $(this).val();
$('.order_name').val(order_name);
});
I have 4 fields that will be dynamically created by users.
<div class="controls" id="exchange-fields">
<p>
<div id='exchange_div'>
<div class="input-append" id='currency_div1'>
{{ Form::select('currency_id[]', $currencies, null, array('name'=>'currency_id', 'id'=>'currency_id', 'value'=>'Input::old("currency_id")[0]' )) }}
</div>
<div class="input-prepend" id='actual_div1'>
<span class="add-on">Actual</span>
{{ Form::text('exchange_rate[]', null, array('class'=>'input-medium rate', 'maxlength'=>10, 'id'=>'exchange_rate', 'value'=>'Input::old("exchange_rate")[0]' )) }}
</div>
<div class="input-append" id='markup_div1'>
{{ Form::text('exchange_rate_markup[]', null, array('class'=>'input-mini yellow rate', 'maxlength'=>4, 'id'=>'exchange_rate_markup', 'value'=>'Input::old("exchange_rate_markup")[0]' )) }}<span class="add-on">%</span>
</div>
<div class="input-prepend" id='rate_div1'>
<span class="add-on">Marked-up</span>
{{ Form::text('after_markup_rate[]', null, array('class'=>'input-medium yellow', 'maxlength'=>10, 'id'=>'after_markup_rate', 'value'=>'Input::old("after_markup_rate")[0]' )) }}
</div>
</div>
</div>
<div class="controls">
<input class="btn btn-primary" type="button" id="addScnt" value="Add">
</div>
I uses javascript to populate these fields dynamically.
var scntDiv = $('#exchange-fields');
var i = $('#exchange-fields p').size() + 1;
$('#addScnt').click(function() {
$(scntDiv).append('<p>');
$("#exchange_div").clone().attr("id",'exchange_div_'+ i).appendTo(scntDiv);
//Append the remove button
$($('#exchange_div_'+ i)).append('<input class="btn btn-primary" name="remove" type="button" id="remScnt" value="Remove">');
i++;
});
This is working perfectly until I POST these values to my controller and if validation fails.
How do I re populate those fields with the old input that are dynamically populated in my view file?
I use return Redirect::back()->withInput()->withErrors($e->getErrors()); to redirect after validation fail to repopulate those fields. But because these 4 fields only accept string values and the input returning back are in array so I am unable to repopulate these 4 fields after validation fails.
Any good ways to fix this?
Thanks in advance.
#a7omiton Instead of returning all inputs. I use Input::except(array) to return the rest of the fields which are not array.
While redirecting back I use an additional with() to return the array of fields like how you render your view when you load the page initially.
// If validation fails, ignore those exchange rate fields
//because they are in array not string
$input = Input::except([
'currency_id',
'exchange_rate',
'exchange_rate_markup',
'after_markup_rate'
]);
// return normally as separate array and it will store in session
return Redirect::back()
->withInput($input)
->withErrors($e->getErrors())
->with('exchange_rate', Input::get('exchange_rate'))
->with('currency_id', Input::get('currency_id'))
->with('exchange_rate_markup', Input::get('exchange_rate_markup'))
->with('after_markup_rate', Input::get('after_markup_rate'));
Don't set the value of the input fields in the HTML. Laravel will do this automatically when validation fails.
The second argument in Form::text is the value. Setting this to null will allow Laravel to automatically populate it.
laravel will autopopulate the form fields.
Just put second parameter with Input::old('column_name').
{{ Form::text('name', Input::old('name'), array('class' => 'span4','placeholder' => 'name')) }}
Though it is an old post, this will help some one land here looking for the solution.
You can get all the input variable in the view after a redirection by adding this code in your laravel view,
print_r(app('request')->old());
you can assign this values to your javascript variable, and populate the fields dynamically.