-
-
Save gt11799/6ba756e4c7be9d07d61a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# corelib.mixin.comment | |
# -*- coding: UTF-8 -*- | |
from datetime import datetime | |
from MySQLdb import IntegrityError, OperationalError | |
from corelib.config import DOUBAN | |
from corelib.sqlstore import store | |
from corelib.mc import mc, cache, pcache | |
from corelib.accounts.public import Account | |
from corelib.consts import K_PEOPLE | |
from corelib.utils import ptrans | |
from corelib.reply_notify import ReplyNotify | |
from corelib.notification import add_notification, del_user_notification | |
from corelib.admin import get_switch | |
from corelib.comment import Comment | |
from corelib.doucaptcha import update_user_info_comment as update_user_info,\ | |
need_captcha_comment as need_captcha | |
from corelib.accounts.privilege import (is_intern_admin, is_music_manager, | |
is_community_manager, is_duty_manager, is_safe_editor, | |
is_movie_manager, is_book_manager) | |
from censorlib.utils import send_comment_update | |
from luzong.admin import insert_admin_record | |
COMMENT_EXPIRES = 3600 * 24 | |
MAX_CACHE_COMMENT = 500 | |
ALL_COMMENT = 'A' | |
# FRIEND_COMMENT = 'F' | |
NONE_COMMENT = 'N' | |
DEFAULT_COMMENT = ALL_COMMENT | |
COMMENT_LIMIT = set([ALL_COMMENT, NONE_COMMENT]) | |
_MC_COMMENT_KEY = '%s:comment:%s' | |
_MC_DELETED_COMMENT_KEY = '%s:%s:deleted_cmt:%s' | |
_MC_REVERSED_COMMENTS_KEY = '%s:%s:rev_comments' | |
class CommentMixin(object): | |
kind = None | |
table = None | |
key = None # cache key for `self` | |
notification = None | |
reply_notify_type = None | |
bits = None | |
order = 'asc' | |
#点对点回复显示的引用的文字长度 | |
QUOTE_TEXT_LEN = 90 | |
#每页显示评论数 | |
COMMENTS_PER_PAGE = 100 | |
#点对点回应的类型 | |
p2p_reply_notification_type = None | |
# @回应类型 | |
mention_notification_type = None | |
@classmethod | |
def comment_table(cls): | |
return cls.table + "_comment" | |
@classmethod | |
def comment_count_table(cls): | |
return cls.table | |
@classmethod | |
def field(cls): | |
return cls.table + "_id" | |
def extra_comment_columns(self): | |
return [] | |
def url(self): | |
return "%s/%s/%s/" % (DOUBAN, self.table, self.id) | |
def path(self): | |
return self.url() | |
def last_comment_url(self, p=None): | |
p = p if p else self.COMMENTS_PER_PAGE | |
last_page_start = ((self.n_comments + p - 1) / p - 1) * p | |
last_page_start = 0 if last_page_start < 0 else last_page_start | |
return '%s?start=%s#comments' % (self.url(), last_page_start) | |
@classmethod | |
def is_latest_first(cls): | |
return cls.order == 'desc' | |
@classmethod | |
def get_cursor(cls): | |
return store.get_cursor(ro=False, tables=[cls.table, cls.comment_table()]) | |
@classmethod | |
@cache(_MC_COMMENT_KEY % ('{cls.table}', '{id}')) | |
def get_comment(cls, id): | |
if cls.p2p_reply_notification_type: | |
rs = store.execute("select id,author_id,"+cls.field() + | |
",text,unix_timestamp(`time`), ref_cid " | |
"from "+cls.comment_table()+" where id=%s", id) | |
else: | |
# no ref_cid, default ref_cid to 0 | |
# pylint: disable=W9801 | |
rs = store.execute("select id,author_id,"+cls.field() + | |
",text,unix_timestamp(`time`), 0 " | |
"from "+cls.comment_table()+" where id=%s", id) | |
return Comment(rs[0]) if rs else '' | |
@classmethod | |
def get_ref_comments(cls, comments): | |
ref_ids = [c.ref_cid for c in comments if c and c.ref_cid != '0'] | |
rs = mc.get_multi([_MC_COMMENT_KEY % (cls.table, i) for i in ref_ids]) | |
rs = [rs.get(_MC_COMMENT_KEY % (cls.table, i)) or cls.get_comment(i) for i in ref_ids] | |
return {c.id: c for c in rs if c} | |
@ptrans(Comment) | |
@pcache('{self.table}:{self.id}:comment3', MAX_CACHE_COMMENT) | |
def comments(self, limit=100, start=0): | |
"return all coments list order by time" | |
if self.p2p_reply_notification_type: | |
sql = ("select id, author_id, %s, text, unix_timestamp(`time`), " | |
"ref_cid from %s where %s=%%s order by id %s limit %%s,%%s" | |
% (self.field(), self.comment_table(), self.field(), self.order)) | |
else: | |
sql = ("select id, author_id, %s, text, unix_timestamp(`time`), 0 " | |
"from %s where %s=%%s order by id %s limit %%s,%%s" | |
% (self.field(), self.comment_table(), self.field(), self.order)) | |
return store.execute(sql, (self.id, start, limit)) | |
@ptrans(Comment) | |
@pcache(_MC_REVERSED_COMMENTS_KEY % ('{self.table}', '{self.id}'), | |
MAX_CACHE_COMMENT) | |
def reversed_comments(self, limit=100, start=0): | |
"return all coments list order by time desc" | |
if self.p2p_reply_notification_type: | |
sql = ("select id, author_id, %s, text, unix_timestamp(`time`), " | |
"ref_cid from %s where %s=%%s order by id desc limit %%s,%%s" | |
% (self.field(), self.comment_table(), self.field())) | |
else: | |
sql = ("select id, author_id, %s, text, unix_timestamp(`time`), 0 " | |
"from %s where %s=%%s order by id desc limit %%s,%%s" | |
% (self.field(), self.comment_table(), self.field())) | |
return store.execute(sql, (self.id, start, limit)) | |
def latest_comments(self, limit=10): | |
"return latest coments list order by time desc" | |
return self.latest_comments_by_key(self.field(), self.id, limit) | |
@classmethod | |
@ptrans(Comment) | |
@cache('{cls.table}:{key}:{value}:comment2') | |
def latest_comments_by_key(cls, key, value, limit=10): | |
return store.execute("select id,author_id,"+cls.field()+",text,unix_timestamp(`time`) from " | |
+ cls.comment_table()+" where "+key+"=%s " | |
"order by id desc limit %s", (value, limit)) | |
def _flush_comments(self): | |
mc.delete('%s:%s:comment3' % (self.table, self.id)) | |
mc.delete(_MC_REVERSED_COMMENTS_KEY % (self.table, self.id)) | |
mc.delete(self.key % self.id) | |
mc.delete('%s:%s:%s:comment2' % (self.table, self.field(), self.id)) | |
for k, v in self.extra_comment_columns(): | |
mc.delete('%s:%s:%s:comment2' % (self.table, k, v)) | |
def _notification_props(self): | |
return {'kind': str(self.kind)} | |
def send_notification(self, user): | |
if self.author_id != user.id and self.can_recv_notification(): | |
add_notification(self.author_id, self.notification, self.id, | |
props=self._notification_props()) | |
else: | |
del_user_notification(self.author_id, self.notification, self.id) | |
def send_comment_mention_notification(self, comment): | |
mentioned_users = comment.mentioned_users() | |
comment_author_id = comment.author_id | |
# filter commentator self | |
mentioned_user_ids = [u.id for u in mentioned_users | |
if u and u.id != comment_author_id] | |
n = self.n_comments + 1 | |
start = n - (n % self.COMMENTS_PER_PAGE if n % | |
self.COMMENTS_PER_PAGE else self.COMMENTS_PER_PAGE) | |
for uid in mentioned_user_ids: | |
add_notification(uid, self.mention_notification_type, | |
comment.id, | |
props=self._reply_notification_props(start)) | |
return mentioned_user_ids | |
def can_recv_notification(self): | |
"""子类需要覆写以支持拒绝新回复提醒""" | |
return True | |
def discard_notification(self): | |
"""子类需覆写来存储新回复提醒状态""" | |
pass | |
def _reply_notification_props(self, start): | |
return {'start': str(start), 'target_id': self.id} | |
def add_comment(self, user, text, time=None, send_notify=True, ref_cid=None): | |
if not text or not text.strip(): | |
##回复不能为空 | |
return | |
# check whether ref_cid is valid | |
ref_comment = None | |
if ref_cid and self.p2p_reply_notification_type: | |
ref_comment = self.get_comment(ref_cid) | |
if not ref_comment or ref_comment.target_id != self.id: | |
ref_comment = None | |
if ref_comment: | |
sql = "insert into %s (%s,author_id,text, ref_cid) " % ( | |
self.comment_table(), self.field()) | |
cid = str(store.execute(sql + "values (%s,%s,%s,%s)", ( | |
self.id, user.id, text, ref_cid))) | |
else: | |
sql = "insert into %s (%s,author_id,text) " % ( | |
self.comment_table(), self.field()) | |
cid = str(store.execute(sql + "values (%s,%s,%s)", ( | |
self.id, user.id, text))) | |
if time: | |
store.execute("update "+self.comment_table()+" set time=%s" | |
" where id=%s", (time, cid)) | |
for k, v in self.extra_comment_columns(): | |
store.execute("update "+self.comment_table()+" set " | |
+ k+"=%s where id=%s", (v, cid)) | |
self.increase_comment_count() | |
store.commit() | |
# mc.delete('commented_%s:%s'%(self.table, user.id)) | |
mc.delete(_MC_COMMENT_KEY % (self.table, cid)) | |
self.n_comments += 1 | |
self._flush_comments() | |
comment = self.get_comment(cid) | |
is_shuo_virtualuser = int(user.id) > 900000000 | |
# 在转移comments时, 应该不发notification | |
if send_notify and not is_shuo_virtualuser: | |
## DONGXI PATCH: 这段是为了支持东西的@回复加的 | |
mentioned_uids = [] | |
if self.mention_notification_type: | |
mentioned_uids = self.send_comment_mention_notification(comment) | |
# when reply himself, don't send notification | |
# or when author receives a reply notification, don't send | |
# notification | |
if (ref_comment and ref_comment.author_id != user.id and | |
ref_comment.author_id not in mentioned_uids and | |
(self.notification and ref_comment.author_id != self.author_id)): | |
n = self.n_comments + 1 | |
start = n - (n % self.COMMENTS_PER_PAGE if n % | |
self.COMMENTS_PER_PAGE else self.COMMENTS_PER_PAGE) | |
add_notification(ref_comment.author_id, self.p2p_reply_notification_type, cid, | |
props=self._reply_notification_props(start)) | |
if self.reply_notify_type and user.kind == K_PEOPLE: | |
## DONGXI PATCH: 被@到的人不该收到新回复提醒 | |
ignore_user_ids = mentioned_uids[:] | |
if ref_comment: | |
ignore_user_ids.append(ref_comment.author_id) | |
ReplyNotify.add(self.reply_notify_type, self.id, | |
user.id if self.author_id != user.id else '', | |
ignore_user_ids=ignore_user_ids) | |
if self.notification and self.author_id not in mentioned_uids: | |
self.send_notification(user) | |
if self.bits and not is_shuo_virtualuser: | |
user.check_bits(self.bits) | |
if not is_shuo_virtualuser: | |
update_user_info(user.id, text) | |
send_comment_update(self.comment_table(), cid) | |
return comment | |
def increase_comment_count(self): | |
store.execute("update %s " % self.comment_count_table() | |
+ "set n_comments=n_comments+1 where id=%s", self.id) | |
def backup_comment(self, c, user, reason): | |
try: | |
store.execute( | |
"insert into deleted_comment (comment_id,author_id,object_kind,object_id,text,`time`,delete_user_id,reason,ref_cid) values (%s,%s,%s,%s,%s,%s,%s,%s,%s)", | |
(c.id, c.author_id, self.kind, self.id, c.text, c.creation_date, user.id, reason, c.ref_cid)) | |
store.commit() | |
self.clear_deleted_comment_cache(c.id) | |
except IntegrityError: | |
store.rollback() | |
def delete_comment(self, c, user, reason=""): | |
"delete a comment" | |
if not isinstance(c, Comment): | |
c = self.get_comment(c) | |
if not c: | |
return | |
if int(user.id) < 900000001: | |
self.backup_comment(c, user, reason) | |
try: | |
if store.execute("delete from %s " % self.comment_table() + "where id=%s", c.id): | |
self.decrease_comment_count() | |
store.commit() | |
self.n_comments -= 1 | |
mc.delete(_MC_COMMENT_KEY % (self.table, c.id)) | |
self._flush_comments() | |
if int(user.id) < 900000001: | |
self.widget_delete_comment_log(c, user, reason) | |
if user.id != c.author_id and user.id != self.author_id and self.can_admin_delete(user): | |
insert_admin_record( | |
self.__class__.__name__, user.id, str('douban'), | |
c.author().id, datetime.now(), 'comment', c.id, | |
c.creation_date, reason, ignore=False) | |
send_comment_update(self.comment_table(), c.id) | |
return True | |
except IntegrityError: | |
store.rollback() | |
@cache(_MC_DELETED_COMMENT_KEY % ('{self.table}', '{self.id}', '{comment_id}')) | |
def get_deleted_comment(self, comment_id): | |
rs = store.execute("select comment_id, author_id, object_id, text, unix_timestamp(`time`), ref_cid " | |
"from deleted_comment where object_kind=%s and comment_id=%s", | |
(self.kind, comment_id)) | |
return Comment(rs[0]) if rs else '' | |
def clear_deleted_comment_cache(self, comment_id): | |
mc.delete(_MC_DELETED_COMMENT_KEY % (self.table, self.id, comment_id)) | |
def decrease_comment_count(self): | |
try: | |
store.execute("update %s " % self.comment_count_table() + | |
"set n_comments=greatest(n_comments-1,0), rtime=rtime where id=%s", self.id) | |
except OperationalError: | |
store.execute("update %s " % self.comment_count_table() + | |
"set n_comments=greatest(n_comments-1,0) where id=%s", self.id) | |
store.commit() | |
# mc.delete('commented_%s:%s'%(self.table, c.author_id)) | |
def widget_delete_comment_log(self, c, user, reason): | |
from luzong.widgets import Widget | |
widget_getter = getattr(self, 'widget', None) or (hasattr( | |
self, 'album') and getattr(self.album, 'widget', None)) | |
if widget_getter: | |
if isinstance(widget_getter, Widget): | |
widget = widget_getter | |
else: | |
widget = widget_getter() | |
if isinstance(widget, Widget) and c.author() != user: | |
widget.admin_log('deletecomment', user, | |
extra={'path': self.path(), 'title': self.title, 'reason': reason}) | |
def recover_comment(self, id): | |
'''notice: 该方法不能使用,add_comment的时候会发提醒,需改造''' | |
rs = store.execute("select comment_id, author_id, object_kind, " | |
"object_id, text, `time`, delete_user_id, ref_cid " | |
"from deleted_comment where comment_id=%s", id) | |
if not rs: | |
return | |
(comment_id, author_id, object_kind, object_id, text, time, | |
delete_user_id, ref_cid) = rs[0] | |
if not (str(object_id) == self.id and object_kind == self.kind): | |
return | |
user = Account.get(str(author_id)) | |
self.add_comment(user, text, time, ref_cid=ref_cid) | |
store.commit() | |
@classmethod | |
#@cache("commented_{cls.table}:{user_id} | |
def get_commented_target_ids(cls, user_id, limit=15): | |
sql = "select distinct %s from %s" % (cls.field(), cls.comment_table()) | |
rs = store.execute( | |
sql+" where author_id=%s order by id desc limit %s", (user_id, limit)) | |
return [str(r[0]) for r in rs] | |
def remove_comments(self): | |
# sql = ("select author_id from "+self.comment_table()+" where "+self.field()+"=%s") | |
# uids = store.execute(sql, self.id) | |
rs = store.execute( | |
"select id from " + self.comment_table() + " where " + self.field() + "=" + self.id) | |
store.execute("delete from "+self.comment_table() | |
+ " where "+self.field()+"=%s", self.id) | |
store.execute("update "+self.comment_count_table() | |
+ " set n_comments=0 where id=%s", self.id) | |
store.commit() | |
self._flush_comments() | |
comment_ids = [str(comment_id) for comment_id, in rs] | |
send_comment_update(self.comment_table(), comment_ids) | |
for comment_id in comment_ids: | |
mc.delete(_MC_COMMENT_KEY % (self.table, comment_id)) | |
# for uid, in uids: | |
# mc.delete('commented_%s:%s'%(self.table, uid)) | |
return True | |
@classmethod | |
def get_user_comments(cls, user_id, limit=5, start=0): | |
sql = "select id, author_id, %s, text, unix_timestamp(`time`) from %s " \ | |
% (cls.field(), cls.comment_table()) | |
rs = store.execute(sql + "where author_id=%s order by id limit %s,%s", ( | |
user_id, start, limit)) | |
return [Comment(r) for r in rs] | |
@classmethod | |
def remove_user_comments(cls, user_id): | |
delta = store.execute("select "+cls.field()+", count(*) n " | |
"from " + | |
cls.comment_table()+" where author_id=%s " | |
"group by "+cls.field(), user_id) | |
# query ids of comments in order to clear cache | |
comment_ids = store.execute("select id from " + cls.comment_table() + | |
" where author_id=%s", user_id) | |
store.execute( | |
"delete from "+cls.comment_table()+" where author_id=%s", user_id) | |
for id, n in delta: | |
t = store.execute( | |
"select max(time) from "+cls.comment_table()+" where "+cls.field()+"=%s", id)[0][0] | |
try: | |
if t: | |
store.execute('update '+cls.comment_count_table()+' set n_comments=greatest(n_comments-%s,0), rtime=%s ' | |
'where id=%s', (n, t, id)) | |
else: | |
store.execute('update '+cls.comment_count_table()+' set n_comments=greatest(n_comments-%s,0), rtime=time ' | |
'where id=%s', (n, id)) | |
except OperationalError: | |
store.execute('update '+cls.comment_count_table() + | |
' set n_comments=greatest(n_comments-%s,0) where id=%s', (n, id)) | |
mc.delete('%s:%s:comment3' % (cls.table, id)) | |
mc.delete(cls.key % id) | |
# TODO: clear keyed cache | |
comment_ids = [str(comment_id) for comment_id, in comment_ids] | |
send_comment_update(cls.comment_table(), comment_ids) | |
# clear cache of single comment | |
for comment_id in comment_ids: | |
mc.delete(_MC_COMMENT_KEY % (cls.table, comment_id)) | |
store.commit() | |
# mc.delete('commented_%s:%s'%(cls.table,user_id)) | |
# policy {{{ | |
def can_add_comment(self, user): | |
if get_switch('add_'+self.table): | |
return False | |
if self.comment_limit == NONE_COMMENT: | |
return False | |
return user is not None | |
def can_delete_comment(self, user, comment): | |
return user and user.id == comment.author_id | |
def can_delete(self, user): | |
return user and user.id == self.author_id | |
def can_admin_delete(self, user): | |
return user and (user.is_admin() or is_intern_admin(user)) | |
def can_admin_delete_comment(self, user, comment): | |
return user and \ | |
(self.can_delete(user) or self.can_admin_delete(user) or is_duty_manager(user) or | |
is_book_manager(user) or is_movie_manager(user) or is_music_manager(user) or | |
is_community_manager(user) or is_safe_editor(user)) | |
# }}} | |
def _get_comment_limit(self): | |
return DEFAULT_COMMENT | |
def _set_comment_limit(self, limit): | |
raise NotImplementedError | |
comment_limit = property(_get_comment_limit, _set_comment_limit) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment