简体   繁体   中英

Adding the like feature to posts and comments in a flask web application

I am developing a social flask web application using Miguel Gringberg's book 'Flask Web Development'. I am having trouble implementing the like feature for posts and comments. I tried replicating the follow feature in the book but I get a sqlalchemy error saying that "a relationship cannot be found for the Post.liked column in the Post model" (I believe there are more errors) here is my models.py code.

# Set website access permissions
class Permission:
    FOLLOW = 0x01
    COMMENT = 0x02
    WRITE_ARTICLES = 0x04
    MODERATE_COMMENTS = 0x08
    ADMINISTER = 0x80


# User roles
class Role(db.Model):
    __tablename__ = 'roles'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True)
    default = db.Column(db.Boolean, default=False, index=True)
    permissions = db.Column(db.Integer)
    users = db.relationship('User', backref='role', lazy='dynamic')

    # Crete roles in the db
    @staticmethod
    def insert_roles():
        roles = {
            'User': (Permission.FOLLOW |
                     Permission.COMMENT |
                     Permission.WRITE_ARTICLES, True),
            'Moderator': (Permission.FOLLOW |
                          Permission.COMMENT |
                          Permission.WRITE_ARTICLES |
                          Permission.MODERATE_COMMENTS, False),
            'Administrator': (0xff, False)
        }

        for r in roles:
            role = Role.query.filter_by(name=r).first()
            if role is None:
                role = Role(name=r)
            role.permissions = roles[r][0]
            role.default = roles[r][1]
            db.session.add(role)
        db.session.commit()

    def __repr__(self):
        return '<Role %r>' % self.name


# Follows association table
class Follow(db.Model):
    __tablename__ = 'follows'
    follower_id = db.Column(db.Integer, db.ForeignKey('users.id'), primary_key=True)
    followed_id = db.Column(db.Integer, db.ForeignKey('users.id'), primary_key=True)
    timestamp = db.Column(db.DateTime, default=datetime.utcnow)


class Like(db.Model):
    __tablename__ = 'likes'
    post_liked_id = db.Column(db.Integer, db.ForeignKey('posts.id'), primary_key=True)
    comment_liked_id =db.Column(db.Integer, db.ForeignKey('comments.id'), primary_key=True)
    liker_id = db.Column(db.Integer, db.ForeignKey('users.id'), primary_key=True)
    timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)


# Users table
class User(UserMixin, db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key= True, autoincrement=True)
    first_name = db.Column(db.String(64))
    last_name = db.Column(db.String(64))
    dob = db.Column(db.Date)
    gender = db.Column(db.String(64))
    nationality = db.Column(db.String(64))
    residence = db.Column(db.String(64))
    postal_code = db.Column(db.String(64))
    username = db.Column(db.String(64), unique=True)
    email = db.Column(db.String(64), unique=True)
    password_hash = db.Column(db.String(128))
    member_since = db.Column(db.DateTime(), default=datetime.utcnow)
    role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))
    confirmed = db.Column(db.Boolean, default=False)
    location = db.Column(db.String(64))
    about_me = db.Column(db.Text())
    last_seen = db.Column(db.DateTime(), default=datetime.utcnow)
    bio = db.Column(db.String(100))
    profile_pic = db.Column(db.String, default=None)
    profile_pic_url = db.Column(db.String, default=None)
    posts = db.relationship('Post', backref='author', lazy='dynamic')
    followed = db.relationship('Follow', foreign_keys=[Follow.follower_id], backref=db.backref('follower', lazy='joined'),
                               lazy='dynamic', cascade='all, delete-orphan')
    followers = db.relationship('Follow', foreign_keys=[Follow.followed_id], backref=db.backref('followed', lazy='joined'),
                                lazy='dynamic', cascade='all, delete-orphan')
    comments = db.relationship('Comment', backref='author', lazy='dynamic')
    liker = db.relationship('Like', backref='liker', lazy='dynamic')


    # Define default role for users
    def __init__(self, **kwargs):
        super(User, self).__init__(**kwargs)
        if self.role is None:
            if self.email == 'hardingalex@live.com':
                self.role = Role.query.filter_by(permissions=0xff).first()
            if self.role is None:
                self.role = Role.query.filter_by(default=True).first()
        self.follow(self)


    # User account confirmation
    def generate_confirmation_token(self, expiration=86400):
        s = Serializer(current_app.config['SECRET_KEY'], expiration)
        return s.dumps({'confirm': self.id})

    def confirm(self, token):
        s = Serializer(current_app.config['SECRET_KEY'])
        try:
            data = s.loads(token)
        except:
            return False
        if data.get('confirm') != self.id:
            return False
        self.confirmed = True
        db.session.add(self)
        return True

    # Password hashing and verification
    @property
    def password(self):
        raise AttributeError('Password is not a readable attribute')

    @password.setter
    def password(self, password):
        self.password_hash = generate_password_hash(password)

    def verify_password(self, password):
        return check_password_hash(self.password_hash, password)

    # Load and remember logged in user
    @login_manager.user_loader
    def load_user(user_id):
        return User.query.get(int(user_id))

    # Evaluate whether a user has a given permission
    def can(self, permissions):
        return self.role is not None and (self.role.permissions & permissions) == permissions

    def is_administrator(self):
        return self.can(Permission.ADMINISTER)

    #Refresh users last visit
    def ping(self):
        self.last_seen = datetime.utcnow()
        db.session.add(self)

    #Connection helper methods
    def follow(self, user):
        if not self.is_following(user):
            f = Follow(follower=self, followed=user)
            db.session.add(f)

    def unfollow(self, user):
        f = self.followed.filter_by(followed_id=user.id).first()
        if f:
            db.session.delete(f)

    def is_following(self, user):
        return self.followed.filter_by(followed_id=user.id).first() is not None

    def is_followed_by(self, user):
        return self.followers.filter_by(follower_id=user.id).first() is not None

    #get followed posts
    @property
    def followed_posts(self):
        return Post.query.join(Follow, Follow.followed_id == Post.author_id).filter(Follow.follower_id == self.id)


