127 else: |
129 else: |
128 return callback() |
130 return callback() |
129 self._default_publish() |
131 self._default_publish() |
130 self.reset() |
132 self.reset() |
131 |
133 |
|
134 def _ordered_formparams(self): |
|
135 """ Return form parameters dictionaries for each edited entity. |
|
136 |
|
137 We ensure that entities can be created in this order accounting for |
|
138 mandatory inlined relations. |
|
139 """ |
|
140 req = self._cw |
|
141 graph = {} |
|
142 get_rschema = self._cw.vreg.schema.rschema |
|
143 # minparams = 2, because at least __type and eid are needed |
|
144 values_by_eid = dict((eid, req.extract_entity_params(eid, minparams=2)) |
|
145 for eid in req.edited_eids()) |
|
146 # iterate over all the edited entities |
|
147 for eid, values in values_by_eid.iteritems(): |
|
148 # add eid to the dependency graph |
|
149 graph.setdefault(eid, set()) |
|
150 # search entity's edited fields for mandatory inlined relation |
|
151 for param in values['_cw_entity_fields'].split(','): |
|
152 try: |
|
153 rtype, role = param.split('-') |
|
154 except ValueError: |
|
155 # e.g. param='__type' |
|
156 continue |
|
157 rschema = get_rschema(rtype) |
|
158 if rschema.inlined: |
|
159 for target in rschema.targets(values['__type'], role): |
|
160 rdef = rschema.role_rdef(values['__type'], target, role) |
|
161 # if cardinality is 1 and if the target entity is being |
|
162 # simultaneously edited, the current entity must be |
|
163 # created before the target one |
|
164 if rdef.cardinality[0] == '1': |
|
165 target_eid = values[param] |
|
166 if target_eid in values_by_eid: |
|
167 # add dependency from the target entity to the |
|
168 # current one |
|
169 graph.setdefault(target_eid, set()).add(eid) |
|
170 break |
|
171 for eid in reversed(ordered_nodes(graph)): |
|
172 yield values_by_eid[eid] |
|
173 |
132 def _default_publish(self): |
174 def _default_publish(self): |
133 req = self._cw |
175 req = self._cw |
134 self.errors = [] |
176 self.errors = [] |
135 self.relations_rql = [] |
177 self.relations_rql = [] |
136 form = req.form |
178 form = req.form |
137 # so we're able to know the main entity from the repository side |
179 # so we're able to know the main entity from the repository side |
138 if '__maineid' in form: |
180 if '__maineid' in form: |
139 req.set_shared_data('__maineid', form['__maineid'], txdata=True) |
181 req.set_shared_data('__maineid', form['__maineid'], txdata=True) |
140 # no specific action, generic edition |
182 # no specific action, generic edition |
141 self._to_create = req.data['eidmap'] = {} |
183 self._to_create = req.data['eidmap'] = {} |
142 self._pending_fields = req.data['pendingfields'] = set() |
184 # those two data variables are used to handle relation from/to entities |
|
185 # which doesn't exist at time where the entity is edited and that |
|
186 # deserves special treatment |
|
187 req.data['pending_inlined'] = defaultdict(set) |
|
188 req.data['pending_others'] = set() |
143 try: |
189 try: |
144 for eid in req.edited_eids(): |
190 for formparams in self._ordered_formparams(): |
145 # __type and eid |
|
146 formparams = req.extract_entity_params(eid, minparams=2) |
|
147 eid = self.edit_entity(formparams) |
191 eid = self.edit_entity(formparams) |
148 except (RequestError, NothingToEdit) as ex: |
192 except (RequestError, NothingToEdit) as ex: |
149 if '__linkto' in req.form and 'eid' in req.form: |
193 if '__linkto' in req.form and 'eid' in req.form: |
150 self.execute_linkto() |
194 self.execute_linkto() |
151 elif not ('__delete' in req.form or '__insert' in req.form): |
195 elif not ('__delete' in req.form or '__insert' in req.form): |
152 raise ValidationError(None, {None: unicode(ex)}) |
196 raise ValidationError(None, {None: unicode(ex)}) |
153 # handle relations in newly created entities |
197 # all pending inlined relations to newly created entities have been |
154 if self._pending_fields: |
198 # treated now (pop to ensure there are no attempt to add new ones) |
155 for form, field in self._pending_fields: |
199 pending_inlined = req.data.pop('pending_inlined') |
156 self.handle_formfield(form, field) |
200 assert not pending_inlined, pending_inlined |
157 # execute rql to set all relations |
201 # handle all other remaining relations now |
|
202 for form_, field in req.data.pop('pending_others'): |
|
203 self.handle_formfield(form_, field) |
|
204 # then execute rql to set all relations |
158 for querydef in self.relations_rql: |
205 for querydef in self.relations_rql: |
159 self._cw.execute(*querydef) |
206 self._cw.execute(*querydef) |
160 # XXX this processes *all* pending operations of *all* entities |
207 # XXX this processes *all* pending operations of *all* entities |
161 if '__delete' in req.form: |
208 if '__delete' in req.form: |
162 todelete = req.list_form_param('__delete', req.form, pop=True) |
209 todelete = req.list_form_param('__delete', req.form, pop=True) |
215 except KeyError: |
262 except KeyError: |
216 raise RequestError(req._('no edited fields specified for entity %s' % entity.eid)) |
263 raise RequestError(req._('no edited fields specified for entity %s' % entity.eid)) |
217 form.formvalues = {} # init fields value cache |
264 form.formvalues = {} # init fields value cache |
218 for field in form.iter_modified_fields(editedfields, entity): |
265 for field in form.iter_modified_fields(editedfields, entity): |
219 self.handle_formfield(form, field, rqlquery) |
266 self.handle_formfield(form, field, rqlquery) |
|
267 # if there are some inlined field which were waiting for this entity's |
|
268 # creation, add relevant data to the rqlquery |
|
269 for form_, field in req.data['pending_inlined'].pop(entity.eid, ()): |
|
270 rqlquery.set_inlined(field.name, form_.edited_entity.eid) |
220 if self.errors: |
271 if self.errors: |
221 errors = dict((f.role_name(), unicode(ex)) for f, ex in self.errors) |
272 errors = dict((f.role_name(), unicode(ex)) for f, ex in self.errors) |
222 raise ValidationError(valerror_eid(entity.eid), errors) |
273 raise ValidationError(valerror_eid(entity.eid), errors) |
223 if eid is None: # creation or copy |
274 if eid is None: # creation or copy |
224 entity.eid = self._insert_entity(etype, formparams['eid'], rqlquery) |
275 entity.eid = self._insert_entity(etype, formparams['eid'], rqlquery) |
258 if rschema.inlined and rqlquery is not None and field.role == 'subject': |
309 if rschema.inlined and rqlquery is not None and field.role == 'subject': |
259 self.handle_inlined_relation(form, field, value, origvalues, rqlquery) |
310 self.handle_inlined_relation(form, field, value, origvalues, rqlquery) |
260 elif form.edited_entity.has_eid(): |
311 elif form.edited_entity.has_eid(): |
261 self.handle_relation(form, field, value, origvalues) |
312 self.handle_relation(form, field, value, origvalues) |
262 else: |
313 else: |
263 self._pending_fields.add( (form, field) ) |
314 form._cw.data['pending_others'].add( (form, field) ) |
264 |
|
265 except ProcessFormError as exc: |
315 except ProcessFormError as exc: |
266 self.errors.append((field, exc)) |
316 self.errors.append((field, exc)) |
267 |
317 |
268 def handle_inlined_relation(self, form, field, values, origvalues, rqlquery): |
318 def handle_inlined_relation(self, form, field, values, origvalues, rqlquery): |
269 """handle edition for the (rschema, x) relation of the given entity |
319 """handle edition for the (rschema, x) relation of the given entity |