Stan Reduta
Stan Reduta

Reputation: 3492

Update field in rendered Django Form

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."

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?

Upvotes: 0

Views: 2432

Answers (2)

Alasdair
Alasdair

Reputation: 309049

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.

Upvotes: 1

Stan Reduta
Stan Reduta

Reputation: 3492

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.

Upvotes: 0

Related Questions