Skip to content
Snippets Groups Projects
views.py 15.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"
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

Sandrine Ribeau's avatar
Sandrine Ribeau committed
from cubicweb.selectors import (one_line_rset, implements,
sylvain thenault's avatar
sylvain thenault committed
                                has_permission, relation_possible, yes,
                                match_kwargs, score_entity,
                                authenticated_user)
sylvain thenault's avatar
sylvain thenault committed
from cubicweb.view import EntityView
Arthur Lutz's avatar
Arthur Lutz committed
from cubicweb.uilib import rql_for_eid, cut, safe_cut
from cubicweb.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
uicfg.autoform_section.tag_subject_of(('*', 'comments', '*'), formtype='main', section='hidden')
uicfg.autoform_section.tag_object_of(('*', 'comments', '*'), formtype='main', section='hidden')
sylvain thenault's avatar
sylvain thenault committed
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')
# XXX this is probably *very* inefficient since we'll fetch all entities created by the user
uicfg.primaryview_section.tag_object_of(('*', 'created_by', 'CWUser'), 'relations')
uicfg.primaryview_display_ctrl.tag_object_of(
    ('*', 'created_by', 'CWUser'),
    {'vid': 'list', 'label': _('latest comment(s):'), 'limit': True,
     'filter': lambda rset: rset.filtered_rset(lambda x: x.e_schema == 'Comment')})
Sylvain Thénault's avatar
Sylvain Thénault committed
def _login_register_link(req):
Sylvain Thénault's avatar
Sylvain Thénault committed
    if 'registration' in req.vreg.config.cubes():
        link = u'<a href="%s">%s</a> or ' % (req.build_url('register'),
                                             req._(u'register'))
    else:
        link = u''
    link += u'<a class="loadPopupLogin">%s</a>' % req._(u'login')
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):
Arthur Lutz's avatar
Arthur Lutz committed
        self._cw.add_css('cubes.comment.css')
Arthur Lutz's avatar
Arthur Lutz committed
        entity = self.cw_rset.complete_entity(row, col)
Nicolas Chauvat's avatar
Nicolas Chauvat committed
        # 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')
Arthur Lutz's avatar
Arthur Lutz committed
            self.w(u'%s %s\n' % (self._cw._('written by'), authorlink))
Arthur Lutz's avatar
Arthur Lutz committed
        self.w(self._cw.format_date(entity.creation_date))
Nicolas Chauvat's avatar
Nicolas Chauvat committed
        # commented object
        if entity.comments:
Arthur Lutz's avatar
Arthur Lutz committed
            self.w(u",  %s " % self._cw._('comments'))
Nicolas Chauvat's avatar
Nicolas Chauvat committed
            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())
Arthur Lutz's avatar
Arthur Lutz committed
            txt = self._cw._('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):
Arthur Lutz's avatar
Arthur Lutz committed
        entity = self.cw_rset.get_entity(row, col)
Nicolas Chauvat's avatar
Nicolas Chauvat committed
        root = entity.root()
        self.w(u'[<a href="%s">#%s</a>] '
               % (xml_escape(root.absolute_url()), root.eid))
Arthur Lutz's avatar
Arthur Lutz committed
        maxsize = self._cw.property_value('navigation.short-line-size')
Nicolas Chauvat's avatar
Nicolas Chauvat committed
        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):
    __regid__ = '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):
Arthur Lutz's avatar
Arthur Lutz committed
        self._cw.add_js('cubicweb.ajax.js')
        self._cw.add_css('cubes.comment.css')
Arthur Lutz's avatar
Arthur Lutz committed
        entity = self.cw_rset.get_entity(row, col)
        actions = self._cw.vreg['actions']
Nicolas Chauvat's avatar
Nicolas Chauvat committed
        self.w(u'<div class="commentInfo">')
Arthur Lutz's avatar
Arthur Lutz committed
        self.w(self._cw.format_date(entity.creation_date))
        self.w(u' %s' % self._cw.format_time(entity.creation_date))
Nicolas Chauvat's avatar
Nicolas Chauvat committed
        if entity.creator:
            authorlink = entity.creator.view('oneline')
Sylvain Thénault's avatar
Sylvain Thénault committed
            self.w(u', %s <span class="author">%s</span> \n'
Arthur Lutz's avatar
Arthur Lutz committed
                   % (self._cw._('written by'), authorlink,))
Arthur Lutz's avatar
Arthur Lutz committed
        replyaction = actions.select_or_none('reply_comment', self._cw,
                                       rset=self.cw_rset, row=row)
Sylvain Thénault's avatar
Sylvain Thénault committed
        if replyaction is not None:
Arthur Lutz's avatar
Arthur Lutz committed
            url = self._cw.build_ajax_replace_url(
Sylvain Thénault's avatar
Sylvain Thénault committed
                'comment%sHolder' % entity.eid, rql_for_eid(entity.eid),
                'inlinecomment')
