web/views/editforms.py
branchtls-sprint
changeset 1147 402e8a8b1d6a
parent 1138 22f634977c95
child 1151 b20677336ee6
equal deleted inserted replaced
1146:547681592765 1147:402e8a8b1d6a
     5 :copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
     5 :copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
     6 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
     6 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
     7 """
     7 """
     8 __docformat__ = "restructuredtext en"
     8 __docformat__ = "restructuredtext en"
     9 
     9 
       
    10 from copy import copy
       
    11 
    10 from simplejson import dumps
    12 from simplejson import dumps
    11 
    13 
    12 from cubicweb.selectors import match_kwargs, one_line_rset, non_final_entity
    14 from logilab.mtconverter import html_escape
       
    15 
       
    16 from cubicweb import typed_eid
       
    17 from cubicweb.selectors import (match_kwargs, one_line_rset, non_final_entity,
       
    18                                 specified_etype_implements, yes)
       
    19 from cubicweb.rtags import RelationTags
    13 from cubicweb.utils import make_uid
    20 from cubicweb.utils import make_uid
    14 from cubicweb.view import EntityView
    21 from cubicweb.view import EntityView
    15 from cubicweb.common import tags
    22 from cubicweb.common import tags
    16 from cubicweb.web import stdmsgs
    23 from cubicweb.web import INTERNAL_FIELD_VALUE, stdmsgs, formwidgets
    17 from cubicweb.web.form import MultipleFieldsForm, EntityFieldsForm, FormMixIn, FormRenderer
    24 from cubicweb.web.form import CompositeForm, EntityFieldsForm, FormMixIn
    18 from cubicweb.web.formfields import guess_field
    25 from cubicweb.web.formfields import guess_field
    19 
    26 from cubicweb.web.formrenderers import (FormRenderer, EntityFormRenderer,
       
    27                                         EntityCompositeFormRenderer,
       
    28                                         EntityInlinedFormRenderer)
    20 _ = unicode
    29 _ = unicode
    21 
    30 
       
    31 def relation_id(eid, rtype, role, reid):
       
    32     """return an identifier for a relation between two entities"""
       
    33     if role == 'subject':
       
    34         return u'%s:%s:%s' % (eid, rtype, reid)
       
    35     return u'%s:%s:%s' % (reid, rtype, eid)
       
    36         
       
    37 def toggable_relation_link(eid, nodeid, label='x'):
       
    38     """return javascript snippet to delete/undelete a relation between two
       
    39     entities
       
    40     """
       
    41     js = u"javascript: togglePendingDelete('%s', %s);" % (nodeid, html_escape(dumps(eid)))
       
    42     return u'[<a class="handle" href="%s" id="handle%s">%s</a>]' % (js, nodeid, label)
       
    43 
    22 
    44 
    23 class DeleteConfForm(EntityView):
    45 class DeleteConfForm(EntityView):
       
    46     """form used to confirm deletion of some entities"""
    24     id = 'deleteconf'
    47     id = 'deleteconf'
    25     title = _('delete')
    48     title = _('delete')
    26     domid = 'deleteconf'
    49     domid = 'deleteconf'
    27     onsubmit = None
    50     onsubmit = None
    28     # don't use navigation, all entities asked to be deleted should be displayed
    51     # don't use navigation, all entities asked to be deleted should be displayed
    35         _ = req._
    58         _ = req._
    36         w(u'<script type="text/javascript">updateMessage(\'%s\');</script>\n'
    59         w(u'<script type="text/javascript">updateMessage(\'%s\');</script>\n'
    37           % _('this action is not reversible!'))
    60           % _('this action is not reversible!'))
    38         # XXX above message should have style of a warning
    61         # XXX above message should have style of a warning
    39         w(u'<h4>%s</h4>\n' % _('Do you want to delete the following element(s) ?'))
    62         w(u'<h4>%s</h4>\n' % _('Do you want to delete the following element(s) ?'))
    40         form = MultipleFieldsForm(req, domid='deleteconf', action=self.build_url('edit'),
    63         form = CompositeForm(req, domid='deleteconf', action=self.build_url('edit'),
    41                                   onsubmit=self.onsubmit, copy_nav_params=True)
    64                                   onsubmit=self.onsubmit, copy_nav_params=True)
       
    65         # XXX tabindex
    42         form.buttons.append(form.button_delete(label=stdmsgs.YES))
    66         form.buttons.append(form.button_delete(label=stdmsgs.YES))
    43         form.buttons.append(form.button_cancel(label=stdmsgs.NO))
    67         form.buttons.append(form.button_cancel(label=stdmsgs.NO))
    44         done = set()
    68         done = set()
    45         w(u'<ul>\n')
    69         w(u'<ul>\n')
    46         for i in xrange(self.rset.rowcount):
    70         for entity in self.rset.entities():
    47             if self.rset[i][0] in done:
    71             if entity.eid in done:
    48                 continue
    72                 continue
    49             done.add(self.rset[i][0])
    73             done.add(entity.eid)
    50             entity = self.rset.get_entity(i, 0)
    74             subform = EntityFieldsForm(req, entity=entity, set_error_url=False)
    51             subform = EntityFieldsForm(req, set_error_url=False,
       
    52                                        entity=entity)
       
    53             form.form_add_subform(subform)
    75             form.form_add_subform(subform)
    54             # don't use outofcontext view or any other that may contain inline edition form
    76             # don't use outofcontext view or any other that may contain inline edition form
    55             w(u'<li>%s</li>' % tags.a(entity.view('textoutofcontext'),
    77             w(u'<li>%s</li>' % tags.a(entity.view('textoutofcontext'),
    56                                       href=entity.absolute_url()))
    78                                       href=entity.absolute_url()))
    57         w(u'</ul>\n')
    79         w(u'</ul>\n')
    58         w(form.form_render())
    80         w(form.form_render())
    59 
    81 
    60 
    82 
    61 class ClickAndEditForm(FormMixIn, EntityView):
    83 class ClickAndEditForm(FormMixIn, EntityView):
       
    84     """form used to permit ajax edition of an attribute of an entity in a view
       
    85     
       
    86     (double-click on the field to see an appropriate edition widget)
       
    87     """
    62     id = 'reledit'
    88     id = 'reledit'
    63     __select__ = non_final_entity() & match_kwargs('rtype')
    89     __select__ = non_final_entity() & match_kwargs('rtype')
    64 
    90     
    65     # FIXME editableField class could be toggleable from userprefs
    91     # FIXME editableField class could be toggleable from userprefs
    66       
    92     
       
    93     onsubmit = ("return inlineValidateForm('%(divid)s-form', '%(rtype)s', "
       
    94                 "'%(eid)s', '%(divid)s', %(reload)s);")
       
    95     ondblclick = "showInlineEditionForm(%(eid)s, '%(rtype)s', '%(divid)s')"
       
    96     
    67     def cell_call(self, row, col, rtype=None, role='subject', reload=False):
    97     def cell_call(self, row, col, rtype=None, role='subject', reload=False):
       
    98         """display field to edit entity's `rtype` relation on double-click"""
    68         entity = self.entity(row, col)
    99         entity = self.entity(row, col)
    69         if getattr(entity, rtype) is None:
   100         if getattr(entity, rtype) is None:
    70             value = self.req._('not specified')
   101             value = self.req._('not specified')
    71         else:
   102         else:
    72             value = entity.printable_value(rtype)
   103             value = entity.printable_value(rtype)
    76         self.req.add_js( ('cubicweb.ajax.js',) )
   107         self.req.add_js( ('cubicweb.ajax.js',) )
    77         eid = entity.eid
   108         eid = entity.eid
    78         edit_key = make_uid('%s-%s' % (rtype, eid))
   109         edit_key = make_uid('%s-%s' % (rtype, eid))
    79         divid = 'd%s' % edit_key
   110         divid = 'd%s' % edit_key
    80         reload = dumps(reload)
   111         reload = dumps(reload)
    81         buttons = [tags.input(klass="validateButton", type="submit", name="__action_apply",
   112         # XXX tab index
    82                               value=self.req._(stdmsgs.BUTTON_OK), tabindex=self.req.next_tabindex()),
   113         buttons = [tags.input(klass="validateButton", type="submit",
       
   114                               name="__action_apply",
       
   115                               value=self.req._(stdmsgs.BUTTON_OK),
       
   116                               tabindex=self.req.next_tabindex()),
    83                    tags.input(klass="validateButton", type="button",
   117                    tags.input(klass="validateButton", type="button",
    84                               value=self.req._(stdmsgs.BUTTON_CANCEL),
   118                               value=self.req._(stdmsgs.BUTTON_CANCEL),
    85                               onclick="cancelInlineEdit(%s,\'%s\',\'%s\')" % (eid, rtype, divid),
   119                               onclick="cancelInlineEdit(%s,\'%s\',\'%s\')" % (eid, rtype, divid),
    86                               tabindex=self.req.next_tabindex())]
   120                               tabindex=self.req.next_tabindex())]
    87         form = self.vreg.select_object('forms', 'edition', self.req, self.rset, row=row, col=col,
   121         form = self.vreg.select_object('forms', 'edition', self.req, self.rset,
    88                                        entity=entity, domid='%s-form' % divid, action='#',
   122                                        row=row, col=col, buttons=buttons,
    89                                        cssstyle='display: none', buttons=buttons,
   123                                        domid='%s-form' % divid, action='#',
    90                                        onsubmit="return inlineValidateForm('%(divid)s-form', '%(rtype)s', '%(eid)s', '%(divid)s', %(reload)s);" % locals())
   124                                        cssstyle='display: none',
       
   125                                        onsubmit=self.onsubmit % locals())
    91         renderer = FormRenderer(display_label=False, display_help=False,
   126         renderer = FormRenderer(display_label=False, display_help=False,
    92                                 display_fields=(rtype,), button_bar_class='buttonbar')
   127                                 display_fields=(rtype,), button_bar_class='buttonbar')
    93         self.w(tags.div(value, klass='editableField', id=divid,
   128         self.w(tags.div(value, klass='editableField', id=divid,
    94                         ondblclick="showInlineEditionForm(%(eid)s, '%(rtype)s', '%(divid)s')" % locals()))
   129                         ondblclick=self.ondblclick % locals()))
    95         self.w(form.render(renderer=renderer))
   130         self.w(form.form_render(renderer=renderer))
    96 
   131 
    97 
   132 
    98 class AutomaticEntityForm(EntityFieldsForm):
   133 class AutomaticEntityForm(EntityFieldsForm):
       
   134     """base automatic form to edit any entity
       
   135 
       
   136     Designed to be flly generated from schema but highly configurable through:
       
   137     * rtags (rcategories, rwidgets, inlined, rpermissions)
       
   138     * various standard form parameters
       
   139 
       
   140     You can also easily customise it by adding/removing fields in
       
   141     AutomaticEntityForm instances.
       
   142     """
    99     id = 'edition'
   143     id = 'edition'
       
   144     
   100     needs_js = EntityFieldsForm.needs_js + ('cubicweb.ajax.js',)
   145     needs_js = EntityFieldsForm.needs_js + ('cubicweb.ajax.js',)
       
   146     cwtarget = 'eformframe'
       
   147     cssclass = 'entityForm'
       
   148     copy_nav_params = True
       
   149     attrcategories = ('primary', 'secondary')
       
   150     
       
   151     # relations'category (eg primary/secondary/generic/metadata/generated)
       
   152     rcategories = RelationTags()
       
   153     # use primary and not generated for eid since it has to be an hidden
       
   154     rcategories.set_rtag('primary', 'subject', 'eid')
       
   155     rcategories.set_rtag('metadata', 'subject', 'creation_date')
       
   156     rcategories.set_rtag('metadata', 'subject', 'modification_date')
       
   157     rcategories.set_rtag('generated', 'subject', 'has_text')        
       
   158 
       
   159     # relations'widget (eg one of available class name in cubicweb.web.formwidgets)
       
   160     rwidgets = RelationTags()
       
   161     # inlined view flag for non final relations
       
   162     inlined = RelationTags()
       
   163     # set of tags of the form <action>_on_new on relations. <action> is a
       
   164     # schema action (add/update/delete/read), and when such a tag is found
       
   165     # permissions checking is by-passed and supposed to be ok
       
   166     rpermissions_overrides = RelationTags(use_set=True)
       
   167 
       
   168     @classmethod
       
   169     def registered(cls, registry):
       
   170         """build class using descriptor at registration time"""
       
   171         super(AutomaticEntityForm, cls).registered(registry)
       
   172         cls.init_rtags_category()
       
   173         return cls
       
   174         
       
   175     @classmethod
       
   176     def init_rtags_category(cls):
       
   177         """set default category tags for relations where it's not yet defined in
       
   178         the category relation tags
       
   179         """
       
   180         for eschema in cls.schema.entities():
       
   181             for rschema, tschemas, role in eschema.relation_definitions(True):
       
   182                 for tschema in tschemas:
       
   183                     if role == 'subject':
       
   184                         X, Y = eschema, tschema
       
   185                         card = rschema.rproperty(X, Y, 'cardinality')[0]
       
   186                         composed = rschema.rproperty(X, Y, 'composite') == 'object'
       
   187                     else:
       
   188                         X, Y = tschema, eschema
       
   189                         card = rschema.rproperty(X, Y, 'cardinality')[1]
       
   190                         composed = rschema.rproperty(X, Y, 'composite') == 'subject'
       
   191                     if not cls.rcategories.rtag(role, rschema, X, Y):
       
   192                         if card in '1+':
       
   193                             if not rschema.is_final() and composed:
       
   194                                 category = 'generated'
       
   195                             else:
       
   196                                 category = 'primary'
       
   197                         elif rschema.is_final():
       
   198                             category = 'secondary'
       
   199                         else: 
       
   200                             category = 'generic'
       
   201                         cls.rcategories.set_rtag(category, role, rschema, X, Y)
       
   202                         
   101     
   203     
   102     def __init__(self, *args, **kwargs):
   204     def __init__(self, *args, **kwargs):
   103         super(AutomaticEntityForm, self).__init__(*args, **kwargs)
   205         super(AutomaticEntityForm, self).__init__(*args, **kwargs)
   104         self.entity.complete()
   206         if self.edited_entity.has_eid():
   105         for rschema, target in self.editable_attributes(self.entity):
   207             self.edited_entity.complete()
   106             field = guess_field(self.entity.__class__, self.entity.e_schema,
   208         for rschema, role in self.editable_attributes():
   107                                 rschema, target)
   209             wdgname = self.rwidgets.etype_rtag(self.edited_entity.id, role, rschema)
       
   210             if wdgname:
       
   211                 field = guess_field(self.edited_entity.__class__, rschema, role,
       
   212                                     eidparam=True, widget=getattr(formwidgets, wdgname))
       
   213             else:
       
   214                 field = guess_field(self.edited_entity.__class__, rschema, role,
       
   215                                     eidparam=True)
   108             self.fields.append(field)
   216             self.fields.append(field)
   109             
   217         
       
   218     def action(self):
       
   219         """return the form's action attribute"""
       
   220         try:
       
   221             return self._action
       
   222         except AttributeError:
       
   223             return self.build_url('validateform')
       
   224         
       
   225     def set_action(self, value):
       
   226         self._action = value
       
   227         
       
   228     action = property(action, set_action)
       
   229     
   110     def form_buttons(self):
   230     def form_buttons(self):
       
   231         """return the form's buttons (as string)"""
   111         return [self.button_ok(tabindex=self.req.next_tabindex()),
   232         return [self.button_ok(tabindex=self.req.next_tabindex()),
   112                 self.button_apply(tabindex=self.req.next_tabindex()),
   233                 self.button_apply(tabindex=self.req.next_tabindex()),
   113                 self.button_cancel(tabindex=self.req.next_tabindex())]
   234                 self.button_cancel(tabindex=self.req.next_tabindex())]
   114 
   235 
   115     def editable_attributes(self, entity):
   236     def editable_attributes(self):
   116         # XXX both (add, delete) required for non final relations
   237         """return a list of (relation schema, role) to edit for the entity
   117         return [(rschema, x) for rschema, _, x in entity.relations_by_category(('primary', 'secondary'), 'add')
   238         """
       
   239         return [(rschema, x) for rschema, _, x in self.relations_by_category(self.attrcategories, 'add')
   118                 if rschema != 'eid']
   240                 if rschema != 'eid']
   119     
   241     
   120 class _EditionForm(EntityView):
   242     def relations_by_category(self, categories=None, permission=None):
   121     """primary entity edition form
   243         """return a list of (relation schema, target schemas, role) matching
   122 
   244         categories and permission
   123     When generating a new attribute_input, the editor will look for a method
   245         """
   124     named 'default_ATTRNAME' on the entity instance, where ATTRNAME is the
   246         if categories is not None:
   125     name of the attribute being edited. You may use this feature to compute
   247             if not isinstance(categories, (list, tuple, set, frozenset)):
   126     dynamic default values such as the 'tomorrow' date or the user's login
   248                 categories = (categories,)
   127     being connected
   249             if not isinstance(categories, (set, frozenset)):
   128     """    
   250                 categories = frozenset(categories)
       
   251         eschema  = self.edited_entity.e_schema
       
   252         rtags = self.rcategories
       
   253         permsoverrides = self.rpermissions_overrides
       
   254         if self.edited_entity.has_eid():
       
   255             eid = self.edited_entity.eid
       
   256         else:
       
   257             eid = None
       
   258         for rschema, targetschemas, role in eschema.relation_definitions(True):
       
   259             if rschema in ('identity', 'has_text'):
       
   260                 continue
       
   261             # check category first, potentially lower cost than checking
       
   262             # permission which may imply rql queries
       
   263             if categories is not None:
       
   264                 targetschemas = [tschema for tschema in targetschemas
       
   265                                  if rtags.etype_rtag(eschema, role, rschema, tschema) in categories]
       
   266                 if not targetschemas:
       
   267                     continue
       
   268             if permission is not None:
       
   269                 # tag allowing to hijack the permission machinery when
       
   270                 # permission is not verifiable until the entity is actually
       
   271                 # created...
       
   272                 if eid is None and '%s_on_new' % permission in permsoverrides.etype_rtags(eschema, role, rschema):
       
   273                     yield (rschema, targetschemas, role)
       
   274                     continue
       
   275                 if rschema.is_final():
       
   276                     if not rschema.has_perm(self.req, permission, eid):
       
   277                         continue
       
   278                 elif role == 'subject':
       
   279                     if not ((eid is None and rschema.has_local_role(permission)) or
       
   280                             rschema.has_perm(self.req, permission, fromeid=eid)):
       
   281                         continue
       
   282                     # on relation with cardinality 1 or ?, we need delete perm as well
       
   283                     # if the relation is already set
       
   284                     if (permission == 'add'
       
   285                         and rschema.cardinality(eschema, targetschemas[0], role) in '1?'
       
   286                         and eid and self.edited_entity.related(rschema.type, role)
       
   287                         and not rschema.has_perm(self.req, 'delete', fromeid=eid,
       
   288                                                  toeid=self.edited_entity.related(rschema.type, role)[0][0])):
       
   289                         continue
       
   290                 elif role == 'object':
       
   291                     if not ((eid is None and rschema.has_local_role(permission)) or
       
   292                             rschema.has_perm(self.req, permission, toeid=eid)):
       
   293                         continue
       
   294                     # on relation with cardinality 1 or ?, we need delete perm as well
       
   295                     # if the relation is already set
       
   296                     if (permission == 'add'
       
   297                         and rschema.cardinality(targetschemas[0], eschema, role) in '1?'
       
   298                         and eid and self.edited_entity.related(rschema.type, role)
       
   299                         and not rschema.has_perm(self.req, 'delete', toeid=eid,
       
   300                                                  fromeid=self.edited_entity.related(rschema.type, role)[0][0])):
       
   301                         continue
       
   302             yield (rschema, targetschemas, role)
       
   303     
       
   304     def srelations_by_category(self, categories=None, permission=None):
       
   305         """filter out result of relations_by_category(categories, permission) by
       
   306         removing final relations
       
   307 
       
   308         return a list of (relation's label, relation'schema, role)
       
   309         """
       
   310         result = []
       
   311         for rschema, ttypes, role in self.relations_by_category(categories,
       
   312                                                                 permission):
       
   313             if rschema.is_final():
       
   314                 continue
       
   315             result.append( (rschema.display_name(self.req, role), rschema, role) )
       
   316         return sorted(result)
       
   317         
       
   318     def relations_table(self):
       
   319         """yiels 3-tuples (rtype, target, related_list)
       
   320         where <related_list> itself a list of :
       
   321           - node_id (will be the entity element's DOM id)
       
   322           - appropriate javascript's togglePendingDelete() function call
       
   323           - status 'pendingdelete' or ''
       
   324           - oneline view of related entity
       
   325         """
       
   326         entity = self.edited_entity
       
   327         pending_deletes = self.req.get_pending_deletes(entity.eid)
       
   328         # XXX add metadata category quick fix to get Folder relations
       
   329         for label, rschema, role in self.srelations_by_category(('generic', 'metadata'), 'add'):
       
   330             relatedrset = entity.related(rschema, role, limit=self.limit)
       
   331             if rschema.has_perm(self.req, 'delete'):
       
   332                 toggable_rel_link_func = toggable_relation_link
       
   333             else:
       
   334                 toggable_rel_link_func = lambda x, y, z: u''
       
   335             related = []
       
   336             for row in xrange(relatedrset.rowcount):
       
   337                 nodeid = relation_id(entity.eid, rschema, role,
       
   338                                      relatedrset[row][0])
       
   339                 if nodeid in pending_deletes:
       
   340                     status = u'pendingDelete'
       
   341                     label = '+'
       
   342                 else:
       
   343                     status = u''
       
   344                     label = 'x'
       
   345                 dellink = toggable_rel_link_func(entity.eid, nodeid, label)
       
   346                 eview = self.view('oneline', relatedrset, row=row)
       
   347                 related.append((nodeid, dellink, status, eview))
       
   348             yield (rschema, role, related)
       
   349             
       
   350     def restore_pending_inserts(self, cell=False):
       
   351         """used to restore edition page as it was before clicking on
       
   352         'search for <some entity type>'
       
   353         """
       
   354         eid = self.edited_entity.eid
       
   355         cell = cell and "div_insert_" or "tr"
       
   356         pending_inserts = set(self.req.get_pending_inserts(eid))
       
   357         for pendingid in pending_inserts:
       
   358             eidfrom, rtype, eidto = pendingid.split(':')
       
   359             if typed_eid(eidfrom) == eid: # subject
       
   360                 label = display_name(self.req, rtype, 'subject')
       
   361                 reid = eidto
       
   362             else:
       
   363                 label = display_name(self.req, rtype, 'object')
       
   364                 reid = eidfrom
       
   365             jscall = "javascript: cancelPendingInsert('%s', '%s', null, %s);" \
       
   366                      % (pendingid, cell, eid)
       
   367             rset = self.req.eid_rset(reid)
       
   368             eview = self.view('text', rset, row=0)
       
   369             # XXX find a clean way to handle baskets
       
   370             if rset.description[0][0] == 'Basket':
       
   371                 eview = '%s (%s)' % (eview, display_name(self.req, 'Basket'))
       
   372             yield rtype, pendingid, jscall, label, reid, eview
       
   373             
       
   374     # should_* method extracted to allow overriding
       
   375     
       
   376     def should_inline_relation_form(self, rschema, targettype, role):
       
   377         """return true if the given relation with entity has role and a
       
   378         targettype target should be inlined
       
   379         """
       
   380         return self.inlined.etype_rtag(self.edited_entity.id, role, rschema, targettype)
       
   381 
       
   382     def should_display_inline_creation_form(self, rschema, existant, card):
       
   383         """return true if a creation form should be inlined
       
   384 
       
   385         by default true if there is no related entity and we need at least one
       
   386         """
       
   387         return not existant and card in '1+'
       
   388 
       
   389     def should_display_add_new_relation_link(self, rschema, existant, card):
       
   390         """return true if we should add a link to add a new creation form
       
   391         (through ajax call)
       
   392 
       
   393         by default true if there is no related entity or if the relation has
       
   394         multiple cardinality
       
   395         """
       
   396         return not existant or card in '+*'
       
   397 
       
   398     
       
   399 class EditionFormView(EntityView):
       
   400     """display primary entity edition form"""    
   129     id = 'edition'
   401     id = 'edition'
   130     __select__ = one_line_rset() & non_final_entity()
   402     # add yes() so it takes precedence over deprecated views in baseforms,
       
   403     # though not baseforms based customized view
       
   404     __select__ = one_line_rset() & non_final_entity() & yes()
   131 
   405 
   132     title = _('edition')
   406     title = _('edition')
   133     controller = 'edit'
   407     controller = 'edit'
   134     skip_relations = FormMixIn.skip_relations.copy()
   408     
   135 
       
   136     def cell_call(self, row, col, **kwargs):
   409     def cell_call(self, row, col, **kwargs):
   137         self.req.add_js( ('cubicweb.ajax.js',) )
   410         entity = self.complete_entity(row, col)
       
   411         self.render_form(entity)
       
   412         
       
   413     def render_form(self, entity):
       
   414         """fetch and render the form"""
       
   415         self.form_title(entity)
       
   416         form = self.vreg.select_object('forms', 'edition', self.req, self.rset,
       
   417                                        row=self.row, col=self.col, entity=entity,
       
   418                                        domid=self.id, submitmsg=self.submited_message())
       
   419         self.init_form(form, entity)
       
   420         self.w(form.form_render(renderer=EntityFormRenderer(), formvid=u'edition'))
       
   421 
       
   422     def init_form(self, form, entity):
       
   423         """customize your form before rendering here"""
       
   424         form.form_add_hidden(u'__maineid', entity.eid)
       
   425         
       
   426     def form_title(self, entity):
       
   427         """the form view title"""
       
   428         ptitle = self.req._(self.title)
       
   429         self.w(u'<div class="formTitle"><span>%s %s</span></div>' % (
       
   430             entity.dc_type(), ptitle and '(%s)' % ptitle))
       
   431     
       
   432     def submited_message(self):
       
   433         """return the message that will be displayed on successful edition"""
       
   434         return self.req._('entity edited')
       
   435 
       
   436         
       
   437 class CreationFormView(EditionFormView):
       
   438     """display primary entity creation form"""    
       
   439     id = 'creation'
       
   440     __select__ = specified_etype_implements('Any') & yes()
       
   441     
       
   442     title = _('creation')
       
   443     
       
   444     def call(self, **kwargs):
       
   445         """creation view for an entity"""
       
   446         etype = kwargs.pop('etype', self.req.form.get('etype'))
       
   447         try:
       
   448             entity = self.vreg.etype_class(etype)(self.req, None, None)
       
   449         except:
       
   450             self.w(self.req._('no such entity type %s') % etype)
       
   451         else:
       
   452             self.initialize_varmaker()
       
   453             entity.eid = self.varmaker.next()
       
   454             self.render_form(entity)
       
   455     
       
   456     def form_title(self, entity):
       
   457         """the form view title"""
       
   458         if '__linkto' in self.req.form:
       
   459             if isinstance(self.req.form['__linkto'], list):
       
   460                 # XXX which one should be considered (case: add a ticket to a
       
   461                 # version in jpl)
       
   462                 rtype, linkto_eid, role = self.req.form['__linkto'][0].split(':')
       
   463             else:
       
   464                 rtype, linkto_eid, role = self.req.form['__linkto'].split(':')
       
   465             linkto_rset = self.req.eid_rset(linkto_eid)
       
   466             linkto_type = linkto_rset.description[0][0]
       
   467             if role == 'subject':
       
   468                 title = self.req.__('creating %s (%s %s %s %%(linkto)s)' % (
       
   469                     entity.e_schema, entity.e_schema, rtype, linkto_type))
       
   470             else:
       
   471                 title = self.req.__('creating %s (%s %%(linkto)s %s %s)' % (
       
   472                     entity.e_schema, linkto_type, rtype, entity.e_schema))
       
   473             msg = title % {'linkto' : self.view('incontext', linkto_rset)}
       
   474             self.w(u'<div class="formTitle notransform"><span>%s</span></div>' % msg)
       
   475         else:
       
   476             super(CreationFormView, self).form_title(entity)
       
   477     
       
   478     def url(self):
       
   479         """return the url associated with this view"""
       
   480         return self.create_url(self.req.form.get('etype'))
       
   481     
       
   482     def submited_message(self):
       
   483         """return the message that will be displayed on successful edition"""
       
   484         return self.req._('entity created')
       
   485 
       
   486 
       
   487 class CopyFormView(EditionFormView):
       
   488     """display primary entity creation form initialized with values from another
       
   489     entity
       
   490     """    
       
   491     id = 'copy'
       
   492     def render_form(self, entity):
       
   493         """fetch and render the form"""
       
   494         # make a copy of entity to avoid altering the entity in the
       
   495         # request's cache. 
       
   496         self.newentity = copy(entity)
       
   497         self.copying = self.newentity.eid
       
   498         self.newentity.eid = None
       
   499         self.w(u'<script type="text/javascript">updateMessage("%s");</script>\n'
       
   500                % self.req._('Please note that this is only a shallow copy'))
       
   501         super(CopyFormView, self).render_form(entity)
       
   502         del self.newentity
       
   503     
       
   504     def init_form(self, form, entity):
       
   505         """customize your form before rendering here"""
       
   506         super(CopyFormView, self).init_form(form, entity)
       
   507         if entity.eid == self.newentity.eid:
       
   508             form.form_add_hidden('__cloned_eid', self.copying, eidparam=True)
       
   509     
       
   510     def submited_message(self):
       
   511         """return the message that will be displayed on successful edition"""
       
   512         return self.req._('entity copied')
       
   513 
       
   514     
       
   515 class TableEditForm(CompositeForm):
       
   516     id = 'muledit'
       
   517     onsubmit = "return validateForm('entityForm', null);"
       
   518     
       
   519     def __init__(self, *args, **kwargs):
       
   520         super(TableEditForm, self).__init__(*args, **kwargs)
       
   521         for row in xrange(len(self.rset)):
       
   522             form = self.vreg.select_object('forms', 'edition', self.req, self.rset,
       
   523                                            row=row, domid=self.id,
       
   524                                            attrcategories=('primary',),
       
   525                                            set_error_url=False)
       
   526             # XXX rely on the MultipleEntityFormRenderer to put the eid input
       
   527             form.remove_field(form.field_by_name('eid'))
       
   528             self.form_add_subform(form)
       
   529 
       
   530     def form_buttons(self):
       
   531         """return the form's buttons (as string)"""
       
   532         okt = self.req._('validate modifications on selected items').capitalize()
       
   533         resett = self.req._('revert changes').capitalize()
       
   534         return [self.button_ok(title=okt), self.button_reset(title=resett)]
       
   535 
       
   536         
       
   537 class TableEditFormView(EntityView):
       
   538     id = 'muledit'
       
   539     __select__ = EntityView.__select__ & yes()
       
   540     title = _('multiple edit')
       
   541     
       
   542     def call(self, **kwargs):
       
   543         """a view to edit multiple entities of the same type the first column
       
   544         should be the eid
       
   545         """
       
   546         #self.form_title(entity)
       
   547         form = self.vreg.select_object('forms', self.id, self.req, self.rset,
       
   548                                        domid=self.id)
       
   549         self.w(form.form_render(renderer=EntityCompositeFormRenderer()))
       
   550 
       
   551 
       
   552 class InlineEntityEditionFormView(EntityView):
       
   553     id = 'inline-edition'
       
   554     __select__ = non_final_entity() & match_kwargs('peid', 'rtype')
       
   555     removejs = "removeInlinedEntity('%s', '%s', '%s')"
       
   556     
       
   557     def call(self, **kwargs):
       
   558         """redefine default call() method to avoid automatic
       
   559         insertions of <div class="section"> between each row of
       
   560         the resultset
       
   561         """
       
   562         rset = self.rset
       
   563         for i in xrange(len(rset)):
       
   564             self.wview(self.id, rset, row=i, **kwargs)
       
   565 
       
   566     def cell_call(self, row, col, peid, rtype, role='subject', **kwargs):
       
   567         """
       
   568         :param peid: the parent entity's eid hosting the inline form
       
   569         :param rtype: the relation bridging `etype` and `peid`
       
   570         :param role: the role played by the `peid` in the relation
       
   571         """
       
   572         entity = self.entity(row, col)
       
   573         divonclick = "restoreInlinedEntity('%s', '%s', '%s')" % (peid, rtype,
       
   574                                                                  entity.eid)
       
   575         self.render_form(entity, peid, rtype, role, divonclick=divonclick)
       
   576         
       
   577     def render_form(self, entity, peid, rtype, role, **kwargs):
       
   578         """fetch and render the form"""
       
   579         rschema = self.schema.rschema(rtype)
       
   580         divid = '%s-%s-%s' % (peid, rtype, entity.eid)
       
   581         title = rschema.display_name(self.req, role)
       
   582         form = self.vreg.select_object('forms', 'edition', self.req,
       
   583                                        entity=entity)
       
   584         removejs = self.removejs % (peid, rtype,entity.eid)
       
   585         if self.keep_entity(entity, peid, rtype):
       
   586             if entity.has_eid():
       
   587                 rval = entity.eid
       
   588             else:
       
   589                 rval = INTERNAL_FIELD_VALUE
       
   590             form.form_add_hidden('edit%s-%s:%s' % (role[0], rtype, peid), rval)
       
   591         form.form_add_hidden(name='%s:%s' % (rtype, peid), value=entity.eid,
       
   592                              id='rel-%s-%s-%s'  % (peid, rtype, entity.eid))
       
   593         self.w(form.form_render(renderer=EntityInlinedFormRenderer(), divid=divid,
       
   594                                 title=title, removejs=removejs,**kwargs))
       
   595 
       
   596     def keep_entity(self, entity, peid, rtype):
       
   597         if not entity.has_eid():
       
   598             return True
       
   599         # are we regenerating form because of a validation error ?
       
   600         erroneous_post = self.req.data.get('formvalues')
       
   601         if erroneous_post:
       
   602             cdvalues = self.req.list_form_param('%s:%s' % (rtype, peid),
       
   603                                                 erroneous_post)
       
   604             if unicode(entity.eid) not in cdvalues:
       
   605                 return False
       
   606         return True
       
   607 
       
   608 
       
   609 class InlineEntityCreationFormView(InlineEntityEditionFormView):
       
   610     id = 'inline-creation'
       
   611     __select__ = (match_kwargs('peid', 'rtype')
       
   612                   & specified_etype_implements('Any'))
       
   613     
       
   614     def call(self, etype, peid, rtype, role='subject', **kwargs):
       
   615         """
       
   616         :param etype: the entity type being created in the inline form
       
   617         :param peid: the parent entity's eid hosting the inline form
       
   618         :param rtype: the relation bridging `etype` and `peid`
       
   619         :param role: the role played by the `peid` in the relation
       
   620         """
       
   621         try:
       
   622             entity = self.vreg.etype_class(etype)(self.req, None, None)
       
   623         except:
       
   624             self.w(self.req._('no such entity type %s') % etype)
       
   625             return
   138         self.initialize_varmaker()
   626         self.initialize_varmaker()
   139         entity = self.complete_entity(row, col)
   627         entity.eid = self.varmaker.next()
   140 
   628         self.render_form(entity, peid, rtype, role)
   141     def initialize_varmaker(self):
       
   142         varmaker = self.req.get_page_data('rql_varmaker')
       
   143         if varmaker is None:
       
   144             varmaker = self.req.varmaker
       
   145             self.req.set_page_data('rql_varmaker', varmaker)
       
   146         self.varmaker = varmaker
       
   147         
       
   148     def edit_form(self, entity, kwargs):
       
   149         form = EntityFieldsForm(self.req, entity=entity)
       
   150         for rschema, target in self.editable_attributes(entity):
       
   151             field = guess_field(entity.__class__, entity.e_schema, rschema, target)
       
   152             form.fields.append(field)
       
   153         form.buttons.append(form.button_ok())
       
   154         form.buttons.append(form.button_apply())
       
   155         form.buttons.append(form.button_cancel())
       
   156         self.w(form.form_render())
       
   157 
       
   158     def editable_attributes(self, entity):
       
   159         # XXX both (add, delete)
       
   160         return [(rschema, x) for rschema, _, x in entity.relations_by_category(('primary', 'secondary'), 'add')
       
   161                 if rschema != 'eid']