web/views/baseforms.py
changeset 2797 de0fcdb65e30
parent 2796 14d2c69e12c4
child 2798 9c650701cb17
equal deleted inserted replaced
2796:14d2c69e12c4 2797:de0fcdb65e30
     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-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
       
     6 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
     7 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
       
     8 """
       
     9 __docformat__ = "restructuredtext en"
       
    10 
       
    11 from copy import copy
       
    12 
       
    13 from simplejson import dumps
       
    14 
       
    15 from logilab.mtconverter import xml_escape
       
    16 from logilab.common.decorators import cached
       
    17 
       
    18 from cubicweb.selectors import (specified_etype_implements, accepts_etype_compat,
       
    19                                 non_final_entity, match_kwargs, one_line_rset)
       
    20 from cubicweb.view import View, EntityView
       
    21 from cubicweb.web import INTERNAL_FIELD_VALUE, eid_param
       
    22 from cubicweb.web.controller import NAV_FORM_PARAMETERS
       
    23 from cubicweb.web.widgets import checkbox, InputWidget, ComboBoxWidget
       
    24 from cubicweb.web.form import FormMixIn
       
    25 from cubicweb.web.views.autoform import AutomaticEntityForm
       
    26 
       
    27 _ = unicode
       
    28 
       
    29 
       
    30 class EditionForm(FormMixIn, EntityView):
       
    31     """primary entity edition form
       
    32 
       
    33     When generating a new attribute_input, the editor will look for a method
       
    34     named 'default_ATTRNAME' on the entity instance, where ATTRNAME is the
       
    35     name of the attribute being edited. You may use this feature to compute
       
    36     dynamic default values such as the 'tomorrow' date or the user's login
       
    37     being connected
       
    38     """
       
    39     id = 'edition'
       
    40     __select__ = one_line_rset() & non_final_entity()
       
    41 
       
    42     title = _('edition')
       
    43     controller = 'edit'
       
    44     skip_relations = set()
       
    45 
       
    46     EDITION_BODY = u'''\
       
    47  %(errormsg)s
       
    48 <form id="%(formid)s" class="entityForm" cubicweb:target="eformframe"
       
    49       method="post" onsubmit="%(onsubmit)s" enctype="%(enctype)s" action="%(action)s">
       
    50  %(title)s
       
    51  <div id="progress">%(inprogress)s</div>
       
    52  <div class="iformTitle"><span>%(mainattrs_label)s</span></div>
       
    53  <div class="formBody"><fieldset>
       
    54  %(base)s
       
    55  %(attrform)s
       
    56  %(relattrform)s
       
    57 </fieldset>
       
    58  %(relform)s
       
    59  </div>
       
    60  <table width="100%%">
       
    61   <tbody>
       
    62    <tr><td align="center">
       
    63      %(validate)s
       
    64    </td><td style="align: right; width: 50%%;">
       
    65      %(apply)s
       
    66      %(cancel)s
       
    67    </td></tr>
       
    68   </tbody>
       
    69  </table>
       
    70 </form>
       
    71 '''
       
    72 
       
    73     def cell_call(self, row, col, **kwargs):
       
    74         self.req.add_js( ('cubicweb.ajax.js', ) )
       
    75         entity = self.complete_entity(row, col)
       
    76         self.edit_form(entity, kwargs)
       
    77 
       
    78     def edit_form(self, entity, kwargs):
       
    79         varmaker = self.req.get_page_data('rql_varmaker')
       
    80         if varmaker is None:
       
    81             varmaker = self.req.varmaker
       
    82             self.req.set_page_data('rql_varmaker', varmaker)
       
    83         self.varmaker = varmaker
       
    84         self.w(self.EDITION_BODY % self.form_context(entity, kwargs))
       
    85 
       
    86     def form_context(self, entity, kwargs):
       
    87         """returns the dictionnary used to fill the EDITION_BODY template
       
    88 
       
    89         If you create your own edition form, you can probably just override
       
    90         `EDITION_BODY` and `form_context`
       
    91         """
       
    92         if self.need_multipart(entity):
       
    93             enctype = 'multipart/form-data'
       
    94         else:
       
    95             enctype = 'application/x-www-form-urlencoded'
       
    96         self._hiddens = []
       
    97         if entity.eid is None:
       
    98             entity.eid = self.varmaker.next()
       
    99         # XXX (hack) action_title might need __linkto req's original value
       
   100         #            and widgets such as DynamicComboWidget might change it
       
   101         #            so we need to compute title before calling atttributes_form
       
   102         formtitle = self.action_title(entity)
       
   103         # be sure to call .*_form first so tabindexes are correct and inlined
       
   104         # fields errors are consumed
       
   105         if not entity.has_eid() or entity.has_perm('update'):
       
   106             attrform = self.attributes_form(entity, kwargs)
       
   107         else:
       
   108             attrform = ''
       
   109         inlineform = self.inline_entities_form(entity, kwargs)
       
   110         relform = self.relations_form(entity, kwargs)
       
   111         vindex = self.req.next_tabindex()
       
   112         aindex = self.req.next_tabindex()
       
   113         cindex = self.req.next_tabindex()
       
   114         self.add_hidden_web_behaviour_params(entity)
       
   115         _ = self.req._
       
   116         return {
       
   117             'formid'   : self.domid,
       
   118             'onsubmit' : self.on_submit(entity),
       
   119             'enctype'  : enctype,
       
   120             'errormsg' : self.error_message(),
       
   121             'action'   : self.build_url('validateform'),
       
   122             'eids'     : entity.has_eid() and [entity.eid] or [],
       
   123             'inprogress': _('validating...'),
       
   124             'title'    : formtitle,
       
   125             'mainattrs_label' : _('main informations'),
       
   126             'reseturl' : self.redirect_url(entity),
       
   127             'attrform' : attrform,
       
   128             'relform'  : relform,
       
   129             'relattrform': inlineform,
       
   130             'base'     : self.base_form(entity, kwargs),
       
   131             'validate' : self.button_ok(tabindex=vindex),
       
   132             'apply'    : self.button_apply(tabindex=aindex),
       
   133             'cancel'   : self.button_cancel(tabindex=cindex),
       
   134             }
       
   135 
       
   136     @property
       
   137     def formid(self):
       
   138         return self.id
       
   139 
       
   140     def action_title(self, entity):
       
   141         """form's title"""
       
   142         ptitle = self.req._(self.title)
       
   143         return u'<div class="formTitle"><span>%s %s</span></div>' % (
       
   144             entity.dc_type(), ptitle and '(%s)' % ptitle)
       
   145 
       
   146 
       
   147     def base_form(self, entity, kwargs):
       
   148         output = []
       
   149         for name, value, iid in self._hiddens:
       
   150             if isinstance(value, basestring):
       
   151                 value = xml_escape(value)
       
   152             if iid:
       
   153                 output.append(u'<input id="%s" type="hidden" name="%s" value="%s" />'
       
   154                               % (iid, name, value))
       
   155             else:
       
   156                 output.append(u'<input type="hidden" name="%s" value="%s" />'
       
   157                               % (name, value))
       
   158         return u'\n'.join(output)
       
   159 
       
   160     def add_hidden_web_behaviour_params(self, entity):
       
   161         """inserts hidden params controlling how errors and redirection
       
   162         should be handled
       
   163         """
       
   164         req = self.req
       
   165         self._hiddens.append( (u'__maineid', entity.eid, u'') )
       
   166         self._hiddens.append( (u'__errorurl', req.url(), u'errorurl') )
       
   167         self._hiddens.append( (u'__form_id', self.formid, u'') )
       
   168         for param in NAV_FORM_PARAMETERS:
       
   169             value = req.form.get(param)
       
   170             if value:
       
   171                 self._hiddens.append( (param, value, u'') )
       
   172         msg = self.submited_message()
       
   173         # If we need to directly attach the new object to another one
       
   174         for linkto in req.list_form_param('__linkto'):
       
   175             self._hiddens.append( ('__linkto', linkto, '') )
       
   176             msg = '%s %s' % (msg, self.req._('and linked'))
       
   177         self._hiddens.append( ('__message', msg, '') )
       
   178 
       
   179 
       
   180     def attributes_form(self, entity, kwargs, include_eid=True):
       
   181         """create a form to edit entity's attributes"""
       
   182         html = []
       
   183         w = html.append
       
   184         eid = entity.eid
       
   185         wdg = entity.get_widget
       
   186         lines = (wdg(rschema, x) for rschema, x in self.editable_attributes(entity))
       
   187         if include_eid:
       
   188             self._hiddens.append( ('eid', entity.eid, '') )
       
   189         self._hiddens.append( (eid_param('__type', eid), entity.e_schema, '') )
       
   190         w(u'<table id="%s" class="%s" style="width:100%%;">' %
       
   191           (kwargs.get('tab_id', 'entityForm%s' % eid),
       
   192            kwargs.get('tab_class', 'attributeForm')))
       
   193         for widget in lines:
       
   194             w(u'<tr>\n<th class="labelCol">%s</th>' % widget.render_label(entity))
       
   195             error = widget.render_error(entity)
       
   196             if error:
       
   197                 w(u'<td class="error" style="width:100%;">')
       
   198             else:
       
   199                 w(u'<td style="width:100%;">')
       
   200             if error:
       
   201                 w(error)
       
   202             w(widget.edit_render(entity))
       
   203             w(widget.render_help(entity))
       
   204             w(u'</td>\n</tr>')
       
   205         w(u'</table>')
       
   206         return u'\n'.join(html)
       
   207 
       
   208     def editable_attributes(self, entity):
       
   209         # XXX both (add, delete)
       
   210         return [(rschema, x) for rschema, _, x in entity.relations_by_category(('primary', 'secondary'), 'add')
       
   211                 if rschema != 'eid']
       
   212 
       
   213     def relations_form(self, entity, kwargs):
       
   214         srels_by_cat = entity.srelations_by_category(('generic', 'metadata'), 'add')
       
   215         if not srels_by_cat:
       
   216             return u''
       
   217         req = self.req
       
   218         _ = self.req._
       
   219         label = u'%s :' % _('This %s' % entity.e_schema).capitalize()
       
   220         eid = entity.eid
       
   221         html = []
       
   222         w = html.append
       
   223         w(u'<fieldset class="subentity">')
       
   224         w(u'<legend class="iformTitle">%s</legend>' % label)
       
   225         w(u'<table id="relatedEntities">')
       
   226         for row in self.relations_table(entity):
       
   227             # already linked entities
       
   228             if row[2]:
       
   229                 w(u'<tr><th class="labelCol">%s</th>' % row[0].display_name(req, row[1]))
       
   230                 w(u'<td>')
       
   231                 w(u'<ul>')
       
   232                 for viewparams in row[2]:
       
   233                     w(u'<li class="invisible">%s<div id="span%s" class="%s">%s</div></li>'
       
   234                       % (viewparams[1], viewparams[0], viewparams[2], viewparams[3]))
       
   235                 if not self.force_display and self.maxrelitems < len(row[2]):
       
   236                     w(u'<li class="invisible">%s</li>' % self.force_display_link())
       
   237                 w(u'</ul>')
       
   238                 w(u'</td>')
       
   239                 w(u'</tr>')
       
   240         pendings = list(self.restore_pending_inserts(entity))
       
   241         if not pendings:
       
   242             w(u'<tr><th>&nbsp;</th><td>&nbsp;</td></tr>')
       
   243         else:
       
   244             for row in pendings:
       
   245                 # soon to be linked to entities
       
   246                 w(u'<tr id="tr%s">' % row[1])
       
   247                 w(u'<th>%s</th>' % row[3])
       
   248                 w(u'<td>')
       
   249                 w(u'<a class="handle" title="%s" href="%s">[x]</a>' %
       
   250                   (_('cancel this insert'), row[2]))
       
   251                 w(u'<a id="a%s" class="editionPending" href="%s">%s</a>'
       
   252                   % (row[1], row[4], xml_escape(row[5])))
       
   253                 w(u'</td>')
       
   254                 w(u'</tr>')
       
   255         w(u'<tr id="relationSelectorRow_%s" class="separator">' % eid)
       
   256         w(u'<th class="labelCol">')
       
   257         w(u'<span>%s</span>' % _('add relation'))
       
   258         w(u'<select id="relationSelector_%s" tabindex="%s" onchange="javascript:showMatchingSelect(this.options[this.selectedIndex].value,%s);">'
       
   259           % (eid, req.next_tabindex(), xml_escape(dumps(eid))))
       
   260         w(u'<option value="">%s</option>' % _('select a relation'))
       
   261         for i18nrtype, rschema, target in srels_by_cat:
       
   262             # more entities to link to
       
   263             w(u'<option value="%s_%s">%s</option>' % (rschema, target, i18nrtype))
       
   264         w(u'</select>')
       
   265         w(u'</th>')
       
   266         w(u'<td id="unrelatedDivs_%s"></td>' % eid)
       
   267         w(u'</tr>')
       
   268         w(u'</table>')
       
   269         w(u'</fieldset>')
       
   270         return '\n'.join(html)
       
   271 
       
   272     def inline_entities_form(self, entity, kwargs):
       
   273         """create a form to edit entity's inlined relations"""
       
   274         result = []
       
   275         _ = self.req._
       
   276         for rschema, targettypes, x in entity.relations_by_category('inlineview', 'add'):
       
   277             # show inline forms only if there's one possible target type
       
   278             # for rschema
       
   279             if len(targettypes) != 1:
       
   280                 self.warning('entity related by the %s relation should have '
       
   281                              'inlined form but there is multiple target types, '
       
   282                              'dunno what to do', rschema)
       
   283                 continue
       
   284             targettype = targettypes[0].type
       
   285             if self.should_inline_relation_form(entity, rschema, targettype, x):
       
   286                 result.append(u'<div id="inline%sslot">' % rschema)
       
   287                 existant = entity.has_eid() and entity.related(rschema)
       
   288                 if existant:
       
   289                     # display inline-edition view for all existing related entities
       
   290                     result.append(self.view('inline-edition', existant,
       
   291                                             ptype=entity.e_schema, peid=entity.eid,
       
   292                                             rtype=rschema, role=x, **kwargs))
       
   293                 if x == 'subject':
       
   294                     card = rschema.rproperty(entity.e_schema, targettype, 'cardinality')[0]
       
   295                 else:
       
   296                     card = rschema.rproperty(targettype, entity.e_schema, 'cardinality')[1]
       
   297                 # there is no related entity and we need at least one : we need to
       
   298                 # display one explicit inline-creation view
       
   299                 if self.should_display_inline_relation_form(rschema, existant, card):
       
   300                     result.append(self.view('inline-creation', None, etype=targettype,
       
   301                                             peid=entity.eid, ptype=entity.e_schema,
       
   302                                             rtype=rschema, role=x, **kwargs))
       
   303                 # we can create more than one related entity, we thus display a link
       
   304                 # to add new related entities
       
   305                 if self.should_display_add_inline_relation_link(rschema, existant, card):
       
   306                     divid = "addNew%s%s%s:%s" % (targettype, rschema, x, entity.eid)
       
   307                     result.append(u'<div class="inlinedform" id="%s" cubicweb:limit="true">'
       
   308                                   % divid)
       
   309                     js = "addInlineCreationForm('%s', '%s', '%s', '%s', '%s')" % (
       
   310                         entity.eid, entity.e_schema, targettype, rschema, x)
       
   311                     if card in '1?':
       
   312                         js = "toggleVisibility('%s'); %s" % (divid, js)
       
   313                     result.append(u'<a class="addEntity" id="add%s:%slink" href="javascript: %s" >+ %s.</a>'
       
   314                                   % (rschema, entity.eid, js,
       
   315                                      self.req.__('add a %s' % targettype)))
       
   316                     result.append(u'</div>')
       
   317                     result.append(u'<div class="trame_grise">&nbsp;</div>')
       
   318                 result.append(u'</div>')
       
   319         return '\n'.join(result)
       
   320 
       
   321     # should_* method extracted to allow overriding
       
   322 
       
   323     def should_inline_relation_form(self, entity, rschema, targettype, role):
       
   324         return AutomaticEntityForm.rinlined.etype_get(entity.id, rschema, role,
       
   325                                                       targettype)
       
   326 
       
   327     def should_display_inline_relation_form(self, rschema, existant, card):
       
   328         return not existant and card in '1+'
       
   329 
       
   330     def should_display_add_inline_relation_link(self, rschema, existant, card):
       
   331         return not existant or card in '+*'
       
   332 
       
   333     def reset_url(self, entity):
       
   334         return entity.absolute_url()
       
   335 
       
   336     def on_submit(self, entity):
       
   337         return u'return freezeFormButtons(\'%s\')' % (self.domid)
       
   338 
       
   339     def submited_message(self):
       
   340         return self.req._('element edited')
       
   341 
       
   342 
       
   343 
       
   344 class CreationForm(EditionForm):
       
   345     __select__ = specified_etype_implements('Any')
       
   346     # XXX bw compat, use View.registered since we don't want accept_compat
       
   347     #    wrapper set in EntityView
       
   348     registered = accepts_etype_compat(View.registered)
       
   349     id = 'creation'
       
   350     title = _('creation')
       
   351 
       
   352     def call(self, **kwargs):
       
   353         """creation view for an entity"""
       
   354         self.req.add_js( ('cubicweb.ajax.js',) )
       
   355         self.initialize_varmaker()
       
   356         etype = kwargs.pop('etype', self.req.form.get('etype'))
       
   357         try:
       
   358             entity = self.vreg.etype_class(etype)(self.req, None, None)
       
   359         except:
       
   360             self.w(self.req._('no such entity type %s') % etype)
       
   361         else:
       
   362             entity.eid = self.varmaker.next()
       
   363             self.edit_form(entity, kwargs)
       
   364 
       
   365     def action_title(self, entity):
       
   366         """custom form title if creating a entity with __linkto"""
       
   367         if '__linkto' in self.req.form:
       
   368             if isinstance(self.req.form['__linkto'], list):
       
   369                 # XXX which one should be considered (case: add a ticket to a version in jpl)
       
   370                 rtype, linkto_eid, role = self.req.form['__linkto'][0].split(':')
       
   371             else:
       
   372                 rtype, linkto_eid, role = self.req.form['__linkto'].split(':')
       
   373             linkto_rset = self.req.eid_rset(linkto_eid)
       
   374             linkto_type = linkto_rset.description[0][0]
       
   375             if role == 'subject':
       
   376                 title = self.req.__('creating %s (%s %s %s %%(linkto)s)' % (
       
   377                     entity.e_schema, entity.e_schema, rtype, linkto_type))
       
   378             else:
       
   379                 title = self.req.__('creating %s (%s %%(linkto)s %s %s)' % (
       
   380                     entity.e_schema, linkto_type, rtype, entity.e_schema))
       
   381             msg = title % {'linkto' : self.view('incontext', linkto_rset)}
       
   382             return u'<div class="formTitle notransform"><span>%s</span></div>' % msg
       
   383         else:
       
   384             return super(CreationForm, self).action_title(entity)
       
   385 
       
   386     @property
       
   387     def formid(self):
       
   388         return 'edition'
       
   389 
       
   390     def relations_form(self, entity, kwargs):
       
   391         return u''
       
   392 
       
   393     def reset_url(self, entity=None):
       
   394         return self.build_url(self.req.form.get('etype', '').lower())
       
   395 
       
   396     def submited_message(self):
       
   397         return self.req._('element created')
       
   398 
       
   399     def url(self):
       
   400         """return the url associated with this view"""
       
   401         return self.create_url(self.req.form.get('etype'))
       
   402 
       
   403 
       
   404 class InlineFormMixIn(object):
       
   405 
       
   406     @cached
       
   407     def card(self, etype):
       
   408         return self.rschema.rproperty(self.parent_schema, etype, 'cardinality')[0]
       
   409 
       
   410     def action_title(self, entity):
       
   411         return self.rschema.display_name(self.req, self.role)
       
   412 
       
   413     def add_hidden_web_behaviour_params(self, entity):
       
   414         pass
       
   415 
       
   416     def edit_form(self, entity, ptype, peid, rtype,
       
   417                   role='subject', **kwargs):
       
   418         self.rschema = self.schema.rschema(rtype)
       
   419         self.role = role
       
   420         self.parent_schema = self.schema.eschema(ptype)
       
   421         self.parent_eid = peid
       
   422         super(InlineFormMixIn, self).edit_form(entity, kwargs)
       
   423 
       
   424     def should_inline_relation_form(self, entity, rschema, targettype, role):
       
   425         if rschema == self.rschema:
       
   426             return False
       
   427         return AutomaticEntityForm.rinlined.etype_get(entity.id, rschema, role,
       
   428                                                       targettype)
       
   429 
       
   430     @cached
       
   431     def keep_entity(self, entity):
       
   432         req = self.req
       
   433         # are we regenerating form because of a validation error ?
       
   434         erroneous_post = req.data.get('formvalues')
       
   435         if erroneous_post:
       
   436             cdvalues = req.list_form_param('%s:%s' % (self.rschema,
       
   437                                                       self.parent_eid),
       
   438                                            erroneous_post)
       
   439             if unicode(entity.eid) not in cdvalues:
       
   440                 return False
       
   441         return True
       
   442 
       
   443     def form_context(self, entity, kwargs):
       
   444         ctx = super(InlineFormMixIn, self).form_context(entity, kwargs)
       
   445         _ = self.req._
       
   446         local_ctx = {'createmsg' : self.req.__('add a %s' % entity.e_schema),
       
   447                      'so': self.role[0], # 's' for subject, 'o' for object
       
   448                      'eid' : entity.eid,
       
   449                      'rtype' : self.rschema,
       
   450                      'parenteid' : self.parent_eid,
       
   451                      'parenttype' : self.parent_schema,
       
   452                      'etype' : entity.e_schema,
       
   453                      'novalue' : INTERNAL_FIELD_VALUE,
       
   454                      'removemsg' : self.req.__('remove this %s' % entity.e_schema),
       
   455                      'notice' : self.req._('click on the box to cancel the deletion'),
       
   456                      }
       
   457         ctx.update(local_ctx)
       
   458         return ctx
       
   459 
       
   460 
       
   461 class CopyEditionForm(EditionForm):
       
   462     id = 'copy'
       
   463     title = _('copy edition')
       
   464 
       
   465     def cell_call(self, row, col, **kwargs):
       
   466         self.req.add_js(('cubicweb.ajax.js',))
       
   467         entity = self.complete_entity(row, col, skip_bytes=True)
       
   468         # make a copy of entity to avoid altering the entity in the
       
   469         # request's cache.
       
   470         self.newentity = copy(entity)
       
   471         self.copying = self.newentity.eid
       
   472         self.newentity.eid = None
       
   473         self.edit_form(self.newentity, kwargs)
       
   474         del self.newentity
       
   475 
       
   476     def action_title(self, entity):
       
   477         """form's title"""
       
   478         msg = super(CopyEditionForm, self).action_title(entity)
       
   479         return msg + (u'<script type="text/javascript">updateMessage("%s");</script>\n'
       
   480                       % self.req._('Please note that this is only a shallow copy'))
       
   481         # XXX above message should have style of a warning
       
   482 
       
   483     @property
       
   484     def formid(self):
       
   485         return 'edition'
       
   486 
       
   487     def relations_form(self, entity, kwargs):
       
   488         return u''
       
   489 
       
   490     def reset_url(self, entity):
       
   491         return self.build_url('view', rql='Any X WHERE X eid %s' % self.copying)
       
   492 
       
   493     def attributes_form(self, entity, kwargs, include_eid=True):
       
   494         # we don't want __clone_eid on inlined edited entities
       
   495         if entity.eid == self.newentity.eid:
       
   496             self._hiddens.append((eid_param('__cloned_eid', entity.eid), self.copying, ''))
       
   497         return EditionForm.attributes_form(self, entity, kwargs, include_eid)
       
   498 
       
   499     def submited_message(self):
       
   500         return self.req._('element copied')
       
   501 
       
   502 
       
   503 class TableEditForm(FormMixIn, EntityView):
       
   504     id = 'muledit'
       
   505     title = _('multiple edit')
       
   506 
       
   507     EDITION_BODY = u'''<form method="post" id="entityForm" onsubmit="return validateForm('entityForm', null);" action="%(action)s">
       
   508   %(error)s
       
   509   <div id="progress">%(progress)s</div>
       
   510   <fieldset>
       
   511   <input type="hidden" name="__errorurl" value="%(url)s" />
       
   512   <input type="hidden" name="__form_id" value="%(formid)s" />
       
   513   <input type="hidden" name="__redirectvid" value="%(redirectvid)s" />
       
   514   <input type="hidden" name="__redirectrql" value="%(redirectrql)s" />
       
   515   <table class="listing">
       
   516     <tr class="header">
       
   517       <th align="left"><input type="checkbox" onclick="setCheckboxesState('eid', this.checked)" value="" title="toggle check boxes" /></th>
       
   518       %(attrheaders)s
       
   519     </tr>
       
   520     %(lines)s
       
   521   </table>
       
   522   <table width="100%%">
       
   523     <tr>
       
   524       <td align="left">
       
   525         <input class="validateButton" type="submit"  value="%(okvalue)s" title="%(oktitle)s" />
       
   526         <input class="validateButton" type="reset" name="__action_cancel" value="%(cancelvalue)s" title="%(canceltitle)s" />
       
   527       </td>
       
   528     </tr>
       
   529   </table>
       
   530   </fieldset>
       
   531 </form>
       
   532 '''
       
   533 
       
   534     WIDGET_CELL = u'''\
       
   535 <td%(csscls)s>
       
   536   %(error)s
       
   537   <div>%(widget)s</div>
       
   538 </td>'''
       
   539 
       
   540     def call(self, **kwargs):
       
   541         """a view to edit multiple entities of the same type
       
   542         the first column should be the eid
       
   543         """
       
   544         req = self.req
       
   545         form = req.form
       
   546         _ = req._
       
   547         sampleentity = self.complete_entity(0)
       
   548         attrheaders = [u'<th>%s</th>' % rdef[0].display_name(req, rdef[-1])
       
   549                        for rdef in sampleentity.relations_by_category('primary', 'add')
       
   550                        if rdef[0].type != 'eid']
       
   551         ctx = {'action' : self.build_url('edit'),
       
   552                'error': self.error_message(),
       
   553                'progress': _('validating...'),
       
   554                'url': xml_escape(req.url()),
       
   555                'formid': self.id,
       
   556                'redirectvid': xml_escape(form.get('__redirectvid', 'list')),
       
   557                'redirectrql': xml_escape(form.get('__redirectrql', self.rset.printable_rql())),
       
   558                'attrheaders': u'\n'.join(attrheaders),
       
   559                'lines': u'\n'.join(self.edit_form(ent) for ent in self.rset.entities()),
       
   560                'okvalue': _('button_ok').capitalize(),
       
   561                'oktitle': _('validate modifications on selected items').capitalize(),
       
   562                'cancelvalue': _('button_reset').capitalize(),
       
   563                'canceltitle': _('revert changes').capitalize(),
       
   564                }
       
   565         self.w(self.EDITION_BODY % ctx)
       
   566 
       
   567 
       
   568     def reset_url(self, entity=None):
       
   569         self.build_url('view', rql=self.rset.printable_rql())
       
   570 
       
   571     def edit_form(self, entity):
       
   572         html = []
       
   573         w = html.append
       
   574         entity.complete()
       
   575         eid = entity.eid
       
   576         values = self.req.data.get('formvalues', ())
       
   577         qeid = eid_param('eid', eid)
       
   578         checked = qeid in values
       
   579         w(u'<tr class="%s">' % (entity.row % 2 and u'even' or u'odd'))
       
   580         w(u'<td>%s<input type="hidden" name="__type:%s" value="%s" /></td>'
       
   581           % (checkbox('eid', eid, checked=checked), eid, entity.e_schema))
       
   582         # attribute relations (skip eid which is handled by the checkbox
       
   583         wdg = entity.get_widget
       
   584         wdgfactories = [wdg(rschema, x) for rschema, _, x in entity.relations_by_category('primary', 'add')
       
   585                         if rschema.type != 'eid'] # XXX both (add, delete)
       
   586         seid = xml_escape(dumps(eid))
       
   587         for wobj in wdgfactories:
       
   588             if isinstance(wobj, ComboBoxWidget):
       
   589                 wobj.attrs['onchange'] = "setCheckboxesState2('eid', %s, 'checked')" % seid
       
   590             elif isinstance(wobj, InputWidget):
       
   591                 wobj.attrs['onkeypress'] = "setCheckboxesState2('eid', %s, 'checked')" % seid
       
   592             error = wobj.render_error(entity)
       
   593             if error:
       
   594                 csscls = u' class="error"'
       
   595             else:
       
   596                 csscls = u''
       
   597             w(self.WIDGET_CELL % {'csscls': csscls, 'error': error,
       
   598                                   'widget': wobj.edit_render(entity)})
       
   599         w(u'</tr>')
       
   600         return '\n'.join(html)
       
   601 
       
   602 
       
   603 # XXX bw compat
       
   604 
       
   605 from logilab.common.deprecation import class_moved
       
   606 from cubicweb.web.views import editviews
       
   607 ComboboxView = class_moved(editviews.ComboboxView)