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.
The problem is that when I submit the form, it raises validation errors for both fields, telling me to select a valid choice.
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.
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)
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.