Skip to content
Snippets Groups Projects
views.py 12.3 KiB
Newer Older
Nicolas Chauvat's avatar
Nicolas Chauvat committed
"""Specific views and actions for application using the Comment entity type

:organization: Logilab
sylvain thenault's avatar
sylvain thenault committed
:copyright: 2003-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
Nicolas Chauvat's avatar
Nicolas Chauvat committed
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"

from itertools import count

from logilab.mtconverter import html_escape
sylvain thenault's avatar
sylvain thenault committed
from logilab.common.decorators import monkeypatch
Nicolas Chauvat's avatar
Nicolas Chauvat committed

from simplejson import dumps

sylvain thenault's avatar
sylvain thenault committed
from cubicweb.selectors import (one_line_rset, but_etype, implements,
sylvain thenault's avatar
sylvain thenault committed
                                has_permission, relation_possible, yes,
                                match_kwargs)
sylvain thenault's avatar
sylvain thenault committed
from cubicweb.view import EntityView
Nicolas Chauvat's avatar
Nicolas Chauvat committed
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, uicfg
from cubicweb.web.action import LinkToEntityAction, Action
from cubicweb.web.form import FormViewMixIn
sylvain thenault's avatar
sylvain thenault committed
from cubicweb.web.formwidgets import Button
Nicolas Chauvat's avatar
Nicolas Chauvat committed
from cubicweb.web.views import baseviews
sylvain thenault's avatar
sylvain thenault committed
from cubicweb.web.component import EntityVComponent
from cubicweb.web.views.basecontrollers import JSonController
Nicolas Chauvat's avatar
Nicolas Chauvat committed

_ = unicode

sylvain thenault's avatar
sylvain thenault committed
uicfg.rcategories.tag_relation('generated', ('*', 'comments', '*'), 'subject')
uicfg.rcategories.tag_relation('generated', ('*', 'comments', '*'), 'object')
uicfg.rmode.tag_relation('link', ('*', 'comments', '*'), 'subject')
uicfg.rmode.tag_relation('link', ('*', 'comments', '*'), 'object')
Nicolas Chauvat's avatar
Nicolas Chauvat committed
# comment views ###############################################################

class CommentPrimaryView(baseviews.PrimaryView):
sylvain thenault's avatar
sylvain thenault committed
    __select__ = implements('Comment')
Nicolas Chauvat's avatar
Nicolas Chauvat committed
    def cell_call(self, row, col):
        self.req.add_css('cubes.comment.css')
Nicolas Chauvat's avatar
Nicolas Chauvat committed
        entity = self.complete_entity(row, col)
        # display text, author and creation date
        self.w(u'<div class="comment">')
        self.w(u'<div class="commentInfo">')
sylvain thenault's avatar
sylvain thenault committed
        # do not try to display creator when you're not allowed to see CWUsers
Nicolas Chauvat's avatar
Nicolas Chauvat committed
        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')
sylvain thenault's avatar
sylvain thenault committed
        self.w(u'<div class="commentBody">%s</div>\n'
               % entity.printable_value('content'))
Nicolas Chauvat's avatar
Nicolas Chauvat committed
        # XXX attribute generated_by added by email component
        if hasattr(self, 'generated_by') and self.generated_by:
sylvain thenault's avatar
sylvain thenault committed
            gen = self.generated_by[0]
            link = '<a href="%s">%s</a>' % (gen.absolute_url(),
                                            gen.dc_type().lower())
            txt = self.req._('this comment has been generated from this %s') % link
Nicolas Chauvat's avatar
Nicolas Chauvat committed
            self.w(u'<div class="commentBottom">%s</div>\n' % txt)
        self.w(u'</div>\n')

Nicolas Chauvat's avatar
Nicolas Chauvat committed

class CommentSecondaryView(baseviews.SecondaryView):
sylvain thenault's avatar
sylvain thenault committed
    __select__ = implements('Comment')
Nicolas Chauvat's avatar
Nicolas Chauvat committed
    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))
sylvain thenault's avatar
sylvain thenault committed
        self.w(u'<a href="%s">#%s <i>%s</i></a>\n' % (
            html_escape(entity.absolute_url()), entity.eid, content))
Nicolas Chauvat's avatar
Nicolas Chauvat committed


class CommentOneLineView(CommentSecondaryView):
    id = 'oneline'


class CommentTreeItemView(baseviews.ListItemView):
    id = 'treeitem'
sylvain thenault's avatar
sylvain thenault committed
    __select__ = implements('Comment')
Nicolas Chauvat's avatar
Nicolas Chauvat committed

    def cell_call(self, row, col, **kwargs):
        _ = self.req._
        self.req.add_js('cubicweb.ajax.js')
        self.req.add_css('cubes.comment.css')
Nicolas Chauvat's avatar
Nicolas Chauvat committed
        entity = self.entity(row, col)
sylvain thenault's avatar
sylvain thenault committed
        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)
Nicolas Chauvat's avatar
Nicolas Chauvat committed
        if action is None:
            reply = u''
        else:
            url = ajax_replace_url('comment%sHolder' % entity.eid,
                                   rql_for_eid(entity.eid), 'inlinecomment')
sylvain thenault's avatar
sylvain thenault committed
            reply = ' | <a href="%s">%s</a>' % (html_escape(url),
                                                _(action.title))
Nicolas Chauvat's avatar
Nicolas Chauvat committed
        if editaction is None:
            edit = u''
        else:
            url = ajax_replace_url('comment%s' % entity.eid,
                                   rql_for_eid(entity.eid), 'editcomment')
sylvain thenault's avatar
sylvain thenault committed
            edit = ' | <a href="%s">%s</a>' % (html_escape(url),
                                               _(editaction.title))
Nicolas Chauvat's avatar
Nicolas Chauvat committed
        text = entity.printable_value('content')
        if not kwargs.get('full'):
            maxsize = self.req.property_value('navigation.short-line-size')
            text = safe_cut(text, maxsize)
Nicolas Chauvat's avatar
Nicolas Chauvat committed
        self.w(u'<div class="commentInfo">')
        self.w(self.format_date(entity.creation_date))
        if entity.creator:
            authorlink = entity.creator.view('oneline')
sylvain thenault's avatar
sylvain thenault committed
            self.w(u', %s <span class="author">%s</span> \n' % (_('written by'),
                                                                authorlink,))
Nicolas Chauvat's avatar
Nicolas Chauvat committed
        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"""
