user3354539
user3354539

Reputation: 1245

django & ajax dependant html select lists (cascade drop down lists)

I am new to Django (i am using Django 1.4 & python 2.7) and cannot understand how to accomplish the following issue.

I have done a lot of searching on SO & Google, and so far become very confused on how to do this problem.

I have two html select lists on a form - Industry & Sector. When a user selects Engineering from the Industry html select list, the Sector html select list should be dynamically filled with only Engineering options. The same with other selections made on the Industry html select list. The Sector html select list should be filled dynamically without a page refresh - so I am assuming that JQuery / AJAX will have to be used.

I am not so sure I have correctly set up the UserProfile model below. The Industry & Sector html select lists appear on the form and work, but are not dependent on each other - they are separate models.PositiveIntegerField fields. Perhaps the Industry & Sector values should be foreign keys on the UserProfile model below. I need some advice on this.

Here is my user 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 have seen django-smart-selects, but I am not sure this is a dynamic solution and I am not sure if I have to add in separate models for the Industry & Sector & then add the foreign keys for the Industry & Sector to the UserProfile model above.

I am hoping I can somehow easily get the Industry & Sector html select lists dependent on each other with AJAX or JQuery.

Any advice and help would be appreciated.

Upvotes: 3

Views: 4815

Answers (1)

AlvaroAV
AlvaroAV

Reputation: 10563

When I face this issue I use AJAX to make 2 selects dependants. First I use a Django Form like:

# 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 the views (how to manage GET/POST methods) and the basic HTML django form, I'll go directly to the AJAX function:

Let's assume the selectors ID's are: #id_sector and #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 set your 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's 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 avoid adding a second AJAX function 'get_sectors' because I supose you understand the logic, it should be the same as this function, but you'll receive industry_id instead of sector_id, I think you can face with the second function.

The last step before setting up the urls is to define the functions that manage the changes on the selects and call the 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'll need to add both urls in your 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'), this refers to the industry selector ID, same as $('#id_sector') refers to the sector selector ID

  • What I call AJAX function goes in the HTML template

  • What I call AJAX view goes in a .py file, I usually add AJAX views to ajax.py file
  • For this example I added @csrf_exempt in the AJAX view, If you want to know how to manage CSRF token in AJAX functions, you can go to: CSRF & AJAX: Official Docs
  • In the AJAX view you have the Sector the user has selected:
    • You need to generate a list of industries
    • The list will be used to generate the options for the select
    • You can do this in so many different ways, you should choose the one that fits your scenario better.
    • I'm sending the ID, but you can send the name if it's better for you

Edit: Filter Sector/Industry

  • get_industries(request) changed to get_sectors(request)

First of all you should have the equivalences, you can have them in different ways, you could have 2 models Industry and Sector, and relate them using Foreign Keys but I think you don't have models so you could have 2 dictionaries, one for industries and one for sector.

I will supose the equivalences, you should change the values of the dict for 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 generate a dict, and if you do INDUSTRY_DICT[industry_id] it will return the ID list of sectors that should appear in the select. Check the function above with the new changes.

  • I also recommend you to have 2 more functions to "restart" both selects and append all the possible options again in case the user would like to change his choice

Upvotes: 4

Related Questions