Skip to content
Snippets Groups Projects
views.py 15 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, score_entity,
                                authenticated_user)
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')
# 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 href="%s">%s</a>' % (req.build_url('login'),
                                      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):
        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.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']
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 Thénault's avatar
Sylvain Thénault committed
            self.w(u', %s <span class="author">%s</span> \n'
                   % (self.req._('written by'), authorlink,))
        replyaction = actions.select_object('reply_comment', self.req,
                                       rset=self.rset, row=row)
        if replyaction is not None:
            url = self.req.build_ajax_replace_url(
                'comment%sHolder' % entity.eid, rql_for_eid(entity.eid),
                'inlinecomment')
Sandrine Ribeau's avatar
Sandrine Ribeau committed
            if self.req.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>'
                       % (_login_register_link(self.req),
Sylvain Thénault's avatar
Sylvain Thénault committed
                          xml_escape(url), self.req._(replyaction.title)))
Sylvain Thénault's avatar
Sylvain Thénault committed
            else:
                self.w(u' | <span class="replyto"><a href="%s">%s</a></span>'
Sylvain Thénault's avatar
Sylvain Thénault committed
                       % (xml_escape(url), self.req._(replyaction.title)))
Sylvain Thénault's avatar
Sylvain Thénault committed
        editaction = actions.select_object('edit_comment', self.req,
                                           rset=self.rset, row=row)
        if editaction is not None:
            url = self.req.build_ajax_replace_url(
                'comment%s' % entity.eid, rql_for_eid(entity.eid),
                'editcomment')
            self.w(u' | <span class="replyto"><a href="%s">%s</a></span>'
Sylvain Thénault's avatar
Sylvain Thénault committed
                   % (xml_escape(url), self.req._(editaction.title)))

        deleteaction = actions.select_object('delete_comment', self.req,
                                             rset=self.rset, row=row)
        if deleteaction is not None:
            url = self.req.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>'
                   % (xml_escape(url), self.req._(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'):
            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="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)
        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):
    id = 'inlinecommentform'
    __select__ = match_kwargs('commented') # explicit call when it makes sense
    def call(self, commented):
        self.initialize_varmaker()
Sylvain Thénault's avatar
Sylvain Thénault committed
        newcomment = self.vreg['etypes'].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))
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')))
        addcomment = self.vreg['actions'].select_object('reply_comment', req,
                                                        rset=self.rset,
                                                        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">%s</a>)' % (url, req._(addcomment.title)))
            # 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:
Sandrine Ribeau's avatar
Sandrine Ribeau committed
            self.w(u'%s %s' % (_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):
    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):
Sylvain Thénault's avatar
Sylvain Thénault committed
        comment = self.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)
        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 Thénault's avatar
Sylvain Thénault committed
class DeleteCommentAction(Action):
    __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):
        return self.build_url(rql=self.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):
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')

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

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

    def cell_call(self, row, col):
        entity = self.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') + \
                      self.req._(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')