sylvain thenault's avatar
sylvain thenault committed
    __select__ = implements('Comment')
Nicolas Chauvat's avatar
Nicolas Chauvat committed
    title = _('thread view')
Nicolas Chauvat's avatar
Nicolas Chauvat committed
    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'
sylvain thenault's avatar
sylvain thenault committed
    __select__ = yes() # explicit call when it makes sense
Nicolas Chauvat's avatar
Nicolas Chauvat committed

    def cell_call(self, row, col):
        entity = self.entity(row, col)
        self.wview('inlinecommentform', None, commented=entity)
class InlineCommentForm(FormViewMixIn, EntityView):
Nicolas Chauvat's avatar
Nicolas Chauvat committed
    id = 'inlinecommentform'
sylvain thenault's avatar
sylvain thenault committed
    __select__ = match_kwargs('commented') # explicit call when it makes sense
sylvain thenault's avatar
sylvain thenault committed
    jsfunc = "processComment(%s, '%s')"
Nicolas Chauvat's avatar
Nicolas Chauvat committed
    def call(self, commented):
        self.initialize_varmaker()
        newcomment = self.vreg.etype_class('Comment')(self.req)
        newcomment.eid = self.varmaker.next()
        self.comment_form(commented, newcomment)
    def comment_form(self, commented, newcomment=None):
        self.req.add_js('cubes.comment.js')
        if newcomment is None:
sylvain thenault's avatar
sylvain thenault committed
            newcomment = commented
Nicolas Chauvat's avatar
Nicolas Chauvat committed
        # hack to avoid tabindex conflicts caused by Ajax requests
        self.req.next_tabindex = count(20).next
        jseid = dumps(commented.eid)
sylvain thenault's avatar
sylvain thenault committed
        buttons = [Button(onclick=self.jsfunc % (jseid, 'add_comment')),
                   Button(stdmsgs.BUTTON_CANCEL,
                          onclick=self.jsfunc % (jseid, ''))]
        form = self.vreg.select_object('forms', 'edition', self.req,
                                       entity=newcomment,
                                       form_buttons=buttons,
                                       attrcategories=('primary',))
        self.w(u'<div id="comment%sSlot">%s</div>' % (
sylvain thenault's avatar
sylvain thenault committed
            commented.eid, form.form_render(display_relations_form=False)))
class InlineEditCommentForm(InlineCommentForm):
Nicolas Chauvat's avatar
Nicolas Chauvat committed
    id = 'editcomment'
sylvain thenault's avatar
sylvain thenault committed
    __select__ = implements('Comment')
Nicolas Chauvat's avatar
Nicolas Chauvat committed
    def cell_call(self, row, col):
        self.comment_form(self.entity(row, col))
Nicolas Chauvat's avatar
Nicolas Chauvat committed
# comment component ###########################################################

class CommentSectionVComponent(EntityVComponent):
    """a component to display a <div> html section including comments
    related to an object
    """
    id = 'commentsection'
sylvain thenault's avatar
sylvain thenault committed
    __select__ = (EntityVComponent.__select__
                  & relation_possible('comments', 'object', 'Comment'))
sylvain thenault's avatar
sylvain thenault committed

Nicolas Chauvat's avatar
Nicolas Chauvat committed
    context = 'navcontentbottom'

