-
Notifications
You must be signed in to change notification settings - Fork 47
Like/dislike feature #115
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Like/dislike feature #115
Changes from 3 commits
891b122
1ce2f5d
9dd4384
08b07d0
91a6fa7
2cb3c87
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| # coding: utf-8 | ||
|
|
||
| from __future__ import absolute_import | ||
| from datetime import datetime | ||
|
|
||
| from firefly.ext import db | ||
| from .user import User | ||
|
|
||
|
|
||
| class Likes(object): | ||
| def __init__(self, product_type): | ||
| self.product_type = product_type | ||
| self._instance = None | ||
|
|
||
| def __get__(self, instance, owner): | ||
| self._instance = instance | ||
| return self | ||
|
|
||
| def __len__(self): | ||
| return Like.objects( | ||
| product_id=self.product_id, | ||
| product_type=self.product_type | ||
| ).count() | ||
|
|
||
| def productidgetter(self, func): | ||
| return func | ||
|
|
||
| @property | ||
| def product_id(self): | ||
| return str(self.productidgetter(self._instance)) | ||
|
|
||
| def add(self, user_id): | ||
| user = User.objects(id=user_id).first() | ||
| if user: | ||
| return Like.objects.create( | ||
| product_id=self.product_id, | ||
| product_type=self.product_type, | ||
| user=user | ||
| ) | ||
|
|
||
| def delete(self, user_id): | ||
| user = User.objects(id=user_id).first() | ||
| if user: | ||
| like = Like.objects.filter( | ||
| product_id=self.product_id, | ||
| product_type=self.product_type, | ||
| user=user | ||
| ).first() | ||
| if like: | ||
| like.delete() | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 直接
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mozillazg 好的,谢谢 |
||
|
|
||
|
|
||
| class Like(db.Document): | ||
| id = db.SequenceField(primary_key=True) | ||
| created_at = db.DateTimeField(default=datetime.utcnow, required=True) | ||
| product_type = db.StringField(required=True) | ||
| product_id = db.StringField(required=True, | ||
| unique_with=['user', 'product_type']) | ||
| user = db.ReferenceField(User) | ||
|
|
||
| meta = { | ||
| 'indexes': ['product_type', 'product_id'] | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,6 +10,7 @@ | |
| from firefly.views.utils import timesince | ||
| from firefly.models.consts import CATEGORY_COLORS | ||
| from .user import User | ||
| from .like import Likes | ||
|
|
||
| __all__ = ["Category", "Post", "Video", "Image", "Comment"] | ||
|
|
||
|
|
@@ -58,6 +59,12 @@ class Post(db.Document): | |
| comments = db.ListField(db.ReferenceField('Comment')) | ||
| category = db.ReferenceField(Category) | ||
|
|
||
| likes = Likes('Post') | ||
|
|
||
| @likes.productidgetter | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 这个好像没什么作用?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 这里是为了让
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 我也没看懂 这里用装饰器的优点是什么
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 我是觉得这里用装饰器会比较灵活,因为
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 那这样呢: def product_id(self):
return self.likes._instance.id
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 设计一个项目 其实 一个大的角度是代码可读性和可维护性. 这段实现一眼看去 真的不好懂..
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 这样目前可以呀,可是假如未来有个对象需要加 likes 的功能但没有
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 问题主要出在 class Likes():
def __init__(self):
self.product_id_func = None
def productidgetter(self, func):
self.product_id_func = func
return func
@property
def product_id(self):
return self.product_id_func(self._instance)
class Post():
@likes.productidgetter
def product_id(self):
return self.id |
||
| def product_id(self): | ||
| return self.id | ||
|
|
||
| def url(self): | ||
| return url_for('post.detail', id=self.id) | ||
|
|
||
|
|
@@ -84,14 +91,17 @@ def recent_activity_time(self): | |
|
|
||
|
|
||
| class Video(Post): | ||
| likes = Likes('Video') | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 我在想 可不可以 在基类里面 用引用的方式 但是这个性能是不是就有问题了
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 要不然 把 放在一个基类里面 这些Topic/Post等等都继承.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 如果是这个用法,那 view 里面怎么添加或者去掉某个 like 呢?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @property
def likes(self):
return Like.objects(
product_id=self.id,
product_type=self.__class__.__name__
)
# View: add Like
Like.create(product_id=post.id, product_type=post.__class__.__name__, user=current_user)那么 view 层用起来会比较麻烦吧?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 还是借用Like 比如 但是觉得product_type应该有个关联 或者常量的列表 product_id 其实就是对应的xx.id. 我觉得可以使用int 而不是str
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @killpanda 但是你使用封装的
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. class Likes(object):
def __init__(self):
self.product_id = None
self.product_type = None
def __get__(self, instance, owner):
self.product_id = str(instance.id)
self.product_type = owner.__name__
return self
def __len__(self):
return Like.objects(
product_id=self.product_id,
product_type=self.product_type
).count()
def add(self, user_id):
user = User.objects(id=user_id).first()
if user:
return Like.objects.create(
product_id=self.product_id,
product_type=self.product_type,
user=user
)
def delete(self, user_id):
user = User.objects(id=user_id).first()
if user:
like = Like.objects.filter(
product_id=self.product_id,
product_type=self.product_type,
user=user
).first()
if like:
like.delete()如果是这样呢?我当初的构想是如果 |
||
| embed_code = db.StringField(required=True) | ||
|
|
||
|
|
||
| class Image(Post): | ||
| likes = Likes('Image') | ||
| image_url = db.StringField(required=True, max_length=255) | ||
|
|
||
|
|
||
| class Quote(Post): | ||
| likes = Likes('Quote') | ||
| content = db.StringField(required=True) | ||
| author = db.ReferenceField(User) | ||
|
|
||
|
|
@@ -103,6 +113,12 @@ class Comment(db.Document): | |
| author = db.ReferenceField(User) | ||
| ref_id = db.IntField(default=0) | ||
|
|
||
| likes = Likes('Comment') | ||
|
|
||
| @likes.productidgetter | ||
| def product_id(self): | ||
| return str(self.id) | ||
|
|
||
| @property | ||
| def post_type(self): | ||
| return self.__class__.__name__ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| # coding: utf-8 | ||
|
|
||
| from __future__ import absolute_import | ||
|
|
||
| from flask_restful import Resource | ||
| from flask_security import login_required | ||
| from flask_login import current_user | ||
|
|
||
| from firefly.models.topic import Post | ||
|
|
||
|
|
||
| class LikePostApi(Resource): | ||
|
|
||
| method_decorators = [login_required] | ||
|
|
||
| def put(self, id): | ||
| post = Post.objects.get_or_404(id=id) | ||
| if post: | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mozillazg 好的,谢谢
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 404 会直接raise |
||
| post.likes.add(current_user.id) | ||
| return '', 201 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. body 为空的话,状态码一般是 204。
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 通常 201 是代表修改成功吧?204 No Content 是否有些奇怪? @mozillazg
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @killpanda @mozillazg +1 你可以看看状态码的说明 比如 https://zh.wikipedia.org/wiki/HTTP%E7%8A%B6%E6%80%81%E7%A0%81 如果只处理 但是返回空的body 正规的做法是204. 而且201一般都是POST
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 202也可以接受
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 那我跟 api/user.py 统一吧,PUT 202 DELETE 204
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 我又翻到一篇 https://developer.yahoo.com/social/rest_api_guide/http-response-codes.html 其实不同方法的状态码还是可以不一样的 关键是用途. |
||
|
|
||
| def delete(self, id): | ||
| post = Post.objects.get_or_404(id=id) | ||
| if post: | ||
| post.likes.delete(current_user.id) | ||
| return '', 201 | ||
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| # coding: utf-8 | ||
|
|
||
| from __future__ import absolute_import | ||
| import pytest | ||
|
|
||
| from flask import url_for | ||
| from flask_login import current_user | ||
|
|
||
| from firefly.models.user import User | ||
| from firefly.models.topic import Category, Post | ||
|
|
||
|
|
||
| @pytest.mark.usefixtures('client_class') | ||
| class TestLike: | ||
|
|
||
| users = [] | ||
|
|
||
| def setup(self): | ||
| c = Category.objects.create( | ||
| name='python', description='描述', _slug='python-slug' | ||
| ) | ||
| Post.objects.create( | ||
| title='标题test', content='内容test', category=c | ||
| ) | ||
|
|
||
| self.users = [] | ||
| for x in range(3): | ||
| self.users.append( | ||
| User.create_user( | ||
| username='user' + str(x), | ||
| password='password123', | ||
| email='user' + str(x) + '@firefly.dev' | ||
| ) | ||
| ) | ||
|
|
||
| def login(self, email): | ||
| form = { | ||
| 'email': email, | ||
| 'password': 'password123' | ||
| } | ||
| self.client.post( | ||
| url_for('home.login'), data=form, | ||
| follow_redirects=True | ||
| ) | ||
| assert current_user.is_authenticated() | ||
|
|
||
| def test_like(self): | ||
| post = Post.objects.first() | ||
| assert len(post.likes) == 0 | ||
|
|
||
| for user in self.users: | ||
| self.login(user.email) | ||
| url = url_for('api.like', id=post.id) | ||
| rv = self.client.put(url) | ||
| assert rv.status_code == 201 | ||
| assert len(post.likes) == len(self.users) | ||
|
|
||
| rv = self.client.delete(url) | ||
| assert rv.status_code == 201 | ||
| assert len(post.likes) == 2 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
这里等价于
return str(self._instance)应该不是你预期的结果吧?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mozillazg 不是呀 这里是要通过
productidgetter来拿到product_idThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@killpanda 其实我也没看懂. 在
productidgetter里面 直接返回了参数 不就是 @mozillazg 说的意思么其次
productidgetter这个方法名字 不是应该用下划线分词的么 比如product_id_getter