Django & ajax html dependent selectlists (cascading dropdowns)
I am new to Django (I am using Django 1.4 and python 2.7) and cannot figure out how to accomplish the following problem.
I've searched SO and Google many times and am still very confused as to how to do this.
I have two html picklists in a form - Industry and Sector. When the user selects "Engineering" from the html Industry picklist, the html sector picklist should be dynamically populated with only technical parameters. The same with other selections made in the html Industry picklist. The html sector select list should be populated dynamically without refreshing the page, so I assume JQuery / AJAX will need to be used.
I'm not sure if I have configured the UserProfile model below correctly. Industry and sector html picklists are rendered in a form and work but are independent of each other - they are separate models. FieldsPicitiveIntegerField. Perhaps industry and sector values ββshould be foreign keys in the UserProfile model below. I need an advice.
Here is my users models.py file:
class UserProfile(models.Model):
SELECT_INDUSTRY = 0
ACCOUNTING = 1
ADMINISTRATION_OFFICE_SUPPORT = 2
BANKING_FINANCIAL_SERVICES = 3
CALL_CENTRE_CUSTOMER_SERVICE = 4
COMMUNITY_SERVICES_DEVELOPMENT = 5
CONSTRUCTION = 6
CONSULTING_STRATEGY = 7
DESIGN_ARCHITECTURE = 8
EDUCATION_TRAINING = 9
ENGINEERING = 10
EXECUTIVE_GENERAL_MANAGEMENT = 11
FARMING_ANIMALS_CONSERVATION = 12
GOVERNMENT_DEFENCE = 13
GRADUATE_ENTRY_LEVEL = 14
HEALTHCARE_MEDICAL = 15
HOSPITALITY_TRAVEL_TOURISM = 16
HUMAN_RESOURCES_RECRUITMENT = 17
INSURANCE_SUPERANNUATION = 18
INFORMATION_TECHNOLOGY_TELECOMMUNICATIONS = 19
LEGAL = 20
MANUFACTURING = 21
MARKETING_COMMUNICATIONS = 22
MEDIA_ADVERTISING_ARTS_ENTERTAINMENT = 23
MINING_RESOURCES_ENERGY = 24
REAL_ESTATE_PROPERTY = 25
RETAIL_CONSUMER_PRODUCTS = 26
SALES = 27
SCIENCE_TECHNOLOGY = 28
SELF_EMPLOYMENT = 29
SPORT_RECREATION = 30
TRADES_SERVICES = 31
TRANSPORT_LOGISTICS = 32
USER_PROFILE_CURRENT_INDUSTRY_TYPES = (
(SELECT_INDUSTRY, _('Select Current Industry')),
(ACCOUNTING, _('Accounting')),
(ADMINISTRATION_OFFICE_SUPPORT, _('Administration & Office Support')),
(BANKING_FINANCIAL_SERVICES, _('Banking & Financial Services')),
(CALL_CENTRE_CUSTOMER_SERVICE, _('Call Centre & Customer Service')),
(COMMUNITY_SERVICES_DEVELOPMENT, _('Community Services & Development')),
(CONSTRUCTION, _('Construction')),
(CONSULTING_STRATEGY, _('Consulting & Strategy')),
(DESIGN_ARCHITECTURE, _('Design & Architecture')),
(EDUCATION_TRAINING, _('Education & Training')),
(ENGINEERING, _('Engineering')),
(EXECUTIVE_GENERAL_MANAGEMENT, _('Executive & General Management')),
(FARMING_ANIMALS_CONSERVATION, _('Farming, Animals & Conservation')),
(GOVERNMENT_DEFENCE, _('Government & Defence')),
(GRADUATE_ENTRY_LEVEL, _('Graduate / Entry Level')),
(HEALTHCARE_MEDICAL, _('Healthcare & Medical')),
(HOSPITALITY_TRAVEL_TOURISM, _('Hospitality, Travel & Tourism')),
(HUMAN_RESOURCES_RECRUITMENT, _('Human Resources & Recruitment')),
(INSURANCE_SUPERANNUATION, _('Insurance & Superannuation')),
(INFORMATION_TECHNOLOGY_TELECOMMUNICATIONS, _('Information Technology & Telecommunications')),
(LEGAL, _('Legal')),
(MANUFACTURING, _('Manufacturing')),
(MARKETING_COMMUNICATIONS, _('Marketing & Communications')),
(MEDIA_ADVERTISING_ARTS_ENTERTAINMENT, _('Media, Advertising, Arts & Entertainment')),
(MINING_RESOURCES_ENERGY, _('Mining, Resources & Energy')),
(REAL_ESTATE_PROPERTY, _('Real Estate & Property')),
(RETAIL_CONSUMER_PRODUCTS, _('Retail & Consumer Products')),
(SALES, _('Sales')),
(SCIENCE_TECHNOLOGY, _('Science & Technology')),
(SELF_EMPLOYMENT, _('Self Employment')),
(SPORT_RECREATION, _('Sport & Recreation')),
(TRADES_SERVICES, _('Trades & Services')),
(TRANSPORT_LOGISTICS, _('Transport & Logistics'))
)
SELECT_SECTOR_TYPE = 0
_ALL_ACCOUNTING_JOBS = 1
....(culled for brevity)
_ALL_ENGINEERING_JOBS = 124
AEROSPACE_ENGINEERING = 125
AUTOMOTIVE_ENGINEERING = 126
BUILDING_SERVICES_ENGINEERING = 127
CHEMICAL_ENGINEERING = 128
CIVIL_STRUCTURAL_ENGINEERING = 129
ELECTRICAL_ELECTRONIC_ENGINEERING = 130
ENGINEERING_DRAFTING = 131
ENVIRONMENTAL_ENGINEERING = 132
FIELD_ENGINEERING = 133
INDUSTRIAL_ENGINEERING = 134
MAINTENANCE = 135
MANAGEMENT = 136
MATERIALS_HANDLING_ENGINEERING = 137
MECHANICAL_ENGINEERING = 138
PROCESS_ENGINEERING = 139
PROJECT_ENGINEERING = 140
PROJECT_MANAGEMENT = 141
SUPERVISORS = 142
SYSTEMS_ENGINEERING = 143
WATER_WASTE_ENGINEERING = 144
OTHER_ENGINEERING_JOBS = 145
_ALL_EXECUTIVE_GENERAL_MANAGEMENT_JOBS = 146
....(culled for brevity)
OTHER_TRANSPORT_LOGISTICS_JOBS = 462
USER_PROFILE_CURRENT_SECTOR_TYPES = (
(SELECT_SECTOR_TYPE, _('Select Current Sector')),
.......(culled for brevity)
(OTHER_TRANSPORT_LOGISTICS_JOBS, _('Other Transport & Logistics Jobs'))
)
user = models.OneToOneField(User)
....(culled for brevity)
current_industry_type = models.PositiveIntegerField(choices=USER_PROFILE_CURRENT_INDUSTRY_TYPES, default=SELECT_INDUSTRY, validators=[MinValueValidator(1)])
current_sector_type = models.PositiveIntegerField(choices=USER_PROFILE_CURRENT_SECTOR_TYPES, default=SELECT_SECTOR_TYPE, validators=[MinValueValidator(1)])
....(culled for brevity)
.
I've seen django-smart-selects , but I'm not sure if this is a dynamic solution, and I'm not sure if I need to add separate industry and sector models, and then add industry and sector foreign keys to the UserProfile model above.
I hope I can somehow easily get industry and sector html selectlists dependent on each other using AJAX or JQuery.
Any advice and help would be appreciated.
source to share
When I run into this issue, I use AJAX to make 2 dependents choices. First I use Django Form , for example:
# I avoid the importation of the choices to make answer shorter
class YourForm(forms.Form):
industry = forms.ChoiceField(choices=USER_PROFILE_CURRENT_INDUSTRY_TYPES)
sector = forms.ChoiceField(choices=USER_PROFILE_CURRENT_SECTOR_TYPES)
# ... other fields
I will avoid the basics of submission (how to manage GET / POST methods) and the basic django HTML form, I will go directly to the AJAX function :
Suppose the selector IDs are: and #id_sector
#id_industry
function get_industry(){
jQuery.ajax({
async: false,
type: "POST",
url: "/a/get/industry/",
data: "sector_id=" + $('#id_sector').val(),
success: function(response) {
result = JSON.parse(response);
if (result) {
// I usually receive a list of items here
// I use this list to replace the dependant select
$('#id_industry').empty() // Use to empty the select
// Now we append the industry options we've received
for(var i=0;i < result.item_list.length;i++){
$('#id_industry').append($('<option>', {
value: result.item_list[i]['id'],
text: result.item_list[i]['name']
}));
}
} else {
console.log('error');
}
}
});
}
function get_sector(){
jQuery.ajax({
async: false,
type: "POST",
url: "/a/get/sector/",
data: "industry_id=" + $('#id_industry').val(),
success: function(response) {
result = JSON.parse(response);
if (result) {
$('#id_sector').empty() // Use to empty the select
// Now we append the sector options we've received
for(var i=0;i < result.item_list.length;i++){
$('#id_sector').append($('<option>', {
value: result.item_list[i]['id'],
text: result.item_list[i]['name']
}));
}
} else {
console.log('error');
}
}
});
}
Now I'm going to show the AJAX view . You can customize the AJAX view in a file called ajax.py:
from yourapp.models import USER_PROFILE_CURRENT_SECTOR_TYPES
INDUSTRY_DICT = {
4: range(14,36),
5: range(36,58),
6: range(58,80),
7: range(80,102),
8: range(102,124),
10: range(124,146) # This is the only true equivalence that you passed to me
}
@csrf_exempt
def get_sectors(request):
response = []
industry_id = int(request.POST['industry_id'])
# With the sector_id you know wich sector the user has selected
# You should generate the list based in your needs
data = []
if industry_id:
sectors = INDUSTRY_DICT[industry_id] # This return a list of ID of sectors
# Then make loop over all sectors
for sector_id in sectors:
# To get the sector name you should use another dict
# I think you already have it in USER_PROFILE_CURRENT_SECTOR_TYPES
# Remember to import it (check above)
sector_name = USER_PROFILE_CURRENT_SECTOR_TYPES[sector_id]
# We append the id and the name to replace options in the HTML
data.append({'id':sector_id, 'name':sector_name})
response = { 'item_list':data } # We send back the list
return HttpResponse(simplejson.dumps(response))
# If we get any error, or cannot get the sector, we send an empty response
response = {}
return HttpResponse(simplejson.dumps(response))
I will not add the second AJAX function get_sectors
'because I suppose you understand the logic, it should be the same as this function, but you get industry_id
instead sector_id
, I think you might run into the second function.
The final step before setting up URLs is defining functions that control changes to the selection and calling of AJAX functions:
$("#id_sector").change(function(){
get_industry(); // AJAX function call when sector is selected
});
$("#id_industry").change(function(){
get_sector(); // AJAX function call when industry is selected
});
You need to add both urls to urls.py :
# ... YOUR OTHER URLS
url(r'^a/get/industry/?$', 'yourproject.ajax.get_industries', name='get_industries'),
url(r'^a/get/sector/?$', 'yourproject.ajax.get_sectors', name='get_sectors'),
Tips:
-
When you see
$('#id_industry')
it refers to the industry selector ID, just like it$('#id_sector')
refers to the sector selector ID -
What I call AJAX function comes in HTML template
- What I call AJAX view goes into the file
.py
, I usually add AJAX views to the fileajax.py
- In this example, I added an AJAX
@csrf_exempt
to the view . If you want to know how to manage CSRF token in AJAX , you can go to: CSRF and AJAX: official docs - In the AJAX view , you have the Sector that the user selected:
- You need to create a list of industries
- The list will be used to create the select parameters
- You can do this in different ways, you should choose the one that best suits your scenario.
- I am posting the ID, but you can post the name if it is better for you.
Edit: filter sector / industry
-
get_industries(request)
changed toget_sectors(request)
First of all you must have equivalents, you can have them in different ways, you can have 2 industry and sector models and link them with foreign keys, but I think you donβt have models so you can have 2 dictionaries one for industries and one for sector.
I'll judge the equivalence, you should change the dict values ββto the ones you need:
INDUSTRY_DICT = {
4: range(14,36),
5: range(36,58),
6: range(58,80),
7: range(80,102),
8: range(102,124),
10: range(124,146) # This is the only equivalence that you passed to me
}
This will create a dict and if you do INDUSTRY_DICT[industry_id]
it will return a list of sector ids that should appear in the select. Check out the above function with new changes.
- I also recommend you 2 more functions to "restart" both options and add all possible options again if the user wants to change their choice.
source to share