class Post(db.Model):
    __tablename__ = 'posts'
    id = db.Column(db.Integer, primary_key=True)
    body = db.Column(db.Text())
    timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
    author_id = db.Column(db.Integer, db.ForeignKey('users.id'))
    comments = db.relationship('Comment', backref='post', lazy='dynamic')
    liked = db.relationship('Like', foreign_keys=[Like.liker_id], backref=db.backref('liker', lazy='joined'),
                            lazy='dynamic', cascade='all, delete-orphan')
    likers = db.relationship('Like', foreign_keys=[Like.post_liked_id], backref=db.backref('liked', lazy='joined'),
                             lazy='dynamic', cascade='all, delete-orphan')

    # Post Likes helper methods
    def like_post(self, post):
        if not self.is_liking_post(post):
            l = Like(liker=self, liked=post)
            db.session.add(l)

    def unlike_post(self, post):
        l = self.liked.filter_by(post_liked_id=post.id).first()
        if l:
            db.session.delete(l)

    def is_liking_post(self, post):
        return self.liked.filter_by(post_liked_id=post.id).first() is not None

    def is_liked_by(self, post):
        return self.likers.filter_by(liker_id=post.id).first() is not None


#comments model
class Comment(db.Model):
    __tablename__ = 'comments'
    id = db.Column(db.Integer, primary_key=True)
    body = db.Column(db.Text)
    timestamp = db.Column(db.DateTime,index=True, default=datetime.utcnow)
    disabled = db.Column(db.Boolean)
    author_id = db.Column(db.Integer, db.ForeignKey('users.id'))
    post_id = db.Column(db.Integer, db.ForeignKey('posts.id'))
    liked = db.relationship('Like', foreign_keys=[Like.liker_id], backref=db.backref('liker', lazy='joined'),
                               lazy='dynamic', cascade='all, delete-orphan')
    likers = db.relationship('Like', foreign_keys=[Like.comment_liked_id], backref=db.backref('liked', lazy='joined'),
                             lazy='dynamic', cascade='all, delete-orphan')

    #Comment Likes helper methods
    def like_comment(self, comment):
        if not self.is_liking_comment(comment):
            c = Like(liker=self, liked=comment)
            db.session.add(c)

    def unlike_comment(self, comment):
        c = self.liked.filter_by(comment_liked_id=comment.id).first()
        if c:
            db.session.delete(c)

    def is_liking_comment(self, comment):
        return self.liked.filter_by(comment_liked_id=comment.id).first() is not None

    def is_liked_by(self, comment):
        return self.likers.filter_by(liker_id=comment.id).first() is not None

First of all, you are mixing likes for posts and for comments. That is not going to go well. I recommend that you separate post likes from comment likes using two separate many-to-many relationships.

The error that you are getting occurs because you are not implementing the many-to-many relationship correctly. For example, to represent post likes, you have a many-to-many relationship between users and posts. The association table could be called PostLikes for example, and it would have two foreign keys, user_id and post_id (maybe also a timestamp if you want to record the time of the like).

On the user side, you can define the relationship more or less like this:

class User:
    post_likes = db.relationship('PostLikes', backref=db.backref('user', lazy='joined'),
                                 lazy='dynamic', cascade='all, delete-orphan')

And on the post side, it would be:

class Post:
    user_likes = db.relationship('PostLikes', backref=db.backref('post', lazy='joined'),
                                 lazy='dynamic', cascade='all, delete-orphan')

So then, given a user, you can say user.post_likes and that will give you a db query that returns all the posts the user liked. And given a post, you can say post.user_likes to get all the users that liked that post.

You will then repeat all the above for comment likes.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM