web/views/editforms.py
changeset 3777 3ef8cdb5fb1c
parent 3720 5376aaadd16b
parent 3767 03924de0014d
child 3890 d7a270f50f54
equal deleted inserted replaced
3739:817e96eeac5c 3777:3ef8cdb5fb1c
    17 from logilab.common.decorators import cached
    17 from logilab.common.decorators import cached
    18 
    18 
    19 from cubicweb import neg_role
    19 from cubicweb import neg_role
    20 from cubicweb.selectors import (match_kwargs, one_line_rset, non_final_entity,
    20 from cubicweb.selectors import (match_kwargs, one_line_rset, non_final_entity,
    21                                 specified_etype_implements, yes)
    21                                 specified_etype_implements, yes)
    22 from cubicweb.utils import make_uid
       
    23 from cubicweb.view import EntityView
    22 from cubicweb.view import EntityView
    24 from cubicweb.common import tags
    23 from cubicweb.common import tags
    25 from cubicweb.web import INTERNAL_FIELD_VALUE, RequestError, stdmsgs, eid_param
    24 from cubicweb.web import stdmsgs, eid_param
    26 from cubicweb.web import uicfg
    25 from cubicweb.web import uicfg
    27 from cubicweb.web.form import FormViewMixIn, FieldNotFound
    26 from cubicweb.web.form import FormViewMixIn, FieldNotFound
    28 from cubicweb.web.formfields import guess_field
    27 from cubicweb.web.formfields import guess_field
    29 from cubicweb.web.formwidgets import Button, SubmitButton, ResetButton
    28 from cubicweb.web.formwidgets import Button, SubmitButton, ResetButton
    30 from cubicweb.web.views import forms
    29 from cubicweb.web.views import forms
   109     __regid__ = 'doreledit'
   108     __regid__ = 'doreledit'
   110     __select__ = non_final_entity() & match_kwargs('rtype')
   109     __select__ = non_final_entity() & match_kwargs('rtype')
   111     # FIXME editableField class could be toggleable from userprefs
   110     # FIXME editableField class could be toggleable from userprefs
   112 
   111 
   113     _onclick = u"showInlineEditionForm(%(eid)s, '%(rtype)s', '%(divid)s')"
   112     _onclick = u"showInlineEditionForm(%(eid)s, '%(rtype)s', '%(divid)s')"
   114     _defaultlandingzone = (u'<img title="%(msg)s" '
   113     _onsubmit = ("return inlineValidateRelationForm('%(rtype)s', '%(role)s', '%(eid)s', "
   115                            'src="data/accessories-text-editor.png" '
   114                  "'%(divid)s', %(reload)s, '%(vid)s', '%(default)s', '%(lzone)s');")
       
   115     _cancelclick = "hideInlineEdit(%s,\'%s\',\'%s\')"
       
   116     _defaultlandingzone = (u'<img title="%(msg)s" src="data/file.gif" '
   116                            'alt="%(msg)s"/>')
   117                            'alt="%(msg)s"/>')
   117     _landingzonemsg = _('click to edit this field')
   118     _landingzonemsg = _('click to edit this field')
   118     # default relation vids according to cardinality
   119     # default relation vids according to cardinality
   119     _one_rvid = 'incontext'
   120     _one_rvid = 'incontext'
   120     _many_rvid = 'csv'
   121     _many_rvid = 'csv'
   121 
   122 
   122     def _compute_best_vid(self, eschema, rschema, role):
       
   123         if eschema.cardinality(rschema, role) in '+*':
       
   124             return self._many_rvid
       
   125         return self._one_rvid
       
   126 
       
   127     def _build_landing_zone(self, lzone):
       
   128         return lzone or self._defaultlandingzone % {
       
   129             'msg': xml_escape(self._cw._(self._landingzonemsg))}
       
   130 
       
   131     def _build_renderer(self, entity, rtype, role):
       
   132         return self._cw.vreg['formrenderers'].select(
       
   133             'base', self._cw, entity=entity, display_label=False,
       
   134             display_help=False, table_class='',
       
   135             button_bar_class='buttonbar', display_progress_div=False)
       
   136 
       
   137     def _build_form(self, entity, rtype, role, default, onsubmit, reload,
       
   138                   extradata=None, **formargs):
       
   139         divid = 'd%s' % make_uid('%s-%s' % (rtype, entity.eid))
       
   140         event_data = {'divid' : divid, 'eid' : entity.eid, 'rtype' : rtype,
       
   141                       'reload' : dumps(reload), 'default' : default}
       
   142         if extradata:
       
   143             event_data.update(extradata)
       
   144         onsubmit %= event_data
       
   145         cancelclick = "hideInlineEdit(%s,\'%s\',\'%s\')" % (entity.eid, rtype,
       
   146                                                             divid)
       
   147         form = self._cw.vreg['forms'].select(
       
   148             'edition', self._cw, entity=entity, domid='%s-form' % divid,
       
   149             cssstyle='display: none', onsubmit=onsubmit, action='#',
       
   150             display_fields=[(rtype, role)],
       
   151             form_buttons=[SubmitButton(), Button(stdmsgs.BUTTON_CANCEL,
       
   152                                                  onclick=cancelclick)],
       
   153             **formargs)
       
   154         form.event_data = event_data
       
   155         return form
       
   156 
   123 
   157     def cell_call(self, row, col, rtype=None, role='subject',
   124     def cell_call(self, row, col, rtype=None, role='subject',
   158                   reload=False,      # controls reloading the whole page after change
   125                   reload=False,      # controls reloading the whole page after change
   159                   rvid=None,         # vid to be applied to other side of rtype (non final relations only)
   126                   rvid=None,         # vid to be applied to other side of rtype (non final relations only)
   160                   default=None,      # default value
   127                   default=None,      # default value
   161                   landing_zone=None  # prepend value with a separate html element to click onto
   128                   landing_zone=None  # prepend value with a separate html element to click onto
   162                                      # (esp. needed when values are links)
   129                                      # (esp. needed when values are links)
   163                   ):
   130                   ):
   164         """display field to edit entity's `rtype` relation on click"""
   131         """display field to edit entity's `rtype` relation on click"""
   165         assert rtype
   132         assert rtype
   166         assert role in ('subject', 'object')
   133         assert role in ('subject', 'object'), '%s is not an acceptable role value' % role
       
   134         self.req.add_js('cubicweb.edition.js')
       
   135         self.req.add_css('cubicweb.form.css')
   167         if default is None:
   136         if default is None:
   168             default = xml_escape(self._cw._('<no value>'))
   137             default = xml_escape(self._cw._('<no value>'))
   169         schema = self._cw.vreg.schema
   138         schema = self._cw.vreg.schema
   170         entity = self.cw_rset.get_entity(row, col)
   139         entity = self.cw_rset.get_entity(row, col)
   171         rschema = schema.rschema(rtype)
   140         rschema = schema.rschema(rtype)
   172         lzone = self._build_landing_zone(landing_zone)
   141         lzone = self._build_landing_zone(landing_zone)
   173         # compute value, checking perms, build form
   142         # compute value, checking perms, build form
   174         if rschema.final:
   143         if rschema.final:
   175             onsubmit = ("return inlineValidateAttributeForm('%(rtype)s', '%(eid)s', '%(divid)s', "
   144             form = self._build_form(entity, rtype, role, 'edition', default, reload, lzone)
   176                         "%(reload)s, '%(default)s');")
       
   177             form = self._build_form(
       
   178                 entity, rtype, role, default, onsubmit, reload)
       
   179             if not self.should_edit_attribute(entity, rschema, role, form):
   145             if not self.should_edit_attribute(entity, rschema, role, form):
   180                 self.w(entity.printable_value(rtype))
   146                 self.w(entity.printable_value(rtype))
   181                 return
   147                 return
   182             value = entity.printable_value(rtype) or default
   148             value = entity.printable_value(rtype) or default
   183             self.attribute_form(lzone, value, form,
   149             self.relation_form(lzone, value, form,
   184                                  self._build_renderer(entity, rtype, role))
   150                                self._build_renderer(entity, rtype, role))
   185         else:
   151         else:
   186             if rvid is None:
   152             if rvid is None:
   187                 rvid = self._compute_best_vid(entity.e_schema, rschema, role)
   153                 rvid = self._compute_best_vid(entity.e_schema, rschema, role)
   188             rset = entity.related(rtype, role)
   154             rset = entity.related(rtype, role)
   189             if rset:
   155             if rset:
   192                 value = default
   158                 value = default
   193             if not self.should_edit_relation(entity, rschema, role, rvid):
   159             if not self.should_edit_relation(entity, rschema, role, rvid):
   194                 if rset:
   160                 if rset:
   195                     self.w(value)
   161                     self.w(value)
   196                 return
   162                 return
   197             onsubmit = ("return inlineValidateRelationForm('%(rtype)s', '%(role)s', '%(eid)s', "
   163             form = self._build_form(entity, rtype, role, 'base', default, reload, lzone,
   198                         "'%(divid)s', %(reload)s, '%(vid)s', '%(default)s', '%(lzone)s');")
   164                                     dict(vid=rvid, lzone=lzone))
   199             form = self._build_form(
   165             field = guess_field(entity.e_schema, entity.schema.rschema(rtype), role)
   200                 entity, rtype, role, default, onsubmit, reload,
   166             form.append_field(field)
   201                 dict(vid=rvid, role=role, lzone=lzone))
       
   202             self.relation_form(lzone, value, form,
   167             self.relation_form(lzone, value, form,
   203                                self._build_renderer(entity, rtype, role))
   168                                self._build_renderer(entity, rtype, role))
   204 
   169 
   205     def should_edit_attribute(self, entity, rschema, role, form):
   170     def should_edit_attribute(self, entity, rschema, role, form):
   206         rtype = str(rschema)
   171         rtype = str(rschema)
   207         ttype = rschema.targets(entity.__regid__, role)[0]
   172         ttype = rschema.targets(entity.id, role)[0]
   208         afs = uicfg.autoform_section.etype_get(entity.__regid__, rtype, role, ttype)
   173         afs = uicfg.autoform_section.etype_get(entity.id, rtype, role, ttype)
   209         if 'main_hidden' in afs or not entity.has_perm('update'):
   174         if 'main_hidden' in afs or not entity.has_perm('update'):
   210             self.w(entity.printable_value(rtype))
       
   211             return False
   175             return False
   212         try:
   176         try:
   213             field = form.field_by_name(rtype, role)
   177             form.field_by_name(rtype, role)
   214         except FieldNotFound:
   178         except FieldNotFound:
   215             self.w(entity.printable_value(rtype))
       
   216             return False
   179             return False
   217         return True
   180         return True
   218 
   181 
   219     def should_edit_relation(self, entity, rschema, role, rvid):
   182     def should_edit_relation(self, entity, rschema, role, rvid):
   220         if ((role == 'subject' and not rschema.has_perm(self._cw, 'add',
   183         if ((role == 'subject' and not rschema.has_perm(self._cw, 'add',
   221                                                         fromeid=entity.eid))
   184                                                         fromeid=entity.eid))
   222             or
   185             or
   223             (role == 'object' and not rschema.has_perm(self._cw, 'add',
   186             (role == 'object' and not rschema.has_perm(self._cw, 'add',
   224                                                        toeid=entity.eid))):
   187                                                        toeid=entity.eid))):
   225             self.wview(rvid, entity.related(str(rschema), role), 'null')
       
   226             return False
   188             return False
   227         return True
   189         return True
   228 
       
   229     def attribute_form(self, lzone, value, form, renderer):
       
   230         """div (class=field)
       
   231               +-xxx div
       
   232               |  +-xxx div (class=editableField)
       
   233               |  |  +-landing zone
       
   234               |  +-value-xxx div
       
   235               |     +-value
       
   236               +-form-xxx div
       
   237         """
       
   238         w = self.w
       
   239         w(u'<div class="field">')
       
   240         w(u'<div id="%s" style="display: inline">' % form.event_data['divid'])
       
   241         w(tags.div(lzone, klass='editableField',
       
   242                    onclick=self._onclick % form.event_data))
       
   243         w(u'<div id="value-%s" style="display: inline">%s</div>' %
       
   244                (form.event_data['divid'], value))
       
   245         w(u'</div>')
       
   246         w(form.form_render(renderer=renderer))
       
   247         w(u'</div>')
       
   248 
   190 
   249     def relation_form(self, lzone, value, form, renderer):
   191     def relation_form(self, lzone, value, form, renderer):
   250         """xxx-reledit div (class=field)
   192         """xxx-reledit div (class=field)
   251               +-xxx div (class="editableField")
   193               +-xxx div (class="editableField")
   252               |   +-landing zone
   194               |   +-landing zone
   253               +-value
   195               +-xxx-value div
   254               +-form-xxx div
   196               +-xxx-form div
   255         """
   197         """
   256         w = self.w
   198         w = self.w
   257         w(u'<div id="%s-reledit" class="field">' % form.event_data['divid'])
   199         divid = form.event_args['divid']
   258         w(tags.div(lzone, klass='editableField', id=form.event_data['divid'],
   200         w(u'<div id="%s-reledit" class="field">' % form.event_args['divid'])
   259                    onclick=self._onclick % form.event_data))
   201         w(u'<div id="%s" class="editableField" onclick="%s" title="%s">' % (
   260         w(value)
   202                 divid, xml_escape(self._onclick % form.event_args),
       
   203                 self.req._(self._landingzonemsg)))
       
   204         w(lzone)
       
   205         w(u'</div>')
       
   206         w(u'<div id="%s-value" class="editableFieldValue">%s</div>' % (divid, value))
   261         w(form.form_render(renderer=renderer))
   207         w(form.form_render(renderer=renderer))
   262         w(u'</div>')
   208         w(u'</div>')
   263 
   209 
       
   210     def _compute_best_vid(self, eschema, rschema, role):
       
   211         if eschema.cardinality(rschema, role) in '+*':
       
   212             return self._many_rvid
       
   213         return self._one_rvid
       
   214 
       
   215     def _build_landing_zone(self, lzone):
       
   216         return lzone or self._defaultlandingzone % {
       
   217             'msg': xml_escape(self._cw._(self._landingzonemsg))}
       
   218 
       
   219     def _build_renderer(self, entity, rtype, role):
       
   220         return self._cw.vreg['formrenderers'].select(
       
   221             'base', self._cw, entity=entity, display_label=False,
       
   222             display_help=False, table_class='',
       
   223             button_bar_class='buttonbar', display_progress_div=False)
       
   224 
       
   225     def _build_args(self, entity, rtype, role, formid, default, reload, lzone,
       
   226                     extradata=None):
       
   227         divid = '%s-%s-%s' % (rtype, role, entity.eid)
       
   228         event_args = {'divid' : divid, 'eid' : entity.eid, 'rtype' : rtype,
       
   229                       'reload' : dumps(reload), 'default' : default, 'role' : role, 'vid' : u'',
       
   230                       'lzone' : lzone}
       
   231         if extradata:
       
   232             event_args.update(extradata)
       
   233         return divid, event_args
       
   234 
       
   235     def _build_form(self, entity, rtype, role, formid, default, reload, lzone,
       
   236                   extradata=None, **formargs):
       
   237         divid, event_args = self._build_args(entity, rtype, role, formid, default,
       
   238                                       reload, lzone, extradata)
       
   239         onsubmit = self._onsubmit % event_args
       
   240         cancelclick = self._cancelclick % (entity.eid, rtype, divid)
       
   241         form = self._cw.vreg['forms'].select(
       
   242             formid, self._cw, entity=entity, domid='%s-form' % divid,
       
   243             cssstyle='display: none', onsubmit=onsubmit, action='#',
       
   244             form_buttons=[SubmitButton(), Button(stdmsgs.BUTTON_CANCEL,
       
   245                                                  onclick=cancelclick)],
       
   246             **formargs)
       
   247         form.event_args = event_args
       
   248         return form
       
   249 
       
   250 class DummyForm(object):
       
   251     __slots__ = ('event_args',)
       
   252     def form_render(self, **_args):
       
   253         return u''
       
   254     def append_field(self, *args):
       
   255         pass
   264 
   256 
   265 class AutoClickAndEditFormView(ClickAndEditFormView):
   257 class AutoClickAndEditFormView(ClickAndEditFormView):
   266     """same as ClickAndEditFormView but checking if the view *should* be applied
   258     """same as ClickAndEditFormView but checking if the view *should* be applied
   267     by checking uicfg configuration and composite relation property.
   259     by checking uicfg configuration and composite relation property.
   268     """
   260     """
   269     __regid__ = 'reledit'
   261     __regid__ = 'reledit'
       
   262     _onclick = (u"loadInlineEditionForm(%(eid)s, '%(rtype)s', '%(role)s', "
       
   263                 "'%(divid)s', %(reload)s, '%(vid)s', '%(default)s', '%(lzone)s');")
   270 
   264 
   271     def should_edit_relation(self, entity, rschema, role, rvid):
   265     def should_edit_relation(self, entity, rschema, role, rvid):
   272         eschema = entity.e_schema
   266         eschema = entity.e_schema
   273         rtype = str(rschema)
   267         rtype = str(rschema)
   274         # XXX check autoform_section. what if 'generic'?
   268         # XXX check autoform_section. what if 'generic'?
   281             self.wview(rvid, entity.related(rtype, role), 'null')
   275             self.wview(rvid, entity.related(rtype, role), 'null')
   282             return False
   276             return False
   283         return super(AutoClickAndEditFormView, self).should_edit_relation(
   277         return super(AutoClickAndEditFormView, self).should_edit_relation(
   284             entity, rschema, role, rvid)
   278             entity, rschema, role, rvid)
   285 
   279 
       
   280     def _build_form(self, entity, rtype, role, formid, default, reload, lzone,
       
   281                   extradata=None, **formargs):
       
   282         _divid, event_args = self._build_args(entity, rtype, role, formid, default,
       
   283                                               reload, lzone, extradata)
       
   284         form = DummyForm()
       
   285         form.event_args = event_args
       
   286         return form
       
   287 
       
   288     def _build_renderer(self, entity, rtype, role):
       
   289         pass
   286 
   290 
   287 class EditionFormView(FormViewMixIn, EntityView):
   291 class EditionFormView(FormViewMixIn, EntityView):
   288     """display primary entity edition form"""
   292     """display primary entity edition form"""
   289     __regid__ = 'edition'
   293     __regid__ = 'edition'
   290     # add yes() so it takes precedence over deprecated views in baseforms,
   294     # add yes() so it takes precedence over deprecated views in baseforms,