"""Specific views and actions for application using the Comment entity type :organization: Logilab :copyright: 2003-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved. :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr """ __docformat__ = "restructuredtext en" from itertools import count from logilab.mtconverter import html_escape from logilab.common.decorators import monkeypatch from simplejson import dumps from cubicweb.selectors import (one_line_rset, but_etype, implements, has_permission, relation_possible, yes) from cubicweb.view import EntityView from cubicweb.selectors import match_kwargs, accept from cubicweb.common.uilib import rql_for_eid, cut, safe_cut, ajax_replace_url from cubicweb.common.mixins import TreeViewMixIn from cubicweb.web import stdmsgs from cubicweb.web.action import (LinkToEntityAction, Action) from cubicweb.web.views import baseviews, baseforms from cubicweb.web.component import EntityVComponent from cubicweb.web.views.basecontrollers import JSonController _ = unicode # comment views ############################################################### class CommentPrimaryView(baseviews.PrimaryView): __select__ = implements('Comment') def cell_call(self, row, col): self.req.add_css('cubes.comment.css') entity = self.complete_entity(row, col) # display text, author and creation date self.w(u'<div class="comment">') self.w(u'<div class="commentInfo">') # do not try to display creator when you're not allowed to see EUsers if entity.creator: authorlink = entity.creator.view('oneline') self.w(u'%s %s\n' % (self.req._('written by'), authorlink)) self.w(self.format_date(entity.creation_date)) # commented object if entity.comments: self.w(u", %s " % self.req._('comments')) entity.comments[0].view('oneline', w=self.w) self.w(u"\n") # don't include responses in this view, since the comment section # component will display them self.w(u'</div>\n') self.w(u'<div class="commentBody">%s</div>\n' % entity.printable_value('content')) # XXX attribute generated_by added by email component if hasattr(self, 'generated_by') and self.generated_by: origlink = '<a href="%s">%s</a>' % (self.generated_by[0].absolute_url(), self.generated_by[0].dc_type().lower()) txt = self.req._('this comment has been generated from this %s') % origlink self.w(u'<div class="commentBottom">%s</div>\n' % txt) self.w(u'</div>\n') class CommentSecondaryView(baseviews.SecondaryView): __select__ = implements('Comment') def cell_call(self, row, col, **kwargs): entity = self.entity(row, col) root = entity.root() self.w(u'[<a href="%s">#%s</a>] ' % (html_escape(root.absolute_url()), root.eid)) maxsize = self.req.property_value('navigation.short-line-size') maxsize = maxsize - len(str(root.eid)) content = entity.printable_value('content', format='text/plain') content = html_escape(cut(content, maxsize)) self.w(u'<a href="%s">#%s <i>%s</i></a>\n' % (html_escape(entity.absolute_url()), entity.eid, content)) class CommentOneLineView(CommentSecondaryView): id = 'oneline' class CommentTreeItemView(baseviews.ListItemView): id = 'treeitem' __select__ = implements('Comment') def cell_call(self, row, col, **kwargs): _ = self.req._ self.req.add_js('cubicweb.ajax.js') self.req.add_css('cubes.comment.css') entity = self.entity(row, col) diem = self.format_date(entity.creation_date) action = self.vreg.select_action('reply_comment', self.req, self.rset, row=row) editaction = self.vreg.select_action('edit_comment', self.req, self.rset, row=row) if action is None: reply = u'' else: url = ajax_replace_url('comment%sHolder' % entity.eid, rql_for_eid(entity.eid), 'inlinecomment') reply = ' | <a href="%s">%s</a>' % (html_escape(url), _(action.title)) if editaction is None: edit = u'' else: url = ajax_replace_url('comment%s' % entity.eid, rql_for_eid(entity.eid), 'editcomment') edit = ' | <a href="%s">%s</a>' % (html_escape(url), _(editaction.title)) text = entity.printable_value('content') if not kwargs.get('full'): maxsize = self.req.property_value('navigation.short-line-size') text = safe_cut(text, maxsize) self.w(u'<div class="commentInfo">') self.w(self.format_date(entity.creation_date)) if entity.creator: authorlink = entity.creator.view('oneline') self.w(u', %s <span class="author">%s</span> \n' % (_('written by'), authorlink,)) self.w(u'<span class="replyto"> %s </span>' % reply) self.w(u'<span class="replyto"> %s </span>' % edit) self.w(u'</div>\n') self.w(u'<div class="commentBody">%s</div>\n' % text) self.w(u'<div id="comment%sHolder"></div>' % entity.eid) class CommentThreadView(TreeViewMixIn, baseviews.ListView): """a recursive tree view""" __select__ = implements('Comment') title = _('thread view') def open_item(self, entity): self.w(u'<li id="comment%s" class="comment">\n' % entity.eid) # comment edition views ####################################################### class InlineCommentView(EntityView): id = 'inlinecomment' __select__ = yes() # explicit call when it makes sense def cell_call(self, row, col): entity = self.entity(row, col) self.wview('inlinecommentform', None, commented=entity) class InlineCommentForm(baseforms.CreationForm): id = 'inlinecommentform' __select__ = match_kwargs('commented') # explicit call when it makes sense title = None # hidden EDITION_BODY = u""" <div id="comment%(eid)sSlot">%(attrform)s <div id="comment%(eid)sbbar"><button onclick="%(onclick)s" tabindex="%(tabindex1)s">%(validate)s</button> <button onclick="%(oncancelclick)s" tabindex="%(tabindex2)s">%(cancel)s</button> </div> </div> """ def call(self, commented): self.req.add_js('cubicweb.ajax.js','cubes.comment.js') newcomment = self.vreg.etype_class('Comment')(self.req, None, None) newcomment.eid = 'INLINE' # hack to avoid tabindex conflicts caused by Ajax requests self.req.next_tabindex = count(20).next self._hiddens = [] attrform = self.attributes_form(newcomment, {}) onclick = html_escape("processComment(%s, 'add_comment')" % dumps(commented.eid)) oncancelclick = html_escape("processComment(%s, '')" % dumps(commented.eid)) self.w(self.EDITION_BODY % dict(attrform=attrform, onclick=onclick, oncancelclick=oncancelclick, eid=commented.eid, tabindex1=self.req.next_tabindex(), tabindex2=self.req.next_tabindex(), validate=self.req._(stdmsgs.BUTTON_OK), cancel=self.req._(stdmsgs.BUTTON_CANCEL))) def editable_attributes(self, entity): return [(rschema, x) for rschema, _, x in entity.relations_by_category(('primary'), 'add') if rschema.is_final() and rschema != 'eid'] # don't show comments relation in edition forms try: baseforms.EditionForm.skip_relations.add('comments') except AttributeError: pass # XXX bw compat cubicweb < 2.49 class InlineEditCommentForm(baseforms.EditionForm): id = 'editcomment' __select__ = implements('Comment') EDITION_BODY = u""" <div id="comment%(eid)sSlot">%(attrform)s <div> <button onclick="%(onclick)s" tabindex="%(tabindex1)s">%(validate)s</button> <button onclick="%(oncancelclick)s" tabindex="%(tabindex2)s">%(cancel)s</button> </div> </div> """ def cell_call(self, row, col): self.req.add_js( ('cubicweb.ajax.js', 'cubes.comment.js') ) # hack to avoid tabindex conflicts caused by Ajax request self.req.next_tabindex = count(20).next comment = self.entity(row, col) self._hiddens = [] attrform = self.attributes_form(comment, {'tab_class': ''}) onclick = html_escape("processComment(%s, 'edit_comment')" % dumps(comment.eid)) oncancelclick = html_escape("processComment(%s, '')" % dumps(comment.eid)) self.w(self.EDITION_BODY % dict(attrform=attrform, onclick=onclick, oncancelclick=oncancelclick, eid=comment.eid, tabindex1=self.req.next_tabindex(), tabindex2=self.req.next_tabindex(), validate=self.req._(stdmsgs.BUTTON_OK), cancel=self.req._(stdmsgs.BUTTON_CANCEL))) def editable_attributes(self, entity): return [(rschema, x) for rschema, _, x in entity.relations_by_category(('primary'), 'add') if rschema.is_final() and rschema != 'eid'] # comment component ########################################################### class CommentSectionVComponent(EntityVComponent): """a component to display a <div> html section including comments related to an object """ id = 'commentsection' __select__ = EntityVComponent.__select__ & relation_possible('comments', 'object', 'Comment') context = 'navcontentbottom' def cell_call(self, row, col, view=None, orderby='diem'): req = self.req req.add_js( ('cubicweb.ajax.js', 'cubes.comment.js') ) eid = self.rset[row][col] self.w(u'<div id="%s" class="%s" cubicweb:rooteid="%s">' % ( self.div_id(), self.div_class(), eid)) action = self.vreg.select_action('reply_comment', req, self.rset, row=0) if action is not None: url = ajax_replace_url('comment%sHolder' % eid, rql_for_eid(eid), 'inlinecomment') reply = u' (<a href="%s">%s</a>)' % (url, req._(action.title)) if req.property_value('ui.fckeditor') and \ req.property_value('ui.default-text-format') == 'text/html': req.add_js('fckeditor.js') req.fckeditor_config() else: reply = u'' if orderby == 'author': rql = u'Any C,CD,CC,CCF,U,UL,US,UF ORDERBY UL WHERE C is Comment, '\ 'C comments X, C creation_date CD, C content CC, C content_format CCF, ' \ 'C created_by U?, U login UL, U firstname UF, U surname US, X eid %(x)s' else: # orderby == 'diem' rql = u'Any C,CD,CC,CCF,U,UL,US,UF ORDERBY CD WHERE C is Comment, '\ 'C comments X, C creation_date CD, C content CC, C content_format CCF, ' \ 'C created_by U?, U login UL, U firstname UF, U surname US, X eid %(x)s' rset = req.execute(rql, {'x': eid}, 'x') if rset.rowcount: self.w(u'<h4>%s</h4>%s' % (req._('Comment_plural'), reply)) self.w(u'<div id="comment%sHolder"></div>' % eid) self.w(u'<ul class="comment">') for i in xrange(rset.rowcount): self.wview('tree', rset, row=i, full=True) self.w(u'</ul>') elif reply: self.w(reply) self.w(u'<div id="comment%sHolder"></div>' % eid) self.w(u'</div>') baseviews.PRIMARY_SKIP_RELS.add('comments') # displayed by the above component # comment actions ############################################################# class ReplyCommentAction(LinkToEntityAction): id = 'reply_comment' __select__ = LinkToEntityAction.__select__ & implements('Comment') etype = 'Comment' rtype = 'comments' target = 'subject' title = _('reply to this comment') category = 'hidden' order = 111 def url(self): rset = self.rset comment = rset.get_entity(self.row or 0, self.col or 0) linkto = '%s:%s:%s' % (self.rtype, comment.eid, self.target) return self.build_url(vid='creation', etype=self.etype, __linkto=linkto, __redirectpath=comment.root().rest_path(), __redirectvid=self.req.form.get('vid', '')) class AddCommentAction(LinkToEntityAction): """add comment is like reply for everything but Comment""" id = 'reply_comment' __select__ = LinkToEntityAction.__select__ & but_etype('Comment') etype = 'Comment' rtype = 'comments' role = 'object' title = _('add comment') category = 'hidden' order = 111 class EditCommentAction(Action): id = 'edit_comment' __select__ = one_line_rset() & implements('Comment') & has_permission('update') title = _('edit comment') category = 'hidden' order = 110 def url(self): return self.build_url(rql=self.rset.printable_rql(), vid='edition') # add some comments related methods to the Jsoncontroller ##################### @monkeypatch(JSonController) def js_add_comment(self, commented, text, format): rql = 'INSERT Comment C: C content %(text)s, C content_format %(format)s, C comments X WHERE X eid %(x)s' rset = self.req.execute(rql, {'format' : format, 'text' : text, 'x' : commented}, 'x') return u'' @monkeypatch(JSonController) def js_edit_comment(self, comment, text, format): rql = 'SET C content %(text)s, C content_format %(format)s WHERE C eid %(x)s' rset = self.req.execute(rql, {'format' : format, 'text' : text, 'x' : comment}, 'x') return u''