0

I'm having a hard time figuring out how to update a Field in Django Form while editing the Form.

In my form I have a ModelChoiceField and ChoiceField: first is a list of Car manufacturers, second is a list of models available for this manufacturer. Obviously I don't want to fill second field with models for every manufacturer but only for one selected in first ModelChoiceField.

Here's my example Form:

class PostForm(forms.ModelForm): make = forms.ModelChoiceField(queryset=Manufacturer.objects.all()) model = forms.ChoiceField() ... 

To render the template with form I use Class-based View:

class CreatePost(View): template_name = 'post/edit_post.html' def get(self, request): form = PostForm() return render(request, self.template_name, {'form': form}) def post(self, request): form = PostForm(request.POST) if form.is_valid(): post = form.save(commit=False) post.owner = request.user post.save() return redirect('homepage') return render(request, self.template_name, {'form': form}) 

In my template after I render PostForm I disable model field with jQuery and wait for user to select some value in make field. Here's my template:

<form class="form-horizontal" action="" method="post" enctype="multipart/form-data"> {% csrf_token %} {{ form.non_field_errors }} {% for field in form %} <div class="form-group"> <label class="control-label">{{ field.label_tag }}</label> {{ field }} {{ field.errors }} </div> <div class="form-group"> <button type="submit" class="btn btn-default">Submit</button> </div> {% endfor %} </form> 

And then use AJAX to request values for model field (in this sample code I only return single item):

$('#id_make').change(function () { $.ajax({ url: '{% url 'get_models' %}', data: {'make': $('#id_make :selected').text()}, dataType: 'json', success: function (data) { $('#id_model').append($('<option>', { value: 1, text: data.model})).removeAttr('disabled'); } }); }) 

Here's the view I'm using to process AJAX call (for display purposes and to keep things simple in this question I have only one record in models table Series for each manufacturer and knowing this I only query for one record. series is CharField that keeps title of model like 'Lancer' for 'Mitsubishi' manufacturer.):

def get_models(request): manufacturer = Manufacturer.objects.get(make=request.GET.get('make')) model = Series.objects.get(make_fk= manufacturer) return JsonResponse({'model': model.series}) 

With this done I do see updated options in <select> element for model ChoiceField, but after I select new value in model ChoiceField and submit form I receive:

Select a valid choice. 1 is not one of the available choices." 
  • '1' is id of the element that I appended to <select> element and then submit.

I then dug deeper and printed the contents of submitted Form and it appeared that although I saw <select> element updated in the template, it stayed blank in the Form I've submitted:

<select name="model" class="form-control" id="id_model"></select> 

Why is this happening and how to update Form fields itself rather than just <select> element?

6
  • Can you please share your template form code? Commented Jan 28, 2018 at 22:59
  • @TheCoder did as you asked Commented Jan 28, 2018 at 23:09
  • hmm.. thanks! could you please add also the view part code? Commented Jan 28, 2018 at 23:29
  • @TheCoder added view for AJAX call, would you also like to see View Class that renders the template? It's pretty standard. Commented Jan 28, 2018 at 23:41
  • 1
    Yes, sharing View Class code would be helpful! Commented Jan 28, 2018 at 23:47

2 Answers 2

1

At the moment, you haven't set any choices of model, so it will never be valid. You could override the __init__ method and set the choices there.

You could set the choices to be all models, and then in the clean method verify that the model matches the make.

class PostForm(forms.ModelForm): make = forms.ModelChoiceField(queryset=Manufacturer.objects.all()) model = forms.ChoiceField() def __init__(self, *args, **kwargs): super(PostForm, self).__init__(*args, **kwargs) if self.data: self.fields['model'].choices = ... def clean(self) # check that model matches make 

Since the model field refers to the series model, it might be easier to use a ModelChoiceField and set self.fields['model'].queryset instead.

This was just a rough idea off the top of my head. The tutorial you found uses a similar approach but is complete and explained in more detail.

Sign up to request clarification or add additional context in comments.

3 Comments

Thanks for your input, but if I understand your answer correctly you're suggesting to populate the "ChoiceField" with all models for all manufacturers and then check if user selected matching make and model in clean() method? Please correct me if that's not what you've meant but if it is - that is exactly kind of flow I'm trying to avoid.
Or what you mean is load all models for all makers to "ModelChoiceField", hide everything once the template loaded and populate the <select> element as before with Ajax request from scratch, only this time every model I potentially will assign to <option> will already be in Form I initially passed to template? This actually might work...
The key point is that you only populate the choices for the POST request (when self.data evaluates to True). For the initial GET request, the choices are still empty and populated in the template using ajax. The tutorial you linked to takes the same approach, but it filters the queryset in the __init__ method, so the clean isn't required. The suggestion to use ModelChoiceField() is completely separate
0

I found explained solution with code examples how to implement chained/dependent dropdowns, coincidentally the solution was posted literally right after I posted this question.

Here's a link.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.