djangochoicefield

How to add choices from a view?


I am trying to dynamically populate two form choice fields, and namely person and work.

The view called assign_work_to_person should allow the user to select a person, a work that has not yet been assigned to a person, and once the form is submitted the selected work should be linked to the selected person (e.g. 'Leonardo da Vinci' should be linked to 'Mona Lisa').

What I am doing is creating the form class with empty choices for the two fields

class AssignWorkToPerson(forms.Form):
    person = forms.ChoiceField()
    work = forms.ChoiceField()

and then retrieving the available persons and authorless works to populate the form fields when the user accesses the assign_work_to_person view.

I am trying to dynamically set these choices in this way:

form.fields['the_field_name'].choices = a_list_of_tuples 

When I access the view, the template renders ok, with all the available choices being correctly displayed.

enter image description here

The problem is that when I submit the form, it raises validation errors for both fields, telling me to select a valid choice.

enter image description here

By running some (very basic print) tests, I have realized that trying to populate the fields this way, doesn't actually add the choices, even though they are there in the template. In fact, as you can see from the second picture, once I have submitted the form, the template renders the form with no choices available.

How can I make this work?

I know from other answers to similar questions that this can be done by specifying an __init__ method in the model (in my case the form, I suppose), and then by setting the choices through the queryset keyword argument, but I'd rather do this in the view, if possible.

Edit

def assign_work_to_person(request):
    form = AssignWorkToPerson()
    persons = Person.objects.all()
    person_choices = []
    for person in persons:
        person_choices.append((person.first_name + ' ' + person.last_name, person.first_name + ' ' + person.last_name))
    form.fields['person'].choices = person_choices
    works = Work.objects.all()
    work_choices = []
    for work in works.filter(author=None):
        work_choices.append((work.name, work.name))
    form.fields['work'].choices = work_choices

    if request.method == 'POST':
        persons = Person.objects.all()
        person_choices = []
        for person in persons:
            person_choices.append(
                (person.first_name + ' ' + person.last_name, person.first_name + ' ' + person.last_name))
        form.fields['person'].choices = person_choices
        works = Work.objects.all()
        work_choices = []
        for work in works.filter(author=None):
            work_choices.append((work.name, work.name))
        form.fields['work'].choices = work_choices
        print(form.fields['person'].choices)
        form = AssignWorkToPerson(request.POST)

        if form.is_valid():

            print('THIS FORM CONTAINS VALID DATA')
    template_name = 'assign_work_to_person.html'
    context = {'form': form}
    return render(request, template_name, context)

Solution

  • You are reinitializing form

    
    print(form.fields['person'].choices)
    form = AssignWorkToPerson(request.POST)
    
    if form.is_valid():
    
    

    It should be like this

    
       if request.method == 'POST':
            form = AssignWorkToPerson(request.POST)
            # skipped
            form.fields['person'].choices = person_choices  
            if form.is_valid():
                pass
    
    

    form = AssignWorkToPerson(request.POST) should be on top just after method check not at the end.