Take the 2-minute tour ×
Stack Overflow is a question and answer site for professional and enthusiast programmers. It's 100% free, no registration required.

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!

share|improve this question
    
possible duplicate of WTForms create variable number of fields –  Sean Vieira Jan 10 at 21:25
    
class GradesForm(Form): pass then for course in courses: setattr(GradesForm, course.name, TextField(course.name, validators=[Required()])) for an example :-) –  Sean Vieira Jan 10 at 21:27
    
@SeanVieira: Thanks for the input. I read that post (as well as the section in the docs demonstrating that technique), but I can't set the course grades in that way...at least not so easily. I can use this technique to populate the correct number of fields, and even use WTForms validation, but I'd still have to loop through the courses again in the controller to assign the form data to the correct objects in the DB (instead of using form.populate_obj(user)). –  bfin Jan 11 at 18:27
    
I am have a related issue with no resolution. In short, how do I pass a string variable to the form so I can use the data pre-poplulate a field. An example use case: a comma separated list of tags stored in a many-to-many database. I think the proper way to do it is to run the query pass the values from the view to the form, but every example I found uses populate_obj(post) which pass the entire post object to the form. What if I need to do something special to one of post's attributes before passing. –  jwogrady Apr 23 at 23:10
    
@bfin agghhh... Can you please post your solution. The original question was posted months ago so I assume you have figured this out. –  jwogrady Apr 23 at 23:13

1 Answer 1

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)
share|improve this answer
    
I'm not clear on how close our issues are related, but I think we are/were looking at this the wrong way. The populate_obj means we are passing data to another object. We are not populating the object with another object, hence the name populate object... In my case, my PostForm maps to my model. That means the value is attached to the field. No need to assign the value. The value is already there. Here is my answered question in case it helps. –  jwogrady Apr 24 at 22:58
    
@jwogrady: The challenge for my use case is that I have no object to pass to the populate_obj method, since this particular form is generated dynamically. If each student was enrolled in the same set of classes, then I could create a static form and set the grades for those specific courses as attributes of a User class and pass an instance of that class for data population/validation/persistence. Unfortunately, that's not my case... –  bfin Apr 24 at 23:31

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.