Search code examples
pythonflaskflask-wtformswtformsfieldlist

Render HTML Table with Editable WTForms FieldList and Non-Editable Values Using Jinja2 Flask


I'm using Flask and Jinja2 and I need to make an attendance table that includes both editable and non-editable fields. I referred to other posts such as here and here which got me to this point. The table successfully displays the editable fields using FieldList. However, I have not been able to render the non-editable fields.

This is what the table should look like: enter image description here

The only fields which should be editable are "attendance code" and "comment". Unfortunately, I have not found a way to include the other fields (class name, start time, end time, first name, last name) as simple text fields.

I have tried using the read-only attribute for WTForms. While this is functional, it displays the text in text boxes which don't look appealing.

My latest attempt shown below defines a WTForms class called updateStudentAttendanceForm that inherits the fields from another class called attendanceLogClass that includes instance variables for the desired fields. I assign the values to the form class in the routes.py file. However, when I reference these variables in the html file, they result in blank fields. I have used a print statement to verify the variable assignments are working properly. I cannot figure out why the variables do not display properly when included in the html template.

forms.py

class attendanceLogClass:
    def __init__(self):
        self.classAttendanceLogId = int()
        self.className = str()
        self.firstName = str()
        self.lastName = str()
        self.startTime = datetime()
        self.endTime = datetime()

    def __repr__(self):
        return f"attendanceLogClass('{self.classAttendanceLogId}','{self.className}','{self.firstName}','{self.lastName}','{self.startTime}','{self.endTime}')"


class updateStudentAttendanceForm(FlaskForm, attendanceLogClass):
    attendanceCode = RadioField(
        "Attendance Code",
        choices=[("P", "P"), ("T", "T"), ("E", "E"), ("U", "U"), ("Q", "?"),],
    )
    comment = StringField("Comment")
    submit = SubmitField("Submit Attendance")


class updateClassAttendanceForm(FlaskForm):
    title = StringField("title")
    classMembers = FieldList(FormField(updateStudentAttendanceForm))

routes.py

@app.route("/classattendancelog")
def displayClassAttendanceLog():
    classAttendanceForm = updateClassAttendanceForm()
    classAttendanceForm.title.data = "My class"
    for studentAttendance in ClassAttendanceLog.query.all():
        studentAttendanceForm = updateStudentAttendanceForm()
        studentAttendanceForm.className = studentAttendance.ClassSchedule.className
        studentAttendanceForm.classAttendanceLogId = studentAttendance.id
        studentAttendanceForm.className = studentAttendance.ClassSchedule.className
        studentAttendanceForm.startTime = studentAttendance.ClassSchedule.startTime
        studentAttendanceForm.endTime = studentAttendance.ClassSchedule.endTime
        studentAttendanceForm.firstName = (
            studentAttendance.ClassSchedule.Student.firstName
        )
        studentAttendanceForm.lastName = (
            studentAttendance.ClassSchedule.Student.lastName
        )
        studentAttendanceForm.attendanceCode = studentAttendance.attendanceCode
        studentAttendanceForm.comment = studentAttendance.comment
        # The following print statement verified that all of the variables are properly defined based on the values retrieved from the database query
        print(studentAttendanceForm)
        classAttendanceForm.classMembers.append_entry(studentAttendanceForm)

    return render_template(
        "classattendancelog.html",
        title="Class Attendance Log",
        classAttendanceForm=classAttendanceForm,
    )

classattendancelog.html:

{% extends 'layout.html'%}
{% block content %}
<h1> Class Attendance </h1>
<form method="POST" action="" enctype="multipart/form-data">
  {{ classAttendanceForm.hidden_tag() }}
  <table class="table table-sm table-hover">
    <thead class="thead-light">
      <tr>
        <th scope="col">Class Name</th>
        <th scope="col">Start Time</th>
        <th scope="col">End Time</th>
        <th scope="col">First Name</th>
        <th scope="col">Last Name</th>
        <th scope="col">Attendance Code</th>
        <th scope="col">Comment</th>
      </tr>
    </thead>
    <tbody>

      {% for studentAttendanceForm in classAttendanceForm.classMembers %}
      <tr>
        <td> {{ studentAttendanceForm.className }}</td>
        <td> {{ studentAttendanceForm.startTime }}</td>
        <td> {{ studentAttendanceForm.endTime }}</td>
        <td> {{ studentAttendanceForm.firstName }}</td>
        <td> {{ studentAttendanceForm.lastName }} </td>
        <td>
          {% for subfield in studentAttendanceForm.attendanceCode %}
          {{ subfield }}
          {{ subfield.label }}
          {% endfor %}
        </td>
        <td>
          {{ studentAttendanceForm.comment(class="form-control form-control-sm") }}
        </td>
      </tr>
      {% endfor %}
    </tbody>
  </table>

  {% endblock content %}

