web/views/editforms.py
changeset 5869 8a129b3a5aff
parent 5727 29afb9e715bb
child 5877 0c7b7b76a84f
equal deleted inserted replaced
5868:c4380d8cfc25 5869:8a129b3a5aff
    24 
    24 
    25 from copy import copy
    25 from copy import copy
    26 
    26 
    27 from logilab.mtconverter import xml_escape
    27 from logilab.mtconverter import xml_escape
    28 from logilab.common.decorators import cached
    28 from logilab.common.decorators import cached
       
    29 from logilab.common.deprecation import class_moved
    29 
    30 
    30 from cubicweb import tags
    31 from cubicweb import tags
    31 from cubicweb.selectors import (match_kwargs, one_line_rset, non_final_entity,
    32 from cubicweb.selectors import (match_kwargs, one_line_rset, non_final_entity,
    32                                 specified_etype_implements, implements, yes)
    33                                 specified_etype_implements, implements, yes)
    33 from cubicweb.view import EntityView
    34 from cubicweb.view import EntityView
    34 from cubicweb.schema import display_name
    35 from cubicweb.schema import display_name
    35 from cubicweb.web import uicfg, stdmsgs, eid_param, dumps, \
    36 from cubicweb.web import uicfg, stdmsgs, eid_param, dumps, \
    36      formfields as ff, formwidgets as fw
    37      formfields as ff, formwidgets as fw
    37 from cubicweb.web.form import FormViewMixIn, FieldNotFound
    38 from cubicweb.web.form import FormViewMixIn, FieldNotFound
    38 from cubicweb.web.views import forms
    39 from cubicweb.web.views import forms, reledit
    39 
    40 
    40 _pvdc = uicfg.primaryview_display_ctrl
    41 _pvdc = uicfg.primaryview_display_ctrl
    41 
    42 
    42 
    43 
    43 class DeleteConfForm(forms.CompositeForm):
    44 class DeleteConfForm(forms.CompositeForm):
   258         self.w(form.render())
   259         self.w(form.render())
   259 
   260 
   260 
   261 
   261 # click and edit handling ('reledit') ##########################################
   262 # click and edit handling ('reledit') ##########################################
   262 
   263 
   263 class DummyForm(object):
   264 ClickAndEditFormView = class_moved(reledit.ClickAndEditFormView)
   264     __slots__ = ('event_args',)
   265 AutoClickAndEditFormView = class_moved(reledit.AutoClickAndEditFormView)
   265     def form_render(self, **_args):
       
   266         return u''
       
   267     def render(self, **_args):
       
   268         return u''
       
   269     def append_field(self, *args):
       
   270         pass
       
   271     def field_by_name(self, rtype, role, eschema=None):
       
   272         return None
       
   273 
       
   274 
       
   275 class ClickAndEditFormView(FormViewMixIn, EntityView):
       
   276     """form used to permit ajax edition of a relation or attribute of an entity
       
   277     in a view, if logged user have the permission to edit it.
       
   278 
       
   279     (double-click on the field to see an appropriate edition widget).
       
   280     """
       
   281     __regid__ = 'doreledit'
       
   282     __select__ = non_final_entity() & match_kwargs('rtype')
       
   283     # FIXME editableField class could be toggleable from userprefs
       
   284 
       
   285     _onclick = u"showInlineEditionForm(%(eid)s, '%(rtype)s', '%(divid)s')"
       
   286     _onsubmit = ("return inlineValidateRelationFormOptions('%(rtype)s', '%(eid)s', "
       
   287                  "'%(divid)s', %(options)s);")
       
   288     _cancelclick = "hideInlineEdit(%s,\'%s\',\'%s\')"
       
   289     _defaultlandingzone = (u'<img title="%(msg)s" src="data/pen_icon.png" '
       
   290                            'alt="%(msg)s"/>')
       
   291     _landingzonemsg = _('click to edit this field')
       
   292     # default relation vids according to cardinality
       
   293     _one_rvid = 'incontext'
       
   294     _many_rvid = 'csv'
       
   295 
       
   296 
       
   297     def cell_call(self, row, col, rtype=None, role='subject',
       
   298                   reload=False,      # controls reloading the whole page after change
       
   299                   rvid=None,         # vid to be applied to other side of rtype (non final relations only)
       
   300                   default=None,      # default value
       
   301                   landing_zone=None  # prepend value with a separate html element to click onto
       
   302                                      # (esp. needed when values are links)
       
   303                   ):
       
   304         """display field to edit entity's `rtype` relation on click"""
       
   305         assert rtype
       
   306         assert role in ('subject', 'object'), '%s is not an acceptable role value' % role
       
   307         self._cw.add_js('cubicweb.edition.js')
       
   308         self._cw.add_css('cubicweb.form.css')
       
   309         if default is None:
       
   310             default = xml_escape(self._cw._('<%s not specified>')
       
   311                                  % display_name(self._cw, rtype, role))
       
   312         schema = self._cw.vreg.schema
       
   313         entity = self.cw_rset.get_entity(row, col)
       
   314         rschema = schema.rschema(rtype)
       
   315         lzone = self._build_landing_zone(landing_zone)
       
   316         # compute value, checking perms, build form
       
   317         if rschema.final:
       
   318             form = self._build_form(entity, rtype, role, 'base', default, reload, lzone)
       
   319             if not self.should_edit_attribute(entity, rschema, form):
       
   320                 self.w(entity.printable_value(rtype))
       
   321                 return
       
   322             value = entity.printable_value(rtype) or default
       
   323         else:
       
   324             rvid = self._compute_best_vid(entity.e_schema, rschema, role)
       
   325             rset = entity.related(rtype, role)
       
   326             if rset:
       
   327                 value = self._cw.view(rvid, rset)
       
   328             else:
       
   329                 value = default
       
   330             if not self.should_edit_relation(entity, rschema, role, rvid):
       
   331                 if rset:
       
   332                     self.w(value)
       
   333                 return
       
   334             # XXX do we really have to give lzone twice?
       
   335             form = self._build_form(entity, rtype, role, 'base', default, reload, lzone,
       
   336                                     dict(vid=rvid, lzone=lzone))
       
   337         field = form.field_by_name(rtype, role, entity.e_schema)
       
   338         form.append_field(field)
       
   339         self.relation_form(lzone, value, form,
       
   340                            self._build_renderer(entity, rtype, role))
       
   341 
       
   342     def should_edit_attribute(self, entity, rschema, form):
       
   343         if not entity.cw_has_perm('update'):
       
   344             return False
       
   345         rdef = entity.e_schema.rdef(rschema)
       
   346         if not rdef.has_perm(self._cw, 'update', eid=entity.eid):
       
   347             return False
       
   348         try:
       
   349             form.field_by_name(str(rschema), 'subject', entity.e_schema)
       
   350         except FieldNotFound:
       
   351             return False
       
   352         return True
       
   353 
       
   354     def should_edit_relation(self, entity, rschema, role, rvid):
       
   355         if ((role == 'subject' and not rschema.has_perm(self._cw, 'add',
       
   356                                                         fromeid=entity.eid))
       
   357             or
       
   358             (role == 'object' and not rschema.has_perm(self._cw, 'add',
       
   359                                                        toeid=entity.eid))):
       
   360             return False
       
   361         return True
       
   362 
       
   363     def relation_form(self, lzone, value, form, renderer):
       
   364         """xxx-reledit div (class=field)
       
   365               +-xxx div (class="editableField")
       
   366               |   +-landing zone
       
   367               +-xxx-value div
       
   368               +-xxx-form div
       
   369         """
       
   370         w = self.w
       
   371         divid = form.event_args['divid']
       
   372         w(u'<div id="%s-reledit" class="field" '
       
   373           u'onmouseout="jQuery(\'#%s\').addClass(\'hidden\')" '
       
   374           u'onmouseover="jQuery(\'#%s\').removeClass(\'hidden\')">'
       
   375           % (divid, divid, divid))
       
   376         w(u'<div id="%s-value" class="editableFieldValue">%s</div>' % (divid, value))
       
   377         w(form.render(renderer=renderer))
       
   378         w(u'<div id="%s" class="editableField hidden" onclick="%s" title="%s">' % (
       
   379                 divid, xml_escape(self._onclick % form.event_args),
       
   380                 self._cw._(self._landingzonemsg)))
       
   381         w(lzone)
       
   382         w(u'</div>')
       
   383         w(u'</div>')
       
   384 
       
   385     def _compute_best_vid(self, eschema, rschema, role):
       
   386         dispctrl = _pvdc.etype_get(eschema, rschema, role)
       
   387         if dispctrl.get('rvid'):
       
   388             return dispctrl['rvid']
       
   389         if eschema.rdef(rschema, role).role_cardinality(role) in '+*':
       
   390             return self._many_rvid
       
   391         return self._one_rvid
       
   392 
       
   393     def _build_landing_zone(self, lzone):
       
   394         return lzone or self._defaultlandingzone % {
       
   395             'msg': xml_escape(self._cw._(self._landingzonemsg))}
       
   396 
       
   397     def _build_renderer(self, entity, rtype, role):
       
   398         return self._cw.vreg['formrenderers'].select(
       
   399             'base', self._cw, entity=entity, display_label=False,
       
   400             display_help=False, table_class='',
       
   401             button_bar_class='buttonbar', display_progress_div=False)
       
   402 
       
   403     def _build_args(self, entity, rtype, role, formid, default, reload, lzone,
       
   404                     extradata=None):
       
   405         divid = '%s-%s-%s' % (rtype, role, entity.eid)
       
   406         options = {'reload' : reload, 'default_value' : default,
       
   407                    'role' : role, 'vid' : '',
       
   408                    'lzone' : lzone}
       
   409         event_args = {'divid' : divid, 'eid' : entity.eid, 'rtype' : rtype,
       
   410                       'options' : dumps(options)}
       
   411         if extradata:
       
   412             event_args.update(extradata)
       
   413         return divid, event_args
       
   414 
       
   415     def _build_form(self, entity, rtype, role, formid, default, reload, lzone,
       
   416                   extradata=None, **formargs):
       
   417         divid, event_args = self._build_args(entity, rtype, role, formid, default,
       
   418                                              reload, lzone, extradata)
       
   419         onsubmit = self._onsubmit % event_args
       
   420         cancelclick = self._cancelclick % (entity.eid, rtype, divid)
       
   421         form = self._cw.vreg['forms'].select(
       
   422             formid, self._cw, entity=entity, domid='%s-form' % divid,
       
   423             cssstyle='display: none', onsubmit=onsubmit, action='#',
       
   424             form_buttons=[fw.SubmitButton(),
       
   425                           fw.Button(stdmsgs.BUTTON_CANCEL, onclick=cancelclick)],
       
   426             **formargs)
       
   427         form.event_args = event_args
       
   428         return form
       
   429 
       
   430 
       
   431 class AutoClickAndEditFormView(ClickAndEditFormView):
       
   432     """same as ClickAndEditFormView but checking if the view *should* be applied
       
   433     by checking uicfg configuration and composite relation property.
       
   434     """
       
   435     __regid__ = 'reledit'
       
   436     _onclick = (u"loadInlineEditionFormOptions(%(eid)s, '%(rtype)s', "
       
   437                 "'%(divid)s', %(options)s);")
       
   438 
       
   439     def should_edit_attribute(self, entity, rschema, form):
       
   440         rdef = entity.e_schema.rdef(rschema)
       
   441         afs = uicfg.autoform_section.etype_get(
       
   442             entity.__regid__, rschema, 'subject', rdef.object)
       
   443         if 'main_hidden' in afs:
       
   444             return False
       
   445         return super(AutoClickAndEditFormView, self).should_edit_attribute(
       
   446             entity, rschema, form)
       
   447 
       
   448     def should_edit_relation(self, entity, rschema, role, rvid):
       
   449         eschema = entity.e_schema
       
   450         dispctrl = _pvdc.etype_get(eschema, rschema, role)
       
   451         vid = dispctrl.get('vid', 'reledit')
       
   452         if vid != 'reledit': # reledit explicitly disabled
       
   453             return False
       
   454         rdef = eschema.rdef(rschema, role)
       
   455         if rdef.composite == role:
       
   456             return False
       
   457         afs = uicfg.autoform_section.etype_get(
       
   458             entity.__regid__, rschema, role, rdef.object)
       
   459         if 'main_hidden' in afs:
       
   460             return False
       
   461         return super(AutoClickAndEditFormView, self).should_edit_relation(
       
   462             entity, rschema, role, rvid)
       
   463 
       
   464     def _build_form(self, entity, rtype, role, formid, default, reload, lzone,
       
   465                   extradata=None, **formargs):
       
   466         _divid, event_args = self._build_args(entity, rtype, role, formid, default,
       
   467                                               reload, lzone, extradata)
       
   468         form = DummyForm()
       
   469         form.event_args = event_args
       
   470         return form
       
   471 
       
   472     def _build_renderer(self, entity, rtype, role):
       
   473         pass
       
   474