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

:organization: Logilab
Sylvain Thénault's avatar
Sylvain Thénault committed
:copyright: 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
Nicolas Chauvat's avatar
Nicolas Chauvat committed
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
from __future__ import with_statement

Nicolas Chauvat's avatar
Nicolas Chauvat committed
__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
Sylvain Thénault's avatar
Sylvain Thénault committed
from cubicweb.utils import json_dumps
from cubicweb.selectors import (is_instance, has_permission, authenticated_user,
                                score_entity, relation_possible, one_line_rset,
                                match_kwargs, match_form_params,
                                partial_relation_possible, traced_selection)
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
from cubicweb.web import stdmsgs, uicfg, component, form, formwidgets as fw
from cubicweb.web.action import LinkToEntityAction, Action
from cubicweb.web.views import primary, baseviews, xmlrss, basecontrollers, treeview
_afs = uicfg.autoform_section
_afs.tag_object_of(('*', 'comments', '*'), formtype='main', section='hidden')
_affk = uicfg.autoform_field_kwargs
_affk.tag_subject_of(('*', 'comments', '*'), {'widget': fw.HiddenInput})

_abaa = uicfg.actionbox_appearsin_addmenu
_abaa.tag_subject_of(('*', 'comments', '*'),  False)
_abaa.tag_object_of(('*', 'comments', '*'), False)

_pvs = uicfg.primaryview_section
_pvs.tag_subject_of(('*', 'comments', '*'),  'hidden')
_pvs.tag_object_of(('*', 'comments', '*'), 'hidden')
Nicolas Chauvat's avatar
Nicolas Chauvat committed
# comment views ###############################################################

sylvain thenault's avatar
sylvain thenault committed
class CommentPrimaryView(primary.PrimaryView):
    __select__ = is_instance('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 CommentRootView(EntityView):
    __regid__ = 'commentroot'
    __select__ = is_instance('Comment')

    def cell_call(self, row, col, **kwargs):
        entity = self.cw_rset.get_entity(row, col)
        root = entity.cw_adapt_to('ITree').root()
        self.w(u'<a href="%s">%s %s</a> ' % (
            xml_escape(root.absolute_url()),
            xml_escape(root.dc_type()),
            xml_escape(cut(root.dc_title(), 40))))


class CommentSummary(EntityView):
    __regid__ = 'commentsummary'
    __select__ = is_instance('Comment')

    def cell_call(self, row, col, **kwargs):
        entity = self.cw_rset.get_entity(row, col)
        maxsize = self._cw.property_value('navigation.short-line-size')
        content = entity.printable_value('content', format='text/plain')
        self.w(xml_escape(cut(content, maxsize)))

class CommentOneLineView(baseviews.OneLineView):
    __select__ = is_instance('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)
        self.w(u'[%s] ' % entity.view('commentroot'))
        self.w(u'<a href="%s"><i>%s</i></a>\n' % (
            xml_escape(entity.absolute_url()),
            entity.view('commentsummary')))
Nicolas Chauvat's avatar
Nicolas Chauvat committed


class CommentTreeItemView(baseviews.ListItemView):
    __regid__ = 'treeitem'
    __select__ = is_instance('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']
        # DOM id of the whole comment's content
        cdivid = 'comment%sDiv' % entity.eid
        self.w(u'<div id="%s">' % cdivid)
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:
            url = self._cw.ajax_replace_url('comment%sHolder' % entity.eid,
                                            eid=entity.eid, vid='addcommentform')
            self.w(u' | <span class="replyto"><a href="%s">%s</a></span>'
                   % (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:
            formjs = self._cw.ajax_replace_url(
                cdivid, 'append', eid=entity.eid,
                vid='editcommentform').split(':', 1)[1]
            url = "javascript: jQuery('#%s div').hide(); %s" % (cdivid, formjs)
Sylvain Thénault's avatar
Sylvain Thénault committed
            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)
            root = entity.cw_adapt_to('ITree').root()
            url = self._cw.ajax_replace_url(
                'comment%s' % entity.eid, eid=entity.eid, vid='deleteconf',
                __redirectpath=root.rest_path())
            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)))
        self.w(u'</div>\n') # close comment's info div
        self.w(u'<div class="commentBody">%s</div>\n'
               % entity.printable_value('content'))
        # holder for reply form
        self.w(u'<div id="comment%sHolder" class="replyComment"></div>' % entity.eid)
        self.w(u'</div>\n') # close comment's content div
