web/views/editviews.py
branchtls-sprint
changeset 824 a5e6acffde30
child 1083 343698c0863d
equal deleted inserted replaced
823:cb8ccbef8fa5 824:a5e6acffde30
       
     1 """Some views used to help to the edition process
       
     2 
       
     3 :organization: Logilab
       
     4 :copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
       
     5 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
     6 """
       
     7 __docformat__ = "restructuredtext en"
       
     8 
       
     9 from simplejson import dumps
       
    10 
       
    11 from logilab.common.decorators import cached
       
    12 from logilab.mtconverter import html_escape
       
    13 
       
    14 from cubicweb import typed_eid
       
    15 from cubicweb.view import EntityView
       
    16 from cubicweb.selectors import (one_line_rset, non_final_entity,
       
    17                                 match_search_state, match_form_params)
       
    18 from cubicweb.common.uilib import cut
       
    19 from cubicweb.web.views import linksearch_select_url
       
    20 from cubicweb.web.form import relation_id
       
    21 from cubicweb.web.views.baseviews import FinalView
       
    22 
       
    23 _ = unicode
       
    24 
       
    25 class SearchForAssociationView(EntityView):
       
    26     """view called by the edition view when the user asks to search for
       
    27     something to link to the edited eid
       
    28     """
       
    29     id = 'search-associate'
       
    30     __select__ = (one_line_rset() & match_search_state('linksearch')
       
    31                   & non_final_entity())
       
    32     
       
    33     title = _('search for association')
       
    34 
       
    35     def cell_call(self, row, col):
       
    36         rset, vid, divid, paginate = self.filter_box_context_info()
       
    37         self.w(u'<div id="%s">' % divid)
       
    38         self.pagination(self.req, rset, w=self.w)
       
    39         self.wview(vid, rset, 'noresult')
       
    40         self.w(u'</div>')
       
    41 
       
    42     @cached
       
    43     def filter_box_context_info(self):
       
    44         entity = self.entity(0, 0)
       
    45         role, eid, rtype, etype = self.req.search_state[1]
       
    46         assert entity.eid == typed_eid(eid)
       
    47         # the default behaviour is to fetch all unrelated entities and display
       
    48         # them. Use fetch_order and not fetch_unrelated_order as sort method
       
    49         # since the latter is mainly there to select relevant items in the combo
       
    50         # box, it doesn't give interesting result in this context
       
    51         rql = entity.unrelated_rql(rtype, etype, role,
       
    52                                    ordermethod='fetch_order',
       
    53                                    vocabconstraints=False)
       
    54         rset = self.req.execute(rql, {'x' : entity.eid}, 'x')
       
    55         return rset, 'list', "search-associate-content", True
       
    56 
       
    57 
       
    58 class OutOfContextSearch(EntityView):
       
    59     id = 'outofcontext-search'
       
    60     def cell_call(self, row, col):
       
    61         entity = self.entity(row, col)
       
    62         erset = entity.as_rset()
       
    63         if self.req.match_search_state(erset):
       
    64             self.w(u'<a href="%s" title="%s">%s</a>&nbsp;<a href="%s" title="%s">[...]</a>' % (
       
    65                 html_escape(linksearch_select_url(self.req, erset)),
       
    66                 self.req._('select this entity'),
       
    67                 html_escape(entity.view('textoutofcontext')),
       
    68                 html_escape(entity.absolute_url(vid='primary')),
       
    69                 self.req._('view detail for this entity')))
       
    70         else:
       
    71             entity.view('outofcontext', w=self.w)
       
    72 
       
    73         
       
    74 class UnrelatedDivs(EntityView):
       
    75     id = 'unrelateddivs'
       
    76     __select__ = match_form_params('relation')
       
    77 
       
    78     @property
       
    79     def limit(self):
       
    80         if self.req.form.get('__force_display'):
       
    81             return None
       
    82         return self.req.property_value('navigation.related-limit') + 1
       
    83 
       
    84     def cell_call(self, row, col):
       
    85         entity = self.entity(row, col)
       
    86         relname, target = self.req.form.get('relation').rsplit('_', 1)
       
    87         rschema = self.schema.rschema(relname)
       
    88         hidden = 'hidden' in self.req.form
       
    89         is_cell = 'is_cell' in self.req.form
       
    90         self.w(self.build_unrelated_select_div(entity, rschema, target,
       
    91                                                is_cell=is_cell, hidden=hidden))
       
    92 
       
    93     def build_unrelated_select_div(self, entity, rschema, target,
       
    94                                    is_cell=False, hidden=True):
       
    95         options = []
       
    96         divid = 'div%s_%s_%s' % (rschema.type, target, entity.eid)
       
    97         selectid = 'select%s_%s_%s' % (rschema.type, target, entity.eid)
       
    98         if rschema.symetric or target == 'subject':
       
    99             targettypes = rschema.objects(entity.e_schema)
       
   100             etypes = '/'.join(sorted(etype.display_name(self.req) for etype in targettypes))
       
   101         else:
       
   102             targettypes = rschema.subjects(entity.e_schema)
       
   103             etypes = '/'.join(sorted(etype.display_name(self.req) for etype in targettypes))
       
   104         etypes = cut(etypes, self.req.property_value('navigation.short-line-size'))
       
   105         options.append('<option>%s %s</option>' % (self.req._('select a'), etypes))
       
   106         options += self._get_select_options(entity, rschema, target)
       
   107         options += self._get_search_options(entity, rschema, target, targettypes)
       
   108         if 'Basket' in self.schema: # XXX
       
   109             options += self._get_basket_options(entity, rschema, target, targettypes)
       
   110         relname, target = self.req.form.get('relation').rsplit('_', 1)
       
   111         return u"""\
       
   112 <div class="%s" id="%s">
       
   113   <select id="%s" onchange="javascript: addPendingInsert(this.options[this.selectedIndex], %s, %s, '%s');">
       
   114     %s
       
   115   </select>
       
   116 </div>
       
   117 """ % (hidden and 'hidden' or '', divid, selectid, html_escape(dumps(entity.eid)),
       
   118        is_cell and 'true' or 'null', relname, '\n'.join(options))
       
   119 
       
   120     def _get_select_options(self, entity, rschema, target):
       
   121         """add options to search among all entities of each possible type"""
       
   122         options = []
       
   123         eid = entity.eid
       
   124         pending_inserts = self.req.get_pending_inserts(eid)
       
   125         rtype = rschema.type
       
   126         for eview, reid in entity.vocabulary(rschema, target, self.limit):
       
   127             if reid is None:
       
   128                 options.append('<option class="separator">-- %s --</option>' % html_escape(eview))
       
   129             else:
       
   130                 optionid = relation_id(eid, rtype, target, reid)
       
   131                 if optionid not in pending_inserts:
       
   132                     # prefix option's id with letters to make valid XHTML wise
       
   133                     options.append('<option id="id%s" value="%s">%s</option>' %
       
   134                                    (optionid, reid, html_escape(eview)))
       
   135         return options
       
   136 
       
   137     def _get_search_options(self, entity, rschema, target, targettypes):
       
   138         """add options to search among all entities of each possible type"""
       
   139         options = []
       
   140         _ = self.req._
       
   141         for eschema in targettypes:
       
   142             mode = '%s:%s:%s:%s' % (target, entity.eid, rschema.type, eschema)
       
   143             url = self.build_url(entity.rest_path(), vid='search-associate',
       
   144                                  __mode=mode)
       
   145             options.append((eschema.display_name(self.req),
       
   146                             '<option value="%s">%s %s</option>' % (
       
   147                 html_escape(url), _('Search for'), eschema.display_name(self.req))))
       
   148         return [o for l, o in sorted(options)]
       
   149 
       
   150     def _get_basket_options(self, entity, rschema, target, targettypes):
       
   151         options = []
       
   152         rtype = rschema.type
       
   153         _ = self.req._
       
   154         for basketeid, basketname in self._get_basket_links(self.req.user.eid,
       
   155                                                             target, targettypes):
       
   156             optionid = relation_id(entity.eid, rtype, target, basketeid)
       
   157             options.append('<option id="%s" value="%s">%s %s</option>' % (
       
   158                 optionid, basketeid, _('link to each item in'), html_escape(basketname)))
       
   159         return options
       
   160 
       
   161     def _get_basket_links(self, ueid, target, targettypes):
       
   162         targettypes = set(targettypes)
       
   163         for basketeid, basketname, elements in self._get_basket_info(ueid):
       
   164             baskettypes = elements.column_types(0)
       
   165             # if every elements in the basket can be attached to the
       
   166             # edited entity
       
   167             if baskettypes & targettypes:
       
   168                 yield basketeid, basketname
       
   169             
       
   170     def _get_basket_info(self, ueid):
       
   171         basketref = []
       
   172         basketrql = 'Any B,N WHERE B is Basket, B owned_by U, U eid %(x)s, B name N'
       
   173         basketresultset = self.req.execute(basketrql, {'x': ueid}, 'x')
       
   174         for result in basketresultset:
       
   175             basketitemsrql = 'Any X WHERE X in_basket B, B eid %(x)s'
       
   176             rset = self.req.execute(basketitemsrql, {'x': result[0]}, 'x')
       
   177             basketref.append((result[0], result[1], rset))
       
   178         return basketref
       
   179 
       
   180 
       
   181 class ComboboxView(EntityView):
       
   182     """the view used in combobox (unrelated entities)
       
   183 
       
   184     THIS IS A TEXT VIEW. DO NOT HTML_ESCAPE
       
   185     """
       
   186     id = 'combobox'
       
   187     title = None
       
   188     
       
   189     def cell_call(self, row, col):
       
   190         """the combo-box view for an entity: same as text out of context view
       
   191         by default
       
   192         """
       
   193         self.wview('textoutofcontext', self.rset, row=row, col=col)
       
   194             
       
   195             
       
   196 # class EditRelationView(EntityView):
       
   197 #     """Note: This is work in progress
       
   198 
       
   199 #     This view is part of the edition view refactoring.
       
   200 #     It is still too big and cluttered with strange logic, but it's a start
       
   201 
       
   202 #     The main idea is to be able to call an edition view for a specific
       
   203 #     relation. For example :
       
   204 #        self.wview('editrelation', person_rset, rtype='firstname')
       
   205 #        self.wview('editrelation', person_rset, rtype='works_for')
       
   206 #     """
       
   207 #     id = 'editrelation'
       
   208 
       
   209 #     __select__ = match_form_params('rtype')
       
   210     
       
   211 #     # TODO: inlineview, multiple edit, (widget view ?)
       
   212 #     def cell_call(self, row, col, rtype=None, role='subject', targettype=None,
       
   213 #                  showlabel=True):
       
   214 #         self.req.add_js( ('cubicweb.ajax.js', 'cubicweb.edition.js') )
       
   215 #         entity = self.entity(row, col)
       
   216 #         rtype = self.req.form.get('rtype', rtype)
       
   217 #         showlabel = self.req.form.get('showlabel', showlabel)
       
   218 #         assert rtype is not None, "rtype is mandatory for 'edirelation' view"
       
   219 #         targettype = self.req.form.get('targettype', targettype)
       
   220 #         role = self.req.form.get('role', role)
       
   221 #         category = entity.rtags.get_category(rtype, targettype, role)
       
   222 #         if category in ('primary', 'secondary') or self.schema.rschema(rtype).is_final():
       
   223 #             if hasattr(entity, '%s_format' % rtype):
       
   224 #                 formatwdg = entity.get_widget('%s_format' % rtype, role)
       
   225 #                 self.w(formatwdg.edit_render(entity))
       
   226 #                 self.w(u'<br/>')
       
   227 #             wdg = entity.get_widget(rtype, role)
       
   228 #             if showlabel:
       
   229 #                 self.w(u'%s' % wdg.render_label(entity))
       
   230 #             self.w(u'%s %s %s' %
       
   231 #                    (wdg.render_error(entity), wdg.edit_render(entity),
       
   232 #                     wdg.render_help(entity),))
       
   233 #         else:
       
   234 #             self._render_generic_relation(entity, rtype, role)
       
   235 
       
   236 #     def _render_generic_relation(self, entity, relname, role):
       
   237 #         text = self.req.__('add %s %s %s' % (entity.e_schema, relname, role))
       
   238 #         # pending operations
       
   239 #         operations = self.req.get_pending_operations(entity, relname, role)
       
   240 #         if operations['insert'] or operations['delete'] or 'unfold' in self.req.form:
       
   241 #             self.w(u'<h3>%s</h3>' % text)
       
   242 #             self._render_generic_relation_form(operations, entity, relname, role)
       
   243 #         else:
       
   244 #             divid = "%s%sreledit" % (relname, role)
       
   245 #             url = ajax_replace_url(divid, rql_for_eid(entity.eid), 'editrelation',
       
   246 #                                    {'unfold' : 1, 'relname' : relname, 'role' : role})
       
   247 #             self.w(u'<a href="%s">%s</a>' % (url, text))
       
   248 #             self.w(u'<div id="%s"></div>' % divid)
       
   249         
       
   250 
       
   251 #     def _build_opvalue(self, entity, relname, target, role):
       
   252 #         if role == 'subject':
       
   253 #             return '%s:%s:%s' % (entity.eid, relname, target)
       
   254 #         else:
       
   255 #             return '%s:%s:%s' % (target, relname, entity.eid)
       
   256         
       
   257     
       
   258 #     def _render_generic_relation_form(self, operations, entity, relname, role):
       
   259 #         rqlexec = self.req.execute
       
   260 #         for optype, targets in operations.items():
       
   261 #             for target in targets:
       
   262 #                 self._render_pending(optype, entity, relname, target, role)
       
   263 #                 opvalue = self._build_opvalue(entity, relname, target, role)
       
   264 #                 self.w(u'<a href="javascript: addPendingDelete(\'%s\', %s);">-</a> '
       
   265 #                        % (opvalue, entity.eid))
       
   266 #                 rset = rqlexec('Any X WHERE X eid %(x)s', {'x': target}, 'x')
       
   267 #                 self.wview('oneline', rset)
       
   268 #         # now, unrelated ones
       
   269 #         self._render_unrelated_selection(entity, relname, role)
       
   270 
       
   271 #     def _render_pending(self, optype, entity, relname, target, role):
       
   272 #         opvalue = self._build_opvalue(entity, relname, target, role)
       
   273 #         self.w(u'<input type="hidden" name="__%s" value="%s" />'
       
   274 #                % (optype, opvalue))
       
   275 #         if optype == 'insert':
       
   276 #             checktext = '-'
       
   277 #         else:
       
   278 #             checktext = '+'
       
   279 #         rset = self.req.execute('Any X WHERE X eid %(x)s', {'x': target}, 'x')
       
   280 #         self.w(u"""[<a href="javascript: cancelPending%s('%s:%s:%s')">%s</a>"""
       
   281 #                % (optype.capitalize(), relname, target, role,
       
   282 #                   self.view('oneline', rset)))
       
   283 
       
   284 #     def _render_unrelated_selection(self, entity, relname, role):
       
   285 #         rschema = self.schema.rschema(relname)
       
   286 #         if role == 'subject':
       
   287 #             targettypes = rschema.objects(entity.e_schema)
       
   288 #         else:
       
   289 #             targettypes = rschema.subjects(entity.e_schema)
       
   290 #         self.w(u'<select onselect="addPendingInsert(this.selected.value);">')
       
   291 #         for targettype in targettypes:
       
   292 #             unrelated = entity.unrelated(relname, targettype, role) # XXX limit
       
   293 #             for rowindex, row in enumerate(unrelated):
       
   294 #                 teid = row[0]
       
   295 #                 opvalue = self._build_opvalue(entity, relname, teid, role)
       
   296 #                 self.w(u'<option name="__insert" value="%s>%s</option>'
       
   297 #                        % (opvalue, self.view('text', unrelated, row=rowindex)))
       
   298 #         self.w(u'</select>')
       
   299 
       
   300 
       
   301 class EditableFinalView(FinalView):
       
   302     """same as FinalView but enables inplace-edition when possible"""
       
   303     id = 'editable-final'
       
   304                 
       
   305     def cell_call(self, row, col, props=None, displaytime=False):
       
   306         entity, rtype = self.rset.related_entity(row, col)
       
   307         if entity is not None:
       
   308             self.w(entity.view('reledit', rtype=rtype))
       
   309         else:
       
   310             super(EditableFinalView, self).cell_call(row, col, props, displaytime)