简体   繁体   中英

Custom Dependent Dropdown menu inside the Django admin

I have a project foreign key in by Phase model. I'm having hard time Create a dependent drop-down list inside my Django admin page.

I want to when user select a project from (project drop-down) phase of that project show in second dop-down

What would be the best way to achieve this?

It would be great if the dropdowns filter items based on the value of its parent.

在此处输入图像描述

class Project(models.Model):
    name                    = models.CharFieldmax_length = 100, unique= True)
    short_name              = models.CharField(max_length= 4, unique= True)
    slug                    = models.SlugField(max_length= 100, allow_unicode=True, null=True, editable= False)
    location                = models.OneToOneField(Location, on_delete = models.SET_NULL, null= True, blank= False, verbose_name= 'موقعیت')
    start_date              = models.DateField(default= timezone.now, null= True, blank= True)      
    end_date                = models.DateField(default= timezone.now, null= True, blank= True)
    duration                = models.IntegerField(default= 0, editable= False)

class Phase(models.Model):
    title                = models.CharField(max_length= 20)

class ProjectPhase(models.Model):
    project                 = models.ForeignKey(Project, on_delete= models.CASCADE, related_name= 'phase')
    phase                   = models.ForeignKey(Phase, on_delete=models.CASCADE, related_name= 'project')
    start_date              = models.DateField(default= timezone.now)      
    end_date                = models.DateField(default= timezone.now)
    duration                = models.IntegerField(default= 0, editable= True)

1. import a js media file in ModelAdmin for Generaldata:

class YourModelAdmin(admin.ModelAdmin):    
    form = YourModelForm
    #list_display = ['your fields',]
    class Media:
        js = ("yourapp/selectajax.js",)

admin.site.register(YourModel, YourModelAdmin)

2. create a new js file which saved yourproject/yourapp/static/yourapp/ directory or another proper directory.

jQuery(function($){
    $(document).ready(function(){
        $("#id_project_select").change(function(){
            // console.log(obj.currentTarget.value);
            $.ajax({
                url:"/get_phases/",
                type:"POST",
                data:{project: $(this).val(),},
                success: function(result) {
                    console.log(result);
                    cols = document.getElementById("id_phase_select");
                    cols.options.length = 0;
                    for(var k in result){
                        cols.options.add(new Option(k, result[k]));
                    }
                },
                error: function(e){
                    console.error(JSON.stringify(e));
                },
            });
        });
    }); 
});

3. create a view to process ajax

@login_required
def get_phases(request):
    project = request.POST.get('project')
    phases = {}
    try:
        if project:
            prophases = Project.objects.get(pk=int(project)).phase
            phases = {pp.phase.title:pp.pk for pp in prophases}
    except:
        pass
    return JsonResponse(data=phases, safe=False)

4. add 'get_phases/ to urlpatterns.

Notice that you should modify some codes as your need.

The answer by Blackdoor is a good approach and it's the one we just implemented, but it has a couple of problems:

  1. It's only executed when you change the main select, and I wanted the dependant select to be filtered also on page load.
  2. Does not keep que selected item in the dependant select.

In his solution, in step 2, replace his code with this one and adapt the names (I'm using service and sub_service instead of project / phase ):

jQuery(function($){
    $(document).ready(function(){
        var clone = document.getElementById("id_sub_service").cloneNode(true);
        $("#id_service").change(function(){
            update_sub_services($(this).val(), clone)
        });
        update_sub_services($("#id_service").val(), clone)
    });

    function update_sub_services(service, clone) {
        $.ajax({
            url:"/chained_dropdowns/get_sub_services/",
            type:"GET",
            data:{service: service,},
            success: function(result) {
                var cols = document.getElementById("id_sub_service");
                cols.innerHTML = clone.innerHTML
                Array.from(cols.options).forEach(function(option_element) { 
                    var existing = false;
                    for (var k in result) {
                        if (option_element.value == k) {
                            existing = true
                        }
                    }
                    if (existing == false) {
                        $("#id_sub_service option[value='"+option_element.value+"']").remove();
                    }
                })
            },
            error: function(e){
                console.error(JSON.stringify(e));
            },
        });
    }
});

As you can see, now instead of removing all the items from the dependant select and then refilling it (which leaves you without the selected property and any other custom property), it removes the options that should not be there.

I'm not a JS developer and I don't know jQuery so my modifications are in native JS, please feel free to improve it:)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM