Multiple, independent, instances of same wtform form on same page - python

I am playing with a project management application. I am looping the projects, the lists in side the projects, and the tasks inside each list. I would like the CreateTask() form to appear for each list, under the tasks, and it does.
The reason is that I have 2 hidden fields, the project id and the list id.
The problem is that, while the forms are showing up correctly, if I enter a task name and submit all it does is refresh the page and every form has the text I entered into the first form.
How can I use the same WTForms form in multiple instances on the same page and have them work independently?
One form, with the project and list id's set to select fields, works. I am able to add tasks.
from flask import Flask, render_template, request, redirect, url_for
from flask_sqlalchemy import SQLAlchemy
from flask_wtf import FlaskForm
from wtforms import StringField, SelectField, TextAreaField, SubmitField, HiddenField
from wtforms.validators import DataRequired, Length, ValidationError
app = Flask(__name__)
app.config['SECRET_KEY'] = 'f0c4803ba453fc7f8d0c43df21413916'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db'
db = SQLAlchemy(app)
##################################
###### Database Tables ######
##################################
class Project(db.Model):
p_id = db.Column(db.Integer, primary_key=True)
p_title = db.Column(db.String(300), nullable=False)
p_list = db.relationship('List', backref="project", lazy=True)
p_task = db.relationship('Task', backref="project", lazy=True)
def __repr__(self):
return f"P('p_id: {self.p_id}','p_title: {self.p_title}','p_list: {self.p_list}', 'p_task: {self.p_task}')"
class List(db.Model):
l_id = db.Column(db.Integer, primary_key=True)
l_title = db.Column(db.String(300), nullable=False)
l_project = db.Column(db.Integer, db.ForeignKey('project.p_id'), nullable=False)
l_task = db.relationship('Task', backref="list", lazy=True)
def __repr__(self):
return f"L('l_id: {self.l_id}','l_title: {self.l_title}','l_project: {self.l_project}')"
class Task(db.Model):
t_id = db.Column(db.Integer, primary_key=True)
t_title = db.Column(db.Integer, nullable=False)
t_list = db.Column(db.Integer, db.ForeignKey('list.l_id'), nullable=False)
t_project = db.Column(db.Integer, db.ForeignKey('project.p_id'), nullable=False)
def __repr__(self):
return f"T('t_id: {self.t_id}',' t_title: {self.t_title}','t_project: {self.t_project}')"
##################################
##### Forms ######
##################################
class CreateTask(FlaskForm):
project = HiddenField('Project:', validators=[DataRequired()])
list_select = HiddenField('List:', validators=[DataRequired()])
task_title = StringField('Task Title:', validators=[DataRequired()])
submit = SubmitField('Add Task')
##################################
###### Run App ######
##################################
#app.route('/try', methods=['GET', 'POST'])
def index():
form = CreateTask(request.values, project="foo", fld2="bar")
if form.validate_on_submit():
p_id = form.project.data
l_id = form.list_select.data
task = form.task_title.data
new_task = Task(t_project=p_id, t_list=l_id, t_title=task)
db.session.add(new_task)
db.session.commit()
project = Project.query.all()
return render_template('index.html', form=form, project=project)
if __name__ == "__main__":
app.run(debug=True)
{% for item in project %}
<div style="padding: 30px; maring: 30px; border: 1px solid #000;">
<h2>{{ item.p_title }}</h2><br><hr><br>
{% for item2 in item.p_list %}
<h3>{{ item2.l_title }}</h3><br><hr><br>
{% for item3 in item2.l_task %}
<h4>{{ item3.t_title }}</h4><br><br>
{% endfor %}
<br><br>
<div style="padding: 40px;">
<form action="" method="POST">
{{ form.hidden_tag() }}
{{ form.project(value=item.p_id) }}
{{ form.list_select(value=item2.l_id) }}
{{ form.task_title.label }}<br>
{{ form.task_title }}<br><br>
{{ form.submit }}
</form>
</div>
{% endfor %}
</div>
{% endfor %}

You don't create two instances of the form, you just render the same instance twice.
So in app.py it should be:
def index():
form1 = CreateTask(request.values, project="foo", fld2="bar")
form2 = CreateTask(request.values, project="foo", fld2="bar")
...
return render_template('index.html', form1=form1, form2=form2, project=project)
And the template has to be changed accordingly. You can put the two instances in a list if you want to iterate over them in the template.
But be aware that if you have several forms on a page only one form will get submitted. If you want to edit several items at once you need to iteratively generate a single form (at least if you don't add some JS magic).

Related

How I could fix this error with SQLalchemy? Does the problem only in SQL code maybe there is something else?

Bellow there is a code which suppose to let me add a book and added book see in route /books, however it gives me an error:
cursor.execute(statement, parameters)
sqlalchemy.exc.InterfaceError: (sqlite3.InterfaceError) Error binding parameter 3 - probably unsupported type.
[SQL: INSERT INTO book (book_name, book_author, publication_date, "ISBN_num", description, img, price) VALUES (?, ?, ?, ?, ?, ?, ?)]
[parameters: ('Robbins Basic Pathology, 10th Edition ', 'Vinay Kumar & Abul K. Abbas & Jon Aster ', '2017-05-01', Decimal('9780323353175'), "Part of the trusted Robbins and Cotran fami
ly, Robbins Basic Pathology provides a readable, well-illustrated and concise overview of the principles o ... (185 characters truncated) ... ew artwork and more schematic diagrams to
further aid in summarizing key pathologic processes and expand the already impressive illustration program.", <FileStorage: 'book1.jpg' ('image/jpeg')>, 49.49)]
(Background on this error at: https://sqlalche.me/e/14/rvf5)
Here is my flask code, it is my first time with a flask and actually it might have more errors, however I want to complete specific problem up there.
from flask import Flask, render_template, redirect, url_for
from flask_bootstrap import Bootstrap
from flask_sqlalchemy import SQLAlchemy
from flask_wtf import FlaskForm
from flask_wtf.file import FileRequired, FileAllowed
from wtforms import StringField, SubmitField, DateField, TextAreaField, FileField, FloatField, DecimalField
from wtforms.validators import DataRequired
app = Flask(__name__)
app.config['SECRET_KEY'] = '8BYkEfBA6O6donzWlSihBXox7C0sKR6b'
Bootstrap(app)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///books.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
class Book(db.Model):
id = db.Column(db.Integer, primary_key=True)
book_name = db.Column(db.String(50), nullable=False)
book_author = db.Column(db.String(50), nullable=False)
publication_date = db.Column(db.Date, nullable=False)
ISBN_num = db.Column(db.Integer, unique=True, nullable=False)
description = db.Column(db.Text(400), nullable=False)
img = db.Column(db.String(50), nullable=False)
price = db.Column(db.Float, nullable=False)
db.create_all()
class BookForm(FlaskForm):
book_name = StringField('Book name', validators=[DataRequired()])
book_author = StringField("Book author name", validators=[DataRequired()])
publication_date = DateField("Publication date", validators=[DataRequired()])
ISBN_num = DecimalField("The ISBN-13 number", validators=[DataRequired()])
description = TextAreaField("Description", validators=[DataRequired()])
img = FileField("Image", validators=[FileRequired(), FileAllowed(['jpg', 'png', 'gif', 'jpeg'])])
price = FloatField("Book price", validators=[DataRequired()])
submit = SubmitField('Submit')
#app.route("/")
def home():
return render_template("index.html")
#app.route('/add', methods=["GET", "POST"])
def add_book():
form = BookForm()
if form.validate_on_submit():
new_book = Book(
book_name=form.book_name.data,
book_author=form.book_author.data,
publication_date=form.publication_date.data,
ISBN_num=form.ISBN_num.data,
description=form.description.data,
img=form.img.data,
price=form.price.data
)
db.session.add(new_book)
db.session.commit()
return redirect(url_for('books'))
return render_template('add.html', form=form)
#app.route('/books')
def books():
all_books = db.session.query(Book).all()
return render_template('index.html', books=all_books)
if __name__ == '__main__':
app.run(debug=True)
Here is my add.html file which let me to add item to index.html.
{% extends 'bootstrap/base.html' %}
{% import "bootstrap/wtf.html" as wtf %}
{% block styles %}
{{ super() }}
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
{% endblock %}
{% block title %}Add A New Book{% endblock %}
{% block content %}
<div class="container">
<div class="row">
<div class="col-sm-12 col-md-8">
<h1>Add a new book into the database</h1>
{{ wtf.quick_form(form, novalidate=True) }}
<p class="space-above">See all books</p>
</div>
</div>
</div>
{% endblock %}
index.html stand for collecting books data and show books in this page.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Library</title>
</head>
<body>
<h1>My Library</h1>
{% if books == [] %}
<p>Library is empty.</p>
{% endif %}
<ul>
{% for book in books %}
<li>
Delete
{{book.name}} - {{book.author}} - {{book.date}} -
{{ book.num }} - {{ book.description }} -
{{ book.image_file }} - {{ book.price }}
</li>
{% endfor %}
</ul>
Add New Book
</body>
</html>

Why do I get a null row in my sqlite database in flask?

Every time I upload a post, it will post a row with null values inside all of the columns except the date and id, and then add a second row with the correct information put in each column. The SQLite data looks like this. Using SQL-alchemy, flask-login, wtf-forms, flask-bootstrap,werkzeug.
id title content posting_user date_posted
1 null null jack 2021-11-01
2 adad test jack 2021-11-01
Post Model
class Posts(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(50))
content = db.Column(db.Text)
posting_user = db.Column(db.String(255))
date_posted = db.Column(db.String(50))
class Postform(FlaskForm):
title = StringField('Title', validators=[InputRequired(), Length(min = 3, max = 50)])
content = StringField('Content', validators=[InputRequired()], widget=TextArea())
HTML block
<div class="container">
<div class="row">
<div class="col-md-4 offset-md-4">
<div class="login-form bg-light mt-4 p-4">
<form action="" method="POST" class="row g-3">
<h4>Add a Post</h4>
{{ form.hidden_tag()}}
{{ form.title(placeholder = "Title") }}
{{ form.content(cols="30", rows="15", placeholder = "Write your review here") }}
<div class="col-12">
<button type="submit" name = "post_submit" class="btn btn-dark float-end">Post</button>
</div>
</form>
</div>
</div>
</div>
</div>
app.py
#app.route('/dashboard/post', methods = ["POST", "GET"])
#login_required
def post():
form = Postform()
posting = Posts(title = form.title.data, content = form.content.data, posting_user = current_user.username, date_posted = datetime.datetime.now().date())
if posting.title != None and posting.content != None:
db.session.add(posting)
db.session.commit()
flash('Your post has been added', 'posted')
return render_template("post.html", form = form, loggedin = current_user.is_active)
It is possible that you send a GET request before your POST request in which the form is still empty.
You can differentiate between the request methods to control the behavior.
#app.route('/dashboard/post', methods = ["POST", "GET"])
#login_required
def post():
form = Postform()
# Validate whether the request method is post and the entries are correct!
if form.validate_on_submit():
posting = Posts(title = form.title.data, content = form.content.data, posting_user = current_user.username, date_posted = datetime.datetime.now().date())
db.session.add(posting)
db.session.commit()
flash('Your post has been added', 'posted')
return render_template("post.html", form = form, loggedin = current_user.is_active)

Flask form TypeError: object of type 'int' has no len()

I am trying to make a registration form with flask. My code seems clean with all the proper data types indicated. The error also gives emphasis to if form.validate_on_submit(): found in my routes.py. I am now lost and I dont really know what to do.
Edit: I need it to successfully POST the data. Please help me.
Routes:
from flask import render_template, url_for, flash, redirect
from registration import main
from registration.forms import RegistrationForm
from registration.models import Student, Colleges
#main.route("/")
def index():
return redirect(url_for('register'))
#main.route("/register", methods=['GET','POST'])
def register():
form = RegistrationForm()
form.college.choices = [(c.id, c.college) for c in Colleges.query.all()]
if form.validate_on_submit():
college = Colleges.query.filter_by(college=form.college.data).first()
reg_student = Student(student_number=form.student_number.data,last_name=form.last_name.data,first_name=form.first_name.data,college=college.id,section=form.section.data,email=form.email.data)
db.session.add(reg_student)
db.session.commit()
return render_template('register.html', form=form)
#main.route("/admin")
def admin():
return "Students list"
Models:
from registration import db
class Colleges(db.Model):
id = db.Column(db.Integer, primary_key=True)
college = db.Column(db.String(120), nullable=False)
students = db.relationship('Student', backref='department', lazy=True)
def __repr__(self):
return f"College('{self.college}')"
class Student(db.Model):
id = db.Column(db.Integer, primary_key=True)
student_number = db.Column(db.String(11), unique=True,nullable=False)
last_name = db.Column(db.String(60), nullable=False)
first_name = db.Column(db.String(60), nullable=False)
college_id = db.Column(db.Integer, db.ForeignKey('colleges.id'), nullable=False)
section = db.Column(db.String(10), nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
def __repr__(self):
return f"Student('{self.student_number}','{self.last_name}','{self.first_name}','{self.college_id}','{self.section}','{self.email}')"
Forms:
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField, BooleanField, IntegerField, SelectField
from wtforms.validators import DataRequired, Length, Email, EqualTo
class RegistrationForm(FlaskForm):
student_number = IntegerField('Student Number', validators=[DataRequired(),Length(min=11,max=11)])
last_name = StringField('Last Name',validators=[DataRequired()])
first_name = StringField('First Name',validators=[DataRequired()])
college = SelectField('College', coerce=int,validators=[DataRequired()])
section = StringField('Section', validators=[DataRequired()])
email = StringField('Email', validators=[DataRequired(), Email()])
submit = SubmitField('Sign Up')
register.html:
{% extends "base.html" %}
{% block content %}
{{ form.hidden_tag() }}
<form method="POST">
<fieldset>
<legend>Register:</legend>
{{ form.student_number.label }}
{{ form.student_number(class="form-control form-control-lg") }}
{{ form.last_name.label }}
{{ form.last_name(class="form-control form-control-lg") }}
{{ form.first_name.label }}
{{ form.first_name(class="form-control form-control-lg") }}
{{ form.college.label }}
{{ form.college(class="form-control form-control-lg") }}
{{ form.section.label }}
{{ form.section(class="form-control form-control-lg") }}
{{ form.email.label }}
{{ form.email(class="form-control form-control-lg") }}
{{ form.submit(class="btn btn-outline-info") }}
</fieldset>
</form>
{% endblock %}
I still haven't tried using Flask. Please do correct me if I'm wrong.
I think the problem lies on this line under RegistrationForm() class:
student_number = IntegerField('Student Number', validators=[DataRequired(),Length(min=11,max=11)])
Upon checking WTForms Documentation, wtforms.validators.Length() validates a string yet you're using this validator on IntegerField(). Try using StringField() instead.

search function (query in Flask, SQLAlchemy)

I'm new to programming and Flask and I am stuck on this problem.
I am trying to implement a search function in a web application that will take data from a form and compare it to a value in the database and list results.
This is what I have so far:
views.py
#app.route('/search', methods=['GET', 'POST'])
def search():
searchForm = searchForm()
courses = models.Course.query.order_by(models.Course.name).all()
if searchForm.validate_on_submit():
for i in courses:
if searchForm.courseName.data == i.name:
searchResult = models.Course.filter(Course.name.like('%searchForm.courseName.data%'))
return render_template('courselist.html', courses = courses, searchResult = searchResult)
form.py
class searchForm(Form):
courseName = StringField('Search course', validators=[DataRequired(), Length(max=60)])
database models.py
class Course(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(40), unique=True)
courseCode = db.Column(db.String(10), unique=True)
duration = db.Column(db.Integer)
maxStudents = db.Column(db.Integer)
startDate = db.Column(db.DateTime)
prerequisites = db.Column(db.String(500))
trainerID = db.Column(db.Integer, db.ForeignKey('trainer.id'))
venueID = db.Column(db.Integer, db.ForeignKey('venue.id'))
sessions = db.relationship('Session', backref='course', lazy='dynamic')
bookings = db.relationship('Booking', backref='course', lazy='dynamic')
html file
{% extends "index.html" %}
{% block content %}
<h3>Courses:</h3>
<ul>
{% for course in courses %}
<li>
<h4>{{course.name}}
<a class="btn btn-success" href="/editcourse?id={{course.id}}">Book</a>
<a class="btn btn-info" href="/editcourse?id={{course.id}}">Edit</a>
<a class="btn btn-danger" href="/deletecourse?id={{course.id}}">Delete</a></h4>
</li>
{% endfor %}
</ul>
{% endblock %}
I think the general logic is right but I need some help adjusting it.
Your logic in views.py seems a bit off. You're retrieving all Course objects from the database and looping through them. Then you check if the course name exactly matches the search input - and if so, try to find matching courses. I think it would be better constructed like this:
#app.route('/search', methods=['GET', 'POST'])
def search():
searchForm = searchForm()
courses = models.Course.query
if searchForm.validate_on_submit():
courses = courses.filter(models.Course.name.like('%' + searchForm.courseName.data + '%'))
courses = courses.order_by(models.Course.name).all()
return render_template('courselist.html', courses = courses)
This is this is the simplest answer :
#app.route("/search", methods=['GET'])
def search():
query = request.args.get("query") # here query will be the search inputs name
allVideos = Videos.query.filter(Videos.title.like("%"+query+"%")).all()
return render_template("search.html", query=query, allVideos=allVideos)

reading from joined query in flask-sqlalchemy

After successfully joining two db tables, I'm trying to read the data from the 2nd table by addressing the first. I'm addressing opinion.topic_name from my jinja2 template, but nothing is returned.
How can I access the Topic.topic_name value from the joined query?
view
#main.route('/', methods=['GET', 'POST'])
def index():
form = IndexForm()
opinions = []
if form.validate_on_submit():
opinions = Opinion.query
.filter_by(party_id=form.party.data)
.filter_by(topic_id=form.topic.data)
.join('topic')
.all()
return render_template('index.html', form=form, opinions=opinions)
model
class Opinion(db.Model):
id = db.Column(db.Integer, primary_key=True)
text = db.Column(db.String(2000))
topic_id = db.Column(db.Integer, db.ForeignKey('topic.id'))
party_id = db.Column(db.Integer, db.ForeignKey('party.id'))
class Topic(db.Model):
id = db.Column(db.Integer, primary_key=True)
topic_name = db.Column(db.String(64))
opinions = db.relationship(Opinion, backref='topic')
template (jinja2)
<div>
{{ wtf.quick_form(form) }}
</div>
<div>
{% for opinion in opinions %}
<div class='jumbotron'>
<h1>{{ opinion.topic_name }}</h1>
<p>{{ opinion.text }}</p>
</div>
{% endfor %}
</div>
This query:
opinions = Opinion.query
.filter_by(party_id=form.party.data)
.filter_by(topic_id=form.topic.data)
.join(Topic)
.all()
will return a list of Opinion models and since in your relationship in Topic model, you defined it as:
opinions = db.relationship(Opinion, backref='topic')
Then, in your jinja2 template, to access Topic.topic_name, you should do:
<h1>{{ opinion.topic.topic_name }}</h1>

Categories

Resources