Skip to content

Commit

Permalink
13. 喜欢文章
Browse files Browse the repository at this point in the history
  • Loading branch information
wangy8961 committed Nov 26, 2018
1 parent 5a43240 commit 2398c05
Show file tree
Hide file tree
Showing 15 changed files with 650 additions and 46 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
- [Flask Vue.js全栈开发|第10章:用户通知](http://www.madmalls.com/blog/post/user-notifications/)
- [Flask Vue.js全栈开发|第11章:私信](http://www.madmalls.com/blog/post/send-private-messages/)
- [Flask Vue.js全栈开发|第12章:黑名单](http://www.madmalls.com/blog/post/blacklist/)
- 收藏文章 (敬请期待)
- [Flask Vue.js全栈开发|第13章:喜欢文章](http://www.madmalls.com/blog/post/like-posts/)
- 权限管理 (敬请期待)
- 邮件的用处 (敬请期待)
- 修改用户设置 (敬请期待)
Expand Down
16 changes: 8 additions & 8 deletions back-end/app/api/comments.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,15 +121,15 @@ def like_comment(id):
comment = Comment.query.get_or_404(id)
comment.liked_by(g.current_user)
db.session.add(comment)
# 切记要先提交,先添加点赞记录到数据库,因为 new_likes() 会查询 comments_likes 关联表
# 切记要先提交,先添加点赞记录到数据库,因为 new_comments_likes() 会查询 comments_likes 关联表
db.session.commit()
# 给评论作者发送新点赞通知
comment.author.add_notification('unread_likes_count',
comment.author.new_likes())
comment.author.add_notification('unread_comments_likes_count',
comment.author.new_comments_likes())
db.session.commit()
return jsonify({
'status': 'success',
'message': 'You are now liking comment [ id: %d ].' % id
'message': 'You are now liking this comment.'
})


Expand All @@ -140,13 +140,13 @@ def unlike_comment(id):
comment = Comment.query.get_or_404(id)
comment.unliked_by(g.current_user)
db.session.add(comment)
# 切记要先提交,先添加点赞记录到数据库,因为 new_likes() 会查询 comments_likes 关联表
# 切记要先提交,先添加点赞记录到数据库,因为 new_comments_likes() 会查询 comments_likes 关联表
db.session.commit()
# 给评论作者发送新点赞通知(需要自动减1)
comment.author.add_notification('unread_likes_count',
comment.author.new_likes())
comment.author.add_notification('unread_comments_likes_count',
comment.author.new_comments_likes())
db.session.commit()
return jsonify({
'status': 'success',
'message': 'You are not liking comment [ id: %d ] anymore.' % id
'message': 'You are not liking this comment anymore.'
})
41 changes: 41 additions & 0 deletions back-end/app/api/posts.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,44 @@ def get_post_comments(id):
from operator import itemgetter
item['descendants'] = sorted(descendants, key=itemgetter('timestamp'))
return jsonify(data)


###
# 文章被喜欢/收藏 或 被取消喜欢/取消收藏
###
@bp.route('/posts/<int:id>/like', methods=['GET'])
@token_auth.login_required
def like_post(id):
'''喜欢文章'''
post = Post.query.get_or_404(id)
post.liked_by(g.current_user)
db.session.add(post)
# 切记要先提交,先添加喜欢记录到数据库,因为 new_posts_likes() 会查询 posts_likes 关联表
db.session.commit()
# 给文章作者发送新喜欢通知
post.author.add_notification('unread_posts_likes_count',
post.author.new_posts_likes())
db.session.commit()
return jsonify({
'status': 'success',
'message': 'You are now liking this post.'
})


@bp.route('/posts/<int:id>/unlike', methods=['GET'])
@token_auth.login_required
def unlike_post(id):
'''取消喜欢文章'''
post = Post.query.get_or_404(id)
post.unliked_by(g.current_user)
db.session.add(post)
# 切记要先提交,先添加喜欢记录到数据库,因为 new_posts_likes() 会查询 posts_likes 关联表
db.session.commit()
# 给文章作者发送新喜欢通知(需要自动减1)
post.author.add_notification('unread_posts_likes_count',
post.author.new_posts_likes())
db.session.commit()
return jsonify({
'status': 'success',
'message': 'You are not liking this post anymore.'
})
90 changes: 79 additions & 11 deletions back-end/app/api/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from app.api.auth import token_auth
from app.api.errors import bad_request, error_response
from app.extensions import db
from app.models import comments_likes, User, Post, Comment, Notification, Message
from app.models import comments_likes, posts_likes, User, Post, Comment, Notification, Message


@bp.route('/users/', methods=['POST'])
Expand Down Expand Up @@ -251,6 +251,21 @@ def get_user_posts(id):
return jsonify(data)


@bp.route('/users/<int:id>/liked-posts/', methods=['GET'])
@token_auth.login_required
def get_user_liked_posts(id):
'''返回该用户喜欢别人的文章列表'''
user = User.query.get_or_404(id)
page = request.args.get('page', 1, type=int)
per_page = min(
request.args.get(
'per_page', current_app.config['POSTS_PER_PAGE'], type=int), 100)
data = Post.to_collection_dict(
user.liked_posts.order_by(Post.timestamp.desc()), page, per_page,
'api.get_user_liked_posts', id=id)
return jsonify(data)


@bp.route('/users/<int:id>/followeds-posts/', methods=['GET'])
@token_auth.login_required
def get_user_followeds_posts(id):
Expand Down Expand Up @@ -334,10 +349,10 @@ def get_user_recived_comments(id):
return jsonify(data)


@bp.route('/users/<int:id>/recived-likes/', methods=['GET'])
@bp.route('/users/<int:id>/recived-comments-likes/', methods=['GET'])
@token_auth.login_required
def get_user_recived_likes(id):
'''返回该用户收到的赞和喜欢'''
def get_user_recived_comments_likes(id):
'''返回该用户收到的评论赞'''
user = User.query.get_or_404(id)
if g.current_user != user:
return error_response(403)
Expand All @@ -357,9 +372,9 @@ def get_user_recived_likes(id):
'total_items': comments.total
},
'_links': {
'self': url_for('api.get_user_recived_likes', page=page, per_page=per_page, id=id),
'next': url_for('api.get_user_recived_likes', page=page + 1, per_page=per_page, id=id) if comments.has_next else None,
'prev': url_for('api.get_user_recived_likes', page=page - 1, per_page=per_page, id=id) if comments.has_prev else None
'self': url_for('api.get_user_recived_comments_likes', page=page, per_page=per_page, id=id),
'next': url_for('api.get_user_recived_comments_likes', page=page + 1, per_page=per_page, id=id) if comments.has_next else None,
'prev': url_for('api.get_user_recived_comments_likes', page=page - 1, per_page=per_page, id=id) if comments.has_prev else None
}
}
for c in comments.items:
Expand All @@ -373,16 +388,69 @@ def get_user_recived_likes(id):
res = db.engine.execute("select * from comments_likes where user_id={} and comment_id={}".format(u.id, c.id))
data['timestamp'] = datetime.strptime(list(res)[0][2], '%Y-%m-%d %H:%M:%S.%f')
# 标记本条点赞记录是否为新的
last_read_time = user.last_likes_read_time or datetime(1900, 1, 1)
last_read_time = user.last_comments_likes_read_time or datetime(1900, 1, 1)
if data['timestamp'] > last_read_time:
data['is_new'] = True
records['items'].append(data)
# 按 timestamp 排序一个字典列表(倒序,最新点赞的人在最前面)
records['items'] = sorted(records['items'], key=itemgetter('timestamp'), reverse=True)
# 更新 last_likes_read_time 属性值
user.last_likes_read_time = datetime.utcnow()
# 更新 last_comments_likes_read_time 属性值
user.last_comments_likes_read_time = datetime.utcnow()
# 将新点赞通知的计数归零
user.add_notification('unread_likes_count', 0)
user.add_notification('unread_comments_likes_count', 0)
db.session.commit()
return jsonify(records)


@bp.route('/users/<int:id>/recived-posts-likes/', methods=['GET'])
@token_auth.login_required
def get_user_recived_posts_likes(id):
'''返回该用户收到的文章喜欢'''
user = User.query.get_or_404(id)
if g.current_user != user:
return error_response(403)
page = request.args.get('page', 1, type=int)
per_page = min(
request.args.get(
'per_page', current_app.config['POSTS_PER_PAGE'], type=int), 100)
# 用户哪些文章被喜欢/收藏了,分页
posts = user.posts.join(posts_likes).paginate(page, per_page)
# 喜欢记录
records = {
'items': [],
'_meta': {
'page': page,
'per_page': per_page,
'total_pages': posts.pages,
'total_items': posts.total
},
'_links': {
'self': url_for('api.get_user_recived_posts_likes', page=page, per_page=per_page, id=id),
'next': url_for('api.get_user_recived_posts_likes', page=page + 1, per_page=per_page, id=id) if posts.has_next else None,
'prev': url_for('api.get_user_recived_posts_likes', page=page - 1, per_page=per_page, id=id) if posts.has_prev else None
}
}
for p in posts.items:
# 重组数据,变成: (谁) (什么时间) 喜欢了你的 (哪篇文章)
for u in p.likers:
if u != user: # 用户自己喜欢自己的文章不需要被通知
data = {}
data['user'] = u.to_dict()
data['post'] = p.to_dict()
# 获取喜欢时间
res = db.engine.execute("select * from posts_likes where user_id={} and post_id={}".format(u.id, p.id))
data['timestamp'] = datetime.strptime(list(res)[0][2], '%Y-%m-%d %H:%M:%S.%f')
# 标记本条喜欢记录是否为新的
last_read_time = user.last_posts_likes_read_time or datetime(1900, 1, 1)
if data['timestamp'] > last_read_time:
data['is_new'] = True
records['items'].append(data)
# 按 timestamp 排序一个字典列表(倒序,最新喜欢的人在最前面)
records['items'] = sorted(records['items'], key=itemgetter('timestamp'), reverse=True)
# 更新 last_posts_likes_read_time 属性值
user.last_posts_likes_read_time = datetime.utcnow()
# 将新喜欢通知的计数归零
user.add_notification('unread_posts_likes_count', 0)
db.session.commit()
return jsonify(records)

Expand Down
66 changes: 60 additions & 6 deletions back-end/app/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,14 @@ def to_collection_dict(query, page, per_page, endpoint, **kwargs):
db.Column('timestamp', db.DateTime, default=datetime.utcnow)
)

# 喜欢文章
posts_likes = db.Table(
'posts_likes',
db.Column('user_id', db.Integer, db.ForeignKey('users.id')),
db.Column('post_id', db.Integer, db.ForeignKey('posts.id')),
db.Column('timestamp', db.DateTime, default=datetime.utcnow)
)


class User(PaginatedAPIMixin, db.Model):
# 设置数据库表名,Post模型中的外键 user_id 会引用 users.id
Expand Down Expand Up @@ -90,8 +98,10 @@ class User(PaginatedAPIMixin, db.Model):
last_recived_comments_read_time = db.Column(db.DateTime)
# 用户最后一次查看 用户的粉丝 页面的时间,用来判断哪些粉丝是新的
last_follows_read_time = db.Column(db.DateTime)
# 用户最后一次查看 收到的点赞 页面的时间,用来判断哪些点赞是新的
last_likes_read_time = db.Column(db.DateTime)
# 用户最后一次查看 收到的文章被喜欢 页面的时间,用来判断哪些喜欢是新的
last_posts_likes_read_time = db.Column(db.DateTime)
# 用户最后一次查看 收到的评论点赞 页面的时间,用来判断哪些点赞是新的
last_comments_likes_read_time = db.Column(db.DateTime)
# 用户最后一次查看 关注的人的博客 页面的时间,用来判断哪些文章是新的
last_followeds_posts_read_time = db.Column(db.DateTime)
# 用户的通知
Expand Down Expand Up @@ -259,9 +269,9 @@ def new_follows(self):
last_read_time = self.last_follows_read_time or datetime(1900, 1, 1)
return self.followers.filter(followers.c.timestamp > last_read_time).count()

def new_likes(self):
'''用户收到的新点赞计数'''
last_read_time = self.last_likes_read_time or datetime(1900, 1, 1)
def new_comments_likes(self):
'''用户收到的新评论点赞计数'''
last_read_time = self.last_comments_likes_read_time or datetime(1900, 1, 1)
# 当前用户发表的所有评论当中,哪些被点赞了
comments = self.comments.join(comments_likes).all()
# 新的点赞记录计数
Expand Down Expand Up @@ -303,6 +313,24 @@ def unblock(self, user):
if self.is_blocking(user):
self.harassers.remove(user)

def new_posts_likes(self):
'''用户收到的文章被喜欢的新计数'''
last_read_time = self.last_posts_likes_read_time or datetime(1900, 1, 1)
# 当前用户发布的文章当中,哪些文章被喜欢了
posts = self.posts.join(posts_likes).all()
# 新的喜欢记录计数
new_likes_count = 0
for p in posts:
# 获取喜欢时间
for u in p.likers:
if u != self: # 用户自己喜欢自己的文章不需要被通知
res = db.engine.execute("select * from posts_likes where user_id={} and post_id={}".format(u.id, p.id))
timestamp = datetime.strptime(list(res)[0][2], '%Y-%m-%d %H:%M:%S.%f')
# 判断本条喜欢记录是否为新的
if timestamp > last_read_time:
new_likes_count += 1
return new_likes_count


class Post(PaginatedAPIMixin, db.Model):
__tablename__ = 'posts'
Expand All @@ -317,6 +345,8 @@ class Post(PaginatedAPIMixin, db.Model):
author_id = db.Column(db.Integer, db.ForeignKey('users.id'))
comments = db.relationship('Comment', backref='post', lazy='dynamic',
cascade='all, delete-orphan')
# 博客文章与喜欢/收藏它的人是多对多关系
likers = db.relationship('User', secondary=posts_likes, backref=db.backref('liked_posts', lazy='dynamic'), lazy='dynamic')

def __repr__(self):
return '<Post {}>'.format(self.title)
Expand All @@ -338,12 +368,22 @@ def to_dict(self):
'body': self.body,
'timestamp': self.timestamp,
'views': self.views,
'likers_id': [user.id for user in self.likers],
'likers': [
{
'id': user.id,
'username': user.username,
'name': user.name,
'avatar': user.avatar(128)
} for user in self.likers
],
'author': {
'id': self.author.id,
'username': self.author.username,
'name': self.author.name,
'avatar': self.author.avatar(128)
},
'likers_count': self.likers.count(),
'comments_count': self.comments.count(),
'_links': {
'self': url_for('api.get_post', id=self.id),
Expand All @@ -358,6 +398,20 @@ def from_dict(self, data):
if field in data:
setattr(self, field, data[field])

def is_liked_by(self, user):
'''判断用户 user 是否已经收藏过该文章'''
return user in self.likers

def liked_by(self, user):
'''收藏'''
if not self.is_liked_by(user):
self.likers.append(user)

def unliked_by(self, user):
'''取消收藏'''
if self.is_liked_by(user):
self.likers.remove(user)


db.event.listen(Post.body, 'set', Post.on_changed_body) # body 字段有变化时,执行 on_changed_body() 方法

Expand All @@ -370,7 +424,7 @@ class Comment(PaginatedAPIMixin, db.Model):
mark_read = db.Column(db.Boolean, default=False) # 文章作者会收到评论提醒,可以标为已读
disabled = db.Column(db.Boolean, default=False) # 屏蔽显示
# 评论与对它点赞的人是多对多关系
likers = db.relationship('User', secondary=comments_likes, backref=db.backref('liked_comments', lazy='dynamic'))
likers = db.relationship('User', secondary=comments_likes, backref=db.backref('liked_comments', lazy='dynamic'), lazy='dynamic')
# 外键,评论作者的 id
author_id = db.Column(db.Integer, db.ForeignKey('users.id'))
# 外键,评论所属文章的 id
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""user likes posts of other
Revision ID: 26064376a874
Revises: 50602804ec4f
Create Date: 2018-11-26 10:27:03.693413
"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = '26064376a874'
down_revision = '50602804ec4f'
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('posts_likes',
sa.Column('user_id', sa.Integer(), nullable=True),
sa.Column('post_id', sa.Integer(), nullable=True),
sa.Column('timestamp', sa.DateTime(), nullable=True),
sa.ForeignKeyConstraint(['post_id'], ['posts.id'], name=op.f('fk_posts_likes_post_id_posts')),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], name=op.f('fk_posts_likes_user_id_users'))
)
with op.batch_alter_table('users', schema=None) as batch_op:
batch_op.add_column(sa.Column('last_comments_likes_read_time', sa.DateTime(), nullable=True))
batch_op.add_column(sa.Column('last_posts_likes_read_time', sa.DateTime(), nullable=True))
batch_op.drop_column('last_likes_read_time')

# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('users', schema=None) as batch_op:
batch_op.add_column(sa.Column('last_likes_read_time', sa.DATETIME(), nullable=True))
batch_op.drop_column('last_posts_likes_read_time')
batch_op.drop_column('last_comments_likes_read_time')

op.drop_table('posts_likes')
# ### end Alembic commands ###
Loading

0 comments on commit 2398c05

Please sign in to comment.