mirror of
https://github.com/Xevion/runnerspace.git
synced 2025-12-08 22:08:30 -06:00
Add like querying/checking/rendering to Feed posts
- Change runnerspace.live to Runnerspace - Change post viewing URL to say /post/🆔 instead of /feed/🆔
This commit is contained in:
28
models.py
28
models.py
@@ -3,6 +3,7 @@ import json
|
|||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
import humanize
|
import humanize
|
||||||
|
from flask import url_for
|
||||||
from flask_login import UserMixin
|
from flask_login import UserMixin
|
||||||
from sqlalchemy import func
|
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')
|
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')
|
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:
|
def get_last_seen_text(self) -> str:
|
||||||
delta: datetime.timedelta = datetime.datetime.utcnow() - self.last_seen
|
delta: datetime.timedelta = datetime.datetime.utcnow() - self.last_seen
|
||||||
if delta > MAXIMUM_ONLINE_DELTA:
|
if delta > MAXIMUM_ONLINE_DELTA:
|
||||||
@@ -70,6 +74,14 @@ class User(UserMixin, db.Model):
|
|||||||
def display_about(self) -> str:
|
def display_about(self) -> str:
|
||||||
return self.about_me or "This user hasn't written a bio yet."
|
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):
|
class Post(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
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
|
delta: datetime.timedelta = datetime.datetime.utcnow() - self.date_posted
|
||||||
return humanize.naturaldelta(delta)
|
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'<a href="{user.get_url()}">{user.name}</a>' 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):
|
class PostLike(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
|||||||
16
routes.py
16
routes.py
@@ -40,17 +40,12 @@ def feed():
|
|||||||
return render_template('pages/feed.html', posts=posts, form=form)
|
return render_template('pages/feed.html', posts=posts, form=form)
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route('/feed/<post_id>')
|
@blueprint.route('/post/<post_id>')
|
||||||
def view_post(post_id: int):
|
def view_post(post_id: int):
|
||||||
post = Post.query.get_or_404(post_id)
|
post = Post.query.get_or_404(post_id)
|
||||||
return render_template('pages/post.html', form=NewCommentForm(), post=post)
|
return render_template('pages/post.html', form=NewCommentForm(), post=post)
|
||||||
|
|
||||||
|
|
||||||
# @blueprint.route('/messages')
|
|
||||||
# def messages():
|
|
||||||
# return render_template('pages/messages.html')
|
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route('/search')
|
@blueprint.route('/search')
|
||||||
def search():
|
def search():
|
||||||
return render_template('pages/search.html')
|
return render_template('pages/search.html')
|
||||||
@@ -81,12 +76,3 @@ def edit_user(username: str):
|
|||||||
|
|
||||||
form.process(obj=user)
|
form.process(obj=user)
|
||||||
return render_template('pages/user_edit.html', form=form)
|
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')
|
|
||||||
|
|||||||
@@ -250,6 +250,16 @@ nav .links li:not(:last-child)::after, footer .links li:not(:last-child)::after
|
|||||||
padding: 1.5em;
|
padding: 1.5em;
|
||||||
padding-bottom: 0.8em;
|
padding-bottom: 0.8em;
|
||||||
margin: 0.45em;
|
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 {
|
.post-box .post-author {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
|
|||||||
@@ -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"}
|
{"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"}
|
||||||
@@ -296,6 +296,17 @@ nav, footer {
|
|||||||
padding: 1.5em;
|
padding: 1.5em;
|
||||||
padding-bottom: 0.8em;
|
padding-bottom: 0.8em;
|
||||||
margin: 0.45em;
|
margin: 0.45em;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.fa-heart {
|
||||||
|
position: absolute;
|
||||||
|
top: 1em;
|
||||||
|
right: 1em;
|
||||||
|
color: #b0c9f3;
|
||||||
|
&.liked {
|
||||||
|
color: #1b53a8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.post-author {
|
.post-author {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<li><a href="{{ url_for('static.about') }}">About</a></li>
|
<li><a href="{{ url_for('static.about') }}">About</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<p class="copyright">
|
<p class="copyright">
|
||||||
<a href="{{ url_for('main.index') }}">©2022 Runnerspace.live All Rights Reserved.</a>
|
<a href="{{ url_for('main.index') }}">©2022 Runnerspace All Rights Reserved.</a>
|
||||||
| {{ now().isoformat(sep=' ', timespec='milliseconds') }} UTC
|
| {{ now().isoformat(sep=' ', timespec='milliseconds') }} UTC
|
||||||
</p>
|
</p>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
@@ -10,16 +10,18 @@
|
|||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<hr style="margin: 2em 0">
|
<hr style="margin: 1.5em 0">
|
||||||
|
|
||||||
{% for post in posts %}
|
{% for post in posts %}
|
||||||
<div class="post-box">
|
<div class="post-box">
|
||||||
|
<i class="fas fa-heart {% if current_user.has_liked_post(post.id) %}liked{% endif %}"></i>
|
||||||
{{ post.text }}
|
{{ post.text }}
|
||||||
<div class="post-author no-border">
|
<div class="post-author no-border">
|
||||||
{% with comment_count = post.comments|length %}
|
{% with comment_count = post.comments|length %}
|
||||||
<em>Posted by <a href="{{ url_for('main.view_user', username=post.author.username) }}">{{ post.author.name }}</a></em>
|
<em>Posted by <a href="{{ url_for('main.view_user', username=post.author.username) }}">{{ post.author.name }}</a></em>
|
||||||
<span title="{{ post.date_posted }}">{{ post.get_time_ago() }} ago</span>. |
|
<span title="{{ post.date_posted }}">{{ post.get_time_ago() }} ago</span>. |
|
||||||
<a href="{{ url_for('main.view_post', post_id=post.id) }}"><span> {{ comment_count }} comment{{ comment_count|pluralize }}</span></a>
|
<a href="{{ url_for('main.view_post', post_id=post.id) }}"><span> {{ comment_count }} comment{{ comment_count|pluralize }}</span></a>
|
||||||
|
| {{ post.get_like_text()|safe }}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user