UPDATE: SOLVED: Nested Form Submission in Flask
Note - I'm a new coder, so thanks for bearing with me!
My Project
I'm trying to create an organization centric data entry & analysis site for my capstone project. The parameters are ones that I set based on a real need we had at my last place of employment (public school district).
The Problem
I've been torturing myself trying to build a data-input table for users to enter student scores for various diagnostics/assessments and their individual components (i.e. a certain literacy assessment may test reading and writing separately). I'm working in Flask with Flask Forms and WTForms, my front end is using Bulma CSS.
Well hallelujah I've almost made it, but I have a few issues, the biggest and most immediate is that I can't figure out the syntax for submitting my nested form data.
forms.py
class ComponentForm(Form):
component_id = HiddenField('Component ID')
component = HiddenField('Component')
score = FloatField('Score', default=0.0, widget=NumberInput())
class AssessmentScoreForm(FlaskForm):
student_id = HiddenField(u'Student ID')
assessment_name=HiddenField(u'Assessment Name')
period = SelectField("Choose an option", validate_choice=False, choices={'1':'fall','2':'winter','3':'spring'})
components = FieldList(FormField(ComponentForm))
submit = SubmitField()
As it stands, I've been able to render a table that has columns representing each assessment component and rows representing each student. From my page source and from playing around with the labels, I can see that each cell is correctly associated with a student and a component.
Trouble is, when I go to submit, I'm receiving an error regarding the score field, which is in my nested ComponentForm
.
Here is my code for rending the page:
data_entry.py
@api.route('/data_entry', methods=['GET', 'POST'])
def data_entry():
#find current user adn their most recent classroom
user=current_user
staff = db.session.scalar(sa.select(Staff).where(Staff.id == user.staff_id))
current_class = staff.staff_classrooms[-1] #classroom object
assessment = 'Predictive Assessment of Reading' #will be a variable
#gather information for all students in that classroom
students = current_class.class_students
roster_data = []
for student in students:
roster_data.append({
'id': student.class_student.id,
'full_name': student.class_student.full_name
})
components = db.session.scalars(sa.select(AssessmentComponent).where(AssessmentComponent.assessment_name == assessment).order_by(AssessmentComponent.component_name)).all()
form = AssessmentScoreForm()
component_form = ComponentForm()
for component in components:
component_form.component_id = component.id
component_form.component = component.component_name
form.components.append_entry(component_form)
for student in students:
form.student_id.data = student.class_student.id
if form.validate_on_submit():
#create score instance and set period, student_id, classroom_id
for i in enumerate(form.components.entries):
new_score = AssessmentScore()
new_score.student_score = form.components.entries[i]['score'].data #score
new_score.period = form.period.data #period
new_score.component_id = form.components.entries[i]['component_id'].data #component
new_score.student_id = student.class_student.id #student
new_score.classroom_id = current_class.id #classroom
print (f'score_instance {new_score}')
db.session.add(new_score)
db.session.commit()
flash('Data entered successfully!','success')
return redirect('main.user', username=user.username)
return render_template('data_entry.html', form=form, components=components, roster_data=roster_data)
Attempt 1
if form.validate_on_submit():
#create score instance and set period, student_id, classroom_id
new_score = AssessmentScore()
new_score.student_score = form.score.data #score
Error 1
AttributeError: 'AssessmentScoreForm' object has no attribute 'score'
Attempt 2
if form.validate_on_submit():
#create score instance and set period, student_id, classroom_id
for component in form.components:
new_score = AssessmentScore()
new_score.student_score = form.components.score.data
Error 2
AttributeError: 'FieldList' object has no attribute 'score'
Attempt 3
if form.validate_on_submit():
for i in enumerate(form.components.entries):
new_score = AssessmentScore()
new_score.student_score = form.components.entries[i]['score'].data
Error 3
TypeError: list indices must be integers or slices, not tuple
What Else I've Tried
I've had many other iterations of this and even tried to use Plot.ly Dash to make an editable table, but with my limited knowledge, I'm not sure how to make this work.
Also, there are a TON of questions on stackoverflow regarding WTForms and, specifically, FieldList and FormField, most of them haven't been edited in 8-10 years, haven't been solved and/or aren't addressing the same issue. The WTForms documentation is also lacking in examples and lord help me if I read another tutorial on how to login and logout (jk I'm probably just using the wrong tool).
That being said, I've been hacking at this way too long and would send a million roses (in my thoughts) to whomever can help me out.
p.s.
I'm also open to conversation regarding other tools (what should I learn when I'm done with my course?) and/or alternative strategies/ deeper discussion about the tools I am using. i.e. Is writing documentation for WTForms a thing that would be cool? Would making a new function for forms be worth it? Is there some WTForms/NumPy/Pandas lovechild waiting to be born so the layman can make a silly little table? I'm wide-eyed and curious.