pythondjangodjango-modelsdynamicchoicefield

In Django, remove options in a choice-field dropdown based on the value selected in other field in a model


I'm new to Django and any help is appreciated, How can I restrict the choice option in one field based on a previous field. For example, if I select 'dog' for animal, I want to remove 'chocolate' from the FOOD_CHOICE. Thank you!!!

ANIMAL_CHOICE = (
     ('a','cat'),
     ('b','dog'),
     ('c','fish'),
)
FOOD_CHOICE = (
      ('a', 'chocolate'),
      ('b', 'kittySnack'),
      ('c', 'steak'),
)

class Animal(models.Model):
    animal = models.CharField(max_length=1, choices= ANIMAL_CHOICE)
    food   = models.CharField(max_length=1, choices= FOOD_CHOICE)

Solution

  • As explained here, you should do the validation on the model form or in the model clean method and raise a ValidationError there.

    Here is an example, in which you could override the clean method of your model's form:

    forms.py

    class AnimalForm(ModelForm):
        class Meta:
            model = Animal
            fields = "__all__"
    
        def clean(self):
            cleaned_data = super(AnimalForm, self).clean()
            animal = self.cleaned_data.get("animal")
            food = self.cleaned_data.get("food")
            if animal == "b" and food == "a": # Might not work, but this is the general idea
                raise forms.ValidationError("Chocolate can't be selected with Dogs")
    
    

    N.B.: At the line where I commented that it might not work, you'll have to debug a bit. I don't remember (and I can't test right now) if the cleaned_data returns a tuple or the actual value, or the humand-readable value.

    Now, I guess that you want your select in your HTML to dynamically change. For the frontend, you'll need to do a bit of JavaScript. There are many ways on how to do it with JS, but here is one:

    (in your template, between <script> tags)

    var selectAnimal = document.getElementById("select-animal");
    var selectFood = document.getElementById("select-food");
    
    selectAnimal.addEventListener("change", function() {
        if(this.value == "a")
        {
            
            // remove from select the "chocolate" option
            for (var i=0; i<selectFood.length; i++) {
                if (selectFood.options[i].text == 'chocolate')
                    selectFood.remove(i);
            }
        }
        else {
            
            // checking if "chocolate" is in select or not
            let has_chocolate = false;
            for (var i=0; i<selectFood.options.length; i++){
                if (selectFood.options[i].text == "chocolate"){
                    has_chocolate = true;
                }
            }
            
            if (!has_chocolate){ // if "chocolate" is missing
                option = document.createElement("option");
                option.text = "chocolate";
                option.value = "a";
                selectFood.add(option);
            }
        }
    });