73 reload=False, # controls reloading the whole page after change |
78 reload=False, # controls reloading the whole page after change |
74 # boolean, eid (to redirect), or |
79 # boolean, eid (to redirect), or |
75 # function taking the subject entity & returning a boolean or an eid |
80 # function taking the subject entity & returning a boolean or an eid |
76 rvid=None, # vid to be applied to other side of rtype (non final relations only) |
81 rvid=None, # vid to be applied to other side of rtype (non final relations only) |
77 default_value=None, |
82 default_value=None, |
78 formid='base' |
83 formid='base', |
|
84 action=None |
79 ): |
85 ): |
80 """display field to edit entity's `rtype` relation on click""" |
86 """display field to edit entity's `rtype` relation on click""" |
81 assert rtype |
87 assert rtype |
82 assert role in ('subject', 'object'), '%s is not an acceptable role value' % role |
|
83 self._cw.add_css('cubicweb.form.css') |
88 self._cw.add_css('cubicweb.form.css') |
84 self._cw.add_js(('cubicweb.reledit.js', 'cubicweb.edition.js', 'cubicweb.ajax.js')) |
89 self._cw.add_js(('cubicweb.reledit.js', 'cubicweb.edition.js', 'cubicweb.ajax.js')) |
85 entity = self.cw_rset.get_entity(row, col) |
90 entity = self.cw_rset.get_entity(row, col) |
86 rschema = self._cw.vreg.schema[rtype] |
91 rschema = self._cw.vreg.schema[rtype] |
87 self._rules = rctrl.etype_get(entity.e_schema.type, rschema.type, role, '*') |
92 self._rules = rctrl.etype_get(self.entity.e_schema.type, rschema.type, role, '*') |
88 if rvid is not None or default_value is not None: |
93 if rvid is not None or default_value is not None: |
89 warn('[3.9] specifying rvid/default_value on select is deprecated, ' |
94 warn('[3.9] specifying rvid/default_value on select is deprecated, ' |
90 'reledit_ctrl rtag to control this' % self, DeprecationWarning) |
95 'reledit_ctrl rtag to control this' % self, DeprecationWarning) |
91 reload = self._compute_reload(entity, rschema, role, reload) |
96 reload = self._compute_reload(rschema, role, reload) |
92 divid = self._build_divid(rtype, role, entity.eid) |
97 divid = self._build_divid(rtype, role, self.entity.eid) |
93 if rschema.final: |
98 if rschema.final: |
94 self._handle_attribute(entity, rschema, role, divid, reload) |
99 self._handle_attribute(rschema, role, divid, reload, action) |
95 else: |
100 else: |
96 if self._is_composite(): |
101 if self._is_composite(): |
97 self._handle_composite(entity, rschema, role, divid, reload, formid) |
102 self._handle_composite(rschema, role, divid, reload, formid, action) |
98 else: |
103 else: |
99 self._handle_relation(entity, rschema, role, divid, reload, formid) |
104 self._handle_relation(rschema, role, divid, reload, formid, action) |
100 |
105 |
101 def _handle_attribute(self, entity, rschema, role, divid, reload): |
106 def _handle_attribute(self, rschema, role, divid, reload, action): |
102 rtype = rschema.type |
107 value = self.entity.printable_value(rschema.type) |
103 value = entity.printable_value(rtype) |
108 if not self._should_edit_attribute(rschema): |
104 if not self._should_edit_attribute(entity, rschema): |
|
105 self.w(value) |
109 self.w(value) |
106 return |
110 return |
107 display_label, related_entity = self._prepare_form(entity, rtype, role) |
111 form, renderer = self._build_form(self.entity, rschema, role, divid, 'base', reload, action) |
108 form, renderer = self._build_form(entity, rtype, role, divid, 'base', |
|
109 reload, display_label, related_entity) |
|
110 value = value or self._compute_default_value(rschema, role) |
112 value = value or self._compute_default_value(rschema, role) |
111 self.view_form(divid, value, form, renderer) |
113 self.view_form(divid, value, form, renderer) |
112 |
114 |
113 def _compute_formid_value(self, entity, rschema, role, rvid, formid): |
115 def _compute_formid_value(self, rschema, role, rvid, formid): |
114 related_rset = entity.related(rschema.type, role) |
116 related_rset = self.entity.related(rschema.type, role) |
115 if related_rset: |
117 if related_rset: |
116 value = self._cw.view(rvid, related_rset) |
118 value = self._cw.view(rvid, related_rset) |
117 else: |
119 else: |
118 value = self._compute_default_value(rschema, role) |
120 value = self._compute_default_value(rschema, role) |
119 if not self._should_edit_relation(entity, rschema, role): |
121 if not self._should_edit_relation(rschema, role): |
120 return None, value |
122 return None, value |
121 return formid, value |
123 return formid, value |
122 |
124 |
123 def _handle_relation(self, entity, rschema, role, divid, reload, formid): |
125 def _handle_relation(self, rschema, role, divid, reload, formid, action): |
124 rvid = self._rules.get('rvid', 'autolimited') |
126 rvid = self._rules.get('rvid', 'autolimited') |
125 formid, value = self._compute_formid_value(entity, rschema, role, rvid, formid) |
127 formid, value = self._compute_formid_value(rschema, role, rvid, formid) |
126 if formid is None: |
128 if formid is None: |
127 return self.w(value) |
129 return self.w(value) |
128 rtype = rschema.type |
130 form, renderer = self._build_form(self.entity, rschema, role, divid, formid, |
129 display_label, related_entity = self._prepare_form(entity, rtype, role) |
131 reload, action, dict(vid=rvid)) |
130 form, renderer = self._build_form(entity, rtype, role, divid, formid, reload, |
|
131 display_label, related_entity, dict(vid=rvid)) |
|
132 self.view_form(divid, value, form, renderer) |
132 self.view_form(divid, value, form, renderer) |
133 |
133 |
134 def _handle_composite(self, entity, rschema, role, divid, reload, formid): |
134 def _handle_composite(self, rschema, role, divid, reload, formid, action): |
135 # this is for attribute-like composites (1 target type, 1 related entity at most, for now) |
135 # this is for attribute-like composites (1 target type, 1 related entity at most, for now) |
136 ttypes = self._compute_ttypes(rschema, role) |
136 entity = self.entity |
137 related_rset = entity.related(rschema.type, role) |
137 related_rset = entity.related(rschema.type, role) |
138 add_related = self._may_add_related(related_rset, entity, rschema, role, ttypes) |
138 add_related = self._may_add_related(related_rset, rschema, role) |
139 edit_related = self._may_edit_related_entity(related_rset, entity, rschema, role, ttypes) |
139 edit_related = self._may_edit_related_entity(related_rset, rschema, role) |
140 delete_related = edit_related and self._may_delete_related(related_rset, entity, rschema, role) |
140 delete_related = edit_related and self._may_delete_related(related_rset, rschema, role) |
141 rvid = self._rules.get('rvid', 'autolimited') |
141 rvid = self._rules.get('rvid', 'autolimited') |
142 formid, value = self._compute_formid_value(entity, rschema, role, rvid, formid) |
142 formid, value = self._compute_formid_value(rschema, role, rvid, formid) |
143 if formid is None or not (edit_related or add_related): |
143 if formid is None or not (edit_related or add_related): |
144 # till we learn to handle cases where not (edit_related or add_related) |
144 # till we learn to handle cases where not (edit_related or add_related) |
145 self.w(value) |
145 self.w(value) |
146 return |
146 return |
147 rtype = rschema.type |
147 form, renderer = self._build_form(entity, rschema, role, divid, formid, |
148 ttype = ttypes[0] |
148 reload, action, dict(vid=rvid)) |
149 _fdata = self._prepare_composite_form(entity, rtype, role, edit_related, |
|
150 add_related and ttype) |
|
151 display_label, related_entity = _fdata |
|
152 form, renderer = self._build_form(entity, rtype, role, divid, formid, reload, |
|
153 display_label, related_entity, dict(vid=rvid)) |
|
154 self.view_form(divid, value, form, renderer, |
149 self.view_form(divid, value, form, renderer, |
155 edit_related, add_related, delete_related) |
150 edit_related, add_related, delete_related) |
156 |
151 |
|
152 @cached |
157 def _compute_ttypes(self, rschema, role): |
153 def _compute_ttypes(self, rschema, role): |
158 dual_role = neg_role(role) |
154 dual_role = neg_role(role) |
159 return getattr(rschema, '%ss' % dual_role)() |
155 return getattr(rschema, '%ss' % dual_role)() |
160 |
156 |
161 def _compute_reload(self, entity, rschema, role, reload): |
157 def _compute_reload(self, rschema, role, reload): |
162 ctrl_reload = self._rules.get('reload', reload) |
158 ctrl_reload = self._rules.get('reload', reload) |
163 if callable(ctrl_reload): |
159 if callable(ctrl_reload): |
164 ctrl_reload = ctrl_reload(entity) |
160 ctrl_reload = ctrl_reload(self.entity) |
165 if isinstance(ctrl_reload, int) and ctrl_reload > 1: # not True/False |
161 if isinstance(ctrl_reload, int) and ctrl_reload > 1: # not True/False |
166 ctrl_reload = self._cw.build_url(ctrl_reload) |
162 ctrl_reload = self._cw.build_url(ctrl_reload) |
167 return ctrl_reload |
163 return ctrl_reload |
168 |
164 |
169 def _compute_default_value(self, rschema, role): |
165 def _compute_default_value(self, rschema, role): |
177 return xml_escape(default) |
173 return xml_escape(default) |
178 |
174 |
179 def _is_composite(self): |
175 def _is_composite(self): |
180 return self._rules.get('edit_target') == 'related' |
176 return self._rules.get('edit_target') == 'related' |
181 |
177 |
182 def _may_add_related(self, related_rset, entity, rschema, role, ttypes): |
178 def _may_add_related(self, related_rset, rschema, role): |
183 """ ok for attribute-like composite entities """ |
179 """ ok for attribute-like composite entities """ |
|
180 ttypes = self._compute_ttypes(rschema, role) |
184 if len(ttypes) > 1: # many etypes: learn how to do it |
181 if len(ttypes) > 1: # many etypes: learn how to do it |
185 return False |
182 return False |
186 rdef = rschema.role_rdef(entity.e_schema, ttypes[0], role) |
183 rdef = rschema.role_rdef(self.entity.e_schema, ttypes[0], role) |
187 card = rdef.role_cardinality(role) |
184 card = rdef.role_cardinality(role) |
188 if related_rset or card not in '?1': |
185 if related_rset or card not in '?1': |
189 return False |
186 return False |
190 if role == 'subject': |
187 if role == 'subject': |
191 kwargs = {'fromeid': entity.eid} |
188 kwargs = {'fromeid': self.entity.eid} |
192 else: |
189 else: |
193 kwargs = {'toeid': entity.eid} |
190 kwargs = {'toeid': self.entity.eid} |
194 return rdef.has_perm(self._cw, 'add', **kwargs) |
191 return rdef.has_perm(self._cw, 'add', **kwargs) |
195 |
192 |
196 def _may_edit_related_entity(self, related_rset, entity, rschema, role, ttypes): |
193 def _may_edit_related_entity(self, related_rset, rschema, role): |
197 """ controls the edition of the related entity """ |
194 """ controls the edition of the related entity """ |
|
195 ttypes = self._compute_ttypes(rschema, role) |
198 if len(ttypes) > 1 or len(related_rset.rows) != 1: |
196 if len(ttypes) > 1 or len(related_rset.rows) != 1: |
199 return False |
197 return False |
200 if entity.e_schema.rdef(rschema, role).role_cardinality(role) not in '?1': |
198 if self.entity.e_schema.rdef(rschema, role).role_cardinality(role) not in '?1': |
201 return False |
199 return False |
202 return related_rset.get_entity(0, 0).cw_has_perm('update') |
200 return related_rset.get_entity(0, 0).cw_has_perm('update') |
203 |
201 |
204 def _may_delete_related(self, related_rset, entity, rschema, role): |
202 def _may_delete_related(self, related_rset, rschema, role): |
205 # we assume may_edit_related, only 1 related entity |
203 # we assume may_edit_related, only 1 related entity |
206 if not related_rset: |
204 if not related_rset: |
207 return False |
205 return False |
208 rentity = related_rset.get_entity(0, 0) |
206 rentity = related_rset.get_entity(0, 0) |
|
207 entity = self.entity |
209 if role == 'subject': |
208 if role == 'subject': |
210 kwargs = {'fromeid': entity.eid, 'toeid': rentity.eid} |
209 kwargs = {'fromeid': entity.eid, 'toeid': rentity.eid} |
211 else: |
210 else: |
212 kwargs = {'fromeid': rentity.eid, 'toeid': entity.eid} |
211 kwargs = {'fromeid': rentity.eid, 'toeid': entity.eid} |
213 # NOTE: should be sufficient given a well built schema/security |
212 # NOTE: should be sufficient given a well built schema/security |
228 |
227 |
229 def _build_divid(self, rtype, role, entity_eid): |
228 def _build_divid(self, rtype, role, entity_eid): |
230 """ builds an id for the root div of a reledit widget """ |
229 """ builds an id for the root div of a reledit widget """ |
231 return '%s-%s-%s' % (rtype, role, entity_eid) |
230 return '%s-%s-%s' % (rtype, role, entity_eid) |
232 |
231 |
233 def _build_args(self, entity, rtype, role, formid, reload, |
232 def _build_args(self, entity, rtype, role, formid, reload, action, |
234 extradata=None): |
233 extradata=None): |
235 divid = self._build_divid(rtype, role, entity.eid) |
234 divid = self._build_divid(rtype, role, entity.eid) |
236 event_args = {'divid' : divid, 'eid' : entity.eid, 'rtype' : rtype, 'formid': formid, |
235 event_args = {'divid' : divid, 'eid' : entity.eid, 'rtype' : rtype, 'formid': formid, |
237 'reload' : json_dumps(reload), |
236 'reload' : json_dumps(reload), 'action': action, |
238 'role' : role, 'vid' : u''} |
237 'role' : role, 'vid' : u''} |
239 if extradata: |
238 if extradata: |
240 event_args.update(extradata) |
239 event_args.update(extradata) |
241 return event_args |
240 return event_args |
242 |
241 |
243 def _prepare_form(self, entity, _rtype, role): |
242 def _prepare_form(self, entity, rschema, role, action): |
244 display_label = False |
243 assert action in ('edit_rtype', 'edit_related', 'add', 'delete'), action |
245 related_entity = entity |
244 if action == 'edit_rtype': |
246 return display_label, related_entity |
245 return False, entity |
247 |
246 label = True |
248 def _prepare_composite_form(self, entity, rtype, role, edit_related, add_related): |
247 if action in ('edit_related', 'delete'): |
249 display_label = True |
248 edit_entity = entity.related(rschema, role).get_entity(0, 0) |
250 if edit_related and not add_related: |
249 elif action == 'add': |
251 related_entity = entity.related(rtype, role).get_entity(0, 0) |
250 add_etype = self._compute_ttypes(rschema, role)[0] |
252 elif add_related: |
251 _new_entity = self._cw.vreg['etypes'].etype_class(add_etype)(self._cw) |
253 _new_entity = self._cw.vreg['etypes'].etype_class(add_related)(self._cw) |
|
254 _new_entity.eid = self._cw.varmaker.next() |
252 _new_entity.eid = self._cw.varmaker.next() |
255 related_entity = _new_entity |
253 edit_entity = _new_entity |
256 # XXX see forms.py ~ 276 and entities.linked_to method |
254 # XXX see forms.py ~ 276 and entities.linked_to method |
257 # is there another way ? |
255 # is there another way ? |
258 self._cw.form['__linkto'] = '%s:%s:%s' % (rtype, entity.eid, neg_role(role)) |
256 self._cw.form['__linkto'] = '%s:%s:%s' % (rschema, entity.eid, neg_role(role)) |
259 return display_label, related_entity |
257 assert edit_entity |
|
258 return label, edit_entity |
260 |
259 |
261 def _build_renderer(self, related_entity, display_label): |
260 def _build_renderer(self, related_entity, display_label): |
262 return self._cw.vreg['formrenderers'].select( |
261 return self._cw.vreg['formrenderers'].select( |
263 self._form_renderer_id, self._cw, entity=related_entity, |
262 self._form_renderer_id, self._cw, entity=related_entity, |
264 display_label=display_label, |
263 display_label=display_label, |
265 table_class='attributeForm' if display_label else '', |
264 table_class='attributeForm' if display_label else '', |
266 display_help=False, button_bar_class='buttonbar', |
265 display_help=False, button_bar_class='buttonbar', |
267 display_progress_div=False) |
266 display_progress_div=False) |
268 |
267 |
269 def _build_form(self, entity, rtype, role, divid, formid, reload, |
268 def _build_form(self, entity, rschema, role, divid, formid, reload, action, |
270 display_label, related_entity, extradata=None, **formargs): |
269 extradata=None, **formargs): |
271 event_args = self._build_args(entity, rtype, role, formid, |
270 rtype = rschema.type |
272 reload, extradata) |
271 event_args = self._build_args(entity, rtype, role, formid, reload, action, extradata) |
|
272 if not action: |
|
273 form = _DummyForm() |
|
274 form.event_args = event_args |
|
275 return form, None |
|
276 label, edit_entity = self._prepare_form(entity, rschema, role, action) |
273 cancelclick = self._cancelclick % divid |
277 cancelclick = self._cancelclick % divid |
274 form = self._cw.vreg['forms'].select( |
278 form = self._cw.vreg['forms'].select( |
275 formid, self._cw, rset=related_entity.as_rset(), entity=related_entity, |
279 formid, self._cw, rset=edit_entity.as_rset(), entity=edit_entity, |
276 domid='%s-form' % divid, formtype='inlined', |
280 domid='%s-form' % divid, formtype='inlined', |
277 action=self._cw.build_url('validateform', __onsuccess='window.parent.cw.reledit.onSuccess'), |
281 action=self._cw.build_url('validateform', __onsuccess='window.parent.cw.reledit.onSuccess'), |
278 cwtarget='eformframe', cssclass='releditForm', |
282 cwtarget='eformframe', cssclass='releditForm', |
279 **formargs) |
283 **formargs) |
280 # pass reledit arguments |
284 # pass reledit arguments |
333 w(u'</div>') |
338 w(u'</div>') |
334 form.render(w=w, renderer=renderer) |
339 form.render(w=w, renderer=renderer) |
335 w(u'<div id="%s" class="editableField hidden">' % divid) |
340 w(u'<div id="%s" class="editableField hidden">' % divid) |
336 |
341 |
337 def _edit_action(self, divid, args, edit_related, add_related, _delete_related): |
342 def _edit_action(self, divid, args, edit_related, add_related, _delete_related): |
|
343 # XXX disambiguate wrt edit_related |
338 if not add_related: # currently, excludes edition |
344 if not add_related: # currently, excludes edition |
339 w = self.w |
345 w = self.w |
340 args['formid'] = 'edition' if edit_related else 'base' |
346 args['formid'] = 'edition' if edit_related else 'base' |
|
347 args['action'] = 'edit_related' if edit_related else 'edit_rtype' |
341 w(u'<div id="%s-update" class="editableField" onclick="%s" title="%s">' % |
348 w(u'<div id="%s-update" class="editableField" onclick="%s" title="%s">' % |
342 (divid, xml_escape(self._onclick % args), self._cw._(self._editzonemsg))) |
349 (divid, xml_escape(self._onclick % args), self._cw._(self._editzonemsg))) |
343 w(self._build_edit_zone()) |
350 w(self._build_edit_zone()) |
344 w(u'</div>') |
351 w(u'</div>') |
345 |
352 |
346 def _add_action(self, divid, args, _edit_related, add_related, _delete_related): |
353 def _add_action(self, divid, args, _edit_related, add_related, _delete_related): |
347 if add_related: |
354 if add_related: |
348 w = self.w |
355 w = self.w |
349 args['formid'] = 'edition' if add_related else 'base' |
356 args['formid'] = 'edition' |
|
357 args['action'] = 'add' |
350 w(u'<div id="%s-add" class="editableField" onclick="%s" title="%s">' % |
358 w(u'<div id="%s-add" class="editableField" onclick="%s" title="%s">' % |
351 (divid, xml_escape(self._onclick % args), self._cw._(self._addmsg))) |
359 (divid, xml_escape(self._onclick % args), self._cw._(self._addmsg))) |
352 w(self._build_add_zone()) |
360 w(self._build_add_zone()) |
353 w(u'</div>') |
361 w(u'</div>') |
354 |
362 |
355 def _del_action(self, divid, args, _edit_related, _add_related, delete_related): |
363 def _del_action(self, divid, args, _edit_related, _add_related, delete_related): |
356 if delete_related: |
364 if delete_related: |
357 w = self.w |
365 w = self.w |
358 args['formid'] = 'deleteconf' |
366 args['formid'] = 'deleteconf' |
|
367 args['action'] = 'delete' |
359 w(u'<div id="%s-delete" class="editableField" onclick="%s" title="%s">' % |
368 w(u'<div id="%s-delete" class="editableField" onclick="%s" title="%s">' % |
360 (divid, xml_escape(self._onclick % args), self._cw._(self._deletemsg))) |
369 (divid, xml_escape(self._onclick % args), self._cw._(self._deletemsg))) |
361 w(self._build_delete_zone()) |
370 w(self._build_delete_zone()) |
362 w(u'</div>') |
371 w(u'</div>') |
363 |
372 |