diff --git a/vj4/handler/discussion.py b/vj4/handler/discussion.py index a76ac24..4bec0ec 100755 --- a/vj4/handler/discussion.py +++ b/vj4/handler/discussion.py @@ -9,6 +9,7 @@ from vj4.model import domain from vj4.model import oplog from vj4.model import user +from vj4.model import system from vj4.model.adaptor import discussion from vj4.model.adaptor import contest from vj4.handler import base @@ -24,19 +25,93 @@ def node_url(handler, name, node_or_dtuple): return handler.reverse_url(name, **kwargs) +class DiscussionMixin(object): + def __init__(self, domain_id, remote_ip, user, own, check_perm): + self.domain_id = domain_id + self.remote_ip = remote_ip + self.user = user + self.own = own + self.check_perm = check_perm + + async def post_reply(self, did: document.convert_doc_id, content: str): + ddoc = await discussion.get(self.domain_id, did) + await discussion.add_reply(self.domain_id, ddoc['doc_id'], self.user['_id'], content, + self.remote_ip) + + async def post_tail_reply(self, + did: document.convert_doc_id, + drid: document.convert_doc_id, + content: str): + ddoc = await discussion.get(self.domain_id, did) + drdoc = await discussion.get_reply(self.domain_id, drid, ddoc['doc_id']) + await discussion.add_tail_reply(self.domain_id, drdoc['doc_id'], self.user['_id'], content, + self.remote_ip) + + async def post_edit_reply(self, did: document.convert_doc_id, + drid: document.convert_doc_id, content: str): + ddoc = await discussion.get(self.domain_id, did) + drdoc = await discussion.get_reply(self.domain_id, drid, ddoc['doc_id']) + if (not self.own(ddoc, builtin.PERM_EDIT_DISCUSSION_REPLY_SELF_DISCUSSION) + and not self.own(drdoc, builtin.PERM_EDIT_DISCUSSION_REPLY_SELF)): + self.check_perm(builtin.PERM_EDIT_DISCUSSION_REPLY) + drdoc = await discussion.edit_reply(self.domain_id, drdoc['doc_id'], + content=content) + + async def post_delete_reply(self, did: document.convert_doc_id, + drid: document.convert_doc_id): + ddoc = await discussion.get(self.domain_id, did) + drdoc = await discussion.get_reply(self.domain_id, drid, ddoc['doc_id']) + if (not self.own(ddoc, builtin.PERM_DELETE_DISCUSSION_REPLY_SELF_DISCUSSION) + and not self.own(drdoc, builtin.PERM_DELETE_DISCUSSION_REPLY_SELF)): + self.check_perm(builtin.PERM_DELETE_DISCUSSION_REPLY) + await oplog.add(self.user['_id'], oplog.TYPE_DELETE_DOCUMENT, doc=drdoc) + drdoc = await discussion.delete_reply(self.domain_id, drdoc['doc_id']) + + async def post_edit_tail_reply(self, did: document.convert_doc_id, + drid: document.convert_doc_id, drrid: document.convert_doc_id, + content: str): + ddoc = await discussion.get(self.domain_id, did) + drdoc, drrdoc = await discussion.get_tail_reply(self.domain_id, drid, drrid) + if not drdoc or drdoc['parent_doc_id'] != ddoc['doc_id']: + raise error.DocumentNotFoundError(domain_id, document.TYPE_DISCUSSION_REPLY, drid) + if (not self.own(ddoc, builtin.PERM_EDIT_DISCUSSION_REPLY_SELF_DISCUSSION) + and not self.own(drrdoc, builtin.PERM_EDIT_DISCUSSION_REPLY_SELF)): + self.check_perm(builtin.PERM_EDIT_DISCUSSION_REPLY) + await discussion.edit_tail_reply(self.domain_id, drid, drrid, content) + + async def post_delete_tail_reply(self, did: document.convert_doc_id, + drid: document.convert_doc_id, drrid: objectid.ObjectId): + ddoc = await discussion.get(self.domain_id, did) + drdoc, drrdoc = await discussion.get_tail_reply(self.domain_id, drid, drrid) + if not drdoc or drdoc['parent_doc_id'] != ddoc['doc_id']: + raise error.DocumentNotFoundError(domain_id, document.TYPE_DISCUSSION_REPLY, drid) + if (not self.own(ddoc, builtin.PERM_DELETE_DISCUSSION_REPLY_SELF_DISCUSSION) + and not self.own(drrdoc, builtin.PERM_DELETE_DISCUSSION_REPLY_SELF)): + self.check_perm(builtin.PERM_DELETE_DISCUSSION_REPLY) + await oplog.add(self.user['_id'], oplog.TYPE_DELETE_SUB_DOCUMENT, sub_doc=drrdoc, + doc_type=drdoc['doc_type'], doc_id=drdoc['doc_id']) + await discussion.delete_tail_reply(self.domain_id, drid, drrid) + + @app.route('/discuss', 'discussion_main') class DiscussionMainHandler(base.Handler): - DISCUSSIONS_PER_PAGE = 15 + DISCUSSIONS_PER_PAGE = 20 @base.require_perm(builtin.PERM_VIEW_DISCUSSION) @base.get_argument @base.sanitize async def get(self, *, page: int=1): + benbenid = None + for dom in builtin.DOMAINS: + if dom['_id'] == self.domain_id: + benbenid = await system.get_benbenid() + break + # TODO(iceboy): continuation based pagination. nodes, (ddocs, dpcount, _) = await asyncio.gather( discussion.get_nodes(self.domain_id), # TODO(twd2): exclude problem/contest discussions? - pagination.paginate(discussion.get_multi(self.domain_id), page, self.DISCUSSIONS_PER_PAGE)) + pagination.paginate(discussion.get_multi(self.domain_id, doc_id={"$ne":objectid.ObjectId(benbenid)}), page, self.DISCUSSIONS_PER_PAGE)) udict, dudict, vndict = await asyncio.gather( user.get_dict(ddoc['owner_uid'] for ddoc in ddocs), domain.get_dict_user_by_uid(domain_id=self.domain_id, uids=(ddoc['owner_uid'] for ddoc in ddocs)), @@ -185,9 +260,8 @@ async def get(self, *, did: document.convert_doc_id, page: int=1): @base.sanitize @base.limit_rate('add_discussion', 3600, 30) async def post_reply(self, *, did: document.convert_doc_id, content: str): - ddoc = await discussion.get(self.domain_id, did) - await discussion.add_reply(self.domain_id, ddoc['doc_id'], self.user['_id'], content, - self.remote_ip) + discussionMixin = DiscussionMixin(self.domain_id, self.remote_ip, self.user, self.own, self.check_perm) + await discussionMixin.post_reply(did, content) self.json_or_redirect(self.url) @base.require_priv(builtin.PRIV_USER_PROFILE) @@ -200,10 +274,8 @@ async def post_tail_reply(self, *, did: document.convert_doc_id, drid: document.convert_doc_id, content: str): - ddoc = await discussion.get(self.domain_id, did) - drdoc = await discussion.get_reply(self.domain_id, drid, ddoc['doc_id']) - await discussion.add_tail_reply(self.domain_id, drdoc['doc_id'], self.user['_id'], content, - self.remote_ip) + discussionMixin = DiscussionMixin(self.domain_id, self.remote_ip, self.user, self.own, self.check_perm) + await discussionMixin.post_tail_reply(did, drid, content) self.json_or_redirect(self.url) @base.require_priv(builtin.PRIV_USER_PROFILE) @@ -212,13 +284,8 @@ async def post_tail_reply(self, *, @base.sanitize async def post_edit_reply(self, *, did: document.convert_doc_id, drid: document.convert_doc_id, content: str): - ddoc = await discussion.get(self.domain_id, did) - drdoc = await discussion.get_reply(self.domain_id, drid, ddoc['doc_id']) - if (not self.own(ddoc, builtin.PERM_EDIT_DISCUSSION_REPLY_SELF_DISCUSSION) - and not self.own(drdoc, builtin.PERM_EDIT_DISCUSSION_REPLY_SELF)): - self.check_perm(builtin.PERM_EDIT_DISCUSSION_REPLY) - drdoc = await discussion.edit_reply(self.domain_id, drdoc['doc_id'], - content=content) + discussionMixin = DiscussionMixin(self.domain_id, self.remote_ip, self.user, self.own, self.check_perm) + await discussionMixin.post_edit_reply(did, drid, content) self.json_or_redirect(self.url) @base.require_priv(builtin.PRIV_USER_PROFILE) @@ -227,13 +294,8 @@ async def post_edit_reply(self, *, did: document.convert_doc_id, @base.sanitize async def post_delete_reply(self, *, did: document.convert_doc_id, drid: document.convert_doc_id): - ddoc = await discussion.get(self.domain_id, did) - drdoc = await discussion.get_reply(self.domain_id, drid, ddoc['doc_id']) - if (not self.own(ddoc, builtin.PERM_DELETE_DISCUSSION_REPLY_SELF_DISCUSSION) - and not self.own(drdoc, builtin.PERM_DELETE_DISCUSSION_REPLY_SELF)): - self.check_perm(builtin.PERM_DELETE_DISCUSSION_REPLY) - await oplog.add(self.user['_id'], oplog.TYPE_DELETE_DOCUMENT, doc=drdoc) - drdoc = await discussion.delete_reply(self.domain_id, drdoc['doc_id']) + discussionMixin = DiscussionMixin(self.domain_id, self.remote_ip, self.user, self.own, self.check_perm) + await discussionMixin.post_delete_reply(did, drid) self.json_or_redirect(self.url) @base.require_priv(builtin.PRIV_USER_PROFILE) @@ -243,14 +305,8 @@ async def post_delete_reply(self, *, did: document.convert_doc_id, async def post_edit_tail_reply(self, *, did: document.convert_doc_id, drid: document.convert_doc_id, drrid: document.convert_doc_id, content: str): - ddoc = await discussion.get(self.domain_id, did) - drdoc, drrdoc = await discussion.get_tail_reply(self.domain_id, drid, drrid) - if not drdoc or drdoc['parent_doc_id'] != ddoc['doc_id']: - raise error.DocumentNotFoundError(domain_id, document.TYPE_DISCUSSION_REPLY, drid) - if (not self.own(ddoc, builtin.PERM_EDIT_DISCUSSION_REPLY_SELF_DISCUSSION) - and not self.own(drrdoc, builtin.PERM_EDIT_DISCUSSION_REPLY_SELF)): - self.check_perm(builtin.PERM_EDIT_DISCUSSION_REPLY) - await discussion.edit_tail_reply(self.domain_id, drid, drrid, content) + discussionMixin = DiscussionMixin(self.domain_id, self.remote_ip, self.user, self.own, self.check_perm) + await discussionMixin.post_edit_tail_reply(did, drid, drrid, content) self.json_or_redirect(self.url) @base.require_priv(builtin.PRIV_USER_PROFILE) @@ -259,16 +315,8 @@ async def post_edit_tail_reply(self, *, did: document.convert_doc_id, @base.sanitize async def post_delete_tail_reply(self, *, did: document.convert_doc_id, drid: document.convert_doc_id, drrid: objectid.ObjectId): - ddoc = await discussion.get(self.domain_id, did) - drdoc, drrdoc = await discussion.get_tail_reply(self.domain_id, drid, drrid) - if not drdoc or drdoc['parent_doc_id'] != ddoc['doc_id']: - raise error.DocumentNotFoundError(domain_id, document.TYPE_DISCUSSION_REPLY, drid) - if (not self.own(ddoc, builtin.PERM_DELETE_DISCUSSION_REPLY_SELF_DISCUSSION) - and not self.own(drrdoc, builtin.PERM_DELETE_DISCUSSION_REPLY_SELF)): - self.check_perm(builtin.PERM_DELETE_DISCUSSION_REPLY) - await oplog.add(self.user['_id'], oplog.TYPE_DELETE_SUB_DOCUMENT, sub_doc=drrdoc, - doc_type=drdoc['doc_type'], doc_id=drdoc['doc_id']) - await discussion.delete_tail_reply(self.domain_id, drid, drrid) + discussionMixin = DiscussionMixin(self.domain_id, self.remote_ip, self.user, self.own, self.check_perm) + await discussionMixin.post_delete_tail_reply(did, drid, drrid) self.json_or_redirect(self.url) @base.require_priv(builtin.PRIV_USER_PROFILE) diff --git a/vj4/handler/domain.py b/vj4/handler/domain.py index a20bbd2..c1bb5b3 100755 --- a/vj4/handler/domain.py +++ b/vj4/handler/domain.py @@ -3,6 +3,7 @@ import datetime import functools import random +from bson import objectid from vj4 import app from vj4 import constant @@ -12,11 +13,13 @@ from vj4.model import document from vj4.model import domain from vj4.model import user +from vj4.model import system from vj4.model.adaptor import discussion from vj4.model.adaptor import contest from vj4.model.adaptor import training from vj4.handler import base from vj4.handler import training as training_handler +from vj4.handler import discussion as discussion_handler from vj4.util import validator from vj4.util import misc from vj4.util import options @@ -29,12 +32,13 @@ async def get(self): self.render('bsod.html', args=self.request.headers) @app.route('/', 'domain_main') -class DomainMainHandler(contest.ContestStatusMixin, base.Handler): +class DomainMainHandler(contest.ContestStatusMixin, base.OperationHandler): CONTESTS_ON_MAIN = 5 HOMEWORK_ON_MAIN = 5 TRAININGS_ON_MAIN = 5 - DISCUSSIONS_ON_MAIN = 20 + DISCUSSIONS_ON_MAIN = 12 USERS_PER_PAGE = 10 + REPLIES_PER_PAGE = 30 async def prepare_contest(self): if self.has_perm(builtin.PERM_VIEW_CONTEST): @@ -75,9 +79,9 @@ async def prepare_training(self): tsdict = {} return tdocs, tsdict - async def prepare_discussion(self): + async def prepare_discussion(self, benbenid=None): if self.has_perm(builtin.PERM_VIEW_DISCUSSION): - ddocs = await discussion.get_multi(self.domain_id) \ + ddocs = await discussion.get_multi(self.domain_id, doc_id={"$ne":objectid.ObjectId(benbenid)}) \ .limit(self.DISCUSSIONS_ON_MAIN) \ .to_list() vndict = await discussion.get_dict_vnodes(self.domain_id, map(discussion.node_id, ddocs)) @@ -86,11 +90,17 @@ async def prepare_discussion(self): vndict = {} return ddocs, vndict - async def get(self): + async def get(self, page: int=1): + benbenid = None + for dom in builtin.DOMAINS: + if dom['_id'] == self.domain_id: + benbenid = await system.get_benbenid() + break + (tdocs, tsdict), (htdocs, htsdict),\ (trdocs, trsdict), (ddocs, vndict) = await asyncio.gather( self.prepare_contest(), self.prepare_homework(), - self.prepare_training(), self.prepare_discussion()) + self.prepare_training(), self.prepare_discussion(benbenid)) dudocs, _, _ = await pagination.paginate( domain.get_multi_user(domain_id=self.domain_id, rp={'$gt': 0.0}).sort([('rank', 1)]), @@ -105,6 +115,7 @@ async def get(self): spdocs = await domain.get_suggest_problem(self.domain_id) sdocs = await domain.get_swiper(self.domain_id) + # 处理运势 if self.has_priv(builtin.PRIV_USER_PROFILE): rnd = random.Random() rnd.seed(int(datetime.date.today().strftime("%y%m%d")) + int(self.user["_id"])) @@ -114,17 +125,105 @@ async def get(self): lucknum = "未登录" wdudoc = {} + drdocs, udictd, pcount, drcount = None, None, None, None + if benbenid: + did = benbenid + ddoc = await discussion.get(self.domain_id, did) + vnode, (drdocs, pcount, drcount) = await asyncio.gather( + discussion.get_vnode(self.domain_id, discussion.node_id(ddoc)), + pagination.paginate(discussion.get_multi_reply(self.domain_id, ddoc['doc_id']), + page, self.REPLIES_PER_PAGE)) + if not vnode: + vnode = builtin.VNODE_MISSING + elif vnode['doc_type'] == document.TYPE_PROBLEM and vnode.get('hidden', False): + self.check_perm(builtin.PERM_VIEW_PROBLEM_HIDDEN) + # TODO(twd2): do more visibility check eg. contest + uids = {ddoc['owner_uid']} + uids.update(drdoc['owner_uid'] for drdoc in drdocs) + for drdoc in drdocs: + if 'reply' in drdoc: + uids.update(drrdoc['owner_uid'] for drrdoc in drdoc['reply']) + if 'owner_uid' in vnode: + uids.add(vnode['owner_uid']) + udictd = await user.get_dict(uids) + self.render('domain_main.html', discussion_nodes=await discussion.get_nodes(self.domain_id), tdocs=tdocs, tsdict=tsdict, htdocs=htdocs, htsdict=htsdict, trdocs=trdocs, trsdict=trsdict, training=training_handler.TrainingMixin(), - ddocs=ddocs, vndict=vndict, + ddocs=ddocs, vndict=vndict, discussions_per_page=self.DISCUSSIONS_ON_MAIN, udict=udict, dudict=dudict, datetime_stamp=self.datetime_stamp, blessing=builtin.BLESSING, blessing_content=builtin.BLESSING_CONTENT, lucknum=lucknum, wdudoc=wdudoc, dudocs=dudocs, udoc=self.user, users_per_page=self.USERS_PER_PAGE, rudict=rudict, rdudict=rdudict, - spdocs=spdocs, sdocs=sdocs) + spdocs=spdocs, sdocs=sdocs, + benbenid=benbenid, drdocs=drdocs, udictd=udictd, pcount=pcount, drcount=drcount) + + @base.require_priv(builtin.PRIV_USER_PROFILE) + @base.require_perm(builtin.PERM_REPLY_DISCUSSION) + @base.require_csrf_token + @base.sanitize + @base.limit_rate('add_discussion', 3600, 30) + async def post_reply(self, *, content: str): + did = await system.get_benbenid() + discussionMixin = discussion_handler.DiscussionMixin(self.domain_id, self.remote_ip, self.user, self.own, self.check_perm) + await discussionMixin.post_reply(did, content) + self.json_or_redirect(self.url) + + @base.require_priv(builtin.PRIV_USER_PROFILE) + @base.require_perm(builtin.PERM_REPLY_DISCUSSION) + @base.require_csrf_token + @base.sanitize + @base.limit_rate('add_discussion', 3600, 30) + async def post_tail_reply(self, *, + drid: document.convert_doc_id, + content: str): + did = await system.get_benbenid() + discussionMixin = discussion_handler.DiscussionMixin(self.domain_id, self.remote_ip, self.user, self.own, self.check_perm) + await discussionMixin.post_tail_reply(did, drid, content) + self.json_or_redirect(self.url) + + @base.require_priv(builtin.PRIV_USER_PROFILE) + @base.require_csrf_token + @base.sanitize + async def post_edit_reply(self, *, + drid: document.convert_doc_id, content: str): + did = await system.get_benbenid() + discussionMixin = discussion_handler.DiscussionMixin(self.domain_id, self.remote_ip, self.user, self.own, self.check_perm) + await discussionMixin.post_edit_reply(did, drid, content) + self.json_or_redirect(self.url) + + @base.require_priv(builtin.PRIV_USER_PROFILE) + @base.require_csrf_token + @base.sanitize + async def post_delete_reply(self, *, + drid: document.convert_doc_id): + did = await system.get_benbenid() + discussionMixin = discussion_handler.DiscussionMixin(self.domain_id, self.remote_ip, self.user, self.own, self.check_perm) + await discussionMixin.post_delete_reply(did, drid) + self.json_or_redirect(self.url) + + @base.require_priv(builtin.PRIV_USER_PROFILE) + @base.require_csrf_token + @base.sanitize + async def post_edit_tail_reply(self, *, + drid: document.convert_doc_id, drrid: document.convert_doc_id, + content: str): + did = await system.get_benbenid() + discussionMixin = discussion_handler.DiscussionMixin(self.domain_id, self.remote_ip, self.user, self.own, self.check_perm) + await discussionMixin.post_edit_tail_reply(did, drid, drrid, content) + self.json_or_redirect(self.url) + + @base.require_priv(builtin.PRIV_USER_PROFILE) + @base.require_csrf_token + @base.sanitize + async def post_delete_tail_reply(self, *, + drid: document.convert_doc_id, drrid: objectid.ObjectId): + did = await system.get_benbenid() + discussionMixin = discussion_handler.DiscussionMixin(self.domain_id, self.remote_ip, self.user, self.own, self.check_perm) + await discussionMixin.post_delete_tail_reply(did, drid, drrid) + self.json_or_redirect(self.url) @app.route('/domain', 'domain_manage') diff --git a/vj4/handler/user.py b/vj4/handler/user.py index 0a46b2a..9deab27 100755 --- a/vj4/handler/user.py +++ b/vj4/handler/user.py @@ -1,5 +1,6 @@ import asyncio import datetime +from bson import objectid from vj4 import app from vj4 import constant @@ -210,7 +211,15 @@ async def get(self, *, uid: int): psdocs_hot = await psdocs_hot.sort([('vote', -1), ('doc_id', -1)]).limit(10).to_list() if self.has_perm(builtin.PERM_VIEW_DISCUSSION): - ddocs = discussion.get_multi(self.domain_id, owner_uid=uid) + benbenid = None + ddocs = None + for dom in builtin.DOMAINS: + if dom['_id'] == self.domain_id: + benbenid = await system.get_benbenid() + ddocs = discussion.get_multi(self.domain_id, owner_uid=uid, doc_id={"$ne":objectid.ObjectId(benbenid)}) + break + else: + ddocs = discussion.get_multi(self.domain_id, owner_uid=uid) dcount = await ddocs.count() ddocs = await ddocs.limit(10).to_list() vndict = await discussion.get_dict_vnodes(self.domain_id, map(discussion.node_id, ddocs)) diff --git a/vj4/locale/zh_CN.yaml b/vj4/locale/zh_CN.yaml index 1ab3613..dd7d92a 100755 --- a/vj4/locale/zh_CN.yaml +++ b/vj4/locale/zh_CN.yaml @@ -726,6 +726,7 @@ Profile Background Image: 背景图片 Choose the background image in your profile page.: 选择您资料页面的背景图片。 '{} is a moderator of this domain.': '{}是这个域的管理者之一。' 'Top {} users': '前{}用户' +'Only show the first {} items': '仅展示前{}条' He: 他 She: 她 Bulletin: 公告 diff --git a/vj4/model/system.py b/vj4/model/system.py index 26d8bc6..7349b2a 100755 --- a/vj4/model/system.py +++ b/vj4/model/system.py @@ -72,6 +72,21 @@ async def get_footer(): return doc['value'] +@argmethod.wrap +async def set_benbenid(benbenid): + coll = db.coll("system") + doc = await coll.find_one_and_update(filter={'_id': 'benbenid'}, + update={'$set': {'value': benbenid}}) + return doc['value'] + + +@argmethod.wrap +async def get_benbenid(): + coll = db.coll("system") + doc = await coll.find_one({"_id":"benbenid"}) + return doc['value'] + + @argmethod.wrap async def set_additional_css(additional_css): coll = db.coll("system") @@ -241,6 +256,8 @@ async def ensure_indexes(): upsert=True) await coll.update_one(filter={'_id': 'suggest_problem'}, update={'$setOnInsert': {'value': []}}, upsert=True) + await coll.update_one(filter={'_id': 'benbenid'}, + update={'$setOnInsert': {'value': None}}, upsert=True) await coll.update_one(filter={'_id': 'swiper'}, update={'$setOnInsert': {'value': []}}, upsert=True) await coll.update_one(filter={'_id': 'bulletin'}, diff --git a/vj4/ui/templates/domain_main.html b/vj4/ui/templates/domain_main.html index 303bc57..7088777 100755 --- a/vj4/ui/templates/domain_main.html +++ b/vj4/ui/templates/domain_main.html @@ -220,11 +220,46 @@

-

{{ _('Discussion') }}

+

+ {{ _('Discussion') }} + {{ _('Only show the first {} items').format(discussions_per_page) }} +

{% include "partials/discussion_list.html" %} {% endif %} + {% if benbenid %}{% import "components/comments.html" as comments with context %} +
+
+

{{ _('犇犇') }}

+
+
+
+ {% endif %}
{% include 'domain_sidebar.html' %}