Django Forms allow user to input a list of tuples of unknown length
Question:
The app I’m creating requires instances of lessons to be created. For each lesson, any number of instructors can be assigned each with a specific user-inputted role.
For example:
- Lesson 1 has the following instructors
- role: head, name: Bob
- role: example, name: Adam
- role: assistant, name: Joy
Because the roles are unknown and the amount of instructors are unknown, the form must be able to accept a list of ‘tuples’. Each tuple would then need a textfield and a select field (selecting form a list of instructors).
The data would then be saved in this model:
class MapInstructorTeach(models.Model):
teach = models.ForeignKey(Teach, on_delete=models.CASCADE)
instructor = models.ForeignKey(Instructor, on_delete=models.CASCADE)
role = models.CharField(max_length=32)
The Teach
model is just a model specifying the name of the lesson. The instructor
model is a list of instructors.
How would I go about creating the forms.py class and the html for something like this? I don’t even know where to begin.
Attempts
jQuery infinite input form
I’ve tried using the above method to generate the fields in the front end but couldn’t figure out a way to link the input fields to the backend.
Answers:
I highly recommend taking a look at inline formsets, or formsets in general. Using formsets, you can use multiple forms on a page, with inline formsets, you could have those forms bound to a parent form. Think of:
- A College will have multiple teachers, amount unknown
- Teacher will have a foreign key to college.
- You might want to add teachers upon college ‘creation’
So with inline formsets, you could have the college form, and then all teacher forms on the same page, already bound to that college form.
Formsets, by themselves allow all that, without a bound-to model.
With some javascript, you could add a button to dynamically add more forms. More info on this post
If you really want to use just front-end forms
Use a JSONfield on your model, and then add teachers accordingly.
An off-stack-network guide to inline formsets:
Using the suggestion given by nigel239, I created a model form and inlineformset_factory to get my desired outcome.
forms.py
class AssignSeniorForm(ModelForm):
BLANK_CHOICE_SENIOR = [("", "Senior")]
BLANK_CHOICE_DASH = BLANK_CHOICE_DASH
role = forms.CharField(max_length=32)
senior = forms.ChoiceField(choices=[])
def __init__(self, *args, night_id, senior_choices, **kwargs):
super(AssignSeniorForm, self).__init__(*args, **kwargs)
self.night_id = night_id
self.fields["senior"].choices = (
self.BLANK_CHOICE_SENIOR + self.BLANK_CHOICE_DASH + senior_choices
)
class Meta:
model = MapSeniorNight
fields = ["role", "senior"]
def clean(self):
cleaned_data = self.cleaned_data
senior_id = cleaned_data.get("senior")
senior_instance = Senior.get_by_id(senior_id)
cleaned_data.update({"senior": senior_instance})
return cleaned_data
AssignSeniorFormset = inlineformset_factory(
TrainingNight, MapSeniorNight, form=AssignSeniorForm, extra=2
)
and then I used the AssignNightFormset in my views.py
The app I’m creating requires instances of lessons to be created. For each lesson, any number of instructors can be assigned each with a specific user-inputted role.
For example:
- Lesson 1 has the following instructors
- role: head, name: Bob
- role: example, name: Adam
- role: assistant, name: Joy
Because the roles are unknown and the amount of instructors are unknown, the form must be able to accept a list of ‘tuples’. Each tuple would then need a textfield and a select field (selecting form a list of instructors).
The data would then be saved in this model:
class MapInstructorTeach(models.Model):
teach = models.ForeignKey(Teach, on_delete=models.CASCADE)
instructor = models.ForeignKey(Instructor, on_delete=models.CASCADE)
role = models.CharField(max_length=32)
The Teach
model is just a model specifying the name of the lesson. The instructor
model is a list of instructors.
How would I go about creating the forms.py class and the html for something like this? I don’t even know where to begin.
Attempts
jQuery infinite input form
I’ve tried using the above method to generate the fields in the front end but couldn’t figure out a way to link the input fields to the backend.
I highly recommend taking a look at inline formsets, or formsets in general. Using formsets, you can use multiple forms on a page, with inline formsets, you could have those forms bound to a parent form. Think of:
- A College will have multiple teachers, amount unknown
- Teacher will have a foreign key to college.
- You might want to add teachers upon college ‘creation’
So with inline formsets, you could have the college form, and then all teacher forms on the same page, already bound to that college form.
Formsets, by themselves allow all that, without a bound-to model.
With some javascript, you could add a button to dynamically add more forms. More info on this post
If you really want to use just front-end forms
Use a JSONfield on your model, and then add teachers accordingly.
An off-stack-network guide to inline formsets:
Using the suggestion given by nigel239, I created a model form and inlineformset_factory to get my desired outcome.
forms.py
class AssignSeniorForm(ModelForm):
BLANK_CHOICE_SENIOR = [("", "Senior")]
BLANK_CHOICE_DASH = BLANK_CHOICE_DASH
role = forms.CharField(max_length=32)
senior = forms.ChoiceField(choices=[])
def __init__(self, *args, night_id, senior_choices, **kwargs):
super(AssignSeniorForm, self).__init__(*args, **kwargs)
self.night_id = night_id
self.fields["senior"].choices = (
self.BLANK_CHOICE_SENIOR + self.BLANK_CHOICE_DASH + senior_choices
)
class Meta:
model = MapSeniorNight
fields = ["role", "senior"]
def clean(self):
cleaned_data = self.cleaned_data
senior_id = cleaned_data.get("senior")
senior_instance = Senior.get_by_id(senior_id)
cleaned_data.update({"senior": senior_instance})
return cleaned_data
AssignSeniorFormset = inlineformset_factory(
TrainingNight, MapSeniorNight, form=AssignSeniorForm, extra=2
)
and then I used the AssignNightFormset in my views.py