web/views/autoform.py
branchstable
changeset 3360 b02df886eb3e
parent 3358 0cddc4d8cad8
child 3408 c92170fca813
child 3470 c9c8b231db7b
equal deleted inserted replaced
3359:5562f8b901f0 3360:b02df886eb3e
    43     rcategories = uicfg.autoform_section
    43     rcategories = uicfg.autoform_section
    44     rfields = uicfg.autoform_field
    44     rfields = uicfg.autoform_field
    45     rfields_kwargs = uicfg.autoform_field_kwargs
    45     rfields_kwargs = uicfg.autoform_field_kwargs
    46     rinlined = uicfg.autoform_is_inlined
    46     rinlined = uicfg.autoform_is_inlined
    47     rpermissions_overrides = uicfg.autoform_permissions_overrides
    47     rpermissions_overrides = uicfg.autoform_permissions_overrides
       
    48 
       
    49     # class methods mapping schema relations to fields in the form ############
    48 
    50 
    49     @classmethod
    51     @classmethod
    50     def erelations_by_category(cls, entity, categories=None, permission=None,
    52     def erelations_by_category(cls, entity, categories=None, permission=None,
    51                                rtags=None, strict=False):
    53                                rtags=None, strict=False):
    52         """return a list of (relation schema, target schemas, role) matching
    54         """return a list of (relation schema, target schemas, role) matching
   156             field = guess_field(eschema, rschema, role, eidparam=True, **kwargs)
   158             field = guess_field(eschema, rschema, role, eidparam=True, **kwargs)
   157             if field is None:
   159             if field is None:
   158                 raise
   160                 raise
   159             return field
   161             return field
   160 
   162 
       
   163     # base automatic entity form methods #######################################
       
   164 
   161     def __init__(self, *args, **kwargs):
   165     def __init__(self, *args, **kwargs):
   162         super(AutomaticEntityForm, self).__init__(*args, **kwargs)
   166         super(AutomaticEntityForm, self).__init__(*args, **kwargs)
   163         entity = self.edited_entity
   167         entity = self.edited_entity
   164         if entity.has_eid():
   168         if entity.has_eid():
   165             entity.complete()
   169             entity.complete()
   182     @property
   186     @property
   183     def related_limit(self):
   187     def related_limit(self):
   184         if self.force_display:
   188         if self.force_display:
   185             return None
   189             return None
   186         return self.maxrelitems + 1
   190         return self.maxrelitems + 1
   187 
       
   188     def relations_by_category(self, categories=None, permission=None):
       
   189         """return a list of (relation schema, target schemas, role) matching
       
   190         given category(ies) and permission
       
   191         """
       
   192         return self.erelations_by_category(self.edited_entity, categories,
       
   193                                            permission)
       
   194 
       
   195     def inlined_relations(self):
       
   196         """return a list of (relation schema, target schemas, role) matching
       
   197         given category(ies) and permission
       
   198         """
       
   199         # we'll need an initialized varmaker if there are some inlined relation
       
   200         self.initialize_varmaker()
       
   201         return self.erelations_by_category(self.edited_entity, True, 'add',
       
   202                                            self.rinlined)
       
   203 
       
   204     def srelations_by_category(self, categories=None, permission=None,
       
   205                                strict=False):
       
   206         """filter out result of relations_by_category(categories, permission) by
       
   207         removing final relations
       
   208 
       
   209         return a sorted list of (relation's label, relation'schema, role)
       
   210         """
       
   211         return self.esrelations_by_category(self.edited_entity, categories,
       
   212                                            permission, strict=strict)
       
   213 
       
   214     def action(self):
       
   215         """return the form's action attribute. Default to validateform if not
       
   216         explicitly overriden.
       
   217         """
       
   218         try:
       
   219             return self._action
       
   220         except AttributeError:
       
   221             return self.build_url('validateform')
       
   222 
       
   223     def set_action(self, value):
       
   224         """override default action"""
       
   225         self._action = value
       
   226 
       
   227     action = property(action, set_action)
       
   228 
       
   229     def editable_attributes(self):
       
   230         """return a list of (relation schema, role) to edit for the entity"""
       
   231         return [(rschema, role) for rschema, _, role in self.relations_by_category(
       
   232                 self.attrcategories, 'add') if rschema != 'eid']
       
   233 
       
   234     def relations_table(self):
       
   235         """yiels 3-tuples (rtype, target, related_list)
       
   236         where <related_list> itself a list of :
       
   237           - node_id (will be the entity element's DOM id)
       
   238           - appropriate javascript's togglePendingDelete() function call
       
   239           - status 'pendingdelete' or ''
       
   240           - oneline view of related entity
       
   241         """
       
   242         entity = self.edited_entity
       
   243         pending_deletes = self.req.get_pending_deletes(entity.eid)
       
   244         for label, rschema, role in self.srelations_by_category('generic', 'add',
       
   245                                                                 strict=True):
       
   246             relatedrset = entity.related(rschema, role, limit=self.related_limit)
       
   247             if rschema.has_perm(self.req, 'delete'):
       
   248                 toggleable_rel_link_func = editforms.toggleable_relation_link
       
   249             else:
       
   250                 toggleable_rel_link_func = lambda x, y, z: u''
       
   251             related = []
       
   252             for row in xrange(relatedrset.rowcount):
       
   253                 nodeid = editforms.relation_id(entity.eid, rschema, role,
       
   254                                                relatedrset[row][0])
       
   255                 if nodeid in pending_deletes:
       
   256                     status = u'pendingDelete'
       
   257                     label = '+'
       
   258                 else:
       
   259                     status = u''
       
   260                     label = 'x'
       
   261                 dellink = toggleable_rel_link_func(entity.eid, nodeid, label)
       
   262                 eview = self.view('oneline', relatedrset, row=row)
       
   263                 related.append((nodeid, dellink, status, eview))
       
   264             yield (rschema, role, related)
       
   265 
       
   266     def restore_pending_inserts(self, cell=False):
       
   267         """used to restore edition page as it was before clicking on
       
   268         'search for <some entity type>'
       
   269         """
       
   270         eid = self.edited_entity.eid
       
   271         cell = cell and "div_insert_" or "tr"
       
   272         pending_inserts = set(self.req.get_pending_inserts(eid))
       
   273         for pendingid in pending_inserts:
       
   274             eidfrom, rtype, eidto = pendingid.split(':')
       
   275             if typed_eid(eidfrom) == eid: # subject
       
   276                 label = display_name(self.req, rtype, 'subject')
       
   277                 reid = eidto
       
   278             else:
       
   279                 label = display_name(self.req, rtype, 'object')
       
   280                 reid = eidfrom
       
   281             jscall = "javascript: cancelPendingInsert('%s', '%s', null, %s);" \
       
   282                      % (pendingid, cell, eid)
       
   283             rset = self.req.eid_rset(reid)
       
   284             eview = self.view('text', rset, row=0)
       
   285             # XXX find a clean way to handle baskets
       
   286             if rset.description[0][0] == 'Basket':
       
   287                 eview = '%s (%s)' % (eview, display_name(self.req, 'Basket'))
       
   288             yield rtype, pendingid, jscall, label, reid, eview
       
   289 
       
   290     # should_* method extracted to allow overriding
       
   291 
       
   292     def should_inline_relation_form(self, rschema, targettype, role):
       
   293         """return true if the given relation with entity has role and a
       
   294         targettype target should be inlined
       
   295         """
       
   296         return self.rinlined.etype_get(self.edited_entity.id, rschema, role,
       
   297                                        targettype)
       
   298 
       
   299     def display_inline_edition_form(self, w, rschema, targettype, role,
       
   300                                      i18nctx):
       
   301         """display inline forms for already related entities.
       
   302 
       
   303         Return True if some inlined form are actually displayed
       
   304         """
       
   305         existant = False
       
   306         entity = self.edited_entity
       
   307         related = entity.has_eid() and entity.related(rschema, role)
       
   308         if related:
       
   309             # display inline-edition view for all existing related entities
       
   310             for i, relentity in enumerate(related.entities()):
       
   311                 if relentity.has_perm('update'):
       
   312                     w(self.view('inline-edition', related, row=i, col=0,
       
   313                                 rtype=rschema, role=role, ptype=entity.e_schema,
       
   314                                 peid=entity.eid, i18nctx=i18nctx))
       
   315                     existant = True
       
   316         return existant
       
   317 
       
   318     def should_display_inline_creation_form(self, rschema, existant, card):
       
   319         """return true if a creation form should be inlined
       
   320 
       
   321         by default true if there is no related entity and we need at least one
       
   322         """
       
   323         return not existant and card in '1+' or self.req.form.has_key('force_%s_display' % rschema)
       
   324 
       
   325     def display_inline_creation_form(self, w, rschema, targettype, role,
       
   326                                      i18nctx):
       
   327         entity = self.edited_entity
       
   328         w(self.view('inline-creation', None, etype=targettype,
       
   329                     peid=entity.eid, ptype=entity.e_schema,
       
   330                     rtype=rschema, role=role, i18nctx=i18nctx))
       
   331 
       
   332     def should_display_add_new_relation_link(self, rschema, existant, card):
       
   333         """return true if we should add a link to add a new creation form
       
   334         (through ajax call)
       
   335 
       
   336         by default true if there is no related entity or if the relation has
       
   337         multiple cardinality
       
   338         """
       
   339         return not existant or card in '+*'
       
   340 
       
   341     def should_hide_add_new_relation_link(self, rschema, card):
       
   342         """return true if once an inlined creation form is added, the 'add new'
       
   343         link should be hidden
       
   344 
       
   345         by default true if the relation has single cardinality
       
   346         """
       
   347         return card in '1?'
       
   348 
   191 
   349     @property
   192     @property
   350     def form_needs_multipart(self):
   193     def form_needs_multipart(self):
   351         """true if the form needs enctype=multipart/form-data"""
   194         """true if the form needs enctype=multipart/form-data"""
   352         if super(AutomaticEntityForm, self).form_needs_multipart:
   195         if super(AutomaticEntityForm, self).form_needs_multipart:
   371                 entity = self.vreg['etypes'].etype_class(targettype)(self.req)
   214                 entity = self.vreg['etypes'].etype_class(targettype)(self.req)
   372                 subform = self.vreg['forms'].select('edition', self.req, entity=entity)
   215                 subform = self.vreg['forms'].select('edition', self.req, entity=entity)
   373                 if subform.form_needs_multipart:
   216                 if subform.form_needs_multipart:
   374                     return True
   217                     return True
   375         return False
   218         return False
       
   219 
       
   220     def action(self):
       
   221         """return the form's action attribute. Default to validateform if not
       
   222         explicitly overriden.
       
   223         """
       
   224         try:
       
   225             return self._action
       
   226         except AttributeError:
       
   227             return self.build_url('validateform')
       
   228 
       
   229     def set_action(self, value):
       
   230         """override default action"""
       
   231         self._action = value
       
   232 
       
   233     action = property(action, set_action)
       
   234 
       
   235     # methods mapping edited entity relations to fields in the form ############
       
   236 
       
   237     def relations_by_category(self, categories=None, permission=None):
       
   238         """return a list of (relation schema, target schemas, role) matching
       
   239         given category(ies) and permission
       
   240         """
       
   241         return self.erelations_by_category(self.edited_entity, categories,
       
   242                                            permission)
       
   243 
       
   244     def inlined_relations(self):
       
   245         """return a list of (relation schema, target schemas, role) matching
       
   246         given category(ies) and permission
       
   247         """
       
   248         # we'll need an initialized varmaker if there are some inlined relation
       
   249         self.initialize_varmaker()
       
   250         return self.erelations_by_category(self.edited_entity, True, 'add',
       
   251                                            self.rinlined)
       
   252 
       
   253     def srelations_by_category(self, categories=None, permission=None,
       
   254                                strict=False):
       
   255         """filter out result of relations_by_category(categories, permission) by
       
   256         removing final relations
       
   257 
       
   258         return a sorted list of (relation's label, relation'schema, role)
       
   259         """
       
   260         return self.esrelations_by_category(self.edited_entity, categories,
       
   261                                            permission, strict=strict)
       
   262 
       
   263     def editable_attributes(self):
       
   264         """return a list of (relation schema, role) to edit for the entity"""
       
   265         return [(rschema, role) for rschema, _, role in self.relations_by_category(
       
   266                 self.attrcategories, 'add') if rschema != 'eid']
       
   267 
       
   268     # generic relations modifier ###############################################
       
   269 
       
   270     def relations_table(self):
       
   271         """yiels 3-tuples (rtype, target, related_list)
       
   272         where <related_list> itself a list of :
       
   273           - node_id (will be the entity element's DOM id)
       
   274           - appropriate javascript's togglePendingDelete() function call
       
   275           - status 'pendingdelete' or ''
       
   276           - oneline view of related entity
       
   277         """
       
   278         entity = self.edited_entity
       
   279         pending_deletes = self.req.get_pending_deletes(entity.eid)
       
   280         for label, rschema, role in self.srelations_by_category('generic', 'add',
       
   281                                                                 strict=True):
       
   282             relatedrset = entity.related(rschema, role, limit=self.related_limit)
       
   283             if rschema.has_perm(self.req, 'delete'):
       
   284                 toggleable_rel_link_func = editforms.toggleable_relation_link
       
   285             else:
       
   286                 toggleable_rel_link_func = lambda x, y, z: u''
       
   287             related = []
       
   288             for row in xrange(relatedrset.rowcount):
       
   289                 nodeid = editforms.relation_id(entity.eid, rschema, role,
       
   290                                                relatedrset[row][0])
       
   291                 if nodeid in pending_deletes:
       
   292                     status = u'pendingDelete'
       
   293                     label = '+'
       
   294                 else:
       
   295                     status = u''
       
   296                     label = 'x'
       
   297                 dellink = toggleable_rel_link_func(entity.eid, nodeid, label)
       
   298                 eview = self.view('oneline', relatedrset, row=row)
       
   299                 related.append((nodeid, dellink, status, eview))
       
   300             yield (rschema, role, related)
       
   301 
       
   302     def restore_pending_inserts(self, cell=False):
       
   303         """used to restore edition page as it was before clicking on
       
   304         'search for <some entity type>'
       
   305         """
       
   306         eid = self.edited_entity.eid
       
   307         cell = cell and "div_insert_" or "tr"
       
   308         pending_inserts = set(self.req.get_pending_inserts(eid))
       
   309         for pendingid in pending_inserts:
       
   310             eidfrom, rtype, eidto = pendingid.split(':')
       
   311             if typed_eid(eidfrom) == eid: # subject
       
   312                 label = display_name(self.req, rtype, 'subject')
       
   313                 reid = eidto
       
   314             else:
       
   315                 label = display_name(self.req, rtype, 'object')
       
   316                 reid = eidfrom
       
   317             jscall = "javascript: cancelPendingInsert('%s', '%s', null, %s);" \
       
   318                      % (pendingid, cell, eid)
       
   319             rset = self.req.eid_rset(reid)
       
   320             eview = self.view('text', rset, row=0)
       
   321             # XXX find a clean way to handle baskets
       
   322             if rset.description[0][0] == 'Basket':
       
   323                 eview = '%s (%s)' % (eview, display_name(self.req, 'Basket'))
       
   324             yield rtype, pendingid, jscall, label, reid, eview
       
   325 
       
   326     # inlined forms support ####################################################
       
   327 
       
   328     def should_inline_relation_form(self, rschema, targettype, role):
       
   329         """return true if the given relation with entity has role and a
       
   330         targettype target should be inlined
       
   331         """
       
   332         return self.rinlined.etype_get(self.edited_entity.id, rschema, role,
       
   333                                        targettype)
       
   334 
       
   335     def display_inline_edition_form(self, w, rschema, targettype, role,
       
   336                                      i18nctx):
       
   337         """display inline forms for already related entities.
       
   338 
       
   339         Return True if some inlined form are actually displayed
       
   340         """
       
   341         existant = False
       
   342         entity = self.edited_entity
       
   343         related = entity.has_eid() and entity.related(rschema, role)
       
   344         if related:
       
   345             # display inline-edition view for all existing related entities
       
   346             for i, relentity in enumerate(related.entities()):
       
   347                 if relentity.has_perm('update'):
       
   348                     w(self.view('inline-edition', related, row=i, col=0,
       
   349                                 rtype=rschema, role=role, ptype=entity.e_schema,
       
   350                                 peid=entity.eid, i18nctx=i18nctx))
       
   351                     existant = True
       
   352         return existant
       
   353 
       
   354     def should_display_inline_creation_form(self, rschema, existant, card):
       
   355         """return true if a creation form should be inlined
       
   356 
       
   357         by default true if there is no related entity and we need at least one
       
   358         """
       
   359         return not existant and card in '1+' or self.req.form.has_key('force_%s_display' % rschema)
       
   360 
       
   361     def display_inline_creation_form(self, w, rschema, targettype, role,
       
   362                                      i18nctx):
       
   363         """display inline forms to a newly related (hence created) entity.
       
   364 
       
   365         Return True if some inlined form are actually displayed
       
   366         """
       
   367         entity = self.edited_entity
       
   368         w(self.view('inline-creation', None, etype=targettype,
       
   369                     peid=entity.eid, ptype=entity.e_schema,
       
   370                     rtype=rschema, role=role, i18nctx=i18nctx))
       
   371 
       
   372     def should_display_add_new_relation_link(self, rschema, existant, card):
       
   373         """return true if we should add a link to add a new creation form
       
   374         (through ajax call)
       
   375 
       
   376         by default true if there is no related entity or if the relation has
       
   377         multiple cardinality
       
   378         """
       
   379         return not existant or card in '+*'
       
   380 
       
   381     def should_hide_add_new_relation_link(self, rschema, card):
       
   382         """return true if once an inlined creation form is added, the 'add new'
       
   383         link should be hidden
       
   384 
       
   385         by default true if the relation has single cardinality
       
   386         """
       
   387         return card in '1?'
       
   388 
   376 
   389 
   377 def etype_relation_field(etype, rtype, role='subject'):
   390 def etype_relation_field(etype, rtype, role='subject'):
   378     eschema = AutomaticEntityForm.schema.eschema(etype)
   391     eschema = AutomaticEntityForm.schema.eschema(etype)
   379     return AutomaticEntityForm.field_by_name(rtype, role, eschema)
   392     return AutomaticEntityForm.field_by_name(rtype, role, eschema)
   380 
   393