class CommentThreadView(treeview.BaseTreeView):
Nicolas Chauvat's avatar
Nicolas Chauvat committed
    """a recursive tree view"""
    __select__ = is_instance('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)


    __select__ = is_instance('Comment')
    def cell_call(self, row, col):
        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)
        root = entity.cw_adapt_to('ITree').root()
        description = entity.dc_description(format='text/html') + \
                      self._cw._(u'about') + \
                      u' <a href=%s>%s</a>' % (root.absolute_url(),
                                               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')


# comment forms ################################################################

class InlineEditCommentFormView(form.FormViewMixIn, EntityView):
    __select__ = is_instance('Comment')
    jsfunc = "processComment(%s, %s, false)"
sylvain thenault's avatar
1.4
sylvain thenault committed

    def entity_call(self, entity):
        self.comment_form(entity)
        self.w(u'<div class="warning">%s ' % self._cw._('You are not authenticated. Your comment will be anonymous if you do not <a onclick="showLoginBox()">login</a>.'))
        if 'registration' in self._cw.vreg.config.cubes():
            self.w(self.cw._(u' If you have no account, you may want to <a href="%s">create one</a>.')
                   % self._cw.build_url('register'))
    def comment_form(self, commented, newcomment=None):
        self._cw.add_js(('cubicweb.edition.js', 'cubes.comment.js'))
        if self._cw.cnx.anonymous_connection:
            self.propose_to_login()
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
Sylvain Thénault's avatar
Sylvain Thénault committed
        jseid = json_dumps(commented.eid)
        buttons = [fw.Button(onclick=self.jsfunc % (jseid, 'false')),
                             onclick=self.jsfunc % (jseid, 'true'))]
        fvreg = self._cw.vreg['forms']
        formvalues = {}
        if newcomment is not None: # creation
            formvalues['comments'] = commented.eid
        if newcomment is None or commented.has_eid(): # creation / edition
            # use formtype=inlined to avoid viewing the relation edition section
            form = fvreg.select('edition', self._cw, entity=newcomment or commented,
                                domid='commentForm%s' % commented.eid,
                                form_buttons=buttons,
                                formtype='inlined')
        else: # creation of both commented and comment entities
            form = fvreg.select('composite', self._cw, form_buttons=buttons,
                                domid='commentForm%s' % commented.eid,
                                form_renderer_id='default')
            form.add_subform(fvreg.select('edition', self._cw, entity=commented,
                                          mainform=False))
            form.add_subform(fvreg.select('edition', self._cw, entity=newcomment,
                                 mainform=False))
        self.w(u'<div id="comment%sSlot">%s</div>' % (
            commented.eid, form.render(formvalues=formvalues,
Sylvain Thénault's avatar
Sylvain Thénault committed
                                       display_label=False)))
class InlineAddCommentFormView(InlineEditCommentFormView):
    __select__ = (relation_possible('comments', 'object', 'Comment', 'add')
                  | match_form_params('etype'))

    jsfunc = "processComment(%s, %s, true)"
    def call(self, **kwargs):
        if self.cw_rset is None:
            entity = self._cw.vreg['etypes'].etype_class(self._cw.form['etype'])(self._cw)
            entity.eid = self._cw.varmaker.next()
            self.entity_call(entity)
        else:
            super(InlineAddCommentFormView, self).call(**kwargs)
Arthur Lutz's avatar
Arthur Lutz committed
        newcomment = self._cw.vreg['etypes'].etype_class('Comment')(self._cw)
        newcomment.eid = self._cw.varmaker.next()
# contextual components ########################################################
class CommentSectionVComponent(component.EntityVComponent):
Nicolas Chauvat's avatar
Nicolas Chauvat committed
    """a component to display a <div> html section including comments
    related to an object
    """
    __regid__ = 'commentsection'
    __select__ = ((component.EntityVComponent.__select__ | match_kwargs('entity'))
sylvain thenault's avatar
sylvain thenault committed
                  & relation_possible('comments', 'object', 'Comment'))
sylvain thenault's avatar
sylvain thenault committed

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

    def entity_call(self, entity, view=None):