Arthur Lutz's avatar
Arthur Lutz committed
            if self._cw.cnx.anonymous_connection:
Sylvain Thénault's avatar
Sylvain Thénault committed
                self.w(u' | <span class="replyto">%s <a href="%s">%s</a></span>'
Arthur Lutz's avatar
Arthur Lutz committed
                       % (_login_register_link(self._cw),
                          xml_escape(url), self._cw._(replyaction.title)))
Sylvain Thénault's avatar
Sylvain Thénault committed
            else:
                self.w(u' | <span class="replyto"><a href="%s">%s</a></span>'
Arthur Lutz's avatar
Arthur Lutz committed
                       % (xml_escape(url), self._cw._(replyaction.title)))
Arthur Lutz's avatar
Arthur Lutz committed
        editaction = actions.select_or_none('edit_comment', self._cw,
                                            rset=self.cw_rset, row=row)
Sylvain Thénault's avatar
Sylvain Thénault committed
        if editaction is not None:
Arthur Lutz's avatar
Arthur Lutz committed
            url = self._cw.build_ajax_replace_url(
Sylvain Thénault's avatar
Sylvain Thénault committed
                'comment%s' % entity.eid, rql_for_eid(entity.eid),
                'editcomment')
            self.w(u' | <span class="replyto"><a href="%s">%s</a></span>'
Arthur Lutz's avatar
Arthur Lutz committed
                   % (xml_escape(url), self._cw._(editaction.title)))
Arthur Lutz's avatar
Arthur Lutz committed
        deleteaction = actions.select_or_none('delete_comment', self._cw,
                                             rset=self.cw_rset, row=row)
Arthur Lutz's avatar
Arthur Lutz committed
            url = self._cw.build_ajax_replace_url(
                'comment%s' % entity.eid, rql_for_eid(entity.eid),
                'deleteconf')
            self.w(u' | <span class="replyto"><a href="%s">%s</a></span>'
Arthur Lutz's avatar
Arthur Lutz committed
                   % (xml_escape(url), self._cw._(deleteaction.title)))
Nicolas Chauvat's avatar
Nicolas Chauvat committed
        self.w(u'</div>\n')
Sylvain Thénault's avatar
Sylvain Thénault committed
        text = entity.printable_value('content')
        if not kwargs.get('full'):
Arthur Lutz's avatar
Arthur Lutz committed
            maxsize = self._cw.property_value('navigation.short-line-size')
Sylvain Thénault's avatar
Sylvain Thénault committed
            text = safe_cut(text, maxsize)
Nicolas Chauvat's avatar
Nicolas Chauvat committed
        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):
    __regid__ = '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):
Arthur Lutz's avatar
Arthur Lutz committed
        entity = self.cw_rset.get_entity(row, col)
Nicolas Chauvat's avatar
Nicolas Chauvat committed
        self.wview('inlinecommentform', None, commented=entity)
class InlineEditCommentForm(FormViewMixIn, EntityView):
    __regid__ = 'editcomment'
sylvain thenault's avatar
1.4
sylvain thenault committed

Arthur Lutz's avatar
Arthur Lutz committed
        self.comment_form(self.cw_rset.get_entity(row, col))
    def comment_form(self, commented, newcomment=None):
Arthur Lutz's avatar
Arthur Lutz committed
        self._cw.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
Arthur Lutz's avatar
Arthur Lutz committed
        self._cw.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, ''))]
Arthur Lutz's avatar
Arthur Lutz committed
        form = self._cw.vreg['forms'].select('edition', self._cw,
                                         entity=newcomment,
                                         form_buttons=buttons)
        self.w(u'<div id="comment%sSlot">%s</div>' % (
            commented.eid, form.form_render(main_form_title=u'',
                                            display_label=False,
Graziella Toutoungis's avatar
Graziella Toutoungis committed
                                            display_relations_form=False)))
class InlineCommentForm(InlineEditCommentForm):
    __regid__ = 'inlinecommentform'
    __select__ = match_kwargs('commented') # explicit call when it makes sense
    def call(self, commented):
        self.initialize_varmaker()
