|
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> <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) |