ISO Help: Nested Form Submission in Flask

francheese9289 - May 8 - - Dev Community

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()
Enter fullscreen mode Exit fullscreen mode

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.

Screenshot of data entry page

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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  
Enter fullscreen mode Exit fullscreen mode

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.

. . .