reading from joined query in flask-sqlalchemy - python

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>

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.

SqlAlchemy InterfaceError: Error binding parameter 1 - probably unsupported type

I'm stuck on this and I'm not sure what's the problem. I have a many to many relationship between User and Address. I'm trying to create a page that displays all addresses for a user and users can be redirected to another page to edit their address information. I have two routes for that, user route only display addresses and provides the address_id to edit_address that deals with updating the info.
I keep getting this error :
sqlalchemy.exc.InterfaceError: (sqlite3.InterfaceError) Error binding parameter 1 - probably unsupported type.
[SQL: UPDATE addresses SET updated_at=?, address=?, country=?, city=?, post_code=? WHERE addresses.id = ?]
[parameters: ('2020-05-27 19:39:33.594937', ('testing',), ('gb',), ('london',), ('lo346bd',), 1)]
(Background on this error at: http://sqlalche.me/e/rvf5)
My Models:
class TimestampMixin(object):
created_at = db.Column(db.DateTime, nullable=False,
default=datetime.utcnow)
updated_at = db.Column(db.DateTime, onupdate=datetime.utcnow)
user_addresses = db.Table('user_addresses',
db.Column("user_id", db.Integer, db.ForeignKey(
"users.id")),
db.Column("address_id", db.Integer, db.ForeignKey("addresses.id")))
class User(db.Model, TimestampMixin, UserMixin):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
(...)
# relationships (many to many)
address = db.relationship("Address", secondary="user_addresses", backref="user", lazy="dynamic")
def __repr__(self):
return f"User('{self.username}')"
class Address(db.Model, TimestampMixin):
__tablename__ = "addresses"
id = db.Column(db.Integer, primary_key=True)
address = db.Column(db.String(200), nullable=False)
country = db.Column(db.String(100), nullable=False)
city = db.Column(db.String(100), nullable=False)
post_code = db.Column(db.String(100), nullable=False)
def __repr__(self):
return f"Address('{self.id}', '{self.address}', '{self.country}', '{self.post_code}')"
My template:
<div class="container custom-bg my-4">
<form method="POST" enctype="multipart/form-data" class="py-4">
{{form.csrf_token()}}
<div class="form-group col-12">
{{render_field(form.address , class="form-control",placeholder="Enter your street address")}}
{% for error in form.errors.address %}
<div class="text-danger">{{error}}</div>
{% endfor %}
</div>
<div class="form-group col-12">
{{render_field(form.city , class="form-control",placeholder="Enter your city")}}
{% for error in form.errors.city %}
<div class="text-danger">{{error}}</div>
{% endfor %}
</div>
<div class="form-group col-12">
{{render_field(form.country , class="form-control",placeholder="Enter your country")}}
{% for error in form.errors.country %}
<div class="text-danger">{{error}}</div>
{% endfor %}
</div>
<div class="form-group col-12">
{{render_field(form.post_code , class="form-control",placeholder="Enter your post code")}}
{% for error in form.errors.post_code %}
<div class="text-danger">{{error}}</div>
{% endfor %}
</div>
<div class="form-row justify-content-center">
{{render_field(form.submit, class="btn btn-block btn-secondary")}}
</div>
</form>
</div>
My view
# app.route("/profile/settings/edit_address/<address_id>", methods=["POST", "GET"])
# login_required
def edit_address(address_id):
addresses = Address.query.filter_by(id=address_id).first()
form = UpdateAddressForm(obj=addresses)
form.populate_obj(addresses)
if form.validate_on_submit():
# Update address
addresses.address = form.address.data.lower(),
addresses.city = form.city.data.lower(),
addresses.post_code = form.post_code.data.lower(),
addresses.country = form.country.data.lower(),
db.session.commit()
flash("You have updated your address!", "success")
return redirect(url_for("user_settings"))
return render_template("edit_address.html", title="Update Address", form=form)
I was able to fix it after searching for hours. However, I'm still not sure why I was getting the error so if someone could claify it would be great. I believe it's something about the many to many relationship cause I use this same logic to update data with other relationships and never came across this. I was getting a tuple back from the data in the form which was then causing the error since it was expecting a string back. I'm not sure why this was happening. Anyway, I found this question that helped me to adapt my view function and now it works.
# app.route("/profile/settings/edit_address/<address_id>", methods=["POST", "GET"])
# login_required
def edit_address(address_id):
address_to_update = Address.query.get(address_id)
form = AddressForm(obj=address_to_update)
if form.validate_on_submit():
# Update address
form.populate_obj(address_to_update)
db.session.commit()
flash("You have updated your address!", "success")
return redirect(url_for("user_settings"))
return render_template("edit_address.html", title="Update Address", form=form)

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 %}

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)

Categories

Resources