Skip to content
Snippets Groups Projects
views.py 12.8 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"
sylvain thenault's avatar
sylvain thenault committed
_ = unicode
Nicolas Chauvat's avatar
Nicolas Chauvat committed

from itertools import count

from logilab.mtconverter import xml_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
from cubicweb.common.uilib import rql_for_eid, cut, safe_cut
Nicolas Chauvat's avatar
Nicolas Chauvat committed
from cubicweb.common.mixins import TreeViewMixIn
sylvain thenault's avatar
sylvain thenault committed
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
from cubicweb.web.views import primary, baseviews, xmlrss
sylvain thenault's avatar
sylvain thenault committed
from cubicweb.web.component import EntityVComponent
from cubicweb.web.views.basecontrollers import JSonController
sylvain thenault's avatar
sylvain thenault committed
uicfg.autoform_section.tag_subject_of(('*', 'comments', '*'),  'generated')
uicfg.autoform_section.tag_object_of(('*', 'comments', '*'), 'generated')
uicfg.actionbox_appearsin_addmenu.tag_subject_of(('*', 'comments', '*'),  False)
uicfg.actionbox_appearsin_addmenu.tag_object_of(('*', 'comments', '*'), False)
uicfg.primaryview_section.tag_subject_of(('*', 'comments', '*'),  'hidden')
uicfg.primaryview_section.tag_object_of(('*', 'comments', '*'), 'hidden')
def _login_register_link(appobj):
    link = u''
    if appobj.req.cnx.anonymous_connection:
        if 'registration' in appobj.vreg.config.cubes():
            link += u' <a href="/register">%s</a> or' % appobj.req._(u'register')
        link += u' <a href="/login">%s</a> %s' % (appobj.req._(u'login'), appobj.req._(u'to'))
    return link

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

sylvain thenault's avatar
sylvain thenault committed
class CommentPrimaryView(primary.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')

class CommentOneLineView(baseviews.OneLineView):
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>] '
               % (xml_escape(root.absolute_url()), root.eid))
Nicolas Chauvat's avatar
Nicolas Chauvat committed
        maxsize = self.req.property_value('navigation.short-line-size')
        maxsize = maxsize - len(str(root.eid))
        content = entity.printable_value('content', format='text/plain')
        content = xml_escape(cut(content, maxsize))
sylvain thenault's avatar
sylvain thenault committed
        self.w(u'<a href="%s">#%s <i>%s</i></a>\n' % (
            xml_escape(entity.absolute_url()), entity.eid, content))
Nicolas Chauvat's avatar
Nicolas Chauvat committed


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)
Nicolas Chauvat's avatar
Nicolas Chauvat committed
        actions = self.vreg['actions']
        action = actions.select_object('reply_comment', self.req,
                                       rset=self.rset, row=row)
        editaction = actions.select_object('edit_comment', self.req,
                                           rset=self.rset, row=row)
Nicolas Chauvat's avatar
Nicolas Chauvat committed
        if action is None:
            reply = u''
        else:
sylvain thenault's avatar
sylvain thenault committed
            url = self.req.build_ajax_replace_url(
                'comment%sHolder' % entity.eid, rql_for_eid(entity.eid),
                'inlinecomment')
            reply = ' | %s <a href="%s"> %s </a>' % ( _login_register_link(self),
                                                      xml_escape(url),
                                                      _(action.title))
Nicolas Chauvat's avatar
Nicolas Chauvat committed
        if editaction is None:
            edit = u''
        else:
sylvain thenault's avatar
sylvain thenault committed
            url = self.req.build_ajax_replace_url(
                'comment%s' % entity.eid, rql_for_eid(entity.eid),
                'editcomment')
            edit = ' | <a href="%s">%s</a>' % (xml_escape(url),
sylvain thenault's avatar
sylvain thenault committed
                                               _(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))
        self.w(u' %s' % self.format_time(entity.creation_date))
Nicolas Chauvat's avatar
Nicolas Chauvat committed
        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 InlineEditCommentForm(FormViewMixIn, EntityView):
    id = 'editcomment'
    __select__ = implements('Comment')
sylvain thenault's avatar
1.4
sylvain thenault committed

    def cell_call(self, row, col):
        self.comment_form(self.entity(row, col))
    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)
        buttons = [Button(onclick=self.jsfunc % (jseid, self.jsonmeth)),
sylvain thenault's avatar
sylvain thenault committed
                   Button(stdmsgs.BUTTON_CANCEL,
                          onclick=self.jsfunc % (jseid, ''))]
        form = self.vreg['forms'].select('edition', self.req,
                                entity=newcomment, form_buttons=buttons,
                                attrcategories=('primary',))
        self.w(u'<div id="comment%sSlot">%s</div>' % (
Graziella Toutoungis's avatar
Graziella Toutoungis committed
            commented.eid, form.form_render(display_label=False,
                                            display_relations_form=False)))
class InlineCommentForm(InlineEditCommentForm):
    id = 'inlinecommentform'
    __select__ = match_kwargs('commented') # explicit call when it makes sense
    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)
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['actions'].select_object('reply_comment', req,
                                                    rset=self.rset, row=0)
Nicolas Chauvat's avatar
Nicolas Chauvat committed
        if action is not None:
sylvain thenault's avatar
sylvain thenault committed
            url = self.req.build_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))
            self.w(u'%s %s' % (_login_register_link(self), self.req._(u'comment')))
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)
            self.w(u'%s %s' % (_login_register_link(self), self.req._(u'to comment')))
            self.w(u'<div id="comment%sHolder"></div>' % eid)
Nicolas Chauvat's avatar
Nicolas Chauvat committed
        self.w(u'</div>')

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')