Arthur Lutz's avatar
Arthur Lutz committed
        newcomment = self._cw.vreg['etypes'].etype_class('Comment')(self._cw)
        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
    """
    __regid__ = '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):
Arthur Lutz's avatar
Arthur Lutz committed
        req = self._cw
        req.add_js( ('cubicweb.ajax.js', 'cubes.comment.js') )
Arthur Lutz's avatar
Arthur Lutz committed
        eid = self.cw_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))
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:
Sylvain Thénault's avatar
Sylvain Thénault committed
            self.w(u'<h4>%s</h4>' % (req._('Comment_plural')))
Arthur Lutz's avatar
Arthur Lutz committed
        addcomment = self._cw.vreg['actions'].select_or_none('reply_comment', req,
                                                        rset=self.cw_rset,
Sylvain Thénault's avatar
Sylvain Thénault committed
                                                        row=row, col=col)
        if addcomment is not None:
            url = req.build_ajax_replace_url(
                'comment%sHolder' % eid, rql_for_eid(eid), 'inlinecomment')
            self.w(u' (<a href="%s" onclick="javascript:toggleVisibility(\'addCommentLinks\');">%s</a>)' % (url, req._(addcomment.title)))
Sylvain Thénault's avatar
Sylvain Thénault committed
            # XXX still necessary?
            #if req.use_fckeditor() and req.property_value('ui.default-text-format') == 'text/html':
            #    req.fckeditor_config()
        if req.cnx.anonymous_connection:
            self.w(u'<div id="addCommentLinks" class="hidden">%s %s</div>' % \
                   (_login_register_link(req), req._(u'to comment')))
Sylvain Thénault's avatar
Sylvain Thénault committed
        self.w(u'<div id="comment%sHolder"></div>' % eid)
        if rset.rowcount:
Nicolas Chauvat's avatar
Nicolas Chauvat committed
            self.w(u'<ul class="comment">')
            for i in xrange(rset.rowcount):
                self.wview('tree', rset, row=i, full=True)
            self.w(u'</ul>')
        self.w(u'</div>')

sylvain thenault's avatar
sylvain thenault committed

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

class ReplyCommentAction(LinkToEntityAction):
    __regid__ = '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):
Arthur Lutz's avatar
Arthur Lutz committed
        comment = self.cw_rset.get_entity(self.row or 0, self.col or 0)
Nicolas Chauvat's avatar
Nicolas Chauvat committed
        linkto = '%s:%s:%s' % (self.rtype, comment.eid, self.target)
Arthur Lutz's avatar
Arthur Lutz committed
        return self._cw.build_url(vid='creation', etype=self.etype,
                                  __linkto=linkto,
                                  __redirectpath=comment.root().rest_path(),
                                  __redirectvid=self._cw.form.get('vid', ''))
class AddCommentAction(LinkToEntityAction):
Nicolas Chauvat's avatar
Nicolas Chauvat committed
    """add comment is like reply for everything but Comment"""
    __regid__ = 'reply_comment'
Sandrine Ribeau's avatar
Sandrine Ribeau committed
    __select__ = LinkToEntityAction.__select__ & ~implements('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):
    __regid__ = '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):
Arthur Lutz's avatar
Arthur Lutz committed
        return self._cw.build_url(rql=self.cw_rset.printable_rql(), vid='edition')
Sylvain Thénault's avatar
Sylvain Thénault committed
class DeleteCommentAction(Action):
    __regid__ = 'delete_comment'
    __select__ = implements('Comment') & \
                 score_entity(lambda x: not x.reverse_comments and x.has_perm('delete'))

    title = _('delete comment')
    category = 'hidden'
    order = 110

    def url(self):
Arthur Lutz's avatar
Arthur Lutz committed
        return self._cw.build_url(rql=self.cw_rset.printable_rql(), vid='deleteconf')
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):
Arthur Lutz's avatar
Arthur Lutz committed
    self._cw.execute('INSERT Comment C: C comments X, C content %(text)s, '
sylvain thenault's avatar
sylvain thenault committed
                     '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):
Arthur Lutz's avatar
Arthur Lutz committed
    self._cw.execute('SET C content %(text)s, C content_format %(format)s '
sylvain thenault's avatar
sylvain thenault committed
                     'WHERE C eid %(x)s',
                     {'format' : format, 'text' : text, 'x' : comment}, 'x')

# RSS view ####################################################################

class RssItemCommentView(xmlrss.RSSItemView):
    __select__ = implements('Comment')

    def cell_call(self, row, col):
Arthur Lutz's avatar
Arthur Lutz committed
        entity = self.cw_rset.complete_entity(row, col)
        self.w(u'<item>\n')
        self.w(u'<guid isPermaLink="true">%s</guid>\n'
               % xml_escape(entity.absolute_url()))
        self.render_title_link(entity)
        description = entity.dc_description(format='text/html') + \
Arthur Lutz's avatar
Arthur Lutz committed
                      self._cw._(u'about') + \
                      u' <a href=%s>%s</a>' % (entity.root().absolute_url(),
                                               entity.root().dc_title())
        self._marker('description', description)
        self._marker('dc:date', entity.dc_date(self.date_format))
        self.render_entity_creator(entity)
        self.w(u'</item>\n')
        self.wview('rssitem', entity.related('comments', 'object'), 'null')