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

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();

Related

"This field is required" error for FormSet when requesting payload, but works fine with Form

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

Dynamically update Django form field options using Ajax to GET new queryset

I'm new to coding and django and I'm struggling to find the solution to the following problem having reviewed the answers I've found.
Im creating a search form with multiple fields. When the user selects the first field category (and before hitting search) I would like to dynamically change the queryset for the second field sub_category such that only related values are shown.
I have models.py as follows:
class Product(models.Model):
category = models.ForeignKey("Category")
sub_category = models.ForeignKey("SubCategory")
class Category(models.Model):
name = models.CharField(max_length=256)
class SubCategory(models.Model):
category = models.ForeignKey("Category")
name = models.CharField(max_length=256)
And my forms.py includes:
class BasicSearchForm(forms.Form):
category = forms.ModelChoiceField(
label='Category',
queryset=Category.objects.all(),
to_field_name="name",
empty_label=None,
initial="Red")
sub_category = forms.ModelMultipleChoiceField(
required=False,
label='Type',
queryset= SubCategory.objects.all(),
to_field_name="name",
widget=forms.Select)
And my views.py includes:
def search(request):
if request.method == 'POST':
form = BasicSearchForm(request.POST)
if form.is_valid():
category = form.cleaned_data['category']
sub_category = form.cleaned_data['sub_category']
return render(request, 'myapp/search.html', {'form': form})
else:
form = BasicSearchForm()
return render(request, 'myapp/search.html', {'form': form})
And finally the search.html includes:
<form class="search-form" role="search" action="/search/" method="get">
{{ form }}
<input type="submit" value="Search" />
</form>
I've played around with a few answers but nothing seems to work. I'd really appreciate some help. Thanks in advance!
Update:
Thanks for the feedback. As a result I updated the following:
In my urls.py:
urlpatterns = [
url(r'^ajax/update_subcategories/$', views.update_subcategories, name='update_subcategories'),
And in my views.py:
def update_subcategories(request):
category = request.GET.get('category', None)
sub_category = list(SubCategory.objects.filter(category__name__exact=category).values('name'))
return JsonResponse(sub_category, safe=False)
And I have this in my myapp/search.html:
{% block javascript %}
<script>
$("#id_category").change(function () {
var category = $(this).val();
$.ajax({
url: '{% url "myapp:update_subcategories" %}',
data: {
'category': category,
},
success: function (response) {
var new_options = response;
alert(new_options[0].name); // works
$('#id_sub_category').empty();
$.each(new_options, function(key, value) {
$('#id_sub_category')
.append($('<option>', { value : key })
.text(value.name));
});
}
});
</script>
{% endblock %}
Update: The sub_category options were showing as [object Object] until I changed value to value.name and it looks like it's working. I'll test it out and close unless there are any comments.
Update: Im still having an issue with the browser back button. When a user clicks back the dropdown values have changed back to the original queryset rather than the updated version.
You can't do this from Django views side, ie, backend. You could try an ajax request for implementing this kind of requests, by sending a GET request to the server for populating the drop-down or whatever you are into.
For a simple example, you could refer
here
How do I POST with jQuery/Ajax in Django?
EDIT
def update_subcategories(request):
category = request.GET.get('category', None)
sub_category = list(SubCategory.objects.filter(category__name__exact=category).values('name'))
return JsonResponse(dict(sub_category=sub_category))
Then in ajax response you could grab it like response.data.sub_category
Use ajax to send the category and retrieve subcategory elements.
For the category, send it via get request, and using the orm return the subcategories in a json format which you can show using jQuery.

Why does my Django form show choice not available error?

I am trying to build a dynamic form in Django template.
I have two dropdown selections. When the user selects an option in the first one, the available choices in the second dropdown changes dynamically. Here is the jQuery code I use to change the choices in the second dropdown element.
$(document).on("change", "[id^=id_form][id$=brand]", function() {
var $value = $(this).val();
var $id_prefix = $(this).attr('id').slice(0, -5);
$auto_type = $("#" + $id_prefix + "auto_type");
$.ajax({
type: 'GET',
url: '/europarts/filter/auto/type/with/brand/' + String($value),
success: function(data) {
$auto_type.html(data);
}
});
});
You can see that it fetches some HTML from a link which is basically a bunch of options which will replace the current ones. Here is the code:
{% for auto_type in auto_types %}
<option value="{{ auto_type.id }}">{{ auto_type.name }}</option>
{% endfor %}
Here are my forms:
class ListForm(forms.ModelForm):
class Meta:
model = List
fields = ['ref_no']
class ProductCreateForm(forms.Form):
brand = forms.ModelChoiceField(queryset=Brand.objects.all(), initial=Brand.objects.first())
auto_type = forms.ModelChoiceField(queryset=AutoType.objects.filter(brand=Brand.objects.first()), empty_label=None)
part_no = forms.CharField(max_length=50)
description = forms.CharField(max_length=255)
quantity = forms.IntegerField()
unit = forms.CharField(max_length=20, initial='piece(s)')
cost_price = forms.IntegerField(required=False)
selling_price = forms.IntegerField(required=False)
I am using a dynamic formset jquery library to save multiple Products with a List.
This works fine till now!
However after I change the options like this and try to submit the form, the form page gets loaded again with the default options in the second dropdown and not the changed ones along with an error that says:
Select a valid choice. That choice is not one of the available choices.
Here is the request.POST value when the form is submitted.
<QueryDict: {'form-MAX_NUM_FORMS': ['1000'], 'form-1-description': ['lskdjf'], 'form-1-auto_type': ['3'], 'form-MIN_NUM_FORMS': ['0'], 'form-0-quantity': ['1'], 'form-0-brand': ['2'], 'form-0-part_no': ['293847'], 'form-0-auto_type': ['2'], 'form-0-unit': ['piece(s)'], 'form-0-description': ['slkdfj'], 'form-1-part_no': ['928374'], 'form-TOTAL_FORMS': ['2'], 'form-1-quantity': ['1'], 'form-INITIAL_FORMS': ['0'], 'ref_no': ['REF230498203948'], 'csrfmiddlewaretoken': ['WYN08Hi2YhwTSKPS8EnLaz94aOz33RVfFMjwYeHr3rMxBImxn7ggSLYHwguIbuL0'], 'form-1-brand': ['2'], 'form-1-unit': ['pieces']}>
You can see 'form-1-auto_type': ['3'] which was 'form-1-auto_type': ['1'] originally. The form is submitted when the original value is there, but after the dynamic change in value it shows the above error.
This error is shown for the second dropdown. How can I fix this?
When the form is being validated it checks whether the option exists in the choices for the field, in this case:
queryset=AutoType.objects.filter(brand=Brand.objects.first())
It works only when you select an option from the first brand.
To make it work you need to update the choices for the auto_type field before it validates the data or it might be easier not to limit the choices of auto_type to any brand and then update the select element in JavaScript when the page loads to show only the options of the first brand.

Django crispy form - use Radio button to hide/show other fields

please I have a question about creating a form using Django-crispy-form. I want to have a form which has 2 fields and the selection of the first field hides/shows the second one.
1st field: Radio button <= this option hides/shows the second field.
2nd field: Float field
What I've tried is:
Assign id to Radio button, put the 2nd field into a div
Run a JS function when clicking the radio button based on the assigned id.
In the JS function, get the value of the radio button
Based on the value, hide/show the div containing the 2nd field.
But at step 3, I always get "undefined value". I guess it's because I couldn't assign id to the choice/option, but only to the whole radio button which has no value. But it's just my guess. Please refer to my simple code, I would be very grateful if someone could help me out.
Thanks alot !!!
forms.py
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Fieldset, ButtonHolder, Submit
from crispy_forms.bootstrap import InlineRadios,PrependedAppendedText,Div
class my_form(forms.Form):
# Radio button field
cargo = forms.ChoiceField(label='Cargo on Deck',
choices=[('true','Yes'),
('false','No')],
initial='false')
# the 2nd field
L = forms.DecimalField(label='Length: L [m]')
### Render form
def __init__(self, *args, **kwargs):
super(my_form, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_id = 'my_form_id'
self.helper.form_class = 'blueForms'
self.helper.form_method = 'post'
self.helper.add_input(Submit('submit', 'Calculate'))
self.helper.form_class = 'form-horizontal'
self.helper.label_class = 'col-lg-4'
self.helper.field_class = 'col-lg-8'
# step 1: Assign id to radio, put 2nd field in a div
self.helper.layout = Layout(
InlineRadios('cargo',id="radio_id"),
Div('L', css_id="div_id"),
)
HTML
<!-- Html -->
{% extends 'home/home_base.html' %}
{% block content %}
<div class="row">
<div class="col-sm-6">
<!-- {% csrf_token %}-->
{% load crispy_forms_tags %}
{% crispy my_form my_form.helper %}
</div>
</div>
<!-- JS -->
<script type="text/javascript" src="http://code.jquery.com/jquery-latest.min.js"></script>
<script type="text/javascript">
// step 2: run function when clicking radio button
$(document).ready(function() {
$('#radio_id').on('change',function(){
// step 3: get selected value
var selected_value = $('input[id="radio_id"]:checked').val();
alert(selected_value); // somehow this always gives "undefined value"
// step 4: hide/show dive based on selected_value
if( $(this).val()=="true"){
$("#div_id").hide()
}
else{
$("#div_id").show()
}
});
});
</script>
{% endblock %}
For step 3 in the HTML file, try using:
$('input[name="cargo"]:checked').val();

Django-autocomplete-light how to get data from html?

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', ...]

Categories

Resources