13 # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more |
13 # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more |
14 # details. |
14 # details. |
15 # |
15 # |
16 # You should have received a copy of the GNU Lesser General Public License along |
16 # You should have received a copy of the GNU Lesser General Public License along |
17 # with CubicWeb. If not, see <http://www.gnu.org/licenses/>. |
17 # with CubicWeb. If not, see <http://www.gnu.org/licenses/>. |
18 """abstract box classes for CubicWeb web client |
18 """abstract box classes for CubicWeb web client""" |
19 |
19 |
20 """ |
|
21 __docformat__ = "restructuredtext en" |
20 __docformat__ = "restructuredtext en" |
22 _ = unicode |
21 _ = unicode |
23 |
22 |
24 from logilab.mtconverter import xml_escape |
23 from logilab.mtconverter import xml_escape |
25 |
24 |
26 from cubicweb import Unauthorized, role as get_role, target as get_target |
25 from cubicweb import Unauthorized, role as get_role, target as get_target |
27 from cubicweb.schema import display_name |
26 from cubicweb.schema import display_name |
28 from cubicweb.selectors import (no_cnx, one_line_rset, primary_view, |
27 from cubicweb.selectors import (no_cnx, one_line_rset, primary_view, |
29 match_context_prop, partial_has_related_entities) |
28 match_context_prop, partial_relation_possible, |
|
29 partial_has_related_entities) |
30 from cubicweb.view import View, ReloadableMixIn |
30 from cubicweb.view import View, ReloadableMixIn |
31 |
31 from cubicweb.uilib import domid, js |
32 from cubicweb.web import INTERNAL_FIELD_VALUE |
32 from cubicweb.web import INTERNAL_FIELD_VALUE, stdmsgs |
33 from cubicweb.web.htmlwidgets import (BoxLink, BoxWidget, SideBoxWidget, |
33 from cubicweb.web.htmlwidgets import (BoxLink, BoxWidget, SideBoxWidget, |
34 RawBoxItem, BoxSeparator) |
34 RawBoxItem, BoxSeparator) |
35 from cubicweb.web.action import UnregisteredAction |
35 from cubicweb.web.action import UnregisteredAction |
36 |
36 |
37 |
37 |
239 entity = self._cw.entity_from_eid(eid) |
239 entity = self._cw.entity_from_eid(eid) |
240 if filteretype is None or entity.__regid__ == filteretype: |
240 if filteretype is None or entity.__regid__ == filteretype: |
241 entities.append(entity) |
241 entities.append(entity) |
242 return entities |
242 return entities |
243 |
243 |
|
244 |
|
245 class AjaxEditRelationBoxTemplate(EntityBoxTemplate): |
|
246 __select__ = EntityBoxTemplate.__select__ & partial_relation_possible() |
|
247 |
|
248 # view used to display related entties |
|
249 item_vid = 'incontext' |
|
250 # values separator when multiple values are allowed |
|
251 separator = ',' |
|
252 # msgid of the message to display when some new relation has been added/removed |
|
253 added_msg = None |
|
254 removed_msg = None |
|
255 |
|
256 # class attributes below *must* be set in concret classes (additionaly to |
|
257 # rtype / role [/ target_etype]. They should correspond to js_* methods on |
|
258 # the json controller |
|
259 |
|
260 # function(eid) |
|
261 # -> expected to return a list of values to display as input selector |
|
262 # vocabulary |
|
263 fname_vocabulary = None |
|
264 |
|
265 # function(eid, value) |
|
266 # -> handle the selector's input (eg create necessary entities and/or |
|
267 # relations). If the relation is multiple, you'll get a list of value, else |
|
268 # a single string value. |
|
269 fname_validate = None |
|
270 |
|
271 # function(eid, linked entity eid) |
|
272 # -> remove the relation |
|
273 fname_remove = None |
|
274 |
|
275 def cell_call(self, row, col, **kwargs): |
|
276 req = self._cw |
|
277 entity = self.cw_rset.get_entity(row, col) |
|
278 related = entity.related(self.rtype, self.role) |
|
279 rdef = entity.e_schema.rdef(self.rtype, self.role, self.target_etype) |
|
280 if self.role == 'subject': |
|
281 mayadd = rdef.has_perm(req, 'add', fromeid=entity.eid) |
|
282 maydel = rdef.has_perm(req, 'delete', fromeid=entity.eid) |
|
283 else: |
|
284 mayadd = rdef.has_perm(req, 'add', toeid=entity.eid) |
|
285 maydel = rdef.has_perm(req, 'delete', toeid=entity.eid) |
|
286 if not (related or mayadd): |
|
287 return |
|
288 if mayadd or maydel: |
|
289 req.add_js(('cubicweb.ajax.js', 'cubicweb.ajax.box.js')) |
|
290 _ = req._ |
|
291 w = self.w |
|
292 divid = domid(self.__regid__) + unicode(entity.eid) |
|
293 w(u'<div class="sideBox" id="%s%s">' % (domid(self.__regid__), entity.eid)) |
|
294 w(u'<div class="sideBoxTitle"><span>%s</span></div>' % |
|
295 rdef.rtype.display_name(req, self.role)) |
|
296 w(u'<div class="sideBox"><div class="sideBoxBody">') |
|
297 if related: |
|
298 w(u'<table>') |
|
299 for rentity in related.entities(): |
|
300 # for each related entity, provide a link to remove the relation |
|
301 subview = rentity.view(self.item_vid) |
|
302 if maydel: |
|
303 jscall = js.ajaxBoxRemoveLinkedEntity( |
|
304 self.__regid__, entity.eid, rentity.eid, |
|
305 self.fname_remove, |
|
306 self.removed_msg and _(self.removed_msg)) |
|
307 w(u'<tr><td>[<a href="javascript: %s">-</a>]</td>' |
|
308 '<td class="tagged">%s</td></tr>' % (xml_escape(jscall), |
|
309 subview)) |
|
310 else: |
|
311 w(u'<tr><td class="tagged">%s</td></tr>' % (subview)) |
|
312 w(u'</table>') |
|
313 else: |
|
314 w(_('no related entity')) |
|
315 if mayadd: |
|
316 req.add_js('jquery.autocomplete.js') |
|
317 req.add_css('jquery.autocomplete.css') |
|
318 multiple = rdef.role_cardinality(self.role) in '*+' |
|
319 w(u'<table><tr><td>') |
|
320 jscall = js.ajaxBoxShowSelector( |
|
321 self.__regid__, entity.eid, self.fname_vocabulary, |
|
322 self.fname_validate, self.added_msg and _(self.added_msg), |
|
323 _(stdmsgs.BUTTON_OK[0]), _(stdmsgs.BUTTON_CANCEL[0]), |
|
324 multiple and self.separator) |
|
325 w('<a class="button sglink" href="javascript: %s">%s</a>' % ( |
|
326 xml_escape(jscall), |
|
327 multiple and _('add_relation') or _('update_relation'))) |
|
328 w(u'</td><td>') |
|
329 w(u'<div id="%sHolder"></div>' % divid) |
|
330 w(u'</td></tr></table>') |
|
331 w(u'</div>\n') |
|
332 w(u'</div></div>\n') |