Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

Python

Creating a like feature on Flask

I'm trying to create a like feature on the app I'm building. The guide I'm using is the follow feature on the Flask course but the difference now is that user will click "like" to some specific post, that's why the Relationship model has a ForeignKeyField pointing to the Post model. Also I've created two methods on the Post model, like and unlike. Here is an example:

class Post(Model):
    timestamp = DateTimeField(default=datetime.datetime.now)
    user = ForeignKeyField(
        rel_model=User,
        related_name='posts'
    )
    content = TextField()

    class Meta:
        database = DATABASE

    def like(self):
        return (
            Post.select().join(
                Relationship, on=Relationship.unlike_post
            ).where(
                Relationship.like_post == self
            )
        )

    def unlike(self):
        return (
            Post.select().join(
                Relationship, on=Relationship.like_post
            ).where(
                Relationship.unlike_post == self
            )
        )


class Relationship(Model):
    like_post = ForeignKeyField(Post, related_name='like')
    unlike_post = ForeignKeyField(Post, related_name='unlike')

    class Meta:
        database = DATABASE
        indexes = (
            (('like_post'), ('unlike_post'), True)
        )

My Problem comes when I have to pass the values when defining the routes that's why I left it with question mark, the original values was 'g.user._get_current_object()':

@app.route('/test')
@login_required
def test():
    post = models.Post.select()
    try:
    models.Relationship.create(like_post=????????,
    unlike_post=unlike_post)

    return render_template('test.html', post=post)

Here is the template with the buttons to like the posts:

<a href="#" class="glyphicon glyphicon-thumbs-down" aria-hidden="true"></a>
<a href="/" class="btn btn-primary radius">Post</a>
<a href="#" class="glyphicon glyphicon-thumbs-up" aria-hidden="true"></a>

Any thoughts?

1 Answer

Guled Ahmed
Guled Ahmed
12,806 Points

Hello Rodrigo,

I recently was implementing a upvoting system for my app, and I believe that what I learned from this experience may be able to help you out. I recently asked this question on stack overflow and got plenty of strategies, and I'm going to share one of them that worked for me. Instead of creating relationships using the follow/unfollow methods on the video (which may work, but I don't know how to do it), I simply created a new table named Vote (You can change this to like, but I just want to give you so inspiration to see if it might work for your system). Here is how my vote table looks like:

class Vote(Model):
    user_id = IntegerField(null=True)
    post_id = IntegerField(null=True)

    class Meta:
        database = DATABASE

What this table is going to do is take a user's id and the post's id. Their defaults are null. Here is how I get the user to send information to my route (which hosts my vote function). I simply pass in the current_user id and the post's id and send that to a route using url_for:

{% if current_user.is_authenticated() %}
        <a href="{{url_for('upvote', post_id=post.id, user_id = current_user.id)}}"><img src="/static/img/upvote.png"></a>
        <p>{{post.upvotes}}</p>
        {%else%}

Also I make sure the user is authenticated in order to vote, this is just personal preference

After they click on the link, and the current_user.id is sent, along with the post's id. The route's function will take the information and follow along with the instructions I gave it:

** Edit (5/28): Code has been fixed to enable proper upvotes.**

@app.route('/vote/<int:post_id>/<int:user_id>')
@login_required
def upvote(post_id,user_id):
    posts = models.Post.select().where(models.Post.id == post_id)
    if posts.count() == 0:
        abort(404)



    post = models.Post.select().where(models.Post.id == post_id).get()
    if models.Vote.select().where(models.Vote.user_id == user_id,models.Vote.post_id == post_id).exists():
        (models.Vote.select().where(models.Vote.user_id == user_id,models.Vote.post_id == post_id).get()).delete_instance()
        count = models.Vote.select().where(models.Vote.post_id == post_id).count()
        query = models.Post.update(upvotes = count).where(models.Post.id == post_id)
        query.execute()
        return redirect(url_for('index'))
    else:
        models.Vote.create(user_id=user_id, post_id = post_id)
        flash("Your vote has been registered.","success")
        count = models.Vote.select().where(models.Vote.post_id == post_id).count()
        query = models.Post.update(upvotes = count).where(models.Post.id == post_id)
        query.execute()
        return redirect(url_for('index'))
    """
    if post.add_vote(user_id):
        flash("Your vote has been registered. Thank you.")
        query = models.Post.update(upvotes = (post.num_votes())).where(models.Post.id == post_id)
        query.execute()
    else:
        query = models.Post.update(upvotes = (post.upvotes-1)).where(models.Post.id == post_id)
        query.execute()
    """

    return redirect(url_for('index'))

Here is the strategy (thanks to the user Bidhan on stack overflow (shout out to you if you ever stumble upon this post):

"I think the best approach here would be to create a separate table called Votes which will have two columns. One will store the id of the Post and the other will store the id of the User. Each entry or row inside the table would count as one vote. If a user tries to vote on a particular post, you would first query the Votes table if a row with that user id exists. If it doesn't exist, you add the vote. If it does exist however, then you simply remove the vote. To get the total vote count of a particular post, you would again query the Votes table and count the number of rows with the given post id. This would also make your application scalable if in case you would like to add a downvote functionality in the future."

I hope this gives you some inspiration into making creating your like system. I'm not sure if you want to count the number of likes, but if you do, my example is pretty good, if not you can always modify it to your liking.

Regards, Guled

Thanks Guled! I will try this in a few. You are the best!

Guled Ahmed
Guled Ahmed
12,806 Points

Rodrigo, I discovered something wrong with the implementation of Bidhan's strategy. The strategy is sound, but I think I created it the wrong way. I first voted one of my posts with the admin and the number of upvotes turned to 1 then, I logged in as another user and tried to upvote the same post again, but this time it decremented to 0. I'm going to try to fix this in the meanwhile.

Guled Ahmed
Guled Ahmed
12,806 Points

Ok I got it fixed now!

@app.route('/vote/<int:post_id>/<int:user_id>')
@login_required
def upvote(post_id,user_id):
    posts = models.Post.select().where(models.Post.id == post_id)
    if posts.count() == 0:
        abort(404)



    post = models.Post.select().where(models.Post.id == post_id).get()
    if models.Vote.select().where(models.Vote.user_id == user_id,models.Vote.post_id == post_id).exists():
        (models.Vote.select().where(models.Vote.user_id == user_id,models.Vote.post_id == post_id).get()).delete_instance()
        count = models.Vote.select().where(models.Vote.post_id == post_id).count()
        query = models.Post.update(upvotes = count).where(models.Post.id == post_id)
        query.execute()
        return redirect(url_for('index'))
    else:
        models.Vote.create(user_id=user_id, post_id = post_id)
        flash("Your vote has been registered.","success")
        count = models.Vote.select().where(models.Vote.post_id == post_id).count()
        query = models.Post.update(upvotes = count).where(models.Post.id == post_id)
        query.execute()
        return redirect(url_for('index'))
    """
    if post.add_vote(user_id):
        flash("Your vote has been registered. Thank you.")
        query = models.Post.update(upvotes = (post.num_votes())).where(models.Post.id == post_id)
        query.execute()
    else:
        query = models.Post.update(upvotes = (post.upvotes-1)).where(models.Post.id == post_id)
        query.execute()
    """

    return redirect(url_for('index'))

Guled. It didn't work for my application. I'm starting to think about creating an API and adding the like feature through Backbone.js. Hope Backbone is a good decision as the frontend MVC framework.