a bit of reorganisation inside web/views:
* move all workflow related stuff into views/workflow.py
* move all schema related stuff into views/schema.py
* move all RSS related stuff into views/xmlrss.py
* start new editforms module, designed to contains new automatic forms code
--- a/web/views/__init__.py Thu Mar 12 16:23:31 2009 +0100
+++ b/web/views/__init__.py Thu Mar 12 16:29:00 2009 +0100
@@ -1,13 +1,17 @@
-"""Views/forms and actions for the CubicWeb web client
+"""Views, forms, actions... for the CubicWeb web client
:organization: Logilab
:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
"""
__docformat__ = "restructuredtext en"
-
+
+import os
+from tempfile import mktemp
+
from rql import nodes
+
def need_table_view(rset, schema):
"""return True if we think that a table view is more appropriate than a
list or primary view to display the given result set
@@ -67,6 +71,7 @@
return 'outofcontext-search'
return 'list'
return 'table'
+
def linksearch_select_url(req, rset):
"""when searching an entity to create a relation, return an url to select
@@ -80,3 +85,21 @@
id_fmt = '%%s:%s:%s' % (r_type, eid)
triplets = '-'.join(id_fmt % row[0] for row in rset.rows)
return "javascript: selectForAssociation('%s', '%s');" % (triplets, eid)
+
+
+class TmpFileViewMixin(object):
+ binary = True
+ content_type = 'application/octet-stream'
+ cache_max_age = 60*60*2 # stay in http cache for 2 hours by default
+
+ def call(self):
+ self.cell_call()
+
+ def cell_call(self, row=0, col=0):
+ self.row, self.col = row, col # in case one need it
+ tmpfile = mktemp('.png')
+ try:
+ self._generate(tmpfile)
+ self.w(open(tmpfile).read())
+ finally:
+ os.unlink(tmpfile)
--- a/web/views/actions.py Thu Mar 12 16:23:31 2009 +0100
+++ b/web/views/actions.py Thu Mar 12 16:29:00 2009 +0100
@@ -266,18 +266,6 @@
order = 20
-class ViewSchemaAction(Action):
- id = 'schema'
- __select__ = yes()
-
- title = _("site schema")
- category = 'siteactions'
- order = 30
-
- def url(self):
- return self.build_url(self.id)
-
-
from logilab.common.deprecation import class_moved
from cubicweb.web.views.bookmark import FollowAction
FollowAction = class_moved(FollowAction)
--- a/web/views/basecomponents.py Thu Mar 12 16:23:31 2009 +0100
+++ b/web/views/basecomponents.py Thu Mar 12 16:29:00 2009 +0100
@@ -2,7 +2,6 @@
* the rql input form
* the logged user link
-* the workflow history section for workflowable objects
:organization: Logilab
:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
@@ -134,41 +133,6 @@
self.w(u'</div>')
-class WFHistoryVComponent(EntityVComponent):
- """display the workflow history for entities supporting it"""
- id = 'wfhistory'
- __select__ = (EntityVComponent.__select__
- & relation_possible('wf_info_for', role='object'))
- context = 'navcontentbottom'
- title = _('Workflow history')
-
- def cell_call(self, row, col, view=None):
- _ = self.req._
- eid = self.rset[row][col]
- sel = 'Any FS,TS,WF,D'
- rql = ' ORDERBY D DESC WHERE WF wf_info_for X,'\
- 'WF from_state FS, WF to_state TS, WF comment C,'\
- 'WF creation_date D'
- if self.vreg.schema.eschema('EUser').has_perm(self.req, 'read'):
- sel += ',U,C'
- rql += ', WF owned_by U?'
- displaycols = range(5)
- headers = (_('from_state'), _('to_state'), _('comment'), _('date'),
- _('EUser'))
- else:
- sel += ',C'
- displaycols = range(4)
- headers = (_('from_state'), _('to_state'), _('comment'), _('date'))
- rql = '%s %s, X eid %%(x)s' % (sel, rql)
- try:
- rset = self.req.execute(rql, {'x': eid}, 'x')
- except Unauthorized:
- return
- if rset:
- self.wview('table', rset, title=_(self.title), displayactions=False,
- displaycols=displaycols, headers=headers)
-
-
class ApplicationName(Component):
"""display the application name"""
id = 'appliname'
--- a/web/views/baseforms.py Thu Mar 12 16:23:31 2009 +0100
+++ b/web/views/baseforms.py Thu Mar 12 16:29:00 2009 +0100
@@ -14,188 +14,18 @@
from logilab.mtconverter import html_escape
from logilab.common.decorators import cached
-from cubicweb.interfaces import IWorkflowable
from cubicweb.selectors import (specified_etype_implements, implements,
match_kwargs, match_form_params, one_line_rset,
non_final_entity, accepts_etype_compat)
from cubicweb.utils import make_uid
from cubicweb.view import View, EntityView
-from cubicweb.common import tags
from cubicweb.common.uilib import cut
from cubicweb.web import INTERNAL_FIELD_VALUE, stdmsgs, eid_param
from cubicweb.web.controller import NAV_FORM_PARAMETERS
from cubicweb.web.widgets import checkbox, InputWidget, ComboBoxWidget
-from cubicweb.web.form import MultipleFieldsForm, EntityFieldsForm, FormMixIn, relation_id
-from cubicweb.web.formfields import StringField, RichTextField, find_field
-from cubicweb.web.formwidgets import HiddenInput
+from cubicweb.web.form import FormMixIn
_ = unicode
-
-
-
-class DeleteConfForm(EntityView):
- id = 'deleteconf'
- title = _('delete')
- domid = 'deleteconf'
- onsubmit = None
- # don't use navigation, all entities asked to be deleted should be displayed
- # else we will only delete the displayed page
- need_navigation = False
-
- def call(self):
- """ask for confirmation before real deletion"""
- req, w = self.req, self.w
- _ = req._
- req.add_css('cubicweb.form.css')
- req.add_js('cubicweb.edition.js')
- w(u'<script type="text/javascript">updateMessage(\'%s\');</script>\n'
- % _('this action is not reversible!'))
- # XXX above message should have style of a warning
- w(u'<h4>%s</h4>\n' % _('Do you want to delete the following element(s) ?'))
- form = MultipleFieldsForm(req, domid='deleteconf', action=self.build_url('edit'),
- onsubmit=self.onsubmit, copy_nav_params=True)
- form.buttons.append(form.button_delete(label=stdmsgs.YES))
- form.buttons.append(form.button_cancel(label=stdmsgs.NO))
- done = set()
- w(u'<ul>\n')
- for i in xrange(self.rset.rowcount):
- if self.rset[i][0] in done:
- continue
- done.add(self.rset[i][0])
- entity = self.rset.get_entity(i, 0)
- subform = EntityFieldsForm(req, set_error_url=False,
- entity=entity)
- form.form_add_subform(subform)
- # don't use outofcontext view or any other that may contain inline edition form
- w(u'<li>%s</li>' % tags.a(entity.view('textoutofcontext'),
- href=entity.absolute_url()))
- w(u'</ul>\n')
- w(form.form_render())
-
-
-class ChangeStateForm(EntityFieldsForm):
- id = 'changestate'
-
- __method = StringField(name='__method', initial='set_state', widget=HiddenInput)
- state = StringField(widget=HiddenInput, eidparam=True)
- trcomment = RichTextField(eidparam=True)
-
- def form_buttons(self):
- return [self.button_ok(label=stdmsgs.YES,
- tabindex=self.req.next_tabindex()),
- self.button_cancel(label=stdmsgs.NO,
- tabindex=self.req.next_tabindex())]
-
-
-class ChangeStateFormView(EntityView):
- id = 'statuschange'
- title = _('status change')
- __select__ = implements(IWorkflowable) & match_form_params('treid')
-
- def cell_call(self, row, col):
- entity = self.entity(row, col)
- eid = entity.eid
- state = entity.in_state[0]
- transition = self.req.eid_rset(self.req.form['treid']).get_entity(0, 0)
- dest = transition.destination()
- self.req.add_js('cubicweb.edition.js')
- self.req.add_css('cubicweb.form.css')
- _ = self.req._
- form = self.vreg.select_object('forms', 'changestate', self.req, self.rset, row, col,
- entity=entity, redirect_path=self.redirectpath(entity))
- self.w(form.error_message())
- self.w(u'<h4>%s %s</h4>\n' % (_(transition.name), entity.view('oneline')))
- msg = _('status will change from %(st1)s to %(st2)s') % {
- 'st1': _(state.name),
- 'st2': _(dest.name)}
- self.w(u'<p>%s</p>\n' % msg)
- self.w(form.form_render(state=dest.eid, trcomment=u''))
-
- def redirectpath(self, entity):
- return entity.rest_path()
-
-
-class ClickAndEditForm(FormMixIn, EntityView):
- id = 'reledit'
- __select__ = match_kwargs('rtype')
-
- # FIXME editableField class could be toggleable from userprefs
-
- def cell_call(self, row, col, rtype=None, role='subject', reload=False):
- entity = self.entity(row, col)
- if getattr(entity, rtype) is None:
- value = self.req._('not specified')
- else:
- value = entity.printable_value(rtype)
- if not entity.has_perm('update'):
- self.w(value)
- return
- self.req.add_js( ('cubicweb.ajax.js', 'cubicweb.edition.js') )
- eid = entity.eid
- edit_key = make_uid('%s-%s' % (rtype, eid))
- divid = 'd%s' % edit_key
- reload = dumps(reload)
- buttons = [tags.input(klass="validateButton", type="submit", name="__action_apply",
- value=self.req._(stdmsgs.BUTTON_OK), tabindex=self.req.next_tabindex()),
- tags.input(klass="validateButton", type="button",
- value=self.req._(stdmsgs.BUTTON_CANCEL),
- onclick="cancelInlineEdit(%s,\'%s\',\'%s\')" % (eid, rtype, divid),
- tabindex=self.req.next_tabindex())]
- form = self.vreg.select_object('forms', 'edition', self.req, self.rset, row=row, col=col,
- entity=entity, domid='%s-form' % divid, action='#',
- cssstyle='display: none', buttons=buttons,
- onsubmit="return inlineValidateForm('%(divid)s-form', '%(rtype)s', '%(eid)s', '%(divid)s', %(reload)s);" % locals())
- renderer = FormRenderer(display_label=False, display_help=False,
- display_fields=(rtype,), button_bar_class='buttonbar')
- self.w(tags.div(value, klass='editableField', id=divid,
- ondblclick="showInlineEditionForm(%(eid)s, '%(rtype)s', '%(divid)s')" % locals()))
- self.w(form.render(renderer=renderer)
-
-
-## work in progress, this class will replace the standard EditionView soon
-class _EditionForm(EntityView):
- """primary entity edition form
-
- When generating a new attribute_input, the editor will look for a method
- named 'default_ATTRNAME' on the entity instance, where ATTRNAME is the
- name of the attribute being edited. You may use this feature to compute
- dynamic default values such as the 'tomorrow' date or the user's login
- being connected
- """
- id = '_edition'
- __select__ = one_line_rset() & non_final_entity()
-
- title = _('edition')
- controller = 'edit'
- skip_relations = FormMixIn.skip_relations.copy()
-
- def cell_call(self, row, col, **kwargs):
- self.req.add_js( ('cubicweb.ajax.js', 'cubicweb.edition.js') )
- self.req.add_css('cubicweb.form.css')
- self.initialize_varmaker()
- entity = self.complete_entity(row, col)
-
- def initialize_varmaker(self):
- varmaker = self.req.get_page_data('rql_varmaker')
- if varmaker is None:
- varmaker = self.req.varmaker
- self.req.set_page_data('rql_varmaker', varmaker)
- self.varmaker = varmaker
-
- def edit_form(self, entity, kwargs):
- form = EntityFieldsForm(self.req, entity=entity)
- for rschema, target in self.editable_attributes(entity):
- field = find_field(entity.__class__, entity.e_schema, rschema, target)
- form.fields.append(field)
- form.buttons.append(form.button_ok())
- form.buttons.append(form.button_apply())
- form.buttons.append(form.button_cancel())
- self.w(form.form_render())
-
- def editable_attributes(self, entity):
- # XXX both (add, delete)
- return [(rschema, x) for rschema, _, x in entity.relations_by_category(('primary', 'secondary'), 'add')
- if rschema != 'eid']
class EditionForm(FormMixIn, EntityView):
@@ -242,8 +72,7 @@
'''
def cell_call(self, row, col, **kwargs):
- self.req.add_js( ('cubicweb.ajax.js', 'cubicweb.edition.js') )
- self.req.add_css('cubicweb.form.css')
+ self.req.add_js( ('cubicweb.ajax.js', ) )
entity = self.complete_entity(row, col)
self.edit_form(entity, kwargs)
@@ -512,7 +341,7 @@
-class CreationForm(_EditionForm):
+class CreationForm(EditionForm):
__select__ = specified_etype_implements('Any')
# XXX bw compat, use View.registered since we don't want accept_compat
# wrapper set in EntityView
@@ -522,8 +351,7 @@
def call(self, **kwargs):
"""creation view for an entity"""
- self.req.add_js( ('cubicweb.ajax.js', 'cubicweb.edition.js') )
- self.req.add_css('cubicweb.form.css')
+ self.req.add_js( ('cubicweb.ajax.js',) )
self.initialize_varmaker()
etype = kwargs.pop('etype', self.req.form.get('etype'))
try:
@@ -657,7 +485,6 @@
:param rtype: the relation bridging `etype` and `parent`
:param role: the role played by the `parent` in the relation
"""
- self.req.add_css('cubicweb.form.css')
try:
entity = self.vreg.etype_class(etype)(self.req, None, None)
except:
@@ -694,7 +521,6 @@
insertions of <div class="section"> between each row of
the resultset
"""
- self.req.add_css('cubicweb.form.css')
rset = self.rset
for i in xrange(len(rset)):
self.wview(self.id, rset, row=i, **kwargs)
@@ -726,8 +552,7 @@
title = _('copy edition')
def cell_call(self, row, col, **kwargs):
- self.req.add_js(('cubicweb.ajax.js', 'cubicweb.edition.js'))
- self.req.add_css('cubicweb.form.css')
+ self.req.add_js(('cubicweb.ajax.js',))
entity = self.complete_entity(row, col, skip_bytes=True)
# make a copy of entity to avoid altering the entity in the
# request's cache.
@@ -807,8 +632,6 @@
"""
req = self.req
form = req.form
- req.add_js('cubicweb.edition.js')
- req.add_css('cubicweb.form.css')
_ = req._
sampleentity = self.complete_entity(0)
attrheaders = [u'<th>%s</th>' % rdef[0].display_name(req, rdef[-1])
--- a/web/views/baseviews.py Thu Mar 12 16:23:31 2009 +0100
+++ b/web/views/baseviews.py Thu Mar 12 16:29:00 2009 +0100
@@ -4,7 +4,6 @@
* primary, sidebox
* secondary, oneline, incontext, outofcontext, text
* list
-* xml, rss
:organization: Logilab
--- a/web/views/boxes.py Thu Mar 12 16:23:31 2009 +0100
+++ b/web/views/boxes.py Thu Mar 12 16:29:00 2009 +0100
@@ -3,7 +3,6 @@
* actions box
* possible views box
-* rss icon
additional (disabled by default) boxes
* schema box
@@ -186,26 +185,6 @@
if not box.is_empty():
box.render(self.w)
-
-class RSSIconBox(BoxTemplate):
- """just display the RSS icon on uniform result set"""
- id = 'rss'
- __select__ = (BoxTemplate.__select__
- & appobject_selectable('components', 'rss_feed_url'))
-
- visible = False
- order = 999
-
- def call(self, **kwargs):
- try:
- rss = self.req.external_resource('RSS_LOGO')
- except KeyError:
- self.error('missing RSS_LOGO external resource')
- return
- urlgetter = self.vreg.select_component('rss_feed_url', self.req, self.rset)
- url = urlgetter.feed_url()
- self.w(u'<a href="%s"><img src="%s" alt="rss"/></a>\n' % (html_escape(url), rss))
-
class StartupViewsBox(BoxTemplate):
"""display a box containing links to all startup views"""
--- a/web/views/dynimages.py Thu Mar 12 16:23:31 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,199 +0,0 @@
-"""dynamically generated image views
-
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-"""
-__docformat__ = "restructuredtext en"
-
-import os
-from tempfile import mktemp
-from itertools import cycle
-
-from logilab.common.graph import escape, GraphGenerator, DotBackend
-from yams import schema2dot as s2d
-
-from cubicweb.selectors import implements
-from cubicweb.view import EntityView, StartupView
-
-
-class RestrictedSchemaDotPropsHandler(s2d.SchemaDotPropsHandler):
- def __init__(self, req):
- # FIXME: colors are arbitrary
- self.nextcolor = cycle( ('#aa0000', '#00aa00', '#0000aa',
- '#000000', '#888888') ).next
- self.req = req
-
- def display_attr(self, rschema):
- return not rschema.meta and (rschema.has_local_role('read')
- or rschema.has_perm(self.req, 'read'))
-
- # XXX remove this method once yams > 0.20 is out
- def node_properties(self, eschema):
- """return default DOT drawing options for an entity schema"""
- label = ['{',eschema.type,'|']
- label.append(r'\l'.join(rel.type for rel in eschema.subject_relations()
- if rel.final and self.display_attr(rel)))
- label.append(r'\l}') # trailing \l ensure alignement of the last one
- return {'label' : ''.join(label), 'shape' : "record",
- 'fontname' : "Courier", 'style' : "filled"}
-
- def edge_properties(self, rschema, subjnode, objnode):
- kwargs = super(RestrictedSchemaDotPropsHandler, self).edge_properties(rschema, subjnode, objnode)
- # symetric rels are handled differently, let yams decide what's best
- if not rschema.symetric:
- kwargs['color'] = self.nextcolor()
- kwargs['fontcolor'] = kwargs['color']
- # dot label decoration is just awful (1 line underlining the label
- # + 1 line going to the closest edge spline point)
- kwargs['decorate'] = 'false'
- return kwargs
-
-
-class RestrictedSchemaVisitorMiIn:
- def __init__(self, req, *args, **kwargs):
- # hack hack hack
- assert len(self.__class__.__bases__) == 2
- self.__parent = self.__class__.__bases__[1]
- self.__parent.__init__(self, *args, **kwargs)
- self.req = req
-
- def nodes(self):
- for etype, eschema in self.__parent.nodes(self):
- if eschema.has_local_role('read') or eschema.has_perm(self.req, 'read'):
- yield eschema.type, eschema
-
- def edges(self):
- for setype, oetype, rschema in self.__parent.edges(self):
- if rschema.has_local_role('read') or rschema.has_perm(self.req, 'read'):
- yield setype, oetype, rschema
-
-class FullSchemaVisitor(RestrictedSchemaVisitorMiIn, s2d.FullSchemaVisitor):
- pass
-
-class OneHopESchemaVisitor(RestrictedSchemaVisitorMiIn, s2d.OneHopESchemaVisitor):
- pass
-
-class OneHopRSchemaVisitor(RestrictedSchemaVisitorMiIn, s2d.OneHopRSchemaVisitor):
- pass
-
-
-class TmpFileViewMixin(object):
- binary = True
- content_type = 'application/octet-stream'
- cache_max_age = 60*60*2 # stay in http cache for 2 hours by default
-
- def call(self):
- self.cell_call()
-
- def cell_call(self, row=0, col=0):
- self.row, self.col = row, col # in case one need it
- tmpfile = mktemp('.png')
- try:
- self._generate(tmpfile)
- self.w(open(tmpfile).read())
- finally:
- os.unlink(tmpfile)
-
-class SchemaImageView(TmpFileViewMixin, StartupView):
- id = 'schemagraph'
- content_type = 'image/png'
- skip_rels = ('owned_by', 'created_by', 'identity', 'is', 'is_instance_of')
- def _generate(self, tmpfile):
- """display global schema information"""
- skipmeta = not int(self.req.form.get('withmeta', 0))
- visitor = FullSchemaVisitor(self.req, self.schema, skiprels=self.skip_rels, skipmeta=skipmeta)
- s2d.schema2dot(outputfile=tmpfile, visitor=visitor,
- prophdlr=RestrictedSchemaDotPropsHandler(self.req))
-
-class EETypeSchemaImageView(TmpFileViewMixin, EntityView):
- id = 'eschemagraph'
- content_type = 'image/png'
- __select__ = implements('EEType')
- skip_rels = ('owned_by', 'created_by', 'identity', 'is', 'is_instance_of')
-
- def _generate(self, tmpfile):
- """display schema information for an entity"""
- entity = self.entity(self.row, self.col)
- eschema = self.vreg.schema.eschema(entity.name)
- visitor = OneHopESchemaVisitor(self.req, eschema, skiprels=self.skip_rels)
- s2d.schema2dot(outputfile=tmpfile, visitor=visitor,
- prophdlr=RestrictedSchemaDotPropsHandler(self.req))
-
-class ERTypeSchemaImageView(EETypeSchemaImageView):
- __select__ = implements('ERType')
-
- def _generate(self, tmpfile):
- """display schema information for an entity"""
- entity = self.entity(self.row, self.col)
- rschema = self.vreg.schema.rschema(entity.name)
- visitor = OneHopRSchemaVisitor(self.req, rschema)
- s2d.schema2dot(outputfile=tmpfile, visitor=visitor,
- prophdlr=RestrictedSchemaDotPropsHandler(self.req))
-
-
-
-class WorkflowDotPropsHandler(object):
- def __init__(self, req):
- self._ = req._
-
- def node_properties(self, stateortransition):
- """return default DOT drawing options for a state or transition"""
- props = {'label': stateortransition.name,
- 'fontname': 'Courier'}
- if hasattr(stateortransition, 'state_of'):
- props['shape'] = 'box'
- props['style'] = 'filled'
- if stateortransition.reverse_initial_state:
- props['color'] = '#88CC88'
- else:
- props['shape'] = 'ellipse'
- descr = []
- tr = stateortransition
- if tr.require_group:
- descr.append('%s %s'% (
- self._('groups:'),
- ','.join(g.name for g in tr.require_group)))
- if tr.condition:
- descr.append('%s %s'% (self._('condition:'), tr.condition))
- if descr:
- props['label'] += escape('\n'.join(descr))
- return props
-
- def edge_properties(self, transition, fromstate, tostate):
- return {'label': '', 'dir': 'forward',
- 'color': 'black', 'style': 'filled'}
-
-class WorkflowVisitor:
- def __init__(self, entity):
- self.entity = entity
-
- def nodes(self):
- for state in self.entity.reverse_state_of:
- state.complete()
- yield state.eid, state
-
- for transition in self.entity.reverse_transition_of:
- transition.complete()
- yield transition.eid, transition
-
- def edges(self):
- for transition in self.entity.reverse_transition_of:
- for incomingstate in transition.reverse_allowed_transition:
- yield incomingstate.eid, transition.eid, transition
- yield transition.eid, transition.destination().eid, transition
-
-
-class EETypeWorkflowImageView(TmpFileViewMixin, EntityView):
- id = 'ewfgraph'
- content_type = 'image/png'
- __select__ = implements('EEType')
-
- def _generate(self, tmpfile):
- """display schema information for an entity"""
- entity = self.entity(self.row, self.col)
- visitor = WorkflowVisitor(entity)
- prophdlr = WorkflowDotPropsHandler(self.req)
- generator = GraphGenerator(DotBackend('workflow', 'LR',
- ratio='compress', size='30,12'))
- return generator.generate(visitor, prophdlr, tmpfile)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/views/editforms.py Thu Mar 12 16:29:00 2009 +0100
@@ -0,0 +1,170 @@
+"""Set of HTML automatic forms to create, delete, copy or edit a single entity
+or a list of entities of the same type
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+from copy import copy
+
+from simplejson import dumps
+
+from logilab.mtconverter import html_escape
+from logilab.common.decorators import cached
+
+from cubicweb.selectors import (specified_etype_implements, implements,
+ match_kwargs, match_form_params, one_line_rset,
+ non_final_entity, accepts_etype_compat)
+from cubicweb.utils import make_uid
+from cubicweb.view import View, EntityView
+from cubicweb.common import tags
+from cubicweb.common.uilib import cut
+from cubicweb.web import INTERNAL_FIELD_VALUE, stdmsgs, eid_param
+from cubicweb.web.controller import NAV_FORM_PARAMETERS
+from cubicweb.web.form import MultipleFieldsForm, EntityFieldsForm, FormMixIn, FormRenderer
+from cubicweb.web.formfields import StringField, RichTextField, guess_field
+from cubicweb.web.formwidgets import HiddenInput
+
+_ = unicode
+
+
+class DeleteConfForm(EntityView):
+ id = 'deleteconf'
+ title = _('delete')
+ domid = 'deleteconf'
+ onsubmit = None
+ # don't use navigation, all entities asked to be deleted should be displayed
+ # else we will only delete the displayed page
+ need_navigation = False
+
+ def call(self):
+ """ask for confirmation before real deletion"""
+ req, w = self.req, self.w
+ _ = req._
+ w(u'<script type="text/javascript">updateMessage(\'%s\');</script>\n'
+ % _('this action is not reversible!'))
+ # XXX above message should have style of a warning
+ w(u'<h4>%s</h4>\n' % _('Do you want to delete the following element(s) ?'))
+ form = MultipleFieldsForm(req, domid='deleteconf', action=self.build_url('edit'),
+ onsubmit=self.onsubmit, copy_nav_params=True)
+ form.buttons.append(form.button_delete(label=stdmsgs.YES))
+ form.buttons.append(form.button_cancel(label=stdmsgs.NO))
+ done = set()
+ w(u'<ul>\n')
+ for i in xrange(self.rset.rowcount):
+ if self.rset[i][0] in done:
+ continue
+ done.add(self.rset[i][0])
+ entity = self.rset.get_entity(i, 0)
+ subform = EntityFieldsForm(req, set_error_url=False,
+ entity=entity)
+ form.form_add_subform(subform)
+ # don't use outofcontext view or any other that may contain inline edition form
+ w(u'<li>%s</li>' % tags.a(entity.view('textoutofcontext'),
+ href=entity.absolute_url()))
+ w(u'</ul>\n')
+ w(form.form_render())
+
+
+class ClickAndEditForm(FormMixIn, EntityView):
+ id = 'reledit'
+ __select__ = non_final_entity() & match_kwargs('rtype')
+
+ # FIXME editableField class could be toggleable from userprefs
+
+ def cell_call(self, row, col, rtype=None, role='subject', reload=False):
+ entity = self.entity(row, col)
+ if getattr(entity, rtype) is None:
+ value = self.req._('not specified')
+ else:
+ value = entity.printable_value(rtype)
+ if not entity.has_perm('update'):
+ self.w(value)
+ return
+ self.req.add_js( ('cubicweb.ajax.js',) )
+ eid = entity.eid
+ edit_key = make_uid('%s-%s' % (rtype, eid))
+ divid = 'd%s' % edit_key
+ reload = dumps(reload)
+ buttons = [tags.input(klass="validateButton", type="submit", name="__action_apply",
+ value=self.req._(stdmsgs.BUTTON_OK), tabindex=self.req.next_tabindex()),
+ tags.input(klass="validateButton", type="button",
+ value=self.req._(stdmsgs.BUTTON_CANCEL),
+ onclick="cancelInlineEdit(%s,\'%s\',\'%s\')" % (eid, rtype, divid),
+ tabindex=self.req.next_tabindex())]
+ form = self.vreg.select_object('forms', 'edition', self.req, self.rset, row=row, col=col,
+ entity=entity, domid='%s-form' % divid, action='#',
+ cssstyle='display: none', buttons=buttons,
+ onsubmit="return inlineValidateForm('%(divid)s-form', '%(rtype)s', '%(eid)s', '%(divid)s', %(reload)s);" % locals())
+ renderer = FormRenderer(display_label=False, display_help=False,
+ display_fields=(rtype,), button_bar_class='buttonbar')
+ self.w(tags.div(value, klass='editableField', id=divid,
+ ondblclick="showInlineEditionForm(%(eid)s, '%(rtype)s', '%(divid)s')" % locals()))
+ self.w(form.render(renderer=renderer))
+
+
+class AutomaticEntityForm(EntityFieldsForm):
+ id = 'edition'
+ needs_js = EntityFieldsForm.needs_js + ('cubicweb.ajax.js',)
+
+ def __init__(self, *args, **kwargs):
+ super(AutomaticEntityForm, self).__init__(*args, **kwargs)
+ self.entity.complete()
+ for rschema, target in self.editable_attributes(entity):
+ field = guess_field(entity.__class__, entity.e_schema, rschema, target)
+ self.fields.append(field)
+
+ def form_buttons(self):
+ return [self.button_ok(tabindex=self.req.next_tabindex()),
+ self.button_apply(tabindex=self.req.next_tabindex()),
+ self.button_cancel(tabindex=self.req.next_tabindex())]
+
+ def editable_attributes(self, entity):
+ # XXX both (add, delete) required for non final relations
+ return [(rschema, x) for rschema, _, x in entity.relations_by_category(('primary', 'secondary'), 'add')
+ if rschema != 'eid']
+
+class _EditionForm(EntityView):
+ """primary entity edition form
+
+ When generating a new attribute_input, the editor will look for a method
+ named 'default_ATTRNAME' on the entity instance, where ATTRNAME is the
+ name of the attribute being edited. You may use this feature to compute
+ dynamic default values such as the 'tomorrow' date or the user's login
+ being connected
+ """
+ id = 'edition'
+ __select__ = one_line_rset() & non_final_entity()
+
+ title = _('edition')
+ controller = 'edit'
+ skip_relations = FormMixIn.skip_relations.copy()
+
+ def cell_call(self, row, col, **kwargs):
+ self.req.add_js( ('cubicweb.ajax.js',) )
+ self.initialize_varmaker()
+ entity = self.complete_entity(row, col)
+
+ def initialize_varmaker(self):
+ varmaker = self.req.get_page_data('rql_varmaker')
+ if varmaker is None:
+ varmaker = self.req.varmaker
+ self.req.set_page_data('rql_varmaker', varmaker)
+ self.varmaker = varmaker
+
+ def edit_form(self, entity, kwargs):
+ form = EntityFieldsForm(self.req, entity=entity)
+ for rschema, target in self.editable_attributes(entity):
+ field = guess_field(entity.__class__, entity.e_schema, rschema, target)
+ form.fields.append(field)
+ form.buttons.append(form.button_ok())
+ form.buttons.append(form.button_apply())
+ form.buttons.append(form.button_cancel())
+ self.w(form.form_render())
+
+ def editable_attributes(self, entity):
+ # XXX both (add, delete)
+ return [(rschema, x) for rschema, _, x in entity.relations_by_category(('primary', 'secondary'), 'add')
+ if rschema != 'eid']
--- a/web/views/massmailing.py Thu Mar 12 16:23:31 2009 +0100
+++ b/web/views/massmailing.py Thu Mar 12 16:29:00 2009 +0100
@@ -61,7 +61,7 @@
def form_buttons(self):
context = {'domid': self.domid,
'cancel' : self.req._(stdmsgs.BUTTON_CANCEL),
- 'cancelimgpath' : self.req.external_resource('CANCEL_EMAIL_ICON'),j
+ 'cancelimgpath' : self.req.external_resource('CANCEL_EMAIL_ICON'),
'send' : self.req._('send email'),
'sendimgpath' : self.req.external_resource('SEND_EMAIL_ICON'),
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/views/schema.py Thu Mar 12 16:29:00 2009 +0100
@@ -0,0 +1,220 @@
+"""Specific views for schema related entities
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+from itertools import cycle
+
+from logilab.mtconverter import html_escape
+from logilab.common.graph import escape, GraphGenerator, DotBackend
+from yams import schema2dot as s2d
+
+from cubicweb.selectors import implements, rql_condition, yes
+from cubicweb.schemaviewer import SchemaViewer
+from cubicweb.view import EntityView, StartupView
+from cubicweb.common.uilib import ureport_as_html
+from cubicweb.web.action import Action
+from cubicweb.web.views import baseviews
+from cubicweb.web.views import TmpFileViewMixin
+
+
+class ViewSchemaAction(Action):
+ id = 'schema'
+ __select__ = yes()
+
+ title = _("site schema")
+ category = 'siteactions'
+ order = 30
+
+ def url(self):
+ return self.build_url(self.id)
+
+
+# schema entity types views ###################################################
+
+class _SchemaEntityPrimaryView(baseviews.PrimaryView):
+ show_attr_label = False
+ cache_max_age = 60*60*2 # stay in http cache for 2 hours by default
+
+ def content_title(self, entity):
+ return html_escape(entity.dc_long_title())
+
+class EETypePrimaryView(_SchemaEntityPrimaryView):
+ __select__ = implements('EEType')
+ skip_attrs = _SchemaEntityPrimaryView.skip_attrs + ('name', 'meta', 'final')
+
+class ERTypePrimaryView(_SchemaEntityPrimaryView):
+ __select__ = implements('ERType')
+ skip_attrs = _SchemaEntityPrimaryView.skip_attrs + ('name', 'meta', 'final',
+ 'symetric', 'inlined')
+
+class ErdefPrimaryView(_SchemaEntityPrimaryView):
+ __select__ = implements('EFRDef', 'ENFRDef')
+ show_attr_label = True
+
+class EETypeOneLineView(baseviews.OneLineView):
+ __select__ = implements('EEType')
+
+ def cell_call(self, row, col, **kwargs):
+ entity = self.entity(row, col)
+ final = entity.final
+ if final:
+ self.w(u'<em class="finalentity">')
+ super(EETypeOneLineView, self).cell_call(row, col, **kwargs)
+ if final:
+ self.w(u'</em>')
+
+
+# in memory schema views (yams class instances) ###############################
+
+class EETypeSchemaView(EETypePrimaryView):
+ id = 'eschema'
+ title = _('in memory entity schema')
+ main_related_section = False
+ skip_rels = ('is', 'is_instance_of', 'identity', 'created_by', 'owned_by',
+ 'has_text',)
+
+ def render_entity_attributes(self, entity, siderelations):
+ super(EETypeSchemaView, self).render_entity_attributes(entity, siderelations)
+ eschema = self.vreg.schema.eschema(entity.name)
+ viewer = SchemaViewer(self.req)
+ layout = viewer.visit_entityschema(eschema, skiprels=self.skip_rels)
+ self.w(ureport_as_html(layout))
+ if not eschema.is_final():
+ self.w(u'<img src="%s" alt="%s"/>' % (
+ html_escape(entity.absolute_url(vid='eschemagraph')),
+ html_escape(self.req._('graphical schema for %s') % entity.name)))
+
+
+class ERTypeSchemaView(ERTypePrimaryView):
+ id = 'eschema'
+ title = _('in memory relation schema')
+ main_related_section = False
+
+ def render_entity_attributes(self, entity, siderelations):
+ super(ERTypeSchemaView, self).render_entity_attributes(entity, siderelations)
+ rschema = self.vreg.schema.rschema(entity.name)
+ viewer = SchemaViewer(self.req)
+ layout = viewer.visit_relationschema(rschema)
+ self.w(ureport_as_html(layout))
+ if not rschema.is_final():
+ self.w(u'<img src="%s" alt="%s"/>' % (
+ html_escape(entity.absolute_url(vid='eschemagraph')),
+ html_escape(self.req._('graphical schema for %s') % entity.name)))
+
+
+# schema images ###############################################################
+
+class ImageView(EntityView):
+ __select__ = implements('EEType')
+ id = 'image'
+ title = _('image')
+
+ def cell_call(self, row, col):
+ entity = self.entity(row, col)
+ url = entity.absolute_url(vid='eschemagraph')
+ self.w(u'<img src="%s" alt="%s"/>' % (
+ html_escape(url),
+ html_escape(self.req._('graphical schema for %s') % entity.name)))
+
+
+class RestrictedSchemaDotPropsHandler(s2d.SchemaDotPropsHandler):
+ def __init__(self, req):
+ # FIXME: colors are arbitrary
+ self.nextcolor = cycle( ('#aa0000', '#00aa00', '#0000aa',
+ '#000000', '#888888') ).next
+ self.req = req
+
+ def display_attr(self, rschema):
+ return not rschema.meta and (rschema.has_local_role('read')
+ or rschema.has_perm(self.req, 'read'))
+
+ # XXX remove this method once yams > 0.20 is out
+ def node_properties(self, eschema):
+ """return default DOT drawing options for an entity schema"""
+ label = ['{',eschema.type,'|']
+ label.append(r'\l'.join(rel.type for rel in eschema.subject_relations()
+ if rel.final and self.display_attr(rel)))
+ label.append(r'\l}') # trailing \l ensure alignement of the last one
+ return {'label' : ''.join(label), 'shape' : "record",
+ 'fontname' : "Courier", 'style' : "filled"}
+
+ def edge_properties(self, rschema, subjnode, objnode):
+ kwargs = super(RestrictedSchemaDotPropsHandler, self).edge_properties(rschema, subjnode, objnode)
+ # symetric rels are handled differently, let yams decide what's best
+ if not rschema.symetric:
+ kwargs['color'] = self.nextcolor()
+ kwargs['fontcolor'] = kwargs['color']
+ # dot label decoration is just awful (1 line underlining the label
+ # + 1 line going to the closest edge spline point)
+ kwargs['decorate'] = 'false'
+ return kwargs
+
+
+class RestrictedSchemaVisitorMiIn:
+ def __init__(self, req, *args, **kwargs):
+ # hack hack hack
+ assert len(self.__class__.__bases__) == 2
+ self.__parent = self.__class__.__bases__[1]
+ self.__parent.__init__(self, *args, **kwargs)
+ self.req = req
+
+ def nodes(self):
+ for etype, eschema in self.__parent.nodes(self):
+ if eschema.has_local_role('read') or eschema.has_perm(self.req, 'read'):
+ yield eschema.type, eschema
+
+ def edges(self):
+ for setype, oetype, rschema in self.__parent.edges(self):
+ if rschema.has_local_role('read') or rschema.has_perm(self.req, 'read'):
+ yield setype, oetype, rschema
+
+
+class FullSchemaVisitor(RestrictedSchemaVisitorMiIn, s2d.FullSchemaVisitor):
+ pass
+
+class OneHopESchemaVisitor(RestrictedSchemaVisitorMiIn, s2d.OneHopESchemaVisitor):
+ pass
+
+class OneHopRSchemaVisitor(RestrictedSchemaVisitorMiIn, s2d.OneHopRSchemaVisitor):
+ pass
+
+
+class SchemaImageView(TmpFileViewMixin, StartupView):
+ id = 'schemagraph'
+ content_type = 'image/png'
+ skip_rels = ('owned_by', 'created_by', 'identity', 'is', 'is_instance_of')
+ def _generate(self, tmpfile):
+ """display global schema information"""
+ skipmeta = not int(self.req.form.get('withmeta', 0))
+ visitor = FullSchemaVisitor(self.req, self.schema, skiprels=self.skip_rels, skipmeta=skipmeta)
+ s2d.schema2dot(outputfile=tmpfile, visitor=visitor,
+ prophdlr=RestrictedSchemaDotPropsHandler(self.req))
+
+class EETypeSchemaImageView(TmpFileViewMixin, EntityView):
+ id = 'eschemagraph'
+ content_type = 'image/png'
+ __select__ = implements('EEType')
+ skip_rels = ('owned_by', 'created_by', 'identity', 'is', 'is_instance_of')
+
+ def _generate(self, tmpfile):
+ """display schema information for an entity"""
+ entity = self.entity(self.row, self.col)
+ eschema = self.vreg.schema.eschema(entity.name)
+ visitor = OneHopESchemaVisitor(self.req, eschema, skiprels=self.skip_rels)
+ s2d.schema2dot(outputfile=tmpfile, visitor=visitor,
+ prophdlr=RestrictedSchemaDotPropsHandler(self.req))
+
+class ERTypeSchemaImageView(EETypeSchemaImageView):
+ __select__ = implements('ERType')
+
+ def _generate(self, tmpfile):
+ """display schema information for an entity"""
+ entity = self.entity(self.row, self.col)
+ rschema = self.vreg.schema.rschema(entity.name)
+ visitor = OneHopRSchemaVisitor(self.req, rschema)
+ s2d.schema2dot(outputfile=tmpfile, visitor=visitor,
+ prophdlr=RestrictedSchemaDotPropsHandler(self.req))
--- a/web/views/schemaentities.py Thu Mar 12 16:23:31 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,124 +0,0 @@
-"""Specific views for schema related entities
-
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-"""
-__docformat__ = "restructuredtext en"
-
-from logilab.mtconverter import html_escape
-
-from cubicweb.selectors import implements, rql_condition
-from cubicweb.schemaviewer import SchemaViewer
-from cubicweb.view import EntityView
-from cubicweb.common.uilib import ureport_as_html
-from cubicweb.web.views import baseviews
-
-
-class ImageView(EntityView):
- __select__ = implements('EEType')
- id = 'image'
- title = _('image')
-
- def cell_call(self, row, col):
- entity = self.entity(row, col)
- url = entity.absolute_url(vid='eschemagraph')
- self.w(u'<img src="%s" alt="%s"/>' % (
- html_escape(url),
- html_escape(self.req._('graphical schema for %s') % entity.name)))
-
-
-class _SchemaEntityPrimaryView(baseviews.PrimaryView):
- show_attr_label = False
- cache_max_age = 60*60*2 # stay in http cache for 2 hours by default
-
- def content_title(self, entity):
- return html_escape(entity.dc_long_title())
-
-class EETypePrimaryView(_SchemaEntityPrimaryView):
- __select__ = implements('EEType')
- skip_attrs = _SchemaEntityPrimaryView.skip_attrs + ('name', 'meta', 'final')
-
-class ERTypePrimaryView(_SchemaEntityPrimaryView):
- __select__ = implements('ERType')
- skip_attrs = _SchemaEntityPrimaryView.skip_attrs + ('name', 'meta', 'final',
- 'symetric', 'inlined')
-
-class ErdefPrimaryView(_SchemaEntityPrimaryView):
- __select__ = implements('EFRDef', 'ENFRDef')
- show_attr_label = True
-
-class EETypeSchemaView(EETypePrimaryView):
- id = 'eschema'
- title = _('in memory entity schema')
- main_related_section = False
- skip_rels = ('is', 'is_instance_of', 'identity', 'created_by', 'owned_by',
- 'has_text',)
-
- def render_entity_attributes(self, entity, siderelations):
- super(EETypeSchemaView, self).render_entity_attributes(entity, siderelations)
- eschema = self.vreg.schema.eschema(entity.name)
- viewer = SchemaViewer(self.req)
- layout = viewer.visit_entityschema(eschema, skiprels=self.skip_rels)
- self.w(ureport_as_html(layout))
- if not eschema.is_final():
- self.w(u'<img src="%s" alt="%s"/>' % (
- html_escape(entity.absolute_url(vid='eschemagraph')),
- html_escape(self.req._('graphical schema for %s') % entity.name)))
-
-class ERTypeSchemaView(ERTypePrimaryView):
- id = 'eschema'
- title = _('in memory relation schema')
- main_related_section = False
-
- def render_entity_attributes(self, entity, siderelations):
- super(ERTypeSchemaView, self).render_entity_attributes(entity, siderelations)
- rschema = self.vreg.schema.rschema(entity.name)
- viewer = SchemaViewer(self.req)
- layout = viewer.visit_relationschema(rschema)
- self.w(ureport_as_html(layout))
- if not rschema.is_final():
- self.w(u'<img src="%s" alt="%s"/>' % (
- html_escape(entity.absolute_url(vid='eschemagraph')),
- html_escape(self.req._('graphical schema for %s') % entity.name)))
-
-
-class EETypeWorkflowView(EntityView):
- id = 'workflow'
- __select__ = implements('EEType')
- cache_max_age = 60*60*2 # stay in http cache for 2 hours by default
-
- def cell_call(self, row, col, **kwargs):
- entity = self.entity(row, col)
- self.w(u'<h1>%s</h1>' % (self.req._('workflow for %s')
- % display_name(self.req, entity.name)))
- self.w(u'<img src="%s" alt="%s"/>' % (
- html_escape(entity.absolute_url(vid='ewfgraph')),
- html_escape(self.req._('graphical workflow for %s') % entity.name)))
-
-
-class EETypeOneLineView(baseviews.OneLineView):
- __select__ = implements('EEType')
-
- def cell_call(self, row, col, **kwargs):
- entity = self.entity(row, col)
- final = entity.final
- if final:
- self.w(u'<em class="finalentity">')
- super(EETypeOneLineView, self).cell_call(row, col, **kwargs)
- if final:
- self.w(u'</em>')
-
-
-from cubicweb.web.action import Action
-
-class ViewWorkflowAction(Action):
- id = 'workflow'
- __select__ = implements('EEType') & rql_condition('S state_of X')
-
- category = 'mainactions'
- title = _('view workflow')
- def url(self):
- entity = self.rset.get_entity(self.row or 0, self.col or 0)
- return entity.absolute_url(vid='workflow')
-
--- a/web/views/wfentities.py Thu Mar 12 16:23:31 2009 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,31 +0,0 @@
-"""html view for workflow related entities
-
-:organization: Logilab
-:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
-:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
-"""
-__docformat__ = "restructuredtext en"
-
-from logilab.mtconverter import html_escape
-
-from cubicweb.selectors import implements
-from cubicweb.view import EntityView
-
-class CellView(EntityView):
- id = 'cell'
- __select__ = implements('TrInfo')
-
- def cell_call(self, row, col, cellvid=None):
- entity = self.entity(row, col)
- self.w(entity.printable_value('comment'))
-
-
-class StateInContextView(EntityView):
- """convenience trick, State's incontext view should not be clickable"""
- id = 'incontext'
- __select__ = implements('State')
-
- def cell_call(self, row, col):
- self.w(html_escape(self.view('textincontext', self.rset,
- row=row, col=col)))
-
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/web/views/workflow.py Thu Mar 12 16:29:00 2009 +0100
@@ -0,0 +1,214 @@
+"""workflow views:
+
+* IWorkflowable views and forms
+* workflow entities views (State, Transition, TrInfo)
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+from logilab.mtconverter import html_escape
+from logilab.common.graph import escape, GraphGenerator, DotBackend
+
+from cubicweb import Unauthorized, view
+from cubicweb.selectors import (implements, has_related_entities,
+ relation_possible, match_form_params)
+from cubicweb.interfaces import IWorkflowable
+from cubicweb.web import stdmsgs, action, component, form
+from cubicweb.web.formfields import StringField, RichTextField
+from cubicweb.web.formwidgets import HiddenInput
+from cubicweb.web.views import TmpFileViewMixin
+
+
+# IWorkflowable views #########################################################
+
+class ChangeStateForm(form.EntityFieldsForm):
+ id = 'changestate'
+
+ __method = StringField(name='__method', initial='set_state', widget=HiddenInput)
+ state = StringField(widget=HiddenInput, eidparam=True)
+ trcomment = RichTextField(eidparam=True)
+
+ def form_buttons(self):
+ return [self.button_ok(label=stdmsgs.YES,
+ tabindex=self.req.next_tabindex()),
+ self.button_cancel(label=stdmsgs.NO,
+ tabindex=self.req.next_tabindex())]
+
+
+class ChangeStateFormView(view.EntityView):
+ id = 'statuschange'
+ title = _('status change')
+ __select__ = implements(IWorkflowable) & match_form_params('treid')
+
+ def cell_call(self, row, col):
+ entity = self.entity(row, col)
+ eid = entity.eid
+ state = entity.in_state[0]
+ transition = self.req.eid_rset(self.req.form['treid']).get_entity(0, 0)
+ dest = transition.destination()
+ _ = self.req._
+ form = self.vreg.select_object('forms', 'changestate', self.req, self.rset, row=row, col=col,
+ entity=entity, redirect_path=self.redirectpath(entity))
+ self.w(form.error_message())
+ self.w(u'<h4>%s %s</h4>\n' % (_(transition.name), entity.view('oneline')))
+ msg = _('status will change from %(st1)s to %(st2)s') % {
+ 'st1': _(state.name),
+ 'st2': _(dest.name)}
+ self.w(u'<p>%s</p>\n' % msg)
+ self.w(form.form_render(state=dest.eid, trcomment=u''))
+
+ def redirectpath(self, entity):
+ return entity.rest_path()
+
+
+class WFHistoryVComponent(component.EntityVComponent):
+ """display the workflow history for entities supporting it"""
+ id = 'wfhistory'
+ __select__ = (component.EntityVComponent.__select__
+ & relation_possible('wf_info_for', role='object'))
+ context = 'navcontentbottom'
+ title = _('Workflow history')
+
+ def cell_call(self, row, col, view=None):
+ _ = self.req._
+ eid = self.rset[row][col]
+ sel = 'Any FS,TS,WF,D'
+ rql = ' ORDERBY D DESC WHERE WF wf_info_for X,'\
+ 'WF from_state FS, WF to_state TS, WF comment C,'\
+ 'WF creation_date D'
+ if self.vreg.schema.eschema('EUser').has_perm(self.req, 'read'):
+ sel += ',U,C'
+ rql += ', WF owned_by U?'
+ displaycols = range(5)
+ headers = (_('from_state'), _('to_state'), _('comment'), _('date'),
+ _('EUser'))
+ else:
+ sel += ',C'
+ displaycols = range(4)
+ headers = (_('from_state'), _('to_state'), _('comment'), _('date'))
+ rql = '%s %s, X eid %%(x)s' % (sel, rql)
+ try:
+ rset = self.req.execute(rql, {'x': eid}, 'x')
+ except Unauthorized:
+ return
+ if rset:
+ self.wview('table', rset, title=_(self.title), displayactions=False,
+ displaycols=displaycols, headers=headers)
+
+
+# workflow entity types views #################################################
+
+class CellView(view.EntityView):
+ id = 'cell'
+ __select__ = implements('TrInfo')
+
+ def cell_call(self, row, col, cellvid=None):
+ self.w(self.entity(row, col).printable_value('comment'))
+
+
+class StateInContextView(view.EntityView):
+ """convenience trick, State's incontext view should not be clickable"""
+ id = 'incontext'
+ __select__ = implements('State')
+
+ def cell_call(self, row, col):
+ self.w(html_escape(self.view('textincontext', self.rset,
+ row=row, col=col)))
+
+
+# workflow images #############################################################
+
+class ViewWorkflowAction(action.Action):
+ id = 'workflow'
+ __select__ = implements('EEType') & has_related_entities('state_of', 'object')
+
+ category = 'mainactions'
+ title = _('view workflow')
+ def url(self):
+ entity = self.rset.get_entity(self.row or 0, self.col or 0)
+ return entity.absolute_url(vid='workflow')
+
+
+class EETypeWorkflowView(view.EntityView):
+ id = 'workflow'
+ __select__ = implements('EEType')
+ cache_max_age = 60*60*2 # stay in http cache for 2 hours by default
+
+ def cell_call(self, row, col, **kwargs):
+ entity = self.entity(row, col)
+ self.w(u'<h1>%s</h1>' % (self.req._('workflow for %s')
+ % display_name(self.req, entity.name)))
+ self.w(u'<img src="%s" alt="%s"/>' % (
+ html_escape(entity.absolute_url(vid='ewfgraph')),
+ html_escape(self.req._('graphical workflow for %s') % entity.name)))
+
+
+class WorkflowDotPropsHandler(object):
+ def __init__(self, req):
+ self._ = req._
+
+ def node_properties(self, stateortransition):
+ """return default DOT drawing options for a state or transition"""
+ props = {'label': stateortransition.name,
+ 'fontname': 'Courier'}
+ if hasattr(stateortransition, 'state_of'):
+ props['shape'] = 'box'
+ props['style'] = 'filled'
+ if stateortransition.reverse_initial_state:
+ props['color'] = '#88CC88'
+ else:
+ props['shape'] = 'ellipse'
+ descr = []
+ tr = stateortransition
+ if tr.require_group:
+ descr.append('%s %s'% (
+ self._('groups:'),
+ ','.join(g.name for g in tr.require_group)))
+ if tr.condition:
+ descr.append('%s %s'% (self._('condition:'), tr.condition))
+ if descr:
+ props['label'] += escape('\n'.join(descr))
+ return props
+
+ def edge_properties(self, transition, fromstate, tostate):
+ return {'label': '', 'dir': 'forward',
+ 'color': 'black', 'style': 'filled'}
+
+
+class WorkflowVisitor:
+ def __init__(self, entity):
+ self.entity = entity
+
+ def nodes(self):
+ for state in self.entity.reverse_state_of:
+ state.complete()
+ yield state.eid, state
+
+ for transition in self.entity.reverse_transition_of:
+ transition.complete()
+ yield transition.eid, transition
+
+ def edges(self):
+ for transition in self.entity.reverse_transition_of:
+ for incomingstate in transition.reverse_allowed_transition:
+ yield incomingstate.eid, transition.eid, transition
+ yield transition.eid, transition.destination().eid, transition
+
+
+class EETypeWorkflowImageView(TmpFileViewMixin, view.EntityView):
+ id = 'ewfgraph'
+ content_type = 'image/png'
+ __select__ = implements('EEType')
+
+ def _generate(self, tmpfile):
+ """display schema information for an entity"""
+ entity = self.entity(self.row, self.col)
+ visitor = WorkflowVisitor(entity)
+ prophdlr = WorkflowDotPropsHandler(self.req)
+ generator = GraphGenerator(DotBackend('workflow', 'LR',
+ ratio='compress', size='30,12'))
+ return generator.generate(visitor, prophdlr, tmpfile)
+
--- a/web/views/xmlrss.py Thu Mar 12 16:23:31 2009 +0100
+++ b/web/views/xmlrss.py Thu Mar 12 16:29:00 2009 +0100
@@ -1,6 +1,5 @@
"""base xml and rss views
-
:organization: Logilab
:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
@@ -11,10 +10,17 @@
from logilab.mtconverter import xml_escape
+from cubicweb.selectors import non_final_entity, one_line_rset, appobject_selectable
from cubicweb.view import EntityView, AnyRsetView
from cubicweb.web.httpcache import MaxAgeHTTPCacheManager
+from cubicweb.web.component import Component
+from cubicweb.web.box import BoxTemplate
from cubicweb.common.uilib import simple_sgml_tag
+_ = unicode
+
+
+# base xml views ##############################################################
class XmlView(EntityView):
"""xml view for entities"""
@@ -60,7 +66,6 @@
self.w(u' <%s>%s</%s>\n' % (attr, value, attr))
self.w(u'</%s>\n' % (entity.e_schema))
-
class XmlRsetView(AnyRsetView):
"""dumps raw rset as xml"""
@@ -97,7 +102,45 @@
w(simple_sgml_tag(tag, val, **attrs))
w(u' </row>\n')
w(u'</%s>\n' % self.xml_root)
+
+# RSS stuff ###################################################################
+
+class RSSFeedURL(Component):
+ id = 'rss_feed_url'
+ __select__ = non_final_entity()
+
+ def feed_url(self):
+ return self.build_url(rql=self.limited_rql(), vid='rss')
+
+
+class RSSEntityFeedURL(Component):
+ id = 'rss_feed_url'
+ __select__ = non_final_entity() & one_line_rset()
+
+ def feed_url(self):
+ return self.entity(0, 0).rss_feed_url()
+
+
+class RSSIconBox(BoxTemplate):
+ """just display the RSS icon on uniform result set"""
+ id = 'rss'
+ __select__ = (BoxTemplate.__select__
+ & appobject_selectable('components', 'rss_feed_url'))
+
+ visible = False
+ order = 999
+
+ def call(self, **kwargs):
+ try:
+ rss = self.req.external_resource('RSS_LOGO')
+ except KeyError:
+ self.error('missing RSS_LOGO external resource')
+ return
+ urlgetter = self.vreg.select_component('rss_feed_url', self.req, self.rset)
+ url = urlgetter.feed_url()
+ self.w(u'<a href="%s"><img src="%s" alt="rss"/></a>\n' % (html_escape(url), rss))
+
class RssView(XmlView):
id = 'rss'