Вопрос

Use case: using a form to enter grades for each course a student is enrolled in.

Model:

Using SQLAlchemy, I defined a Student object, a Course object, and a StudentCourse association object that stores each student's grade for each course.

class Student(Base):
    __tablename__ = 'students'
    id = Column(Integer, primary_key=True)
    name = Column(Text)
    courses = association_proxy('student_courses', 'grade',
        creator=lambda k, v: StudentCourse(course_title=k, grade=v))
    ...

class Course(Base):
    __tablename__ = 'courses'
    id = Column(Integer, primary_key=True)
    title = Column(Text, unique=True)
    ...

# Link students to courses and store grades
class StudentCourse(Base):
    __tablename__ = 'student_courses'
    student_id = Column(Integer, ForeignKey(Student.id), primary_key=True)
    course_id = Column(Integer, ForeignKey(Course.id), primary_key=True)
    grade = Column(Integer)
    student = relationship(Student,backref=backref(
            'student_courses',
            collection_class=attribute_mapped_collection('course_title'),
            cascade='all, delete-orphan'))
    course = relationship(Course)

    @property
    def course_title(self):
        if self.course is not None:
            return self.course.title
        else:
            return self._course_title
    ...

View:

I can query the StudentCourses model and construct a relevant form, but I can't figure out how to pass/retrieve the data from the query as an object.

def view(request):
    student = Student.from_request(request)
    student_courses = DBSession.query(StudentCourse).\
        filter(StudentCourse.student_id == student.id).\
        all()

    class GradesForm(Form):
        pass

    # Add form field for each course the student is enrolled in
    for c in student_courses:
        setattr(GradesForm,
                c.course_title,
                IntegerField()
                )

    form = GradesForm(request.POST, obj=student_courses) # this doesn't populate the form

    return {'form': form}

This produces a blank form, so I obviously can't populate the form with data directly from the Query object. But I've been unsuccessful populating the form with any kind of object, even when creating a form with a FormField type for each course:

class StudentCourseForm(Form):
    course_title = StringField()
    grade = IntegerField()

def view(request):
    ...
    class GradesForm(Form):
        pass

    # Add form field for each course
    for c in student_courses:
        setattr(GradesForm,
                c.course_title,
                FormField(StudentCourseForm)
                )

    form = GradesForm(request.POST, obj=student_courses)

    return {'form': form}

Using a query, if possible, would be the easiest. Per the SQLAlchemy docs, using the query() method on a session creates a Query object. When iterated like I did in my controller, this object is a list of StudentCourse objects.

[<app.models.StudentCourse object at 0x10875bd50>, <app.models.StudentCourse object at 0x10875bed0>]

...and my progress ends here. Any help appreciated!

Это было полезно?

Решение

The only way I've been able to populate these dynamically-created forms is by passing **kwargs, so I'll post this method as an answer until someone else can figure out an object-based solution.

To populate the form:

def view(request):
    ...
    data = {}
    for c in student_courses:
        data[c.course_title] = c.grade

    # Populate form
    form = GradesForm(request.POST, **data)
    ...

In this way, I can render a form in a template by iterating over the fields, and when submitted, I'll have a list of dicts which I can then validate and use to update my database records.

Form validation requires the same method:

def view(request):
    ...
    # Validate and persist form data
    if request.method == 'POST' and form.validate():
        for c in student_courses:
            student.courses[c.title] = form[c.title].data

This works, but it'd be great if I could use the WTForms populate_obj() method:

def view(request):
    ...
    if request.method == 'POST' and form.validate():
        form.populate_obj(student_courses)
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top