Note: I haven't yet written the code to handle the form response.


Solution

  • I solved the problem by using the zip function to iterate simultaneously through two lists: one list with the FormField data and a second list with the non-editable "fixed field" data.

    To use "zip" in the HTML template, I followed the instructions here and added this line to my init.py

    app.jinja_env.globals.update(zip=zip)
    

    updated forms.py (eliminated attendanceLogClass with fixed field variables):

    class updateStudentAttendanceForm(FlaskForm):
        attendanceCode = RadioField(
            "Attendance Code",
            choices=[("P", "P"), ("T", "T"), ("E", "E"), ("U", "U"), ("Q", "?"),],
        )
        comment = StringField("Comment")
        submit = SubmitField("Submit Attendance")
    
    
    class updateClassAttendanceForm(FlaskForm):
        title = StringField("title")
        classMembers = FieldList(FormField(updateStudentAttendanceForm))
    

    updated routes.py (added new variable for fixed fields called classAttendanceFixedFields):

    @app.route("/classattendancelog")
    def displayClassAttendanceLog():
        classAttendanceFixedFields = ClassAttendanceLog.query.all()
        classAttendanceForm = updateClassAttendanceForm()
        classAttendanceForm.title.data = "My class"
    
        for studentAttendance in ClassAttendanceLog.query.all():
            studentAttendanceForm = updateStudentAttendanceForm()
            studentAttendanceForm.attendanceCode = studentAttendance.attendanceCode
            studentAttendanceForm.comment = studentAttendance.comment
            classAttendanceForm.classMembers.append_entry(studentAttendanceForm)
    
        return render_template(
            "classattendancelog.html",
            title="Class Attendance Log",
            classAttendanceForm=classAttendanceForm,
            classAttendanceFixedFields=classAttendanceFixedFields,
        )
    

    updated classattendancelog.html (incorporated zip function in the for loop to simultaneously iterate through the editable fields and fixed fields).

    {% extends 'layout.html'%}
    {% block content %}
    <h1> Class Attendance </h1>
    <form method="POST" action="" enctype="multipart/form-data">
      {{ classAttendanceForm.hidden_tag() }}
      <table class="table table-sm table-hover">
        <thead class="thead-light">
          <tr>
            <th scope="col">Class Name</th>
            <th scope="col">Start Time</th>
            <th scope="col">End Time</th>
            <th scope="col">First Name</th>
            <th scope="col">Last Name</th>
            <th scope="col">Attendance Code</th>
            <th scope="col">Comment</th>
          </tr>
        </thead>
        <tbody>
    
          {% for studentAttendanceForm, studentFixedFields in zip(classAttendanceForm.classMembers, classAttendanceFixedFields) %}
          <tr>
            <td> {{ studentFixedFields.ClassSchedule.className }}</td>
            <td> {{ studentFixedFields.ClassSchedule.startTime.strftime('%-I:%M') }}</td>
            <td> {{ studentFixedFields.ClassSchedule.endTime.strftime('%-I:%M') }}</td>
            <td> {{ studentFixedFields.ClassSchedule.Student.firstName }}</td>
            <td> {{ studentFixedFields.ClassSchedule.Student.lastName }} </td>
            <td>
              {% for subfield in studentAttendanceForm.attendanceCode %}
              {{ subfield }}
              {{ subfield.label }}
              {% endfor %}
            </td>
            <td>
              {{ studentAttendanceForm.comment(class="form-control form-control-sm") }}
            </td>
          </tr>
          {% endfor %}
        </tbody>
      </table>
    
      {% endblock content %}