# HG changeset patch # User sylvain.thenault@logilab.fr # Date 1236871740 -3600 # Node ID b5e253c0dd133e0f8bb1a49a34b544b4ec2908cf # Parent a99dc223c583a1ae48132a212806fc9e7d5c58ea 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 diff -r a99dc223c583 -r b5e253c0dd13 web/views/__init__.py --- 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) diff -r a99dc223c583 -r b5e253c0dd13 web/views/actions.py --- 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) diff -r a99dc223c583 -r b5e253c0dd13 web/views/basecomponents.py --- 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'') -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' diff -r a99dc223c583 -r b5e253c0dd13 web/views/baseforms.py --- 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'\n' - % _('this action is not reversible!')) - # XXX above message should have style of a warning - w(u'

%s

\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'\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'

%s %s

\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'

%s

\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
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'%s' % rdef[0].display_name(req, rdef[-1]) diff -r a99dc223c583 -r b5e253c0dd13 web/views/baseviews.py --- 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 diff -r a99dc223c583 -r b5e253c0dd13 web/views/boxes.py --- 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'rss\n' % (html_escape(url), rss)) - class StartupViewsBox(BoxTemplate): """display a box containing links to all startup views""" diff -r a99dc223c583 -r b5e253c0dd13 web/views/dynimages.py --- 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) diff -r a99dc223c583 -r b5e253c0dd13 web/views/editforms.py --- /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'\n' + % _('this action is not reversible!')) + # XXX above message should have style of a warning + w(u'

%s

\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'\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'] diff -r a99dc223c583 -r b5e253c0dd13 web/views/massmailing.py --- 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'), } diff -r a99dc223c583 -r b5e253c0dd13 web/views/schema.py --- /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'') + super(EETypeOneLineView, self).cell_call(row, col, **kwargs) + if final: + self.w(u'') + + +# 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'%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'%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'%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)) diff -r a99dc223c583 -r b5e253c0dd13 web/views/schemaentities.py --- 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'%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'%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'%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'

%s

' % (self.req._('workflow for %s') - % display_name(self.req, entity.name))) - self.w(u'%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'') - super(EETypeOneLineView, self).cell_call(row, col, **kwargs) - if final: - self.w(u'') - - -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') - diff -r a99dc223c583 -r b5e253c0dd13 web/views/wfentities.py --- 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))) - diff -r a99dc223c583 -r b5e253c0dd13 web/views/workflow.py --- /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'

%s %s

\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'

%s

\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'

%s

' % (self.req._('workflow for %s') + % display_name(self.req, entity.name))) + self.w(u'%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) + diff -r a99dc223c583 -r b5e253c0dd13 web/views/xmlrss.py --- 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\n' % (attr, value, attr)) self.w(u'\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' \n') w(u'\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'rss\n' % (html_escape(url), rss)) + class RssView(XmlView): id = 'rss'