Как заполнить списки объектов с помощью WTForms?

Пример использования: использование формы для ввода оценок по каждому курсу, на который зачислен учащийся.

Модель:

Используя SQLAlchemy, я определил объект Student, объект Course и объект ассоциации StudentCourse, в которых хранится оценка каждого учащегося по каждому курсу.

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
    ...

Просмотр:

Я могу запросить модель StudentCourses и создать соответствующую форму, но не могу понять, как передать/получить данные из запроса в виде объекта.

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}

Это создает пустую форму, поэтому я, очевидно, не могу заполнить форму данными непосредственно из объекта Query. Но мне не удалось заполнить форму любым объектом, даже при создании формы с типом FormField для каждого курса:

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}

Использование запроса, если это возможно, было бы самым простым. Согласно документам SQLAlchemy, использование метода query() в сеансе создает объект Query. При повторении, как я сделал в своем контроллере, этот объект представляет собой список StudentCourse объектов.

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

...и мой прогресс заканчивается здесь. Любая помощь приветствуется!


person bfin    schedule 10.01.2014    source источник
comment
возможный дубликат WTForms создает переменное количество полей   -  person Sean Vieira    schedule 11.01.2014
comment
class GradesForm(Form): pass затем for course in courses: setattr(GradesForm, course.name, TextField(course.name, validators=[Required()])) для примера :-)   -  person Sean Vieira    schedule 11.01.2014
comment
@SeanVieira: Спасибо за вклад. Я прочитал это сообщение (а также раздел в docs, демонстрирующие эту технику), но я не могу выставить оценки за курс таким образом... по крайней мере, не так просто. Я могу использовать эту технику для заполнения правильного числа полей и даже использовать проверку WTForms, но мне все равно придется снова проходить курсы в контроллере, чтобы назначить данные формы правильным объектам. в БД (вместо использования form.populate_obj(user)).   -  person bfin    schedule 11.01.2014
comment
У меня есть связанная проблема, решение которой не найдено. Короче говоря, как мне передать строковую переменную в форму, чтобы я мог использовать данные для предварительного заполнения поля. Пример использования: разделенный запятыми список тегов, хранящийся в базе данных «многие ко многим». Я думаю, что правильный способ сделать это - запустить запрос и передать значения из представления в форму, но каждый найденный мной пример использует populate_obj(post), который передает весь объект сообщения в форму. Что, если мне нужно сделать что-то особенное с одним из атрибутов сообщения перед передачей?   -  person jwogrady    schedule 24.04.2014
comment
@bfin agghhh... Не могли бы вы пожалуйста опубликовать свое решение. Первоначальный вопрос был опубликован несколько месяцев назад, поэтому я предполагаю, что вы поняли это.   -  person jwogrady    schedule 24.04.2014
comment
@jwogrady: мне не удалось заполнить/проверить/сохранить данные формы с помощью объектов, но я опубликовал решение на основе kwargs, которое в итоге использовал, на случай, если оно поможет.   -  person bfin    schedule 24.04.2014
comment
@bfin, большое спасибо за это. Я очень ценю это.   -  person jwogrady    schedule 25.04.2014


Ответы (1)


Единственный способ, которым я смог заполнить эти динамически созданные формы, — это передать **kwargs, поэтому я опубликую этот метод в качестве ответа, пока кто-то другой не сможет найти объектно-ориентированное решение.

Чтобы заполнить форму:

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

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

Таким образом, я могу отображать форму в шаблоне, перебирая поля, и при отправке у меня будет список диктов, которые я затем могу проверить и использовать для обновления записей моей базы данных.

Для проверки формы требуется тот же метод:

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

Это работает, но было бы здорово, если бы я мог использовать метод WTForms populate_obj():

def view(request):
    ...
    if request.method == 'POST' and form.validate():
        form.populate_obj(student_courses)
person bfin    schedule 24.04.2014
comment
Я не знаю, насколько тесно связаны наши проблемы, но я думаю, что мы смотрим на это неправильно. Populate_obj означает, что мы передаем данные другому объекту. Мы не заполняем объект другим объектом, отсюда и название объекта заполнения... В моем случае моя PostForm сопоставляется с моей моделью. Это означает, что значение привязано к полю. Не нужно присваивать значение. Ценность уже есть. Вот мой ответ на вопрос на случай, если это поможет. - person jwogrady; 25.04.2014
comment
@jwogrady: проблема для моего варианта использования заключается в том, что у меня нет объекта для передачи методу populate_obj, поскольку эта конкретная форма генерируется динамически. Если бы каждый студент был зачислен в один и тот же набор классов, я мог бы создать статическую форму и установить оценки для этих конкретных курсов как атрибуты класса пользователя и передать экземпляр этого класса для заполнения/проверки/сохранения данных. К сожалению, это не мой случай... - person bfin; 25.04.2014