Arthur Lutz's avatar
Arthur Lutz committed
        req = self._cw
        req.add_js( ('cubicweb.ajax.js', 'cubes.comment.js') )
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))
        if entity.has_eid():
            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})
            if rset.rowcount:
                self.w(u'<h4>%s</h4>' % (req._('Comment_plural')))
                self.w(u'<ul class="comment">')
                for i in xrange(rset.rowcount):
                    self.wview('tree', rset, row=i)
                self.w(u'</ul>')
        addcomment = self._cw.vreg['actions'].select_or_none(
            'reply_comment', req, entity=entity,
            rset=entity.cw_rset, row=entity.cw_row, col=entity.cw_col)
Sylvain Thénault's avatar
Sylvain Thénault committed
        if addcomment is not None:
            self.w(u'<div id="comment%sHolder"></div>' % eid)
            params = self.cw_extra_kwargs
            if entity.has_eid():
                params['eid'] = eid
            else:
                params['etype'] = entity.__regid__
            url = req.ajax_replace_url(
                'comment%sHolder' % eid, vid='addcommentform', **params)
            self.w(u' (<a href="%s">%s</a>)' % (xml_escape(url), req._(addcomment.title)))
sylvain thenault's avatar
sylvain thenault committed

class UserLatestCommentsSection(component.EntityVComponent):
    """a section to display latest comments by a user"""
    __select__ = component.EntityVComponent.__select__ & is_instance('CWUser')
    __regid__ = 'latestcomments'

    def cell_call(self, row, col, view=None):
        user = self.cw_rset.get_entity(row, col)
        maxrelated = self._cw.property_value('navigation.related-limit') + 1
        rset = self._cw.execute(
            'Any C,CD,C,CCF ORDERBY CD DESC LIMIT %s WHERE C is Comment, '
            'C creation_date CD, C content CC, C content_format CCF, '
            'C created_by U, U eid %%(u)s' % maxrelated,
            {'u': user.eid})
        if rset:
            self.w(u'<div class="section">')
            self.w(u'<h4>%s</h4>\n' % self._cw._('Latest comments').capitalize())
            self.wview('table', rset,
                       headers=[_('about'), _('on date'),
                                _('comment content')],
                       cellvids={0: 'commentroot',
                                 2: 'commentsummary',
                                 })
            self.w(u'</div>')

# adapters #####################################################################

from cubicweb.web.views.editcontroller import IEditControlAdapter

class CommentIEditControlAdapter(IEditControlAdapter):
    __select__ = is_instance('Comment')

    def after_deletion_path(self):
        """return (path, parameters) which should be used as redirect
        information when this entity is being deleted
        """
        return self.entity.cw_adapt_to('ITree').root().rest_path(), {}

# actions ######################################################################
class ReplyCommentAction(LinkToEntityAction):
    __regid__ = 'reply_comment'
    __select__ = LinkToEntityAction.__select__ & is_instance('Comment')
Nicolas Chauvat's avatar
Nicolas Chauvat committed

    rtype = 'comments'
Sylvain Thénault's avatar
Sylvain Thénault committed
    role = 'object'
    target_etype = 'Comment'
    title = _('reply to this comment')
    category = 'hidden'
Nicolas Chauvat's avatar
Nicolas Chauvat committed
    order = 111

    def url(self):
Sylvain Thénault's avatar
Sylvain Thénault committed
        comment = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
Sylvain Thénault's avatar
Sylvain Thénault committed
        linkto = '%s:%s:subject' % (self.rtype, comment.eid)
        root = comment.cw_adapt_to('ITree').root()
Sylvain Thénault's avatar
Sylvain Thénault committed
        return self._cw.build_url(vid='creation', etype=self.target_etype,
Arthur Lutz's avatar
Arthur Lutz committed
                                  __linkto=linkto,
                                  __redirectpath=root.rest_path(),
Arthur Lutz's avatar
Arthur Lutz committed
                                  __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'
    __select__ = ((LinkToEntityAction.__select__
                   | (match_kwargs('entity') & partial_relation_possible(action='add', strict=True)))
                   & ~is_instance('Comment'))
Nicolas Chauvat's avatar
Nicolas Chauvat committed
    rtype = 'comments'
sylvain thenault's avatar
sylvain thenault committed
    role = 'object'
Sylvain Thénault's avatar
Sylvain Thénault committed
    target_etype = 'Comment'
    title = _('add comment')
    category = 'hidden'
Nicolas Chauvat's avatar
Nicolas Chauvat committed
    order = 111


class EditCommentAction(Action):
    __regid__ = 'edit_comment'
    __select__ = one_line_rset() & is_instance('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__ = is_instance('Comment') & authenticated_user() & \
                 score_entity(lambda x: not x.reverse_comments and x.cw_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')