diff --git a/models.py b/models.py index bdc7b9d..472da85 100644 --- a/models.py +++ b/models.py @@ -3,6 +3,7 @@ import json from typing import List import humanize +from flask import url_for from flask_login import UserMixin from sqlalchemy import func @@ -27,6 +28,9 @@ class User(UserMixin, db.Model): posts_liked = db.relationship("PostLike", backref=db.backref('user', lazy='joined'), lazy='dynamic', cascade='all, delete-orphan') comments_liked = db.relationship("CommentLike", backref=db.backref('user', lazy='joined'), lazy='dynamic', cascade='all, delete-orphan') + def get_url(self) -> str: + return url_for('main.view_user', username=self.username) + def get_last_seen_text(self) -> str: delta: datetime.timedelta = datetime.datetime.utcnow() - self.last_seen if delta > MAXIMUM_ONLINE_DELTA: @@ -70,6 +74,14 @@ class User(UserMixin, db.Model): def display_about(self) -> str: return self.about_me or "This user hasn't written a bio yet." + def has_liked_post(self, post_id: int) -> bool: + """Check whether a user has liked a given post.""" + return db.session.query(PostLike.id).filter_by(post_id=post_id, user_id=self.id).first() is not None + + def has_liked_comment(self, comment_id: int) -> bool: + """Check whether a user has liked a given post.""" + return db.session.query(CommentLike.id).filter_by(comment_id=comment_id, user_id=user_id).first() is not None + class Post(db.Model): id = db.Column(db.Integer, primary_key=True) @@ -84,6 +96,22 @@ class Post(db.Model): delta: datetime.timedelta = datetime.datetime.utcnow() - self.date_posted return humanize.naturaldelta(delta) + def get_like_count(self) -> bool: + return PostLike.query.filter_by(post_id=self.id).count() + + def get_like_text(self) -> str: + like_count = self.get_like_count() + top_likes = PostLike.query.filter_by(post_id=self.id).order_by(PostLike.timestamp.asc()).limit(3) + users = [like.user for like in top_likes] + names = [f'{user.name}' for user in users] + + if like_count >= 3: format_string = '{0}, {1} and {2} has liked this post.' + elif like_count == 2: format_string = '{0} and {1} has liked this post.' + elif like_count == 1: format_string = '{0} has liked this post.' + else: format_string = '0 likes' + + return format_string.format(*names) + class PostLike(db.Model): id = db.Column(db.Integer, primary_key=True) diff --git a/routes.py b/routes.py index b66af4d..67f71fb 100644 --- a/routes.py +++ b/routes.py @@ -40,17 +40,12 @@ def feed(): return render_template('pages/feed.html', posts=posts, form=form) -@blueprint.route('/feed/') +@blueprint.route('/post/') def view_post(post_id: int): post = Post.query.get_or_404(post_id) return render_template('pages/post.html', form=NewCommentForm(), post=post) -# @blueprint.route('/messages') -# def messages(): -# return render_template('pages/messages.html') - - @blueprint.route('/search') def search(): return render_template('pages/search.html') @@ -81,12 +76,3 @@ def edit_user(username: str): form.process(obj=user) return render_template('pages/user_edit.html', form=form) - -# @blueprint.route('/blogs') -# def blogs(): -# return render_template('pages/blogs.html') -# -# -# @blueprint.route('/groups') -# def groups(): -# return render_template('pages/groups.html') diff --git a/static/styles.css b/static/styles.css index 3a50c1c..e141901 100644 --- a/static/styles.css +++ b/static/styles.css @@ -250,6 +250,16 @@ nav .links li:not(:last-child)::after, footer .links li:not(:last-child)::after padding: 1.5em; padding-bottom: 0.8em; margin: 0.45em; + position: relative; +} +.post-box .fa-heart { + position: absolute; + top: 1em; + right: 1em; + color: #b0c9f3; +} +.post-box .fa-heart.liked { + color: #1b53a8; } .post-box .post-author { margin-top: 1em; diff --git a/static/styles.css.map b/static/styles.css.map index abf669c..0731870 100644 --- a/static/styles.css.map +++ b/static/styles.css.map @@ -1 +1 @@ -{"version":3,"sourceRoot":"","sources":["styles.scss"],"names":[],"mappings":"AAAA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EAOE;;AALA;EAEE;;;AASJ;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;;;AAKJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;;;AAKN;EACE;EACA;EACA;EACA;;AAEA;EACE;;;AAIJ;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIA;EACE;EACA;;AAEA;EACE;;AAEA;EACE;;;AAMR;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;;;AAIA;EACE;EACA;;;AAIJ;EACE;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;;AAIJ;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;;AAIJ;EACE;;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;;;AAMF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EAGA;EACA;EACA;EACA;;AAKF;EACE;EACA;EACA;EACA;;;AAMR;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;;AAGF;EACE;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;;AAGF;EACE;;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAEA;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAKN;EACE;EACA;;AAIJ;EAEE;EACA;EACA;;;AAMR;EACE;EACA;;AACA;EACE;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAKN;EACE;EACA;;;AAIJ;EACE;EACA;EACA;;AAEA;EACE;EACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EAME;EACA;EACA;EAEA;EACA;EACA;;AAXA;EACE;;;AAcN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;;;AAGF;EACE","file":"styles.css"} \ No newline at end of file +{"version":3,"sourceRoot":"","sources":["styles.scss"],"names":[],"mappings":"AAAA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EAOE;;AALA;EAEE;;;AASJ;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;;;AAKJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;;;AAKN;EACE;EACA;EACA;EACA;;AAEA;EACE;;;AAIJ;EACE;;;AAGF;EACE;;;AAGF;EACE;;;AAIA;EACE;EACA;;AAEA;EACE;;AAEA;EACE;;;AAMR;EACE;EACA;;;AAGF;EACE;EACA;EACA;EACA;;;AAGF;EACE;;;AAIA;EACE;EACA;;;AAIJ;EACE;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;;AAIJ;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;;;AAIJ;EACE;;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;;;AAMF;EACE;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;EACA;EAGA;EACA;EACA;EACA;;AAKF;EACE;EACA;EACA;EACA;;;AAMR;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;;AAGF;EACE;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;;AAGF;EACE;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AACA;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAIJ;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAEA;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAKN;EACE;EACA;;AAIJ;EAEE;EACA;EACA;;;AAMR;EACE;EACA;;AACA;EACE;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAKN;EACE;EACA;;;AAIJ;EACE;EACA;EACA;;AAEA;EACE;EACA;;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EAME;EACA;EACA;EAEA;EACA;EACA;;AAXA;EACE;;;AAcN;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;;;AAGF;EACE","file":"styles.css"} \ No newline at end of file diff --git a/static/styles.scss b/static/styles.scss index 4e307fb..314d0b2 100644 --- a/static/styles.scss +++ b/static/styles.scss @@ -296,6 +296,17 @@ nav, footer { padding: 1.5em; padding-bottom: 0.8em; margin: 0.45em; + position: relative; + + .fa-heart { + position: absolute; + top: 1em; + right: 1em; + color: #b0c9f3; + &.liked { + color: #1b53a8; + } + } .post-author { margin-top: 1em; diff --git a/templates/layouts/footer.html b/templates/layouts/footer.html index e7b3bf9..1da8218 100644 --- a/templates/layouts/footer.html +++ b/templates/layouts/footer.html @@ -9,7 +9,7 @@
  • About
  • diff --git a/templates/pages/feed.html b/templates/pages/feed.html index e350b4b..d37bf04 100644 --- a/templates/pages/feed.html +++ b/templates/pages/feed.html @@ -10,16 +10,18 @@ {% endif %} -
    +
    {% for post in posts %}
    + {{ post.text }}