Skip to content

Instantly share code, notes, and snippets.

@gt11799
Forked from anonymous/comment.py
Last active August 29, 2015 14:27
Show Gist options
  • Save gt11799/6ba756e4c7be9d07d61a to your computer and use it in GitHub Desktop.
Save gt11799/6ba756e4c7be9d07d61a to your computer and use it in GitHub Desktop.
# 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