search function (query in Flask, SQLAlchemy) - python

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)

Related

HTML shows Flask-SQLAlchemy syntax?

When trying to display replies of users on my page it displays them with SQL syntax, like so: ('reply',) I've tried str() when returning from my route.
html:
<ul style="float:left;" class="list-group list-group-flush" id="anons">
{% for reply in replies %}
<li class="list-group-item" align="left">
{{ reply }}
</li>
{% endfor %}
model:
class Reply(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(30))
data = db.Column(db.String(10000))
date = db.Column(db.DateTime(timezone=True), default=func.now())
route:
#views.route('/', methods=["GET", "POST"])
def home():
if request.method == "POST":
name = request.form.get("name")
data = request.form.get("reply")
if len(data) >= 1:
new_reply = Reply(name=name, data=data)
db.session.add(new_reply)
db.session.commit()
replies = db.session.query(Reply.data)
return render_template('home.html', replies=replies)
return render_template('home.html')
Try modifying your replies definition to this:
replies = Reply.query.all()
This essentially creates a list of all the 'Reply' objects, which you then pass to the template. If you were wanting to only show the .data attribute of each 'Reply', you would access that with {{ reply.data }} in the for loop.

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)

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

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

How to display results of SQLalchemy query correctly in Flask Jinja templates

I am having trouble displaying the results of a SQLAlchemy query correctly in a Jinja template page.
I am basing my project on Formula 1 Drivers and the Teams that they drive for. I have a page which lists the Teams in F1 and it should list the Drivers who race for each individual team (Many-to-Many relationship between Driver and Team classes).
The output I receive is correct, however it appears to be grouped together in a list type output. I would like the drivers for each team to be listed independently as strings rather than a list grouped together.
Here is my code:
models.py
from datetime import datetime
from app import db
class Team_driver(db.Model):
# Many-to-many relationship table between Driver and Team
__tablename__ = "team_driver"
id = db.Column(db.Integer, primary_key=True)
team_id = db.Column(db.Integer, db.ForeignKey('team.id'), nullable=False)
driver_id = db.Column(db.Integer, db.ForeignKey('driver.id'), nullable=False)
class Team(db.Model):
__tablename__ = "team"
id = db.Column(db.Integer, primary_key=True)
teamName = db.Column(db.String(64))
nationality = db.Column(db.String(64))
# relationship between Team table and Driver table, reference 'team' in forms
drivers = db.relationship('Driver', secondary="team_driver", backref='team')
def __repr__(self):
# specify variables to return to web page from backref
return '{}'.format(self.teamName)
class Driver(db.Model):
__tablename__ = "driver"
id = db.Column(db.Integer, primary_key=True)
firstName = db.Column(db.String(64))
lastName = db.Column(db.String(64))
raceNum = db.Column(db.Integer)
nationality = db.Column(db.String(64))
# relationship between Driver table and Team table, reference 'driver' in forms
teams = db.relationship('Team', secondary="team_driver", backref='driver')
def __repr__(self):
# specify variables to return to web page from backref
return '{} {}'.format(self.firstName,self.lastName)
routes.py (only for specific route)
#app.route('/teamDetails', methods=['GET', 'POST'])
def teamDetails():
details = Team.query.all()
return render_template('teamDetails.html', title='Team Details', details=details)
driverDetails.html
<!-- teamDetails.html -->
<!-- all other html files must now extend index.html and not base.html -->
{% extends "index.html" %}
{% block content %}
<h1>Team Details</h1>
{% for d in details %}
<div><p>Team Name: {{ d.teamName }}<br>Nationality: {{ d.nationality }}
<br>Drivers: {{ d.driver }}</p></div>
{% endfor %}
{% endblock %}
Here is the output I get from the webpage
screenshot of webpage output
What I would like is the page to display the relevant drivers, but separately, without the [ ] brackets.
Can anyone help with where I am going wrong?
Many thanks.
Since d.driver is a list of drivers, you should print each item of the list separately, because if you print a list of something, it will also include the brackets.
{% block content %}
<h1>Team Details</h1>
{% for d in details %}
<div><p>Team Name: {{ d.teamName }}<br>Nationality: {{ d.nationality }}
<br>Drivers:
{% for driver in d.drivers %}
{{ driver }}
{% endfor %}
</p></div>
{% endfor %}
{% endblock %}

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