Reputation: 47
I'm trying to make multiple updates on a database model through one flask form. In essence, I want to display the questions for a test, and then have the user put in answers for the test, then to take all of the individual responses and update each corresponding db.Model listed below.
What I have now is the following:
Form:
class InputStudentTestForm(FlaskForm):
answer = StringField('Answer', validators=[DataRequired()])
submit = SubmitField('Submit Answers')
Page:
{% extends "base.html" %}
{% block content %}
<h1>{{ test.name }}</h1>
<form action="" method="post" >
{{ form.hidden_tag() }}
{% for q in questions %}
<p>
{{ q.text }} <br>
{{ form.answer }}
</p>
{% endfor %}
<p>
{{ form.submit() }}
</p>
</form>
{% endblock %}
Routing:
@app.route('/input_answer/<testname>', methods=['GET', 'POST'])
def input_test_answer(testname):
test = Test.query.filter_by(name=testname).first_or_404()
questions = test.questions.all()
form = InputStudentTestForm()
if form.validate_on_submit():
i = 0
for value in form.data:
q = questions[i]
a = Student_Answers(current_user, test, q)
a.add_response(value)
db.session.add(a)
db.session.commit()
i = i + 1
flash('You have just added new test responses')
return redirect(url_for('index'))
return render_template('input_test_answers.html', test=test, questions=questions, form=form)
Model:
class Student_Answers(db.Model):
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=True, primary_key=True)
test_id = db.Column(db.Integer, db.ForeignKey('test.id'), primary_key=True)
question_id = db.Column(db.Integer, db.ForeignKey('question.id'), primary_key=True)
student_option = db.Column(db.String(1))
db.UniqueConstraint('user_id', 'test_id', 'question_id')
db.relationship('User', uselist=False, backref='student_answers', lazy='dynamic')
db.relationship('Test', uselist=False, backref='student_answers', lazy='dynamic')
db.relationship('Question', uselist=False, backref='student_answers', lazy='dynamic')
def add_response(self, answer):
if not self.is_response(answer):
self.student_option = answer
def remove_response(self,answer):
if self.is_response(answer):
self.student_option = ''
def is_response(self, answer):
return self.student_option == answer
def __init__(self, user, test, question):
self.user_id = user.id
self.test_id = test.id
self.question_id = question.id
def __repr__(self):
return '<Student_Answer {}>'.format(self.student_option)
But when the user submits the data, I am receiving a unique constraint fail, as the Student_Answers is being updated with parameters (1,1,1 'answer').
I think the fail comes from the constraint of the student response having to be a single character, however I am not sure where the string 'answer' is being taken from the form.data. Any help would be great! Thanks.
Edit: I am trying to implement the dynamic form approach, and get an UnboundField error. Here is the updated code:
Form:
def DynamicTestForm(questions, *args, **kwargs):
class TestForm(FlaskForm):
pass
for name, value in questions.items():
setattr(TestForm, name, value)
return TestForm(*args, **kwargs)
Route:
def input_test_answer(testname):
test = Test.query.filter_by(name=testname).first_or_404()
questions = test.questions.all()
questions_as_field = {}
for q in questions:
p = field.SelectField('{}' .format(q.text), choices=[('A', 'A'), ('B', 'B'), ('C', 'C'), ('D', 'D')], validators=[DataRequired()])
questions_as_field.update(p)
form = DynamicTestForm(questions_as_field)
if form.validate_on_submit():
i = 0
for value in form.data.values():
a = Student_Answers.query.filter_by(user_id=current_user.id,test_id=test.id, question_id=q.id).one()
if not a.is_response(str(value)):
a.student_option = str(value)
i = i + 1
db.session.commit()
flash('You have just added new test responses')
return redirect(url_for('user', username=current_user.name))
and Page:
{% extends "base.html" %}
{% block content %}
<h1>{{ test.name }}</h1>
<form action="" method="post" >
{{ form.hidden_tag() }}
{% for field in form %}
<p>
{{ field.label }} <br>
{{ field() }}
</p>
{% endfor %}
<p>
{{ form.submit() }}
</p>
</form>
{% endblock %}
But at this point, I am recieving an UnboundField error, and I am not so sure why.
Upvotes: 0
Views: 596
Reputation: 714
With a Dynamic Form approach, I would try something like this:
def DynamicTestForm(questions, *args, **kwargs):
class TestForm(FlaskForm):
pass
for name, value in questions.items():
setattr(TestForm, name, value)
return TestForm(*args, **kwargs)
questions = {
'question_1': fields.StringField('Question 1', description='The quick brown fox jumps over which lazy animal?'),
'question_2': fields.StringField('Question 2', description='How many gigawatts does it take to go Back to the Future?')
}
f = DynamicTestForm(questions)
Upvotes: 1