I am trying to create a blog using Flask. Every post can have multiple tags also every tag could be associated with multiple posts. So I created a many-to-many relationship. My questions is how do i save multiple tags when creating a new post. And since every post can have different number of tags how do i show this is in the form? Also, how can i create new tags along with the post and then use those tags with other posts?
This is models.py -
postcategory = db.Table('tags',
db.Column('posts_id', db.Integer, db.ForeignKey('posts.id')),
db.Column('categories_id', db.Integer, db.ForeignKey('categories.id'))
)
class Post(db.Model):
__tablename__ = 'posts'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String)
content = db.Column(db.Text)
slug = db.Column(db.String, unique=True)
published = db.Column(db.Boolean, index=True)
timestamp = db.Column(db.DateTime, index=True)
categories = db.relationship('Category', secondary=postcategory, backref='posts' )
def __init__(self, title, content):
self.title = title
self.content = content
class Category(db.Model):
__tablename__ = 'categories'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String, index=True)
This is the view i am working on -
def create_article():
if request.method == 'POST':
if request.form.get('title') and request.form.get('content') and request.form.get('slug') and request.form.get('published'):
post = Post(request.form['title'], request.form['content'], request.form['slug'], request.form['published'])
I am sure there is a easy solution and i am just complicating this, but i am new to web development, so please help.
You can pull the categories out of the form with getlist and add them to the Post object. If you have checkboxes like the following:
<form>
<input type="checkbox" name="categories" value="foo">
<input type="checkbox" name="categories" value="bar" checked>
</form>
In your view method you can just do:
categories_from_form = request.form.getlist('categories') # ['bar']
# create Category objects with the form data
categories = [Category(title=title) for title in categories_from_form]
post = Post(request.form['title'], request.form['content'], request.form['slug'], request.form['published'])
post.categories = categories # attach the Category objects to the post
...
Related
i get 'list' object has no attribute 'id' error. I dont know why.
#posts.route("/post/<int:post_id>", methods=['GET','POST'])
#login_required
def course_post(post_id):
post=Post.query.get_or_404(post_id)
chapters=Chapter.query.filter_by(post_id=post_id).all()
chapter_id = chapters.id
videos=Videos.query.filter_by(chapter_id=chapter_id).all()
return render_template('course.html', title=post.course_name, post=post, chapters = chapters)
class Chapter(db.Model):
id = db.Column(db.Integer, primary_key= True)
chapter_no = db.Column(db.Integer)
chapter_name = db.Column(db.String(50))
chapter_desc = db.Column(db.String(50))
chapter_date = db.Column(db.DateTime, default=datetime.utcnow)
vi = db.relationship('Videos', backref='topic', lazy=True)
post_id = db.Column(db.Integer, db.ForeignKey('post.id'), nullable=False)
def __repr__(self):
return f"Chapter('{self.chapter_no}', '{self.chapter_name}', '{self.chapter_desc}', '{self.chapter_date}', '{self.post_id}'"
class Videos(db.Model):
id = db.Column(db.Integer, primary_key= True)
video_no = db.Column(db.Integer)
video_name = db.Column(db.String(50), nullable=False, index=True)
video_file = db.Column(db.String(200))
yout_code = db.Column(db.String(200))
video_description = db.Column(db.Text(100))
uploaded_date = db.Column(db.DateTime, default=datetime.utcnow)
rating = db.Column(db.Integer())
chapter_id = db.Column(db.Integer, db.ForeignKey('chapter.id'), nullable=False)
c_v = db.relationship("Chapter", foreign_keys=chapter_id) #
def __repr__(self):
return f"Videos('{self.video_no}','{self.video_name}', '{self.video_file}', '{self.yout_code}', '{self.video_description}', '{self.uploaded_date}', '{self.rating}', '{self.chapter_id}'"
i am trying to get all the videos for each chapter in the Videos table using chapters.id as foreign key
Please what am i doing wrong ?
Try updating your function like this
#posts.route("/post/<int:post_id>", methods=['GET','POST'])
#login_required
def course_post(post_id):
post=Post.query.get_or_404(post_id)
chapters=Chapter.query.filter_by(post_id=post_id).all()
chapter_id = chapters and chapters[0].id
videos=Videos.query.filter_by(chapter_id=chapter_id).all()
return render_template('course.html', title=post.course_name, post=post, chapters = chapters)
Now it uses first chapter ID it founds. I don't know if this is something you want though.
If you want to get list of chapter ids you can do it like this
chapter_id = [c.id for c in chapters]
But I don't know how ORM library you are using handles filtering by lists.
I added this to the parent class (Chapter) of the model.py
medias = db.relationship('Videos', backref='Chapter', lazy='subquery')
The code below pulls the query and sub query of parent and child. This code does that:
chapters = db.session.query(Chapter).filter_by(post_id=post_id).all()
i.e:
#posts.route("/post/<int:post_id>", methods=['GET','POST'])
#login_required
def course_post(post_id):
post=Post.query.get_or_404(post_id)
#chapters=Chapter.query.filter_by(post_id=post_id).all()
chapters = db.session.query(Chapter).filter_by(post_id=post_id).all()
return render_template('course.html', title=post.course_name, post=post, chapters = chapters)
Then in my template, to loop the chapters and videos for each chapter in the template:
{% for v in chapters %}
<h7><span class="plus">+</span>{{ v.chapter_name }}</h7> <br><br>
<div class="descDiv">
{% for media in v.medias %}
<p class="desc">{{ media.video_name }}</p>
{% endfor %}
</div>
{% endfor %}
Here are my models:
class Entry(db.Model):
id = db.Column(db.Integer, primary_key=True)
manifest = db.Column(db.String, default=None, nullable=True)
name = db.Column(db.String, default=None, nullable=True)
actions = db.relationship('Action', backref='entry', lazy='dynamic')
class Action(db.Model):
id = db.Column(db.Integer, primary_key=True)
action_date = db.Column(db.DateTime, default=datetime.utcnow, nullable=True)
location = db.Column(db.String, default=None, nullable=True)
entry_id = db.Column(db.Integer, db.ForeignKey('entry.id'))
routes.py:
#app.route('/manifests/<manifest_to_view>')
#login_required
def view_manifest(manifest_to_view):
page = request.args.get('page', 1, type=int)
entries = Entry.query.filter_by(manifest=manifest_to_view).paginate(
page, app.config['POSTS_PER_PAGE'], False)
next_url = url_for('view_manifest', manifest_to_view=manifest_to_view, page=entries.next_num) \
if entries.has_next else None
prev_url = url_for('view_manifest', manifest_to_view=manifest_to_view, page=entries.prev_num) \
if entries.has_prev else None
return render_template("view_manifest.html", title='View Manifest', manifest_to_view=manifest_to_view, entries=entries.items, next_url=next_url, prev_url=prev_url)
And from the template:
{% for entry in entries %}
<td>{{ entry.actions.first().location }}</td>
{% endfor %}
This page displays all rows in the Entry table that share a specific "manifest" (an alphanumeric identifier). So you can see my query in routes.py starts:
entries = Entry.query.filter_by(manifest=manifest_to_view)...
For each row from the Entry table, I also need to display the most recent location from the related Action table, but my current line displays the wrong location:
{{ entry.actions.first().location }}
Is there a way to sort locations by the Action.action_date column using order_by() instead of using first()? Or any way to print the most recent location?
Thanks.
found the answer here: SQLAlchemy - order_by on relationship for join table
I just had to change the relationship in model.py
actions = db.relationship('Action', backref='entry', order_by="desc(Action.id)", lazy='dynamic')
I am using flask, sqlalchemy, jinja2 and python and I am struggling after getting data from the database and displaying them in my html while they are inside a list and in an anchor to post them with anchor click inside a form to submit them afterwords in another database table.
Here is what I have so far.
My html
<div class="container-fluid">
<div id="training_list">
<h1>List of Exercises</h1>
{% for exercises in exlist %}
<li id="li_ex_list">{{ exercises.name }} | Reputations:
{{ exercises.reps }}</li>
{% endfor %}
</div>
<div id="training_content">
<h1>My Workout Plan</h1>
<form method="POST" action="/my_workouts">
</form>
</br>
<button type="submit" class="btn btn-info">Save my plan</button>
</div>
</div>
My flask-python
class Exercises(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(40), unique=True, nullable=False)
reps = db.Column(db.Integer(), nullable=False)
complete = db.Column(db.Boolean, default=False)
def __init__(self, name, reps, complete):
self.name = name
self.reps = reps
self.complete = complete
def __repr__(self):
return '<Exercises %r>' % self.name
class Traningplan(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
ex_id = db.Column(db.Integer, db.ForeignKey('exercises.id'), nullable=False)
ex_name = db.Column(db.String(40), db.ForeignKey('exercises.name'), nullable=False)
class Usertrainingplan(FlaskForm):
user_id = IntegerField('Userid', render_kw={'readonly': True})
ex_id = IntegerField('Exerciseid', render_kw={'readonly': True})
ex_name = StringField('Exercisename', render_kw={'readonly': True})
#app.route('/my_workouts', methods=['GET', 'POST'])
#login_required
def my_workouts():
exlist = Exercises.query.all()
form = Usertrainingplan()
return render_template('My_Training_Programs.html', exlist=exlist, form=form)
I have tried filtering for complete True/False and passing 2 variables for complete and incomplete(also added them with jinja2 template in my html) but I can't make it work.
---- This part was inside "def_myworkouts():"-----
incomplete = Exercises.query.filter_by(complete=False).all()
complete = Exercises.query.filter_by(complete=True).all()
#app.route('/complete/<id>')
def complete(id):
exercises = Exercises.query.filter_by(id=int(id)).first()
exercises.complete = True
db.session.commit()
return redirect(url_for('my_workouts'))
Additional Information:
Current page looks like this:
current page
So what I want to do is:
Once the user clicks on the links on the left side, I want the text of the links (e.g. Landmine) to be added in the right side of the page where "My workout plan" is, and create a list with all the selected items.
Additionaly I want to be able to place them inside a form and the submit(POST) the form inside a table in my database, with the columns: user_id, ex_id, ex_name as foreign keys. user_id(foreign key) should be the current logged in users id(primary key), ex_id(foreign key) should be the id of the exercise and finally ex_name(foreign key) should be the name of the exercise. The primary keys ofcourse are already commited in the database in the required tables and sames goes with exercises names.
Thanks for your time in advance. Any help highly appriceated.
Stackoverflow you are my only savior
I'm having a hard time figuring out how to use flask-babel to translate variables inside a loop. Give this (non-working) example what would I need to change for each iteration to have it's own translation?
{% for key, values in products.items() %}
<h4>{{ _('%(name)s', name=values['name']) }}</h4>
<p>{{ _('%(caption)s', caption=values['caption']) }}</p>
{% endfor %}
With this example the PO is generated like:
msgid "%(name)s"
msgstr ""
This, as far as I know, only allows me to insert a single value for the translation
So you have static and dynamic texts. The Babel-way to translate texts is only effective for static texts. For dynamically generated texts it would be very ineffective: Babel does not know about the dynamic texts, you would have to add manually the news text to the PO each time when a new product appears. I don't recommend it.
You should use a different approach for dynamic texts. I guess that you periodically import the products into your DB through a 3rd party API so you have a Product model. If there are only a few languages and texts to translate, it's still adequate to have only one model with many translated-fields (one for each language). For example:
class Product(db.Model):
__tablename__ = 'product'
id = db.Column(db.Integer, primary_key=True)
category = db.Column(db.SmallInteger, index=True)
added = db.Column(db.DateTime, index=True)
name_en = db.Column(db.String(255))
name_eo = db.Column(db.String(255))
name_hu = db.Column(db.String(255))
description_en = db.Column(db.Text)
description_eo = db.Column(db.Text)
description_hu = db.Column(db.Text)
So after you import new products, you can translate their texts through an online interface. If you have many fields to translate you can separate the language dependent and independent part of the Product, and have separated models for them:
class Product(db.Model):
__tablename__ = 'product'
id = db.Column(db.Integer, primary_key=True)
category = db.Column(db.SmallInteger, index=True)
added = db.Column(db.DateTime, index=True)
class ProductText(db.Model):
__tablename__ = 'product'
id = db.Column(db.Integer, primary_key=True)
pid = db.Column(db.Integer, db.ForeignKey('product.id'))
language = db.Column(db.String(10), index=True)
name = db.Column(db.String(255))
description = db.Column(db.Text)
Thus when you want to show a product to the client, first check the active language, and load the corresponding translated ProductText with the current Product.
I'm trying out Flask but I'm having the error sqlalchemy.exc.InterfaceError: <unprintable InterfaceError object> while submitting a wtforms. The model class is:
class Post(db.Model):
__tablename__ = 'blog_posts'
id = db.Column(db.Integer, unique=True, primary_key=True)
title = db.Column(db.String(50), unique=False)
content = db.Column(db.Text, unique=False)
user_id = db.Column(db.String, db.ForeignKey('users.username'))
#staticmethod
def post_new_entry(title, content, user_id):
""" Post new entry to database """
new_post = Post(title=title, content=content, user_id=user_id)
db.session.add(new_post)
db.session.commit()
return new_post
def __repr__(self):
return 'PostID {}: {} by {}'.format(self.id, self.title, self.user_id)
For my Form, I have the following:
class PostForm(Form):
title = StringField('Title', validators=[DataRequired(), Length(10, 65)])
post_content = TextAreaField('Content', validators=[DataRequired(), Length(50, 500)])
submit = SubmitField('Publish Post')
The route is:
#main.route('/new_post/', methods=['GET', 'POST'])
#login_required
def add_post():
form = PostForm()
if form.validate_on_submit():
Post.post_new_entry(title=form.title.data,
content=form.post_content.data,
user_id=current_user)
flash("Amazing stuff! Thanks for your submission.")
return redirect(url_for('main.index'))
return render_template('single.html', form=form)
On my html, I'm importing the wtf.html page of the flask-bootstrap:
{{ wtf.quick_form(form) }}
The form shows right but I get the above error on form submission. Any tip or idea on how to proceed would be helpful.
Under def add_post() you write user_id=current_user, but that's not right.
Since you defined for class Post:
user_id = db.Column(db.String, db.ForeignKey('users.username'))
in def add_post() you should use user_id=current_user.username.
In your table class definition you need to add one more line to complete the foreign key relationship.
class Post(db.Model):
__tablename__ = 'blog_posts'
id = db.Column(db.Integer, unique=True, primary_key=True)
title = db.Column(db.String(50), unique=False)
content = db.Column(db.Text, unique=False)
user_id = db.Column(db.String, db.ForeignKey('users.username'))
# Setup the relationship to the User table
users = db.relationship(User)
I was having the same error message in an app which was working one day then not the next. Drove me nuts, the solution was that I had removed a relationship() somewhere.
I have received a similar message when writing data from my application to a database. This is due to the fact that the data that is written from the application needs to have the same format as a defined in the database a db.Column(db.String()) data type cannot have a list as input for example, or any other form.data. You need to use ``str()``` in these cases to prevent this error.
I think your problem came from this area:
Post.post_new_entry(title=form.title.data, content=form.post_content.data, user_id=current_user)
Try to be specific and do it this way:
Post.post_new_entry(title=form.title.data, content=form.post_content.data, user_id=current_user.id)