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.

+3


source to share


1 answer


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 to get_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.
+4


source







All Articles