web/views/baseforms.py
changeset 0 b97547f5f1fa
child 62 ef06f71533d9
equal deleted inserted replaced
-1:000000000000 0:b97547f5f1fa
       
     1 """Set of HTML automatic forms to create, delete, copy or edit a single entity
       
     2 or a list of entities of the same type
       
     3 
       
     4 :organization: Logilab
       
     5 :copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
       
     6 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
     7 """
       
     8 __docformat__ = "restructuredtext en"
       
     9 
       
    10 from copy import copy
       
    11 
       
    12 from simplejson import dumps
       
    13 
       
    14 from logilab.mtconverter import html_escape
       
    15 from logilab.common.decorators import cached
       
    16 
       
    17 from cubicweb.interfaces import IWorkflowable
       
    18 from cubicweb.common.utils import make_uid
       
    19 from cubicweb.common.uilib import cut
       
    20 from cubicweb.common.selectors import (etype_form_selector, kwargs_selector,
       
    21                                     onelinerset_selector, interface_selector,
       
    22                                     req_form_params_selector, accept_selector)
       
    23 from cubicweb.common.view import EntityView
       
    24 from cubicweb.web import INTERNAL_FIELD_VALUE, stdmsgs, eid_param
       
    25 from cubicweb.web.controller import NAV_FORM_PARAMETERS
       
    26 from cubicweb.web.widgets import checkbox, InputWidget, ComboBoxWidget
       
    27 from cubicweb.web.form import EntityForm, relation_id
       
    28 
       
    29 _ = unicode
       
    30 
       
    31 class DeleteConfForm(EntityForm):
       
    32     id = 'deleteconf'
       
    33     title = _('delete')
       
    34     domid = 'deleteconf'
       
    35     onsubmit = None
       
    36     
       
    37     def call(self):
       
    38         """ask for confirmation before real deletion"""
       
    39         _ = self.req._
       
    40         self.req.add_js('cubicweb.edition.js')
       
    41         self.w(u'<script type="text/javascript">updateMessage(\'%s\');</script>\n' % _('this action is not reversible!'))
       
    42         # XXX above message should have style of a warning
       
    43         self.w(u'<h4>%s</h4>\n' % _('Do you want to delete the following element(s) ?'))
       
    44         if self.onsubmit:
       
    45             self.w(u'<form id="deleteconf" action="%s" onsubmit="%s" method="post">'
       
    46                    % (self.build_url(), self.onsubmit))
       
    47         else:
       
    48             self.w(u'<form id="deleteconf" action="%s" method="post">'
       
    49                    % (self.build_url()))
       
    50             
       
    51         self.w(u'<fieldset>\n')
       
    52         self.display_rset()
       
    53         #self.w(u'<input type="hidden" name="rql" value="%s"/>' % self.req.form['rql'])
       
    54         self.w(u'<input type="hidden" name="__form_id" value="%s"/>' % self.id)
       
    55         self.w(self.button_delete(label=stdmsgs.YES))
       
    56         self.w(self.button_cancel(label=stdmsgs.NO))
       
    57         for param in NAV_FORM_PARAMETERS:
       
    58             value = self.req.form.get(param)
       
    59             if value:
       
    60                 self.w(u'<input type="hidden" name="%s" value="%s"/>' % (param, value))
       
    61         self.w(u'</fieldset></form>\n')
       
    62 
       
    63     def display_rset(self):
       
    64         self.w(u'<ul>\n')
       
    65         done = set()
       
    66         for i in xrange(self.rset.rowcount):
       
    67             if self.rset[i][0] in done:
       
    68                 continue
       
    69             done.add(self.rset[i][0])
       
    70             self.cell_call(i, 0)
       
    71         self.w(u'</ul>\n')
       
    72         
       
    73     def cell_call(self, row, col):
       
    74         entity = self.entity(row, col)
       
    75         self.w(u'<li>')
       
    76         self.w(u'<input type="hidden" name="eid" value="%s" />' % entity.eid)
       
    77         self.w(u'<input type="hidden" name="%s" value="%s"/>\n'
       
    78                % (eid_param('__type', entity.eid), self.rset.description[row][0]))
       
    79         self.w(u'<a href="%s">' % html_escape(entity.absolute_url()))
       
    80         # don't use outofcontext view or any other that may contain inline edition form
       
    81         self.w(html_escape(entity.view('textoutofcontext')))
       
    82         self.w(u'</a>')
       
    83         self.w(u'</li>')
       
    84 
       
    85 
       
    86 class ChangeStateForm(EntityForm):
       
    87     id = 'statuschange'
       
    88     title = _('status change')
       
    89 
       
    90     __selectors__ = (interface_selector, req_form_params_selector)
       
    91     accepts_interfaces = (IWorkflowable,)
       
    92     form_params = ('treid',)
       
    93 
       
    94     def cell_call(self, row, col, vid='secondary'):
       
    95         entity = self.entity(row, col)
       
    96         eid = entity.eid
       
    97         state = entity.in_state[0]
       
    98         transition = self.req.eid_rset(self.req.form['treid']).get_entity(0, 0)
       
    99         dest = transition.destination()
       
   100         self.req.add_js('cubicweb.edition.js')
       
   101         _ = self.req._
       
   102         self.w(self.error_message())
       
   103         self.w(u'<h4>%s %s</h4>\n' % (_(transition.name), entity.view('oneline')))
       
   104         self.w(u'<p>%s</p>\n' % (_('status will change from %s to %s')
       
   105                                % (_(state.name), _(dest.name))))
       
   106         self.w(u'<form action="%s" onsubmit="return freezeFormButtons(\'entityForm\');" method="post" id="entityForm">\n'
       
   107                % self.build_url('edit'))
       
   108         self.w(u'<div id="progress">%s</div>' % _('validating...'))
       
   109         self.w(u'<fieldset>\n')
       
   110         #self.w(u'<input id="errorurl" type="hidden" name="__errorurl" value="%s"/>\n'
       
   111         #       % html_escape(self.req.url()))
       
   112         self.w(u'<input type="hidden" name="__form_id" value="%s"/>\n' % self.id)
       
   113         self.w(u'<input type="hidden" name="eid" value="%s" />' % eid)
       
   114         self.w(u'<input type="hidden" name="%s" value="%s"/>\n'
       
   115                % (eid_param('__type', eid), entity.e_schema))
       
   116         self.w(u'<input type="hidden" name="%s" value="%s"/>\n'
       
   117                % (eid_param('state', eid), dest.eid))
       
   118         self.w(u'<input type="hidden" name="__redirectpath" value="%s"/>\n'
       
   119                % html_escape(entity.rest_path()))
       
   120         self.fill_form(entity, state, dest)
       
   121         self.w(u'<input type="hidden" name="__method" value="set_state"/>\n')
       
   122         self.w(self.button_ok(label=stdmsgs.YES, tabindex=self.req.next_tabindex()))
       
   123         self.w(self.button_cancel(label=stdmsgs.NO, tabindex=self.req.next_tabindex()))
       
   124         self.w(u'</fieldset>\n')
       
   125         self.w(u'</form>')
       
   126         
       
   127     def fill_form(self, entity, state, dest):
       
   128         # hack to use the widget for comment_format
       
   129         trinfo = self.vreg.etype_class('TrInfo')(self.req, None)
       
   130         # widget are cached, copy it since we want to modify its name attribute
       
   131         wdg = trinfo.get_widget('comment_format')
       
   132         wdg.name = 'trcommentformat'
       
   133         # set a value in entity to avoid lookup for a non existant attribute...
       
   134         trinfo['trcommentformat'] = u''
       
   135         # comment format/content have to be grouped using the original entity eid
       
   136         wdg.rname = eid_param('trcommentformat', entity.eid)
       
   137         self.w(wdg.render_label(trinfo))
       
   138         self.w(wdg._edit_render(trinfo))
       
   139         self.w(u'<br/>\n')
       
   140         cformname = eid_param('trcomment', entity.eid)
       
   141         self.w(u'<label for="%s">%s</label>\n' % (cformname, self.req._('comment:')))
       
   142         self.w(u'<textarea rows="10" cols="80" name="%s" tabindex="%s"></textarea><br/>\n'
       
   143                % (cformname, self.req.next_tabindex()))
       
   144 
       
   145 
       
   146 class ClickAndEditForm(EntityForm):
       
   147     id = 'reledit'
       
   148     __selectors__ = (kwargs_selector, )
       
   149     expected_kwargs = ('rtype',)
       
   150 
       
   151     #FIXME editableField class could be toggleable from userprefs
       
   152 
       
   153     EDITION_BODY = '''
       
   154 <div class="editableField" id="%(divid)s"
       
   155       ondblclick="showInlineEditionForm(%(eid)s, '%(rtype)s', '%(divid)s')">%(value)s</div>
       
   156 <form style="display: none;" onsubmit="return inlineValidateForm('%(divid)s-form', '%(rtype)s', '%(eid)s', '%(divid)s', %(reload)s);" id="%(divid)s-form" action="#">
       
   157 <fieldset>
       
   158 <input type="hidden" name="eid" value="%(eid)s" />
       
   159 <input type="hidden" name="__maineid" value="%(eid)s" />
       
   160 <input type="hidden" name="__type:%(eid)s" value="%(etype)s" />
       
   161 %(attrform)s
       
   162 </fieldset>
       
   163 <div class="buttonbar">
       
   164 %(ok)s
       
   165 %(cancel)s
       
   166 </div>
       
   167 </form>
       
   168 '''
       
   169     def cell_call(self, row, col, rtype=None, role='subject', reload=False):
       
   170         entity = self.entity(row, col)
       
   171         if getattr(entity, rtype) is None:
       
   172             value = self.req._('not specified')
       
   173         else:
       
   174             value = entity.printable_value(rtype)
       
   175         if not entity.has_perm('update'):
       
   176             self.w(value)
       
   177             return
       
   178         self.req.add_js( ('cubicweb.ajax.js', 'cubicweb.edition.js') )
       
   179         eid = entity.eid
       
   180         edit_key = make_uid('%s-%s' % (rtype, eid))
       
   181         divid = 'd%s' % edit_key
       
   182         widget = entity.get_widget(rtype, 'subject')
       
   183         eschema = entity.e_schema
       
   184         attrform = widget.edit_render(entity, useid='i%s' % edit_key)
       
   185         ok = (u'<input class="validateButton" type="submit" name="__action_apply" value="%s" tabindex="%s" />'
       
   186               % (self.req._(stdmsgs.BUTTON_OK), self.req.next_tabindex()))
       
   187         cancel = (u'<input class="validateButton" type="button" '
       
   188                   'value="%s" onclick="cancelInlineEdit(%s, \'%s\', \'%s\')"  tabindex="%s" />'
       
   189                   % (self.req._(stdmsgs.BUTTON_CANCEL), eid, rtype, divid,
       
   190                      self.req.next_tabindex()))
       
   191         self.w(self.EDITION_BODY % {
       
   192                 'eid': eid,
       
   193                 'rtype': rtype,
       
   194                 'etype': entity.e_schema,
       
   195                 'attrform': attrform,
       
   196                 'action' : self.build_url('edit'), # NOTE: actually never gets called
       
   197                 'ok': ok,
       
   198                 'cancel': cancel,
       
   199                 'value': value,
       
   200                 'reload': dumps(reload),
       
   201                 'divid': divid,
       
   202                 })
       
   203 
       
   204 
       
   205 class EditionForm(EntityForm):
       
   206     """primary entity edition form
       
   207 
       
   208     When generating a new attribute_input, the editor will look for a method
       
   209     named 'default_ATTRNAME' on the entity instance, where ATTRNAME is the
       
   210     name of the attribute being edited. You may use this feature to compute
       
   211     dynamic default values such as the 'tomorrow' date or the user's login
       
   212     being connected
       
   213     """    
       
   214     __selectors__ = (onelinerset_selector, accept_selector)
       
   215 
       
   216     id = 'edition'
       
   217     title = _('edition')
       
   218     controller = 'edit'
       
   219     skip_relations = EntityForm.skip_relations.copy()
       
   220     
       
   221     EDITION_BODY = u'''\
       
   222  %(errormsg)s
       
   223 <form id="%(formid)s" class="entityForm" cubicweb:target="eformframe"
       
   224       method="post" onsubmit="%(onsubmit)s" enctype="%(enctype)s" action="%(action)s">
       
   225  %(title)s
       
   226  <div id="progress">%(inprogress)s</div>
       
   227  <div class="iformTitle"><span>%(mainattrs_label)s</span></div>
       
   228  <div class="formBody"><fieldset>
       
   229  %(base)s
       
   230  %(attrform)s
       
   231  %(relattrform)s
       
   232 </fieldset>
       
   233  %(relform)s
       
   234  </div>
       
   235  <table width="100%%">
       
   236   <tbody>
       
   237    <tr><td align="center">
       
   238      %(validate)s
       
   239    </td><td style="align: right; width: 50%%;">
       
   240      %(apply)s
       
   241      %(cancel)s
       
   242    </td></tr>
       
   243   </tbody>
       
   244  </table>
       
   245 </form>
       
   246 '''
       
   247 
       
   248     def cell_call(self, row, col, **kwargs):
       
   249         self.req.add_js( ('cubicweb.ajax.js', 'cubicweb.edition.js') )
       
   250         self.req.add_css('cubicweb.form.css')
       
   251         entity = self.complete_entity(row, col)
       
   252         self.edit_form(entity, kwargs)
       
   253 
       
   254     def edit_form(self, entity, kwargs):
       
   255         varmaker = self.req.get_page_data('rql_varmaker')
       
   256         if varmaker is None:
       
   257             varmaker = self.req.varmaker
       
   258             self.req.set_page_data('rql_varmaker', varmaker)
       
   259         self.varmaker = varmaker
       
   260         self.w(self.EDITION_BODY % self.form_context(entity, kwargs))
       
   261 
       
   262     def form_context(self, entity, kwargs):
       
   263         """returns the dictionnary used to fill the EDITION_BODY template
       
   264 
       
   265         If you create your own edition form, you can probably just override
       
   266         `EDITION_BODY` and `form_context`
       
   267         """
       
   268         if self.need_multipart(entity):
       
   269             enctype = 'multipart/form-data'
       
   270         else:
       
   271             enctype = 'application/x-www-form-urlencoded'
       
   272         self._hiddens = []
       
   273         if entity.eid is None:
       
   274             entity.eid = self.varmaker.next()
       
   275         # XXX (hack) action_title might need __linkto req's original value
       
   276         #            and widgets such as DynamicComboWidget might change it
       
   277         #            so we need to compute title before calling atttributes_form
       
   278         formtitle = self.action_title(entity)
       
   279         # be sure to call .*_form first so tabindexes are correct and inlined
       
   280         # fields errors are consumed
       
   281         if not entity.has_eid() or entity.has_perm('update'):
       
   282             attrform = self.attributes_form(entity, kwargs)
       
   283         else:
       
   284             attrform = ''
       
   285         inlineform = self.inline_entities_form(entity, kwargs)
       
   286         relform = self.relations_form(entity, kwargs)
       
   287         vindex = self.req.next_tabindex()
       
   288         aindex = self.req.next_tabindex()
       
   289         cindex = self.req.next_tabindex()
       
   290         self.add_hidden_web_behaviour_params(entity)
       
   291         _ = self.req._
       
   292         return {
       
   293             'formid'   : self.domid,
       
   294             'onsubmit' : self.on_submit(entity),
       
   295             'enctype'  : enctype,
       
   296             'errormsg' : self.error_message(),
       
   297             'action'   : self.build_url('validateform'),
       
   298             'eids'     : entity.has_eid() and [entity.eid] or [],
       
   299             'inprogress': _('validating...'),
       
   300             'title'    : formtitle,
       
   301             'mainattrs_label' : _('main informations'),
       
   302             'reseturl' : self.redirect_url(entity),
       
   303             'attrform' : attrform,
       
   304             'relform'  : relform,
       
   305             'relattrform': inlineform,
       
   306             'base'     : self.base_form(entity, kwargs),
       
   307             'validate' : self.button_ok(tabindex=vindex),
       
   308             'apply'    : self.button_apply(tabindex=aindex),
       
   309             'cancel'   : self.button_cancel(tabindex=cindex),
       
   310             }
       
   311 
       
   312     @property
       
   313     def formid(self):
       
   314         return self.id
       
   315     
       
   316     def action_title(self, entity):
       
   317         """form's title"""
       
   318         ptitle = self.req._(self.title)
       
   319         return u'<div class="formTitle"><span>%s %s</span></div>' % (
       
   320             entity.dc_type(), ptitle and '(%s)' % ptitle)
       
   321 
       
   322 
       
   323     def base_form(self, entity, kwargs):
       
   324         output = []
       
   325         for name, value, iid in self._hiddens:
       
   326             if isinstance(value, basestring):
       
   327                 value = html_escape(value)
       
   328             if iid:
       
   329                 output.append(u'<input id="%s" type="hidden" name="%s" value="%s" />'
       
   330                               % (iid, name, value))
       
   331             else:
       
   332                 output.append(u'<input type="hidden" name="%s" value="%s" />'
       
   333                               % (name, value))
       
   334         return u'\n'.join(output)
       
   335                 
       
   336     def add_hidden_web_behaviour_params(self, entity):
       
   337         """inserts hidden params controlling how errors and redirection
       
   338         should be handled
       
   339         """
       
   340         req = self.req
       
   341         self._hiddens.append( (u'__maineid', entity.eid, u'') )
       
   342         self._hiddens.append( (u'__errorurl', req.url(), u'errorurl') )
       
   343         self._hiddens.append( (u'__form_id', self.formid, u'') )
       
   344         for param in NAV_FORM_PARAMETERS:
       
   345             value = req.form.get(param)
       
   346             if value:
       
   347                 self._hiddens.append( (param, value, u'') )
       
   348         msg = self.submited_message()
       
   349         # If we need to directly attach the new object to another one
       
   350         for linkto in req.list_form_param('__linkto'):
       
   351             self._hiddens.append( ('__linkto', linkto, '') )
       
   352             msg = '%s %s' % (msg, self.req._('and linked'))
       
   353         self._hiddens.append( ('__message', msg, '') )
       
   354         
       
   355     
       
   356     def attributes_form(self, entity, kwargs, include_eid=True):
       
   357         """create a form to edit entity's attributes"""
       
   358         html = []
       
   359         w = html.append
       
   360         eid = entity.eid
       
   361         wdg = entity.get_widget
       
   362         lines = (wdg(rschema, x) for rschema, x in self.editable_attributes(entity))
       
   363         if include_eid:
       
   364             self._hiddens.append( ('eid', entity.eid, '') )
       
   365         self._hiddens.append( (eid_param('__type', eid), entity.e_schema, '') )
       
   366         w(u'<table id="%s" class="%s" style="width:100%%;">' %
       
   367           (kwargs.get('tab_id', 'entityForm%s' % eid),
       
   368            kwargs.get('tab_class', 'attributeForm')))
       
   369         for widget in lines:
       
   370             w(u'<tr>\n<th class="labelCol">%s</th>' % widget.render_label(entity))
       
   371             error = widget.render_error(entity)
       
   372             if error:
       
   373                 w(u'<td class="error" style="width:100%;">')
       
   374             else:
       
   375                 w(u'<td style="width:100%;">')
       
   376             if error:
       
   377                 w(error)
       
   378             w(widget.edit_render(entity))
       
   379             w(widget.render_help(entity))
       
   380             w(u'</td>\n</tr>')
       
   381         w(u'</table>')
       
   382         return u'\n'.join(html)
       
   383 
       
   384     def editable_attributes(self, entity):
       
   385         # XXX both (add, delete)
       
   386         return [(rschema, x) for rschema, _, x in entity.relations_by_category(('primary', 'secondary'), 'add')
       
   387                 if rschema != 'eid']
       
   388     
       
   389     def relations_form(self, entity, kwargs):
       
   390         req = self.req
       
   391         _ = self.req._
       
   392         label = u'%s :' % _('This %s' % entity.e_schema).capitalize()
       
   393         eid = entity.eid
       
   394         html = []
       
   395         pendings = list(self.restore_pending_inserts(entity))
       
   396         w = html.append
       
   397         w(u'<fieldset class="subentity">')
       
   398         w(u'<legend class="iformTitle">%s</legend>' % label)
       
   399         w(u'<table id="relatedEntities">')
       
   400         for row in self.relations_table(entity):
       
   401             if row[2]:
       
   402                 w(u'<tr><th class="labelCol">%s</th>' % row[0].display_name(req, row[1]))
       
   403                 w(u'<td>')
       
   404                 w(u'<ul>')
       
   405                 for viewparams in row[2]:
       
   406                     w(u'<li class="invisible">%s<div id="span%s" class="%s">%s</div></li>'
       
   407                       % (viewparams[1], viewparams[0], viewparams[2], viewparams[3]))
       
   408                 if not self.force_display and self.maxrelitems < len(row[2]):
       
   409                     w(u'<li class="invisible">%s</li>' % self.force_display_link())
       
   410                 w(u'</ul>')
       
   411                 w(u'</td>')
       
   412                 w(u'</tr>')
       
   413         if not pendings:
       
   414             w(u'<tr><th>&nbsp;</th><td>&nbsp;</td></tr>')
       
   415         else:
       
   416             for row in pendings:
       
   417                 w(u'<tr id="tr%s">' % row[1])
       
   418                 w(u'<th>%s</th>' % row[3])
       
   419                 w(u'<td>')
       
   420                 w(u'<a class="handle" title="%s" href="%s">[x]</a>' %
       
   421                   (_('cancel this insert'), row[2]))
       
   422                 w(u'<a id="a%s" class="editionPending" href="%s">%s</a>'
       
   423                   % (row[1], row[4], html_escape(row[5])))
       
   424                 w(u'</td>')
       
   425                 w(u'</tr>')
       
   426         w(u'<tr id="relationSelectorRow_%s" class="separator">' % eid)
       
   427         w(u'<th class="labelCol">')
       
   428         w(u'<span>%s</span>' % _('add relation'))
       
   429         w(u'<select id="relationSelector_%s" tabindex="%s" onchange="javascript:showMatchingSelect(this.options[this.selectedIndex].value,%s);">'
       
   430           % (eid, req.next_tabindex(), html_escape(dumps(eid))))
       
   431         w(u'<option value="">%s</option>' % _('select a relation'))
       
   432         for i18nrtype, rschema, target in entity.srelations_by_category(('generic', 'metadata'), 'add'):
       
   433             w(u'<option value="%s_%s">%s</option>' % (rschema, target, i18nrtype))
       
   434         w(u'</select>')
       
   435         w(u'</th>')
       
   436         w(u'<td id="unrelatedDivs_%s"></td>' % eid)
       
   437         w(u'</tr>')
       
   438         w(u'</table>')
       
   439         w(u'</fieldset>')
       
   440         return '\n'.join(html)
       
   441         
       
   442     def inline_entities_form(self, entity, kwargs):
       
   443         """create a form to edit entity's inlined relations"""
       
   444         result = []
       
   445         _ = self.req._
       
   446         for rschema, targettypes, x in entity.relations_by_category('inlineview', 'add'):
       
   447             # show inline forms only if there's one possible target type
       
   448             # for rschema
       
   449             if len(targettypes) != 1:
       
   450                 self.warning('entity related by the %s relation should have '
       
   451                              'inlined form but there is multiple target types, '
       
   452                              'dunno what to do', rschema)
       
   453                 continue
       
   454             targettype = targettypes[0].type
       
   455             if self.should_inline_relation_form(entity, rschema, targettype, x):
       
   456                 result.append(u'<div id="inline%sslot">' % rschema)
       
   457                 existant = entity.has_eid() and entity.related(rschema)
       
   458                 # display inline-edition view for all existing related entities
       
   459                 result.append(self.view('inline-edition', existant, 'null',
       
   460                                         ptype=entity.e_schema, peid=entity.eid,
       
   461                                         rtype=rschema, role=x, **kwargs))
       
   462                 if x == 'subject':
       
   463                     card = rschema.rproperty(entity.e_schema, targettype, 'cardinality')[0]
       
   464                 else:
       
   465                     card = rschema.rproperty(targettype, entity.e_schema, 'cardinality')[1]
       
   466                 # there is no related entity and we need at least one : we need to
       
   467                 # display one explicit inline-creation view
       
   468                 if self.should_display_inline_relation_form(rschema, existant, card):
       
   469                     result.append(self.view('inline-creation', None, etype=targettype,
       
   470                                             peid=entity.eid, ptype=entity.e_schema,
       
   471                                             rtype=rschema, role=x, **kwargs))
       
   472                 # we can create more than one related entity, we thus display a link
       
   473                 # to add new related entities
       
   474                 if self.should_display_add_inline_relation_link(rschema, existant, card):
       
   475                     divid = "addNew%s%s%s:%s" % (targettype, rschema, x, entity.eid)
       
   476                     result.append(u'<div class="inlinedform" id="%s" cubicweb:limit="true">'
       
   477                                   % divid)
       
   478                     js = "addInlineCreationForm('%s', '%s', '%s', '%s', '%s')" % (
       
   479                         entity.eid, entity.e_schema, targettype, rschema, x)
       
   480                     if card in '1?':
       
   481                         js = "toggleVisibility('%s'); %s" % (divid, js)
       
   482                     result.append(u'<a class="addEntity" id="add%s:%slink" href="javascript: %s" >+ %s.</a>'
       
   483                                   % (rschema, entity.eid, js,
       
   484                                      self.req.__('add a %s' % targettype)))
       
   485                     result.append(u'</div>')
       
   486                     result.append(u'<div class="trame_grise">&nbsp;</div>')
       
   487                 result.append(u'</div>')
       
   488         return '\n'.join(result)
       
   489 
       
   490     # should_* method extracted to allow overriding
       
   491     
       
   492     def should_inline_relation_form(self, entity, rschema, targettype, role):
       
   493         return entity.rtags.is_inlined(rschema, targettype, role)
       
   494 
       
   495     def should_display_inline_relation_form(self, rschema, existant, card):
       
   496         return not existant and card in '1+'
       
   497 
       
   498     def should_display_add_inline_relation_link(self, rschema, existant, card):
       
   499         return not existant or card in '+*'
       
   500     
       
   501     def reset_url(self, entity):
       
   502         return entity.absolute_url()
       
   503     
       
   504     def on_submit(self, entity):
       
   505         return u'return freezeFormButtons(\'%s\')' % (self.domid)
       
   506 
       
   507 
       
   508     def submited_message(self):
       
   509         return self.req._('element edited')
       
   510 
       
   511 
       
   512     
       
   513 class CreationForm(EditionForm):
       
   514     __selectors__ = (etype_form_selector, )
       
   515     id = 'creation'
       
   516     title = _('creation')
       
   517     
       
   518     def call(self, **kwargs):
       
   519         """creation view for an entity"""
       
   520         self.req.add_js( ('cubicweb.ajax.js', 'cubicweb.edition.js') )
       
   521         self.req.add_css('cubicweb.form.css')
       
   522         etype = kwargs.pop('etype', self.req.form.get('etype'))
       
   523         try:
       
   524             entity = self.vreg.etype_class(etype)(self.req, None, None)
       
   525         except:
       
   526             self.w(self.req._('no such entity type %s') % etype)
       
   527         else:
       
   528             self.edit_form(entity, kwargs)
       
   529 
       
   530     def action_title(self, entity):
       
   531         """custom form title if creating a entity with __linkto"""
       
   532         if '__linkto' in self.req.form:
       
   533             if isinstance(self.req.form['__linkto'], list):
       
   534                 # XXX which one should be considered (case: add a ticket to a version in jpl)
       
   535                 rtype, linkto_eid, role = self.req.form['__linkto'][0].split(':')
       
   536             else:
       
   537                 rtype, linkto_eid, role = self.req.form['__linkto'].split(':')
       
   538             linkto_rset = self.req.eid_rset(linkto_eid)
       
   539             linkto_type = linkto_rset.description[0][0]
       
   540             if role == 'subject':
       
   541                 title = self.req.__('creating %s (%s %s %s %%(linkto)s)' % (
       
   542                     entity.e_schema, entity.e_schema, rtype, linkto_type))
       
   543             else:
       
   544                 title = self.req.__('creating %s (%s %%(linkto)s %s %s)' % (
       
   545                     entity.e_schema, linkto_type, rtype, entity.e_schema))
       
   546             msg = title % {'linkto' : self.view('incontext', linkto_rset)}
       
   547             return u'<div class="formTitle notransform"><span>%s</span></div>' % msg
       
   548         else:
       
   549             return super(CreationForm, self).action_title(entity)
       
   550 
       
   551     @property
       
   552     def formid(self):
       
   553         return 'edition'
       
   554     
       
   555     def relations_form(self, entity, kwargs):
       
   556         return u''
       
   557 
       
   558     def reset_url(self, entity=None):
       
   559         return self.build_url(self.req.form.get('etype', '').lower())
       
   560     
       
   561     def submited_message(self):
       
   562         return self.req._('element created')
       
   563     
       
   564     def url(self):
       
   565         """return the url associated with this view"""
       
   566         return self.create_url(self.req.form.get('etype'))
       
   567 
       
   568 
       
   569 class InlineFormMixIn(object):
       
   570 
       
   571     @cached
       
   572     def card(self, etype):
       
   573         return self.rschema.rproperty(self.parent_schema, etype, 'cardinality')[0]
       
   574     
       
   575     def action_title(self, entity):
       
   576         return self.rschema.display_name(self.req, self.role)
       
   577         
       
   578     def add_hidden_web_behaviour_params(self, entity):
       
   579         pass
       
   580     
       
   581     def edit_form(self, entity, ptype, peid, rtype,
       
   582                   role='subject', **kwargs):
       
   583         self.rschema = self.schema.rschema(rtype)
       
   584         self.role = role        
       
   585         self.parent_schema = self.schema.eschema(ptype)
       
   586         self.parent_eid = peid
       
   587         super(InlineFormMixIn, self).edit_form(entity, kwargs)
       
   588     
       
   589     def should_inline_relation_form(self, entity, rschema, targettype, role):
       
   590         if rschema == self.rschema:
       
   591             return False
       
   592         return entity.rtags.is_inlined(rschema, targettype, role)
       
   593 
       
   594     @cached
       
   595     def keep_entity(self, entity):
       
   596         req = self.req
       
   597         # are we regenerating form because of a validation error ?
       
   598         erroneous_post = req.data.get('formvalues')
       
   599         if erroneous_post:
       
   600             cdvalues = req.list_form_param('%s:%s' % (self.rschema,
       
   601                                                       self.parent_eid),
       
   602                                            erroneous_post)
       
   603             if unicode(entity.eid) not in cdvalues:
       
   604                 return False
       
   605         return True
       
   606 
       
   607     def form_context(self, entity, kwargs):
       
   608         ctx = super(InlineFormMixIn, self).form_context(entity, kwargs)
       
   609         _ = self.req._
       
   610         local_ctx = {'createmsg' : self.req.__('add a %s' % entity.e_schema),
       
   611                      'so': self.role[0], # 's' for subject, 'o' for object
       
   612                      'eid' : entity.eid,
       
   613                      'rtype' : self.rschema,
       
   614                      'parenteid' : self.parent_eid,
       
   615                      'parenttype' : self.parent_schema,
       
   616                      'etype' : entity.e_schema,
       
   617                      'novalue' : INTERNAL_FIELD_VALUE,
       
   618                      'removemsg' : self.req.__('remove this %s' % entity.e_schema),
       
   619                      'notice' : self.req._('click on the box to cancel the deletion'),
       
   620                      }
       
   621         ctx.update(local_ctx)
       
   622         return ctx
       
   623 
       
   624 
       
   625 class InlineEntityCreationForm(InlineFormMixIn, CreationForm):
       
   626     id = 'inline-creation'
       
   627     __selectors__ = (kwargs_selector, etype_form_selector)
       
   628     expected_kwargs = ('ptype', 'peid', 'rtype')
       
   629     
       
   630     EDITION_BODY = u'''\
       
   631 <div id="div-%(parenteid)s-%(rtype)s-%(eid)s" class="inlinedform">
       
   632  <div class="iformBody">
       
   633  <div class="iformTitle"><span>%(title)s</span> #<span class="icounter">1</span> [<a href="javascript: removeInlineForm('%(parenteid)s', '%(rtype)s', '%(eid)s'); noop();">%(removemsg)s</a>]</div>
       
   634  <fieldset class="subentity">
       
   635  %(attrform)s
       
   636  %(relattrform)s
       
   637  </fieldset>
       
   638  </div>
       
   639  <fieldset class="hidden" id="fs-%(parenteid)s-%(rtype)s-%(eid)s">
       
   640 %(base)s
       
   641  <input type="hidden" value="%(novalue)s" name="edit%(so)s-%(rtype)s:%(parenteid)s" />
       
   642  <input id="rel-%(parenteid)s-%(rtype)s-%(eid)s" type="hidden" value="%(eid)s" name="%(rtype)s:%(parenteid)s" />
       
   643  </fieldset>
       
   644 </div>''' # do not insert trailing space or \n here !
       
   645 
       
   646     def call(self, etype, ptype, peid, rtype, role='subject', **kwargs):
       
   647         """
       
   648         :param etype: the entity type being created in the inline form
       
   649         :param parent: the parent entity hosting the inline form
       
   650         :param rtype: the relation bridging `etype` and `parent`
       
   651         :param role: the role played by the `parent` in the relation
       
   652         """
       
   653         self.req.add_css('cubicweb.form.css')
       
   654         try:
       
   655             entity = self.vreg.etype_class(etype)(self.req, None, None)
       
   656         except:
       
   657             self.w(self.req._('no such entity type %s') % etype)
       
   658             return
       
   659         self.edit_form(entity, ptype, peid, rtype, role, **kwargs)
       
   660     
       
   661     
       
   662 
       
   663 
       
   664 class InlineEntityEditionForm(InlineFormMixIn, EditionForm):
       
   665     id = 'inline-edition'
       
   666     __selectors__ = (accept_selector, kwargs_selector)
       
   667     expected_kwargs = ('ptype', 'peid', 'rtype')
       
   668     
       
   669     EDITION_BODY = u'''\
       
   670 <div onclick="restoreInlinedEntity('%(parenteid)s', '%(rtype)s', '%(eid)s')" id="div-%(parenteid)s-%(rtype)s-%(eid)s" class="inlinedform">   
       
   671 <div id="notice-%(parenteid)s-%(rtype)s-%(eid)s" class="notice">%(notice)s</div>
       
   672 <div class="iformTitle"><span>%(title)s</span>  #<span class="icounter">%(count)s</span> [<a href="javascript: removeInlinedEntity('%(parenteid)s', '%(rtype)s', '%(eid)s'); noop();">%(removemsg)s</a>]</div>
       
   673  <div class="iformBody">
       
   674  <fieldset class="subentity">
       
   675  %(attrform)s
       
   676  </fieldset>
       
   677  %(relattrform)s
       
   678  </div>
       
   679  <fieldset id="fs-%(parenteid)s-%(rtype)s-%(eid)s">
       
   680 %(base)s
       
   681  <input type="hidden" value="%(eid)s" name="edit%(so)s-%(rtype)s:%(parenteid)s" />
       
   682  %(rinput)s
       
   683  </fieldset>
       
   684 </div>''' # do not insert trailing space or \n here !
       
   685 
       
   686     rel_input = u'''<input id="rel-%(parenteid)s-%(rtype)s-%(eid)s" type="hidden" value="%(eid)s" name="%(rtype)s:%(parenteid)s" />'''
       
   687  
       
   688     def call(self, **kwargs):
       
   689         """redefine default View.call() method to avoid automatic
       
   690         insertions of <div class="section"> between each row of
       
   691         the resultset
       
   692         """
       
   693         self.req.add_css('cubicweb.form.css')
       
   694         rset = self.rset
       
   695         for i in xrange(len(rset)):
       
   696             self.wview(self.id, rset, row=i, **kwargs)
       
   697 
       
   698     def cell_call(self, row, col, ptype, peid, rtype, role='subject', **kwargs):
       
   699         """
       
   700         :param parent: the parent entity hosting the inline form
       
   701         :param rtype: the relation bridging `etype` and `parent`
       
   702         :param role: the role played by the `parent` in the relation
       
   703         """
       
   704         entity = self.entity(row, col)
       
   705         self.edit_form(entity, ptype, peid, rtype, role, **kwargs)
       
   706 
       
   707 
       
   708     def form_context(self, entity, kwargs):
       
   709         ctx = super(InlineEntityEditionForm, self).form_context(entity, kwargs)
       
   710         if self.keep_entity(entity):
       
   711             ctx['rinput'] = self.rel_input % ctx
       
   712             ctx['todelete'] = u''
       
   713         else:
       
   714             ctx['rinput'] = u''
       
   715             ctx['todelete'] = u'checked="checked"'
       
   716         ctx['count'] = entity.row + 1
       
   717         return ctx
       
   718     
       
   719     
       
   720 
       
   721 class CopyEditionForm(EditionForm):
       
   722     id = 'copy'
       
   723     title = _('copy edition')
       
   724 
       
   725     def cell_call(self, row, col, **kwargs):
       
   726         self.req.add_js(('cubicweb.ajax.js', 'cubicweb.edition.js'))
       
   727         self.req.add_css('cubicweb.form.css')
       
   728         entity = self.complete_entity(row, col, skip_bytes=True)
       
   729         # make a copy of entity to avoid altering the entity in the
       
   730         # request's cache. 
       
   731         self.newentity = copy(entity)
       
   732         self.copying = self.newentity.eid
       
   733         self.newentity.eid = None
       
   734         self.edit_form(self.newentity, kwargs)
       
   735         del self.newentity
       
   736 
       
   737     def action_title(self, entity):
       
   738         """form's title"""
       
   739         msg = super(CopyEditionForm, self).action_title(entity)
       
   740         return msg + (u'<script type="text/javascript">updateMessage("%s");</script>\n'
       
   741                       % self.req._('Please note that this is only a shallow copy'))
       
   742         # XXX above message should have style of a warning
       
   743 
       
   744     @property
       
   745     def formid(self):
       
   746         return 'edition'
       
   747         
       
   748     def relations_form(self, entity, kwargs):
       
   749         return u''
       
   750 
       
   751     def reset_url(self, entity):
       
   752         return self.build_url('view', rql='Any X WHERE X eid %s' % self.copying)
       
   753     
       
   754     def attributes_form(self, entity, kwargs, include_eid=True):
       
   755         # we don't want __clone_eid on inlined edited entities
       
   756         if entity.eid == self.newentity.eid:
       
   757             self._hiddens.append((eid_param('__cloned_eid', entity.eid), self.copying, ''))
       
   758         return EditionForm.attributes_form(self, entity, kwargs, include_eid)
       
   759     
       
   760     def submited_message(self):
       
   761         return self.req._('element copied')
       
   762        
       
   763     
       
   764 
       
   765 class TableEditForm(EntityForm):
       
   766     id = 'muledit'
       
   767     title = _('multiple edit')
       
   768 
       
   769     EDITION_BODY = u'''<form method="post" id="entityForm" onsubmit="return validateForm('entityForm', null);" action="%(action)s">
       
   770   %(error)s
       
   771   <div id="progress">%(progress)s</div>
       
   772   <fieldset>
       
   773   <input type="hidden" name="__errorurl" value="%(url)s" />
       
   774   <input type="hidden" name="__form_id" value="%(formid)s" />
       
   775   <input type="hidden" name="__redirectvid" value="%(redirectvid)s" />
       
   776   <input type="hidden" name="__redirectrql" value="%(redirectrql)s" />
       
   777   <table class="listing">
       
   778     <tr class="header">
       
   779       <th align="left"><input type="checkbox" onclick="setCheckboxesState('eid', this.checked)" value="" title="toggle check boxes" /></th>
       
   780       %(attrheaders)s
       
   781     </tr>
       
   782     %(lines)s
       
   783   </table>
       
   784   <table width="100%%">
       
   785     <tr>
       
   786       <td align="left">
       
   787         <input class="validateButton" type="submit"  value="%(okvalue)s" title="%(oktitle)s" />
       
   788         <input class="validateButton" type="reset" name="__action_cancel" value="%(cancelvalue)s" title="%(canceltitle)s" />
       
   789       </td>
       
   790     </tr>
       
   791   </table>
       
   792   </fieldset>    
       
   793 </form>
       
   794 '''
       
   795 
       
   796     WIDGET_CELL = u'''\
       
   797 <td%(csscls)s>
       
   798   %(error)s
       
   799   <div>%(widget)s</div>
       
   800 </td>'''
       
   801     
       
   802     def call(self, **kwargs):
       
   803         """a view to edit multiple entities of the same type
       
   804         the first column should be the eid
       
   805         """
       
   806         req = self.req
       
   807         form = req.form
       
   808         req.add_js('cubicweb.edition.js')
       
   809         req.add_css('cubicweb.form.css')
       
   810         _ = req._
       
   811         sampleentity = self.complete_entity(0)
       
   812         attrheaders = [u'<th>%s</th>' % rdef[0].display_name(req, rdef[-1])
       
   813                        for rdef in sampleentity.relations_by_category('primary', 'add')
       
   814                        if rdef[0].type != 'eid']
       
   815         ctx = {'action' : self.build_url('edit'),
       
   816                'error': self.error_message(),
       
   817                'progress': _('validating...'),
       
   818                'url': html_escape(req.url()),
       
   819                'formid': self.id,
       
   820                'redirectvid': html_escape(form.get('__redirectvid', 'list')),
       
   821                'redirectrql': html_escape(form.get('__redirectrql', self.rset.printable_rql())),
       
   822                'attrheaders': u'\n'.join(attrheaders),
       
   823                'lines': u'\n'.join(self.edit_form(ent) for ent in self.rset.entities()),
       
   824                'okvalue': _('button_ok').capitalize(),
       
   825                'oktitle': _('validate modifications on selected items').capitalize(),
       
   826                'cancelvalue': _('button_reset').capitalize(),
       
   827                'canceltitle': _('revert changes').capitalize(),
       
   828                }        
       
   829         self.w(self.EDITION_BODY % ctx)
       
   830         
       
   831         
       
   832     def reset_url(self, entity=None):
       
   833         self.build_url('view', rql=self.rset.printable_rql())
       
   834         
       
   835     def edit_form(self, entity):
       
   836         html = []
       
   837         w = html.append
       
   838         entity.complete()
       
   839         eid = entity.eid
       
   840         values = self.req.data.get('formvalues', ())
       
   841         qeid = eid_param('eid', eid)
       
   842         checked = qeid in values
       
   843         w(u'<tr class="%s">' % (entity.row % 2 and u'even' or u'odd'))
       
   844         w(u'<td>%s<input type="hidden" name="__type:%s" value="%s" /></td>'
       
   845           % (checkbox('eid', eid, checked=checked), eid, entity.e_schema))
       
   846         # attribute relations (skip eid which is handled by the checkbox
       
   847         wdg = entity.get_widget
       
   848         wdgfactories = [wdg(rschema, x) for rschema, _, x in entity.relations_by_category('primary', 'add')
       
   849                         if rschema.type != 'eid'] # XXX both (add, delete)
       
   850         seid = html_escape(dumps(eid))
       
   851         for wobj in wdgfactories:
       
   852             if isinstance(wobj, ComboBoxWidget):
       
   853                 wobj.attrs['onchange'] = "setCheckboxesState2('eid', %s, 'checked')" % seid
       
   854             elif isinstance(wobj, InputWidget):
       
   855                 wobj.attrs['onkeypress'] = "setCheckboxesState2('eid', %s, 'checked')" % seid
       
   856             error = wobj.render_error(entity)
       
   857             if error:
       
   858                 csscls = u' class="error"'
       
   859             else:
       
   860                 csscls = u''
       
   861             w(self.WIDGET_CELL % {'csscls': csscls, 'error': error,
       
   862                                   'widget': wobj.edit_render(entity)})
       
   863         w(u'</tr>')
       
   864         return '\n'.join(html)
       
   865         
       
   866 
       
   867 class UnrelatedDivs(EntityView):
       
   868     id = 'unrelateddivs'
       
   869     __selectors__ = (req_form_params_selector,)
       
   870     form_params = ('relation',)
       
   871 
       
   872     @property
       
   873     def limit(self):
       
   874         if self.req.form.get('__force_display'):
       
   875             return None
       
   876         return self.req.property_value('navigation.related-limit') + 1
       
   877 
       
   878     def cell_call(self, row, col):
       
   879         entity = self.entity(row, col)
       
   880         relname, target = self.req.form.get('relation').rsplit('_', 1)
       
   881         rschema = self.schema.rschema(relname)
       
   882         hidden = 'hidden' in self.req.form
       
   883         is_cell = 'is_cell' in self.req.form
       
   884         self.w(self.build_unrelated_select_div(entity, rschema, target,
       
   885                                                is_cell=is_cell, hidden=hidden))
       
   886 
       
   887     def build_unrelated_select_div(self, entity, rschema, target,
       
   888                                    is_cell=False, hidden=True):
       
   889         options = []
       
   890         divid = 'div%s_%s_%s' % (rschema.type, target, entity.eid)
       
   891         selectid = 'select%s_%s_%s' % (rschema.type, target, entity.eid)
       
   892         if rschema.symetric or target == 'subject':
       
   893             targettypes = rschema.objects(entity.e_schema)
       
   894             etypes = '/'.join(sorted(etype.display_name(self.req) for etype in targettypes))
       
   895         else:
       
   896             targettypes = rschema.subjects(entity.e_schema)
       
   897             etypes = '/'.join(sorted(etype.display_name(self.req) for etype in targettypes))
       
   898         etypes = cut(etypes, self.req.property_value('navigation.short-line-size'))
       
   899         options.append('<option>%s %s</option>' % (self.req._('select a'), etypes))
       
   900         options += self._get_select_options(entity, rschema, target)
       
   901         options += self._get_search_options(entity, rschema, target, targettypes)
       
   902         if 'Basket' in self.schema: # XXX
       
   903             options += self._get_basket_options(entity, rschema, target, targettypes)
       
   904         relname, target = self.req.form.get('relation').rsplit('_', 1)
       
   905         return u"""\
       
   906 <div class="%s" id="%s">
       
   907   <select id="%s" onchange="javascript: addPendingInsert(this.options[this.selectedIndex], %s, %s, '%s');">
       
   908     %s
       
   909   </select>
       
   910 </div>
       
   911 """ % (hidden and 'hidden' or '', divid, selectid, html_escape(dumps(entity.eid)),
       
   912        is_cell and 'true' or 'null', relname, '\n'.join(options))
       
   913 
       
   914     def _get_select_options(self, entity, rschema, target):
       
   915         """add options to search among all entities of each possible type"""
       
   916         options = []
       
   917         eid = entity.eid
       
   918         pending_inserts = self.req.get_pending_inserts(eid)
       
   919         rtype = rschema.type
       
   920         for eview, reid in entity.vocabulary(rschema, target, self.limit):
       
   921             if reid is None:
       
   922                 options.append('<option class="separator">-- %s --</option>' % html_escape(eview))
       
   923             else:
       
   924                 optionid = relation_id(eid, rtype, target, reid)
       
   925                 if optionid not in pending_inserts:
       
   926                     # prefix option's id with letters to make valid XHTML wise
       
   927                     options.append('<option id="id%s" value="%s">%s</option>' %
       
   928                                    (optionid, reid, html_escape(eview)))
       
   929         return options
       
   930 
       
   931     def _get_search_options(self, entity, rschema, target, targettypes):
       
   932         """add options to search among all entities of each possible type"""
       
   933         options = []
       
   934         _ = self.req._
       
   935         for eschema in targettypes:
       
   936             mode = '%s:%s:%s:%s' % (target, entity.eid, rschema.type, eschema)
       
   937             url = self.build_url(entity.rest_path(), vid='search-associate',
       
   938                                  __mode=mode)
       
   939             options.append((eschema.display_name(self.req),
       
   940                             '<option value="%s">%s %s</option>' % (
       
   941                 html_escape(url), _('Search for'), eschema.display_name(self.req))))
       
   942         return [o for l, o in sorted(options)]
       
   943 
       
   944     def _get_basket_options(self, entity, rschema, target, targettypes):
       
   945         options = []
       
   946         rtype = rschema.type
       
   947         _ = self.req._
       
   948         for basketeid, basketname in self._get_basket_links(self.req.user.eid,
       
   949                                                             target, targettypes):
       
   950             optionid = relation_id(entity.eid, rtype, target, basketeid)
       
   951             options.append('<option id="%s" value="%s">%s %s</option>' % (
       
   952                 optionid, basketeid, _('link to each item in'), html_escape(basketname)))
       
   953         return options
       
   954 
       
   955     def _get_basket_links(self, ueid, target, targettypes):
       
   956         targettypes = set(targettypes)
       
   957         for basketeid, basketname, elements in self._get_basket_info(ueid):
       
   958             baskettypes = elements.column_types(0)
       
   959             # if every elements in the basket can be attached to the
       
   960             # edited entity
       
   961             if baskettypes & targettypes:
       
   962                 yield basketeid, basketname
       
   963             
       
   964     def _get_basket_info(self, ueid):
       
   965         basketref = []
       
   966         basketrql = 'Any B,N WHERE B is Basket, B owned_by U, U eid %(x)s, B name N'
       
   967         basketresultset = self.req.execute(basketrql, {'x': ueid}, 'x')
       
   968         for result in basketresultset:
       
   969             basketitemsrql = 'Any X WHERE X in_basket B, B eid %(x)s'
       
   970             rset = self.req.execute(basketitemsrql, {'x': result[0]}, 'x')
       
   971             basketref.append((result[0], result[1], rset))
       
   972         return basketref
       
   973 
       
   974 
       
   975 class ComboboxView(EntityView):
       
   976     """the view used in combobox (unrelated entities)
       
   977 
       
   978     THIS IS A TEXT VIEW. DO NOT HTML_ESCAPE
       
   979     """
       
   980     id = 'combobox'
       
   981     accepts = ('Any',)
       
   982     title = None
       
   983     
       
   984     def cell_call(self, row, col):
       
   985         """the combo-box view for an entity: same as text out of context view
       
   986         by default
       
   987         """
       
   988         self.wview('textoutofcontext', self.rset, row=row, col=col)
       
   989