# HG changeset patch # User Sylvain Thénault # Date 1294146714 -3600 # Node ID 8124455048355dc09929c8c0ca266f5fe27d7a3b # Parent 02091c91520f29b11002a093a147b82fd742ec9e# Parent 84b67ae41a0de8c4b2fdc81ce674f72b72b9f2fa backport stable diff -r 02091c91520f -r 812445504835 devtools/repotest.py --- a/devtools/repotest.py Sat Dec 18 23:12:14 2010 +0100 +++ b/devtools/repotest.py Tue Jan 04 14:11:54 2011 +0100 @@ -141,7 +141,7 @@ from rql import RQLHelper from cubicweb.devtools.fake import FakeRepo, FakeSession -from cubicweb.server import set_debug +from cubicweb.server import set_debug, debugged from cubicweb.server.querier import QuerierHelper from cubicweb.server.session import Session from cubicweb.server.sources.rql2sql import SQLGenerator, remove_unused_solutions @@ -171,6 +171,8 @@ def set_debug(self, debug): set_debug(debug) + def debugged(self, debug): + return debugged(debug) def _prepare(self, rql): #print '******************** prepare', rql @@ -222,6 +224,8 @@ def set_debug(self, debug): set_debug(debug) + def debugged(self, debug): + return debugged(debug) def _rqlhelper(self): rqlhelper = self.repo.vreg.rqlhelper diff -r 02091c91520f -r 812445504835 doc/book/en/devrepo/repo/hooks.rst --- a/doc/book/en/devrepo/repo/hooks.rst Sat Dec 18 23:12:14 2010 +0100 +++ b/doc/book/en/devrepo/repo/hooks.rst Tue Jan 04 14:11:54 2011 +0100 @@ -97,7 +97,7 @@ .. sourcecode:: python - from cubicweb.server.hook import Hook, Operation, match_rtype + from cubicweb.server.hook import Hook, DataOperationMixIn, Operation, match_rtype def check_cycle(self, session, eid, rtype, role='subject'): parents = set([eid]) @@ -110,7 +110,7 @@ parents.add(parent.eid) - class CheckSubsidiaryCycleOp(Operation): + class CheckSubsidiaryCycleOp(DataOperationMixIn, Operation): def precommit_event(self): check_cycle(self.session, self.eidto, 'subsidiary_of') @@ -131,7 +131,7 @@ In the above example, our hook will instantiate an operation each time the hook is called, i.e. each time the `subsidiary_of` relation is set. There is an alternative method to schedule an operation from a hook, using the -:func:`set_operation` function. +:func:`get_instance` class method. .. sourcecode:: python @@ -143,13 +143,12 @@ __select__ = Hook.__select__ & match_rtype('subsidiary_of') def __call__(self): - set_operation(self._cw, 'subsidiary_cycle_detection', self.eidto, - CheckSubsidiaryCycleOp) + CheckSubsidiaryCycleOp.get_instance(self._cw).add_data(self.eidto) class CheckSubsidiaryCycleOp(Operation): def precommit_event(self): - for eid in self.session.transaction_data['subsidiary_cycle_detection']: + for eid in self.get_data(): check_cycle(self.session, eid, self.rtype) @@ -169,30 +168,31 @@ Reminder ~~~~~~~~ -Never, ever use the `entity.foo = 42` notation to update an entity. It will not -work.To updating an entity attribute or relation, uses :meth:`set_attributes` and +You should never use the `entity.foo = 42` notation to update an +entity. It will not do what you expect (updating the +database). Instead, use the :meth:`set_attributes` and :meth:`set_relations` methods. How to choose between a before and an after event ? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -'before_*' hooks give you access to the old attribute (or relation) -values. You can also hi-jack actually edited stuff in the case of entity -modification. Needing one of this will definitly guide your choice. +`before_*` hooks give you access to the old attribute (or relation) +values. You can also intercept and update edited values in the case of +entity modification before they reach the database. Else the question is: should I need to do things before or after the actual -modification. If the answer is "it doesn't matter", use an 'after' event. +modification ? If the answer is "it doesn't matter", use an 'after' event. Validation Errors ~~~~~~~~~~~~~~~~~ -When a hook is responsible to maintain the consistency of the data model detect -an error, it must use a specific exception named +When a hook which is responsible to maintain the consistency of the +data model detects an error, it must use a specific exception named :exc:`~cubicweb.ValidationError`. Raising anything but a (subclass of) -:exc:`~cubicweb.ValidationError` is a programming error. Raising a it entails -aborting the current transaction. +:exc:`~cubicweb.ValidationError` is a programming error. Raising it +entails aborting the current transaction. This exception is used to convey enough information up to the user interface. Hence its constructor is different from the default Exception @@ -204,6 +204,11 @@ an end-user facing message (hence properly translated) relating the problem. +.. sourcecode:: python + + raise ValidationError(earth.eid, {'sea_level': self._cw._('too high'), + 'temperature': self._cw._('too hot')}) + Checking for object created/deleted in the current transaction ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -228,7 +233,8 @@ Relations which are defined in the schema as `inlined` (see :ref:`RelationType` for details) are inserted in the database at the same time as entity attributes. -This may have some side effect, for instance when creating entity and setting an -inlined relation in the same rql query, when 'before_add_relation' for that -relation will be run, the relation will already exist in the database (it's -usually not the case). + +This may have some side effect, for instance when creating an entity +and setting an inlined relation in the same rql query, then at +`before_add_relation` time, the relation will already exist in the +database (it is otherwise not the case). diff -r 02091c91520f -r 812445504835 entities/adapters.py --- a/entities/adapters.py Sat Dec 18 23:12:14 2010 +0100 +++ b/entities/adapters.py Tue Jan 04 14:11:54 2011 +0100 @@ -484,5 +484,5 @@ def raise_user_exception(self): etype, rtypes = self.exc.args msg = self._cw._('violates unique_together constraints (%s)') % ( - ', '.join(self._cw._(rtypes))) + ', '.join([self._cw._(rtype) for rtype in rtypes])) raise ValidationError(self.entity.eid, dict((col, msg) for col in rtypes)) diff -r 02091c91520f -r 812445504835 hooks/workflow.py --- a/hooks/workflow.py Sat Dec 18 23:12:14 2010 +0100 +++ b/hooks/workflow.py Tue Jan 04 14:11:54 2011 +0100 @@ -191,6 +191,8 @@ msg = session._('mandatory relation') raise ValidationError(entity.eid, {qname: msg}) forentity = session.entity_from_eid(foreid) + # see comment in the TrInfo entity definition + entity.cw_edited['tr_count']=len(forentity.reverse_wf_info_for) iworkflowable = forentity.cw_adapt_to('IWorkflowable') # then check it has a workflow set, unless we're in the process of changing # entity's workflow diff -r 02091c91520f -r 812445504835 misc/migration/3.10.7_Any.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/misc/migration/3.10.7_Any.py Tue Jan 04 14:11:54 2011 +0100 @@ -0,0 +1,2 @@ +add_attribute('TrInfo', 'tr_count') +sync_schema_props_perms('TrInfo') diff -r 02091c91520f -r 812445504835 schemas/workflow.py --- a/schemas/workflow.py Sat Dec 18 23:12:14 2010 +0100 +++ b/schemas/workflow.py Tue Jan 04 14:11:54 2011 +0100 @@ -22,7 +22,7 @@ _ = unicode from yams.buildobjs import (EntityType, RelationType, SubjectRelation, - RichString, String) + RichString, String, Int) from cubicweb.schema import RQLConstraint, RQLUniqueConstraint from cubicweb.schemas import (META_ETYPE_PERMS, META_RTYPE_PERMS, HOOKS_RTYPE_PERMS) @@ -159,13 +159,21 @@ 'delete': (), # XXX should we allow managers to delete TrInfo? 'update': ('managers', 'owners',), } - - from_state = SubjectRelation('State', cardinality='1*') - to_state = SubjectRelation('State', cardinality='1*') + # The unique_together constraint ensures that 2 repositories + # sharing the db won't be able to fire a transition simultaneously + # on the same entity tr_count is filled in the FireTransitionHook + # to the number of TrInfo attached to the entity on which we + # attempt to fire a transition. In other word, it contains the + # rank of the TrInfo for that entity, and the constraint says we + # cannot have 2 TrInfo with the same rank. + __unique_together__ = [('tr_count', 'wf_info_for')] + from_state = SubjectRelation('State', cardinality='1*', inlined=True) + to_state = SubjectRelation('State', cardinality='1*', inlined=True) # make by_transition optional because we want to allow managers to set # entity into an arbitrary state without having to respect wf transition by_transition = SubjectRelation('BaseTransition', cardinality='?*') comment = RichString(fulltextindexed=True) + tr_count = Int(description='autocomputed attribute used to ensure transition coherency') # get actor and date time using owned_by and creation_date class from_state(RelationType): diff -r 02091c91520f -r 812445504835 server/hook.py --- a/server/hook.py Sat Dec 18 23:12:14 2010 +0100 +++ b/server/hook.py Tue Jan 04 14:11:54 2011 +0100 @@ -105,17 +105,17 @@ When called for one of these events, hook will have an `entity` attribute containing the entity instance. -* 'before_add_entity', 'before_update_entity': +* `before_add_entity`, `before_update_entity`: - on those events, you can check what attributes of the entity are modified in `entity.cw_edited` (by definition the database is not yet updated in a before event) - - you are allowed to further modify the entity before database operations, - using the dictionary notation. By doing this, you'll avoid the need for a - whole new rql query processing, the only difference is that the underlying - backend query (eg usually sql) will contains the additional data. For - example: + - you are allowed to further modify the entity before database + operations, using the dictionary notation on `cw_edited`. By doing + this, you'll avoid the need for a whole new rql query processing, + the only difference is that the underlying backend query (eg + usually sql) will contains the additional data. For example: .. sourcecode:: python @@ -136,17 +136,17 @@ Similarly, removing an attribute from `cw_edited` will cancel its modification. - - on 'before_update_entity' event, you can access to old and new values in + - on `before_update_entity` event, you can access to old and new values in this hook, by using `entity.cw_edited.oldnewvalue(attr)` -* 'after_add_entity', 'after_update_entity' +* `after_add_entity`, `after_update_entity` - on those events, you can still check what attributes of the entity are modified in `entity.cw_edited` but you can't get anymore the old value, nor modify it. -* 'before_delete_entity', 'after_delete_entity' +* `before_delete_entity`, `after_delete_entity` - on those events, the entity has no `cw_edited` set. @@ -158,11 +158,11 @@ attributes containing respectivly the eid of the subject entity, the relation type and the eid of the object entity. -* 'before_add_relation', 'before_delete_relation' +* `before_add_relation`, `before_delete_relation` - on those events, you can still get original relation by issuing a rql query -* 'after_add_relation', 'after_delete_relation' +* `after_add_relation`, `after_delete_relation` This is an occasion to remind us that relations support the add / delete operation, but no update. @@ -171,8 +171,8 @@ Non data events ~~~~~~~~~~~~~~~ -Hooks called on server start/maintenance/stop event (eg 'server_startup', -'server_maintenance', 'server_shutdown') have a `repo` attribute, but *their +Hooks called on server start/maintenance/stop event (eg `server_startup`, +`server_maintenance`, `server_shutdown`) have a `repo` attribute, but *their `_cw` attribute is None*. The `server_startup` is called on regular startup, while `server_maintenance` is called on cubicweb-ctl upgrade or shell commands. `server_shutdown` is called anyway. @@ -180,7 +180,7 @@ Hooks called on backup/restore event (eg 'server_backup', 'server_restore') have a `repo` and a `timestamp` attributes, but *their `_cw` attribute is None*. -Hooks called on session event (eg 'session_open', 'session_close') have no +Hooks called on session event (eg `session_open`, `session_close`) have no special attribute. @@ -400,7 +400,7 @@ .. Note:: - Do not forget to extend the base class selectors as in :: + Do not forget to extend the base class selectors as in: .. sourcecode:: python @@ -609,7 +609,7 @@ An operation is triggered on connections pool events related to commit / rollback transations. Possible events are: - * 'precommit': + * `precommit`: the transaction is being prepared for commit. You can freely do any heavy computation, raise an exception if the commit can't go. or even add some @@ -618,13 +618,13 @@ instance), you'll have to support the 'revertprecommit' event to revert things by yourself - * 'revertprecommit': + * `revertprecommit`: if an operation failed while being pre-commited, this event is triggered for all operations which had their 'precommit' event already fired to let them revert things (including the operation which made the commit fail) - * 'rollback': + * `rollback`: the transaction has been either rollbacked either: @@ -632,7 +632,7 @@ * a 'precommit' event failed, in which case all operations are rollbacked once 'revertprecommit'' has been called - * 'postcommit': + * `postcommit`: the transaction is over. All the ORM entities accessed by the earlier transaction are invalid. If you need to work on the database, you need to @@ -642,7 +642,7 @@ For an operation to support an event, one has to implement the `_event` method with no arguments. - Notice order of operations may be important, and is controlled according to + The order of operations may be important, and is controlled according to the insert_index's method output (whose implementation vary according to the base hook class used). """ @@ -736,6 +736,7 @@ `value`, since handling operations becomes costly on massive data import. Usage looks like: + .. sourcecode:: python class MyEntityHook(Hook): diff -r 02091c91520f -r 812445504835 server/msplanner.py --- a/server/msplanner.py Sat Dec 18 23:12:14 2010 +0100 +++ b/server/msplanner.py Tue Jan 04 14:11:54 2011 +0100 @@ -856,6 +856,25 @@ needsel = set() if not self._sourcesterms: terms += scope.defined_vars.values() + scope.aliases.values() + if isinstance(term, Relation) and len(sources) > 1: + variants = set() + partterms = [term] + for vref in term.get_nodes(VariableRef): + if not vref.variable._q_invariant: + variants.add(vref.name) + if len(variants) == 2: + # we need an extra-step to fetch relations from each source + # before a join with prefetched inputs + # (see test_crossed_relation_noeid_needattr in + # unittest_msplanner / unittest_multisources) + needsel2 = needsel.copy() + needsel2.update(variants) + lhs, rhs = term.get_variable_parts() + steps.append( (sources, [term, getattr(lhs, 'variable', lhs), + getattr(rhs, 'variable', rhs)], + solindices, scope, + needsel2, False) ) + sources = [self.system_source] final = True else: # suppose this is a final step until the contrary is proven @@ -1310,8 +1329,8 @@ # in this case we have to merge input maps before call to # filter so already processed restriction are correctly # removed - solsinputmaps = ppi.merge_input_maps(solindices, - complete=not (final and multifinal)) + solsinputmaps = ppi.merge_input_maps( + solindices, complete=not (final and multifinal)) for solindices, inputmap in solsinputmaps: minrqlst, insertedvars = vfilter.filter( sources, terms, scope, set(solindices), needsel, final) @@ -1328,7 +1347,8 @@ minrqlst, insertedvars = vfilter.filter( sources, terms, scope, solindices, needsel, final) if final: - solsinputmaps = ppi.merge_input_maps(solindices) + solsinputmaps = ppi.merge_input_maps( + solindices, complete=not (final and multifinal)) if len(solsinputmaps) > 1: refrqlst = minrqlst for solindices, inputmap in solsinputmaps: @@ -1548,7 +1568,7 @@ def visit_relation(self, node, newroot, terms): if not node.is_types_restriction(): - if node in self.skip and self.solindices.issubset(self.skip[node]): + if not node in terms and node in self.skip and self.solindices.issubset(self.skip[node]): if not self.schema.rschema(node.r_type).final: # can't really skip the relation if one variable is selected # and only referenced by this relation diff -r 02091c91520f -r 812445504835 server/test/data/extern_mapping.py --- a/server/test/data/extern_mapping.py Sat Dec 18 23:12:14 2010 +0100 +++ b/server/test/data/extern_mapping.py Tue Jan 04 14:11:54 2011 +0100 @@ -15,8 +15,9 @@ # # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . -""" +"""mapping file for source used in unittest_multisources.py""" -""" support_entities = {'Card': True, 'Affaire': True, 'State': True} support_relations = {'in_state': True, 'documented_by': True, 'multisource_inlined_rel': True} + +cross_relations = set( ('documented_by',) ) diff -r 02091c91520f -r 812445504835 server/test/unittest_msplanner.py --- a/server/test/unittest_msplanner.py Sat Dec 18 23:12:14 2010 +0100 +++ b/server/test/unittest_msplanner.py Tue Jan 04 14:11:54 2011 +0100 @@ -18,6 +18,7 @@ from logilab.common.decorators import clear_cache +from yams.buildobjs import RelationDefinition from rql import BadRQLQuery from cubicweb.devtools import init_test_database @@ -1607,20 +1608,84 @@ ('FetchStep', [('Any Y,T WHERE Y type T, Y is Note', [{'T': 'String', 'Y': 'Note'}])], [self.cards, self.system], None, {'T': 'table1.C1', 'Y': 'table1.C0', 'Y.type': 'table1.C1'}, []), - ('UnionStep', None, None, - [('OneFetchStep', [('Any X,Y,T WHERE X multisource_crossed_rel Y, Y type T, X type T, X is Note, Y is Note', - [{'T': 'String', 'X': 'Note', 'Y': 'Note'}])], - None, None, [self.cards], None, - []), - ('OneFetchStep', [('Any X,Y,T WHERE X multisource_crossed_rel Y, Y type T, X type T, X is Note, Y is Note', - [{'T': 'String', 'X': 'Note', 'Y': 'Note'}])], - None, None, [self.system], - {'T': 'table1.C1', 'X': 'table0.C0', 'X.type': 'table0.C1', - 'Y': 'table1.C0', 'Y.type': 'table1.C1'}, - [])] - )], + ('FetchStep', [('Any X,Y WHERE X multisource_crossed_rel Y, X is Note, Y is Note', + [{'X': 'Note', 'Y': 'Note'}])], + [self.cards, self.system], None, + {'X': 'table2.C0', 'Y': 'table2.C1'}, + []), + ('OneFetchStep', [('Any X,Y,T WHERE X multisource_crossed_rel Y, Y type T, X type T, ' + 'X is Note, Y is Note, Y identity A, X identity B, A is Note, B is Note', + [{u'A': 'Note', u'B': 'Note', 'T': 'String', 'X': 'Note', 'Y': 'Note'}])], + None, None, + [self.system], + {'A': 'table1.C0', + 'B': 'table0.C0', + 'T': 'table1.C1', + 'X': 'table2.C0', + 'X.type': 'table0.C1', + 'Y': 'table2.C1', + 'Y.type': 'table1.C1'}, + []), + ], {'x': 999999,}) + def test_crossed_relation_noeid_needattr(self): + # http://www.cubicweb.org/ticket/1382452 + self._test('DISTINCT Any DEP WHERE DEP is Note, P type "cubicweb-foo", P multisource_crossed_rel DEP, DEP type LIKE "cubicweb%"', + [('FetchStep', [(u'Any DEP WHERE DEP type LIKE "cubicweb%", DEP is Note', + [{'DEP': 'Note'}])], + [self.cards, self.system], None, + {'DEP': 'table0.C0'}, + []), + ('FetchStep', [(u'Any P WHERE P type "cubicweb-foo", P is Note', [{'P': 'Note'}])], + [self.cards, self.system], None, {'P': 'table1.C0'}, + []), + ('FetchStep', [('Any DEP,P WHERE P multisource_crossed_rel DEP, DEP is Note, P is Note', + [{'DEP': 'Note', 'P': 'Note'}])], + [self.cards, self.system], None, {'DEP': 'table2.C0', 'P': 'table2.C1'}, + []), + ('OneFetchStep', + [('DISTINCT Any DEP WHERE P multisource_crossed_rel DEP, DEP is Note, ' + 'P is Note, DEP identity A, P identity B, A is Note, B is Note', + [{u'A': 'Note', u'B': 'Note', 'DEP': 'Note', 'P': 'Note'}])], + None, None, [self.system], + {'A': 'table0.C0', 'B': 'table1.C0', 'DEP': 'table2.C0', 'P': 'table2.C1'}, + [])]) + + def test_crossed_relation_noeid_invariant(self): + # see comment in http://www.cubicweb.org/ticket/1382452 + self.schema.add_relation_def( + RelationDefinition(subject='Note', name='multisource_crossed_rel', object='Affaire')) + self.repo.set_schema(self.schema) + try: + self._test('DISTINCT Any P,DEP WHERE P type "cubicweb-foo", P multisource_crossed_rel DEP', + [('FetchStep', + [('Any DEP WHERE DEP is Note', [{'DEP': 'Note'}])], + [self.cards, self.system], None, {'DEP': 'table0.C0'}, []), + ('FetchStep', + [(u'Any P WHERE P type "cubicweb-foo", P is Note', [{'P': 'Note'}])], + [self.cards, self.system], None, {'P': 'table1.C0'}, []), + ('UnionStep', None, None, + [('OneFetchStep', + [('DISTINCT Any P,DEP WHERE P multisource_crossed_rel DEP, DEP is Note, P is Note', + [{'DEP': 'Note', 'P': 'Note'}])], + None, None, [self.cards], None, []), + ('OneFetchStep', + [('DISTINCT Any P,DEP WHERE P multisource_crossed_rel DEP, DEP is Note, P is Note', + [{'DEP': 'Note', 'P': 'Note'}])], + None, None, [self.system], + {'DEP': 'table0.C0', 'P': 'table1.C0'}, + []), + ('OneFetchStep', + [('DISTINCT Any P,DEP WHERE P multisource_crossed_rel DEP, DEP is Affaire, P is Note', + [{'DEP': 'Affaire', 'P': 'Note'}])], + None, None, [self.system], {'P': 'table1.C0'}, + [])]) + ]) + finally: + self.schema.del_relation_def('Note', 'multisource_crossed_rel', 'Affaire') + self.repo.set_schema(self.schema) + # edition queries tests ################################################### def test_insert_simplified_var_1(self): diff -r 02091c91520f -r 812445504835 server/test/unittest_multisources.py --- a/server/test/unittest_multisources.py Sat Dec 18 23:12:14 2010 +0100 +++ b/server/test/unittest_multisources.py Tue Jan 04 14:11:54 2011 +0100 @@ -86,7 +86,9 @@ TestServerConfiguration.no_sqlite_wrap = False class TwoSourcesTC(CubicWebTC): - + """Main repo -> extern-multi -> extern + \-------------/ + """ @classmethod def _refresh_repo(cls): super(TwoSourcesTC, cls)._refresh_repo() @@ -333,6 +335,19 @@ cu.execute('DELETE Card X WHERE X eid %(x)s', {'x':ceid}) cnx3.commit() + def test_crossed_relation_noeid_needattr(self): + """http://www.cubicweb.org/ticket/1382452""" + aff1 = self.sexecute('INSERT Affaire X: X ref "AFFREF"')[0][0] + # link within extern source + ec1 = self.sexecute('Card X WHERE X wikiid "zzz"')[0][0] + self.sexecute('SET A documented_by C WHERE E eid %(a)s, C eid %(c)s', + {'a': aff1, 'c': ec1}) + # link from system to extern source + self.sexecute('SET A documented_by C WHERE E eid %(a)s, C eid %(c)s', + {'a': aff1, 'c': self.ic2}) + rset = self.sexecute('DISTINCT Any DEP WHERE P ref "AFFREF", P documented_by DEP, DEP wikiid LIKE "z%"') + self.assertEqual(sorted(rset.rows), [[ec1], [self.ic2]]) + def test_nonregr1(self): ueid = self.session.user.eid affaire = self.sexecute('Affaire X WHERE X ref "AFFREF"').get_entity(0, 0) diff -r 02091c91520f -r 812445504835 view.py --- a/view.py Sat Dec 18 23:12:14 2010 +0100 +++ b/view.py Tue Jan 04 14:11:54 2011 +0100 @@ -40,6 +40,11 @@ NOINDEX = u'' NOFOLLOW = u'' + +CW_XMLNS = '''[ + + ] +''' CW_XHTML_EXTENSIONS = '''[ @@ -81,9 +86,9 @@ "> ] ''' TRANSITIONAL_DOCTYPE = u'\n' % CW_XHTML_EXTENSIONS -TRANSITIONAL_DOCTYPE_NOEXT = u'\n' +TRANSITIONAL_DOCTYPE_NOEXT = u'\n' % CW_XMLNS STRICT_DOCTYPE = u'\n' % CW_XHTML_EXTENSIONS -STRICT_DOCTYPE_NOEXT = u'\n' +STRICT_DOCTYPE_NOEXT = u'\n' % CW_XMLNS # base view object ############################################################ diff -r 02091c91520f -r 812445504835 web/views/reledit.py --- a/web/views/reledit.py Sat Dec 18 23:12:14 2010 +0100 +++ b/web/views/reledit.py Tue Jan 04 14:11:54 2011 +0100 @@ -78,7 +78,7 @@ assert rtype assert role in ('subject', 'object'), '%s is not an acceptable role value' % role self._cw.add_css('cubicweb.form.css') - self._cw.add_js('cubicweb.reledit.js', 'cubicweb.edition.js') + self._cw.add_js(('cubicweb.reledit.js', 'cubicweb.edition.js', 'cubicweb.ajax.js')) entity = self.cw_rset.get_entity(row, col) rschema = self._cw.vreg.schema[rtype] self._rules = rctrl.etype_get(entity.e_schema.type, rschema.type, role, '*')