sylvain thenault's avatar
sylvain thenault committed
    def cell_call(self, row, col, view=None):
Nicolas Chauvat's avatar
Nicolas Chauvat committed
        req = self.req
        req.add_js( ('cubicweb.ajax.js', 'cubes.comment.js') )
sylvain thenault's avatar
sylvain thenault committed
        eid = self.rset[row][col]
Nicolas Chauvat's avatar
Nicolas Chauvat committed
        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:
sylvain thenault's avatar
sylvain thenault committed
            url = ajax_replace_url('comment%sHolder' % eid, rql_for_eid(eid),
                                   'inlinecomment')
Nicolas Chauvat's avatar
Nicolas Chauvat committed
            reply = u' (<a href="%s">%s</a>)' % (url, req._(action.title))
sylvain thenault's avatar
sylvain thenault committed
            if req.use_fckeditor() and req.property_value('ui.default-text-format') == 'text/html':
Nicolas Chauvat's avatar
Nicolas Chauvat committed
                req.fckeditor_config()
        else:
            reply = u''
sylvain thenault's avatar
sylvain thenault committed
        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')
Nicolas Chauvat's avatar
Nicolas Chauvat committed
        if rset.rowcount:
            self.w(u'<h4>%s</h4>%s' % (req._('Comment_plural'), reply))
            # XXX check registration is open. Also, propose to login.
            #if self.vreg.config['anonymous-user'] == self.req.user.login:
            #    self.w(u' (<a href="/?vid=register">register to comment</a>)')
Nicolas Chauvat's avatar
Nicolas Chauvat committed
            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>')
Nicolas Chauvat's avatar
Nicolas Chauvat committed
            self.w(reply)
            # XXX check registration is open. Also, propose to login.
            #if self.vreg.config['anonymous-user'] == self.req.user.login:
            #    self.w(u' (<a href="/?vid=register">register to comment</a>)')
            self.w(u'<div id="comment%sHolder"></div>' % eid)
Nicolas Chauvat's avatar
Nicolas Chauvat committed
        self.w(u'</div>')

baseviews.PRIMARY_SKIP_RELS.add('comments') # displayed by the above component

sylvain thenault's avatar
sylvain thenault committed

Nicolas Chauvat's avatar
Nicolas Chauvat committed
# comment actions #############################################################

class ReplyCommentAction(LinkToEntityAction):
    id = 'reply_comment'
    __select__ = LinkToEntityAction.__select__ & implements('Comment')
Nicolas Chauvat's avatar
Nicolas Chauvat committed

    etype = 'Comment'
    rtype = 'comments'
    target = 'subject'
    title = _('reply to this comment')
    category = 'hidden'
Nicolas Chauvat's avatar
Nicolas Chauvat committed
    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):
Nicolas Chauvat's avatar
Nicolas Chauvat committed
    """add comment is like reply for everything but Comment"""
    id = 'reply_comment'
sylvain thenault's avatar
sylvain thenault committed
    __select__ = LinkToEntityAction.__select__ & but_etype('Comment')
Nicolas Chauvat's avatar
Nicolas Chauvat committed
    etype = 'Comment'
    rtype = 'comments'
sylvain thenault's avatar
sylvain thenault committed
    role = 'object'
    title = _('add comment')
    category = 'hidden'
Nicolas Chauvat's avatar
Nicolas Chauvat committed
    order = 111


class EditCommentAction(Action):
    id = 'edit_comment'
sylvain thenault's avatar
sylvain thenault committed
    __select__ = one_line_rset() & implements('Comment') & has_permission('update')
    title = _('edit comment')
Nicolas Chauvat's avatar
Nicolas Chauvat committed
    category = 'hidden'
    order = 110

    def url(self):
        return self.build_url(rql=self.rset.printable_rql(), vid='edition')

sylvain thenault's avatar
sylvain thenault committed

Nicolas Chauvat's avatar
Nicolas Chauvat committed
# add some comments related methods to the Jsoncontroller #####################

sylvain thenault's avatar
sylvain thenault committed
@monkeypatch(JSonController)
Nicolas Chauvat's avatar
Nicolas Chauvat committed
def js_add_comment(self, commented, text, format):
sylvain thenault's avatar
sylvain thenault committed
    self.req.execute('INSERT Comment C: C comments X, C content %(text)s, '
                     'C content_format %(format)s  WHERE X eid %(x)s',
                     {'format' : format, 'text' : text, 'x' : commented}, 'x')
sylvain thenault's avatar
sylvain thenault committed
@monkeypatch(JSonController)
Nicolas Chauvat's avatar
Nicolas Chauvat committed
def js_edit_comment(self, comment, text, format):
sylvain thenault's avatar
sylvain thenault committed
    self.req.execute('SET C content %(text)s, C content_format %(format)s '
                     'WHERE C eid %(x)s',
                     {'format' : format, 'text' : text, 'x' : comment}, 'x')