I am building a blogging website using Flask, I have added a functionality where you can follow a user and view their posts just like Instagram/Twitter. I have created 2 tables in my models.py file namely User, Post and followers I am learning to build this site referring to Corey Schafer's YouTube video series on Flask and Miguel Grinberg's Flask Website Tutorial, I have used flask sqlalchemy database
Here is the models.py file
followers = db.Table('followers',
db.Column('follower_id', db.Integer, db.ForeignKey('user.id')),
db.Column('followed_id', db.Integer, db.ForeignKey('user.id'))
)
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(20), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
image_file = db.Column(db.String(20), nullable=False, default='default.jpg')
password = db.Column(db.String(60), nullable=False)
posts = db.relationship('Post', backref='author', lazy=True)
followed = db.relationship(
'User', secondary=followers,
primaryjoin=(followers.c.follower_id == id),
secondaryjoin=(followers.c.followed_id == id),
backref=db.backref('followers', lazy='dynamic'), lazy='dynamic')
def __repr__(self):
return f"User('{self.username}', '{self.email}', '{self.image_file}')"
def follow(self, user):
if not self.is_following(user):
self.followed.append(user)
def unfollow(self, user):
if self.is_following(user):
self.followed.remove(user)
def is_following(self, user):
return self.followed.filter(
followers.c.followed_id == user.id).count() > 0
def followed_posts(self):
return Post.query.join(
followers, (followers.c.followed_id == Post.user_id)).filter(
followers.c.follower_id == self.id).order_by(
Post.timestamp.desc())
#login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), unique=True, nullable=False)
date_posted = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
content = db.Column(db.Text, nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
def __repr__(self):
return f"User('{self.title}', '{self.date_posted}')"
here are the routes I have written to follow and unfollow a user in my routes.py file
#app.route('/follow/<username>')
#login_required
def follow(username):
user = User.query.filter_by(username=username).first()
if user is None:
flash('User {} not found.'.format(username))
return redirect(url_for('home'))
if user == current_user:
flash('You cannot follow yourself!')
return redirect(url_for('user', username=username))
current_user.follow(user)
db.session.commit()
flash('You are following {}!'.format(username))
return redirect(url_for('user', username=username))
#app.route('/unfollow/<username>')
#login_required
def unfollow(username):
user = User.query.filter_by(username=username).first()
if user is None:
flash('User {} not found.'.format(username))
return redirect(url_for('home'))
if user == current_user:
flash('You cannot unfollow yourself!')
return redirect(url_for('user', username=username))
current_user.unfollow(user)
db.session.commit()
flash('You are not following {}.'.format(username))
return redirect(url_for('user', username=username))
when I type 'url/user/' the page that leads me the user's profile page it throws an error like this
sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such table: followers [SQL: 'SELECT count(*) AS count_1 \nFROM (SELECT user.id AS user_id, user.username AS user_username, user.email AS user_email, user.image_file AS user_image_file, user.password AS user_password \nFROM user, followers \nWHERE followers.followed_id = ? AND followers.follower_id = user.id) AS anon_1'] [parameters: (1,)]
How do I get this working?
Its over two years since this question was posted, but because it hasn't been answered, let me answer it.
The problem is occurring because no user has been created yet. A look at your unfollow(username) and unfollow(username)view functions means that you are supposed to have a user with a username already in the system. The two view functions are retrieving the specific user using user = User.query.filter_by(username=username).first(). This means your view function for the user is created using a username, which should be unique for each user, ie.
#app.route('/user/<username>')
#login_required
def user(username):
user = User.query.filter_by(username=username).first()
#....
Once a user is registered and is logged in you can then type '127.0.0.1:5000/user/foo' assuring the username is foo and assuming you are using the default method to your application.
Related
I have created a web app using Flask where you can leave a note as text. Ig goes well you leave a text and it got saved in our database. But it only happens when you are an authorized user and can't be done if you are an unauthorized (anonymous user) and if you attempt as such this error message pops up: "'AnonymousUserMixin' object has no attribute 'id'".
Here the code goes:
#home.py
def home():
if request.method == 'POST':
note = request.form.get('note')
if len(note) < 1:
flash('Note is too short!', category='error')
else:
new_note = Note(data=note, user_id=current_user.id)
db.session.add(new_note)
db.session.commit()
flash('Note added!', category='success')
return render_template("home.html", user=current_user)
And the DB models:
#models.py
class Note(db.Model):
id = db.Column(db.Integer, primary_key=True)
data = db.Column(db.String(10000))
date = db.Column(db.DateTime(timezone=True), default=func.now())
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(150), unique=True)
password = db.Column(db.String(150))
first_name = db.Column(db.String(150))
confirmed = db.Column(db.Boolean, nullable=False, default=False)
notes = db.relationship('Note')
You are setting a user_id value when creating a Note:
new_note = Note(data=note, user_id=current_user.id)
Evidently anonymous users don't have an id attribute, so you must either set user_id=None when creating notes for anonymous users or create a default user to represent anonymous users and user that user's id when creating notes.
I want to allow anonymous users to post their comments in a blog without requiring them to login or sign up. One anonymous user can post several comments using the same credentials during subsequent visits. However, I get SQLAlchemy IntegrityError during a second attempt.
I have a simple form, a simple model and a basic route.
comment.py
class CommentForm(FlaskForm):
username = StringField('Username', validators=[DataRequired()])
email = StringField('Email', validators=[DataRequired(), Email()])
comment = TextAreaField('Comment', validators=[DataRequired()])
submit = SubmitField('Post')
models.py
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), index=True, unique=True)
email = db.Column(db.String(120), index=True, unique=True)
posts = db.relationship('Post', backref='author', lazy='dynamic')
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
body = db.Column(db.String(500))
timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
route.py
#app.route('/personal_blog', methods = ['GET', 'POST'])
def personal_blog():
form = CommentForm()
if form.validate_on_submit():
user = User(username = form.username.data, email = form.email.data)
post = Post(body = form.comment.data, author = user)
db.session.add(user)
db.session.add(post)
db.session.commit()
flash('Your comment is now live!')
return redirect(url_for('personal_blog', _anchor='translate-hover'))
posts = Post.query.order_by(Post.timestamp.desc()).all()
return render_template('personal_blog.html', form = form, posts = posts)
I have tried several suggested solutions and the closet I have come to was to add validation to the form, such as this:
class CommentForm(FlaskForm):
username = StringField('Username', validators=[DataRequired()])
email = StringField('Email', validators=[DataRequired(), Email()])
comment = TextAreaField('Comment', validators=[DataRequired()])
submit = SubmitField('Post')
def __init__(self, original_username, *args, **kwargs):
super(CommentForm, self).__init__(*args, **kwargs)
self.original_username = original_username
def validate_username(self, username):
if username.data != self.original_username:
user = User.query.filter_by(username=self.username.data).first()
if user is not None:
raise ValidationError('Please use a different username.')
This, however, needs the addition of #app.route('/personal_blog/<username>') which essentially does not work for my case. I want to have this anonymous user post subsequent comments without the need of changing either username or email.
Personally, I have tried to add validation within the route (if user.username is not None: flash('Use different credentials!')) to notify the user that the email or username they are trying to use has already been used, and it works, but this is not what I want the app to do. The user can simply use the very same credentials as before. How can this be achieved?
The solution to this is to remove the unique constraint from the User table.
Instead of:
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), index=True, unique=True) <----------here
email = db.Column(db.String(120), index=True, unique=True) <----------here
posts = db.relationship('Post', backref='author', lazy='dynamic')
I have this:
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), index=True) <----------here
email = db.Column(db.String(120), index=True) <----------here
posts = db.relationship('Post', backref='author', lazy='dynamic')
As soon as I update the database, the changes take effect and I am able to allow a single anonymous user to post more than one comment using the same credentials.
So what I basically want to do is have a user fill out a form and have a boolean value set to True which tells me their a user. I set it to True when I send the POST request to the database, but when I go look at the column it says "[null]". I don't understand what I'm doing wrong....
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
first_name = db.Column(db.String(120))
last_name = db.Column(db.String(120))
email = db.Column(db.String(180), unique=True)
password = db.Column(db.String(255))
isUser = db.Column(db.Boolean())
active = db.Column(db.Boolean())
confirmed_at = db.Column(db.DateTime())
roles = db.relationship('Role', secondary=roles_users,backref=db.backref('users', lazy='dynamic'))
#app.route('/post_user', methods=['POST'])
def post_user():
user = User(request.form['first_name'], request.form['last_name'], request.form['email'], request.form['password'])
user.isUser = True
db.session.add(user)
db.session.commit()
return redirect(url_for('login'))
I'm getting AttributeError: 'BaseQuery' object has no attribute 'check_password' when I attempt to call check_password on the User object I create in forms.py below. Here are the two lines where the problem occurs:
user = User.query.filter_by(userID = self.userID.data)
if user and user.check_password(self.password.data):
...
The relevant source code is below. I know the check_password() method works because I can call it from within models.py. I'm not sure why I have access to the User model but not the check_password function.
forms.py
...
from .models import User
class LoginForm(Form):
...
def validate(self):
if not Form.validate(self):
return False
***user = User.query.filter_by(userID = self.userID.data)***
***if user and user.check_password(self.password.data)***:
return True
else:
self.userID.errors.append("Invalid user ID or password")
return False
models.py
from app import db
from flask.ext.login import LoginManager, UserMixin
from werkzeug import generate_password_hash, check_password_hash
class User(UserMixin, db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
social_id = db.Column(db.String(64), nullable=False) #could have duplicate
userID = db.Column(db.String(64), nullable=False)
email = db.Column(db.String(64), nullable=True)
pwdhash = db.Column(db.String(54), nullable=True)
posts = db.relationship('Post', backref='author', lazy='dynamic')
def __init__(self, social_id, userID, email, password):
if social_id:
self.social_id = social_id #not required if not using Facebook login
if userID:
self.userID = userID
self.email = email.lower()
if password:
self.set_password(password)
assert self.check_password(password)
def set_password(self, password):
self.pwdhash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.pwdhash, password)
User.query.filter_by() will return the query. If you want the first result of the query you need to call first(), i.e.
User.query.filter_by(...).first()
I have a simple many to many relationship using sql Alchemy like this:
file_favorites = db.Table('file_favorites',
db.Column('file_id', db.Integer(), db.ForeignKey('file.id')),
db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
db.Column('created_at', db.DateTime(), default=db.func.now()))
class File(db.Model, helpers.ModelMixin):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.Unicode, nullable=False)
description = db.Column(db.Unicode, nullable=False)
created_at = db.Column(db.DateTime(), default=func.now())
last_updated = db.Column(db.DateTime(), default=func.now(), onupdate=func.now())
user_id = db.Column('user_id', db.Integer(), db.ForeignKey('user.id'), nullable=False, index=True)
user = db.relationship('User')
favorited_by = db.relationship('User', secondary=file_favorites, lazy='dynamic')
def is_favorite_of(self, user):
query = File.query
query = query.join(file_favorites)
query = query.filter(file_favorites.c.file_id == self.id)
query = query.filter(file_favorites.c.user_id == user.id)
return query.count() > 0
def favorite(self, user):
if not self.is_favorite_of(user):
self.favorited_by.append(user)
def unfavorite(self, user):
if self.is_favorite_of(user):
self.favorited_by.remove(user)
I would expect that accessing the favorited_by property would result in a query that tries to return a list of users that have favorited this file. However, it seems that the query is only accessing the FIRST user to have favorited this file. I'm puzzled by this, and expect that I don't correctly understand sqlalchemy relationships. Here is the result i'm experiencing:
def create_model(model_class, *args, **kwargs):
model = model_class(*args, **kwargs)
db.session.add(model)
db.session.commit()
return model
def test_favorited_by(self):
user = create_model(User, username='user', email='user#user.net', password='password')
user1 = create_model(User, username='user1', email='user1#user.net', password='password')
user2 = create_model(User, username='user2', email='user2#user.net', password='password')
file = create_model(File, name='file', description='a description', user=user)
file.favorite(user1)
file.favorite(user)
file.favorite(user2)
db.session.commit()
print file.favorited_by
results in this query:
SELECT "user".id AS user_id, "user".email AS user_email, "user".username AS user_username, "user".password AS user_password, "user".active AS user_active, "user".last_login_at AS user_last_login_at, "user".current_login_at AS user_current_login_at, "user".last_login_ip AS user_last_login_ip, "user".current_login_ip AS user_current_login_ip, "user".login_count AS user_login_count, "user".last_updated AS user_last_updated, "user".created_at AS user_created_at
FROM "user", file_favorites
WHERE :param_1 = file_favorites.file_id AND "user".id = file_favorites.user_id
Which ultimately returns user1, if the order is switched, the first user to favorite the file will always be the user returned.
Your problem is with is_favorite_of. It doesn't incorporate user into the check. You need to add another filter.
def is_favorite_of(self, user):
query = File.query
query = query.join(file_favorites)
query = query.filter(file_favorites.c.file_id == self.id)
query = query.filter(file_favorites.c.user_id == user.id)
return query.count() > 0
Alternatively, this whole function can be simplified to:
def is_favorite_of(self, user):
return user in self.favorited_by