Newer
Older
"""Specific views and actions for application using the Comment entity type
:organization: Logilab
:copyright: 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
from __future__ import with_statement
from logilab.mtconverter import xml_escape
from cubicweb.selectors import (is_instance, has_permission, authenticated_user,
Sylvain Thénault
committed
score_entity, relation_possible, one_line_rset,
match_kwargs, match_form_params,
partial_relation_possible, traced_selection)
from cubicweb.uilib import rql_for_eid, cut, safe_cut
from cubicweb.mixins import TreeViewMixIn
Sylvain Thénault
committed
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
Sylvain Thénault
committed
_afs = uicfg.autoform_section
_afs.tag_object_of(('*', 'comments', '*'), formtype='main', section='hidden')
Sylvain Thénault
committed
_affk = uicfg.autoform_field_kwargs
_affk.tag_subject_of(('*', 'comments', '*'), {'widget': fw.HiddenInput})
Sylvain Thénault
committed
_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')
# comment views ###############################################################
__select__ = is_instance('Comment')
entity = self.cw_rset.complete_entity(row, col)
# display text, author and creation date
self.w(u'<div class="comment">')
self.w(u'<div class="commentInfo">')
# do not try to display creator when you're not allowed to see CWUsers
if entity.creator:
authorlink = entity.creator.view('oneline')
self.w(u'%s %s\n' % (self._cw._('written by'), authorlink))
self.w(self._cw.format_date(entity.creation_date))
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')
self.w(u'<div class="commentBody">%s</div>\n'
% entity.printable_value('content'))
# XXX attribute generated_by added by email component
if hasattr(self, 'generated_by') and self.generated_by:
gen = self.generated_by[0]
link = '<a href="%s">%s</a>' % (gen.absolute_url(),
gen.dc_type().lower())
txt = self._cw._('this comment has been generated from this %s') % link
self.w(u'<div class="commentBottom">%s</div>\n' % txt)
self.w(u'</div>\n')
Sylvain Thénault
committed
class CommentRootView(EntityView):
__regid__ = 'commentroot'
__select__ = is_instance('Comment')
Sylvain Thénault
committed
def cell_call(self, row, col, **kwargs):
entity = self.cw_rset.get_entity(row, col)
root = entity.cw_adapt_to('ITree').root()
Sylvain Thénault
committed
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')
Sylvain Thénault
committed
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')
self.w(u'[%s] ' % entity.view('commentroot'))
Sylvain Thénault
committed
self.w(u'<a href="%s"><i>%s</i></a>\n' % (
xml_escape(entity.absolute_url()),
entity.view('commentsummary')))
class CommentTreeItemView(baseviews.ListItemView):
__select__ = is_instance('Comment')
self._cw.add_js('cubicweb.ajax.js')
self._cw.add_css('cubes.comment.css')
entity = self.cw_rset.get_entity(row, col)
actions = self._cw.vreg['actions']
Sylvain Thénault
committed
# DOM id of the whole comment's content
cdivid = 'comment%sDiv' % entity.eid
self.w(u'<div id="%s">' % cdivid)
self.w(self._cw.format_date(entity.creation_date))
self.w(u' %s' % self._cw.format_time(entity.creation_date))
if entity.creator:
authorlink = entity.creator.view('oneline')
self.w(u', %s <span class="author">%s</span> \n'
replyaction = actions.select_or_none('reply_comment', self._cw,
rset=self.cw_rset, row=row)
url = self._cw.ajax_replace_url('comment%sHolder' % entity.eid,
eid=entity.eid, vid='addcommentform')
Sylvain Thénault
committed
self.w(u' | <span class="replyto"><a href="%s">%s</a></span>'
% (xml_escape(url), self._cw._(replyaction.title)))
editaction = actions.select_or_none('edit_comment', self._cw,
rset=self.cw_rset, row=row)
Sylvain Thénault
committed
# split(':', 1)[1] to remove javascript:
formjs = self._cw.ajax_replace_url(
cdivid, 'append', eid=entity.eid,
vid='editcommentform').split(':', 1)[1]
Sylvain Thénault
committed
url = "javascript: jQuery('#%s div').hide(); %s" % (cdivid, formjs)
self.w(u' | <span class="replyto"><a href="%s">%s</a></span>'
% (xml_escape(url), self._cw._(editaction.title)))
Sandrine Ribeau
committed
deleteaction = actions.select_or_none('delete_comment', self._cw,
rset=self.cw_rset, row=row)
Sandrine Ribeau
committed
if deleteaction is not None:
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())
Sandrine Ribeau
committed
Sandrine Ribeau
committed
self.w(u' | <span class="replyto"><a href="%s">%s</a></span>'
% (xml_escape(url), self._cw._(deleteaction.title)))
Sylvain Thénault
committed
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)
Sylvain Thénault
committed
self.w(u'</div>\n') # close comment's content div
class CommentThreadView(treeview.BaseTreeView):
__select__ = is_instance('Comment')
def open_item(self, entity):
self.w(u'<li id="comment%s" class="comment">\n' % entity.eid)
Sylvain Thénault
committed
class RssItemCommentView(xmlrss.RSSItemView):
__select__ = is_instance('Comment')
Sylvain Thénault
committed
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()
Sylvain Thénault
committed
description = entity.dc_description(format='text/html') + \
self._cw._(u'about') + \
u' <a href=%s>%s</a>' % (root.absolute_url(),
root.dc_title())
Sylvain Thénault
committed
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 ################################################################
Sylvain Thénault
committed
class InlineEditCommentFormView(form.FormViewMixIn, EntityView):
Sylvain Thénault
committed
__regid__ = 'editcommentform'
__select__ = is_instance('Comment')
Sylvain Thénault
committed
jsfunc = "processComment(%s, %s, false)"
Sylvain Thénault
committed
def entity_call(self, entity):
self.comment_form(entity)
Sylvain Thénault
committed
def propose_to_login(self):
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>.'))
Sylvain Thénault
committed
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'))
Sylvain Thénault
committed
self.w(u'</div>')
def comment_form(self, commented, newcomment=None):
Sylvain Thénault
committed
self._cw.add_js(('cubicweb.edition.js', 'cubes.comment.js'))
Sylvain Thénault
committed
if self._cw.cnx.anonymous_connection:
self.propose_to_login()
# hack to avoid tabindex conflicts caused by Ajax requests
Sylvain Thénault
committed
buttons = [fw.Button(onclick=self.jsfunc % (jseid, 'false')),
Sylvain Thénault
committed
fw.Button(stdmsgs.BUTTON_CANCEL,
Sylvain Thénault
committed
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
Sylvain Thénault
committed
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">' % commented.eid)
form.render(w=self.w, formvalues=formvalues,
main_form_title=u'', display_label=False)
self.w(u'</div>')
Sylvain Thénault
committed
class InlineAddCommentFormView(InlineEditCommentFormView):
Sylvain Thénault
committed
__regid__ = 'addcommentform'
Sylvain Thénault
committed
__select__ = (relation_possible('comments', 'object', 'Comment', 'add')
| match_form_params('etype'))
jsfunc = "processComment(%s, %s, true)"
Sylvain Thénault
committed
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)
Sylvain Thénault
committed
def entity_call(self, commented):
newcomment = self._cw.vreg['etypes'].etype_class('Comment')(self._cw)
Graziella Toutoungis
committed
self.comment_form(commented, newcomment)
Sylvain Thénault
committed
# contextual components ########################################################
class CommentSectionVComponent(component.EntityCtxComponent):
"""a component to display a <div> html section including comments
related to an object
"""
__regid__ = 'commentsection'
Sylvain Thénault
committed
__select__ = (component.EntityCtxComponent.__select__
req.add_js( ('cubicweb.ajax.js', 'cubes.comment.js') )
Sylvain Thénault
committed
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
committed
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'
Sylvain Thénault
committed
rset = req.execute(rql, {'x': entity.eid})
else:
rset = None
if not (rset or addcomment):
return
if rset.rowcount:
w(u'<h4>%s</h4>' % (req._('Comment_plural')))
w(u'<ul class="comment">')
Sylvain Thénault
committed
for i in xrange(rset.rowcount):
self._cw.view('tree', rset, row=i, w=w)
w(u'<div id="comment%sHolder"></div>' % entity.eid)
Sylvain Thénault
committed
params = self.cw_extra_kwargs.copy()
params.pop('view', None)
params.pop('context', None)
Sylvain Thénault
committed
if entity.has_eid():
Sylvain Thénault
committed
params['eid'] = entity.eid
Sylvain Thénault
committed
else:
params['etype'] = entity.__regid__
url = req.ajax_replace_url(
Sylvain Thénault
committed
'comment%sHolder' % entity.eid, vid='addcommentform', **params)
w(u' (<a href="%s">%s</a>)' % (xml_escape(url), req._(addcomment.title)))
class UserLatestCommentsSection(component.EntityCtxComponent):
Sylvain Thénault
committed
"""a section to display latest comments by a user"""
__select__ = component.EntityCtxComponent.__select__ & is_instance('CWUser')
Sylvain Thénault
committed
__regid__ = 'latestcomments'
Sylvain Thénault
committed
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,
Sylvain Thénault
committed
if rset:
w(u'<div class="section">')
w(u'<h4>%s</h4>\n' % self._cw._('Latest comments').capitalize())
self._cw.view('table', rset, w=w,
headers=[_('about'), _('on date'),
_('comment content')],
cellvids={0: 'commentroot',
2: 'commentsummary',
})
w(u'</div>')
Sylvain Thénault
committed
# 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(), {}
Sylvain Thénault
committed
# actions ######################################################################
class ReplyCommentAction(LinkToEntityAction):
__regid__ = 'reply_comment'
__select__ = LinkToEntityAction.__select__ & is_instance('Comment')
title = _('reply to this comment')
category = 'hidden'
comment = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
root = comment.cw_adapt_to('ITree').root()
return self._cw.build_url(vid='creation', etype=self.target_etype,
__redirectpath=root.rest_path(),
class AddCommentAction(LinkToEntityAction):
"""add comment is like reply for everything but Comment"""
__regid__ = 'reply_comment'
Sylvain Thénault
committed
__select__ = ((LinkToEntityAction.__select__
| (match_kwargs('entity') & partial_relation_possible(action='add', strict=True)))
& ~is_instance('Comment'))
title = _('add comment')
category = 'hidden'
__regid__ = 'edit_comment'
__select__ = one_line_rset() & is_instance('Comment') & has_permission('update')
Sylvain Thénault
committed
title = 'modify' # not internationalized on purpose (wanna use cw translation)
category = 'hidden'
order = 110
def url(self):
return self._cw.build_url(rql=self.cw_rset.printable_rql(), vid='edition')
__regid__ = 'delete_comment'
__select__ = is_instance('Comment') & authenticated_user() & \
score_entity(lambda x: not x.reverse_comments and x.cw_has_perm('delete'))
Sandrine Ribeau
committed
Sylvain Thénault
committed
title = 'delete' # not internationalized on purpose (wanna use cw translation)
Sandrine Ribeau
committed
category = 'hidden'
order = 110
def url(self):
return self._cw.build_url(rql=self.cw_rset.printable_rql(), vid='deleteconf')