backport stable branch
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Thu, 15 Oct 2009 10:31:54 +0200
changeset 3674 387d51af966d
parent 3661 12ea53a4c0da (current diff)
parent 3673 9342e6783bd2 (diff)
child 3675 5d143781a604
backport stable branch
__init__.py
cwctl.py
cwvreg.py
devtools/devctl.py
entities/wfobjs.py
entity.py
etwist/server.py
hooks/workflow.py
req.py
selectors.py
server/serverconfig.py
server/sources/__init__.py
server/test/unittest_msplanner.py
server/test/unittest_rql2sql.py
test/unittest_entity.py
vregistry.py
web/views/editcontroller.py
web/views/editforms.py
--- a/__init__.py	Tue Oct 13 18:21:24 2009 +0200
+++ b/__init__.py	Thu Oct 15 10:31:54 2009 +0200
@@ -21,6 +21,7 @@
 
 from logilab.common.logging_ext import set_log_methods
 
+
 if os.environ.get('APYCOT_ROOT'):
     logging.basicConfig(level=logging.CRITICAL)
 else:
@@ -71,7 +72,6 @@
                   }
 
 
-
 # XXX cubic web cube migration map
 CW_MIGRATION_MAP = {'erudi': 'cubicweb',
                     'eaddressbook': 'addressbook',
--- a/cwconfig.py	Tue Oct 13 18:21:24 2009 +0200
+++ b/cwconfig.py	Thu Oct 15 10:31:54 2009 +0200
@@ -5,10 +5,71 @@
 :copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 
-.. envvar:: CW_CUBES_PATH
+
+If cubicweb is a mercurial checkout (eg `CWDEV` is true), located in
+`CW_SOFTWARE_ROOT`:
+
+ * main cubes directory is `<CW_SOFTWARE_ROOT>/../cubes`. You can specify
+   another one with `CW_INSTANCES_DIR` environment variable or simply add some
+   other directories by using `CW_CUBES_PATH`.
+
+ * cubicweb migration files are by default searched in
+   `<CW_SOFTWARE_ROOT>/misc/migration` instead of
+   `/usr/share/cubicweb/migration/`(unless another emplacement is specified
+   using `CW_MIGRATION_DIR`.
+
+ * Cubicweb will start in 'user' mode (see below)
+
+
+On startup, Cubicweb is using a specific *mode*. A mode corresponds to some
+default setting for various resource directories. There are currently 2 main
+modes : 'system', for system wide installation, and 'user', fur user local
+installation (e.g. no root privileges).
+
+'user' mode is activated automatically when cubicweb is a mercurial checkout
+(e.g.  has a .hg directory). You can also force mode by using the `CW_MODE`
+environment variable, to:
+
+* use system wide installation but user specific instances and all, without root
+  privileges on the system (`export CW_MODE=user`)
+
+* use local checkout of cubicweb on system wide instances (requires root
+  privileges on the system (`export CW_MODE=system`)
 
+ Here is the default resource directories settings according to mode:
+
+* 'system': ::
+
+        CW_INSTANCES_DIR = /etc/cubicweb.d/
+        CW_INSTANCES_DATA_DIR = /var/lib/cubicweb/instances/
+        CW_RUNTIME_DIR = /var/run/cubicweb/
+
+ * 'user': ::
+
+        CW_INSTANCES_DIR = ~/etc/cubicweb.d/
+        CW_INSTANCES_DATA_DIR = ~/etc/cubicweb.d/
+        CW_RUNTIME_DIR = /tmp
+
+
+.. envvar:: CW_MODE
+   Resource mode: user or system
+
+.. envvar:: CW_CUBES_PATH
    Augments the default search path for cubes
 
+.. envvar:: CW_INSTANCES_DIR
+   Directory where cubicweb instances will be found
+
+.. envvar:: CW_INSTANCES_DATA_DIR
+   Directory where cubicweb instances data will be written
+
+.. envvar:: CW_RUNTIME_DIR
+   Directory where pid files will be written
+
+.. envvar:: CW_MIGRATION_DIR
+   Directory where cubicweb migration files will be found
+
+
 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
 """
 __docformat__ = "restructuredtext en"
@@ -138,6 +199,8 @@
 _forced_mode = os.environ.get('CW_MODE')
 assert _forced_mode in (None, 'system', 'user')
 
+CWDEV = exists(join(CW_SOFTWARE_ROOT, '.hg'))
+
 class CubicWebNoAppConfiguration(ConfigurationMixIn):
     """base class for cubicweb configuration without a specific instance directory
     """
@@ -148,17 +211,22 @@
     log_format = '%(asctime)s - (%(name)s) %(levelname)s: %(message)s'
     # nor remove appobjects based on unused interface
     cleanup_interface_sobjects = True
+    # debug mode
+    debugmode = False
 
     if os.environ.get('APYCOT_ROOT'):
         mode = 'test'
         CUBES_DIR = '%(APYCOT_ROOT)s/local/share/cubicweb/cubes/' % os.environ
         # create __init__ file
         file(join(CUBES_DIR, '__init__.py'), 'w').close()
-    elif (exists(join(CW_SOFTWARE_ROOT, '.hg')) and _forced_mode != 'system') or _forced_mode == 'user':
-        mode = 'dev'
+    elif (CWDEV and _forced_mode != 'system'):
+        mode = 'user'
         CUBES_DIR = abspath(normpath(join(CW_SOFTWARE_ROOT, '../cubes')))
     else:
-        mode = 'installed'
+        if _forced_mode == 'user':
+            mode = 'user'
+        else:
+            mode = 'system'
         CUBES_DIR = '/usr/share/cubicweb/cubes/'
 
     options = (
@@ -245,14 +313,14 @@
         """return the shared data directory (i.e. directory where standard
         library views and data may be found)
         """
-        if cls.mode in ('dev', 'test') and not os.environ.get('APYCOT_ROOT'):
+        if CWDEV:
             return join(CW_SOFTWARE_ROOT, 'web')
         return cls.cube_dir('shared')
 
     @classmethod
     def i18n_lib_dir(cls):
         """return instance's i18n directory"""
-        if cls.mode in ('dev', 'test') and not os.environ.get('APYCOT_ROOT'):
+        if CWDEV:
             return join(CW_SOFTWARE_ROOT, 'i18n')
         return join(cls.shared_dir(), 'i18n')
 
@@ -261,7 +329,7 @@
         cubes = set()
         for directory in cls.cubes_search_path():
             for cube in os.listdir(directory):
-                if isdir(join(directory, cube)) and not cube in ('CVS', '.svn', 'shared', '.hg'):
+                if isdir(join(directory, cube)) and not cube == 'shared':
                     cubes.add(cube)
         return sorted(cubes)
 
@@ -497,6 +565,7 @@
                 logthreshold = 'DEBUG'
             else:
                 logthreshold = self['log-threshold']
+        self.debugmode = debug
         init_log(debug, syslog, logthreshold, logfile, self.log_format)
         # configure simpleTal logger
         logging.getLogger('simpleTAL').setLevel(logging.ERROR)
@@ -538,7 +607,7 @@
 class CubicWebConfiguration(CubicWebNoAppConfiguration):
     """base class for cubicweb server and web configurations"""
 
-    INSTANCE_DATA_DIR = None
+    INSTANCES_DATA_DIR = None
     if CubicWebNoAppConfiguration.mode == 'test':
         root = os.environ['APYCOT_ROOT']
         REGISTRY_DIR = '%s/etc/cubicweb.d/' % root
@@ -546,15 +615,19 @@
         MIGRATION_DIR = '%s/local/share/cubicweb/migration/' % root
         if not exists(REGISTRY_DIR):
             os.makedirs(REGISTRY_DIR)
-    elif CubicWebNoAppConfiguration.mode == 'dev':
-        REGISTRY_DIR = expanduser('~/etc/cubicweb.d/')
-        RUNTIME_DIR = tempfile.gettempdir()
-        MIGRATION_DIR = join(CW_SOFTWARE_ROOT, 'misc', 'migration')
-    else: #mode = 'installed'
-        REGISTRY_DIR = '/etc/cubicweb.d/'
-        INSTANCE_DATA_DIR = '/var/lib/cubicweb/instances/'
-        RUNTIME_DIR = '/var/run/cubicweb/'
-        MIGRATION_DIR = '/usr/share/cubicweb/migration/'
+    else:
+        if CubicWebNoAppConfiguration.mode == 'user':
+            REGISTRY_DIR = expanduser('~/etc/cubicweb.d/')
+            RUNTIME_DIR = tempfile.gettempdir()
+            INSTANCES_DATA_DIR = REGISTRY_DIR
+        else: #mode = 'system'
+            REGISTRY_DIR = '/etc/cubicweb.d/'
+            RUNTIME_DIR = '/var/run/cubicweb/'
+            INSTANCES_DATA_DIR = '/var/lib/cubicweb/instances/'
+        if CWDEV:
+            MIGRATION_DIR = join(CW_SOFTWARE_ROOT, 'misc', 'migration')
+        else:
+            MIGRATION_DIR = '/usr/share/cubicweb/migration/'
 
     # for some commands (creation...) we don't want to initialize gettext
     set_language = True
@@ -612,8 +685,7 @@
     @classmethod
     def instance_data_dir(cls):
         """return the instance data directory"""
-        return env_path('CW_INSTANCES_DATA_DIR',
-                        cls.INSTANCE_DATA_DIR or cls.REGISTRY_DIR,
+        return env_path('CW_INSTANCES_DATA_DIR', cls.INSTANCES_DATA_DIR,
                         'additional data')
 
     @classmethod
@@ -666,7 +738,7 @@
 
     def default_log_file(self):
         """return default path to the log file of the instance'server"""
-        if self.mode == 'dev':
+        if self.mode == 'user':
             basepath = join(tempfile.gettempdir(), '%s-%s' % (basename(self.appid), self.name))
             path = basepath + '.log'
             i = 1
--- a/cwctl.py	Tue Oct 13 18:21:24 2009 +0200
+++ b/cwctl.py	Thu Oct 15 10:31:54 2009 +0200
@@ -18,10 +18,9 @@
 from logilab.common.shellutils import ASK
 
 from cubicweb import ConfigurationError, ExecutionError, BadCommandUsage
-from cubicweb.cwconfig import CubicWebConfiguration as cwcfg, CONFIGURATIONS
+from cubicweb.cwconfig import CubicWebConfiguration as cwcfg, CWDEV, CONFIGURATIONS
 from cubicweb.toolsutils import Command, main_run, rm, create_dir, underline_title
 
-
 def wait_process_end(pid, maxtry=10, waittime=1):
     """wait for a process to actually die"""
     import signal
@@ -657,7 +656,7 @@
         for cube, fromversion, toversion in toupgrade:
             print '-> migration needed from %s to %s for %s' % (fromversion, toversion, cube)
         # only stop once we're sure we have something to do
-        if not (cwcfg.mode == 'dev' or self.config.nostartstop):
+        if not (CWDEV or self.config.nostartstop):
             StopInstanceCommand().stop_instance(appid)
         # run cubicweb/componants migration scripts
         mih.migrate(vcconf, reversed(toupgrade), self.config)
@@ -680,7 +679,7 @@
         mih.shutdown()
         print
         print '-> instance migrated.'
-        if not (cwcfg.mode == 'dev' or self.config.nostartstop):
+        if not (CWDEV or self.config.nostartstop):
             StartInstanceCommand().start_instance(appid)
         print
 
--- a/cwvreg.py	Tue Oct 13 18:21:24 2009 +0200
+++ b/cwvreg.py	Thu Oct 15 10:31:54 2009 +0200
@@ -344,7 +344,7 @@
     def register_objects(self, path, force_reload=None):
         """overriden to remove objects requiring a missing interface"""
         if force_reload is None:
-            force_reload = self.config.mode == 'dev'
+            force_reload = self.config.debugmode
         try:
             self._register_objects(path, force_reload)
         except RegistryOutOfDate:
--- a/devtools/devctl.py	Tue Oct 13 18:21:24 2009 +0200
+++ b/devtools/devctl.py	Thu Oct 15 10:31:54 2009 +0200
@@ -492,8 +492,6 @@
         if len(args) != 1:
             raise BadCommandUsage("exactly one argument (cube name) is expected")
         cubename, = args
-        #if ServerConfiguration.mode != "dev":
-        #    self.fail("you can only create new cubes in development mode")
         verbose = self.get('verbose')
         cubesdir = self.get('directory')
         if not cubesdir:
--- a/devtools/fill.py	Tue Oct 13 18:21:24 2009 +0200
+++ b/devtools/fill.py	Thu Oct 15 10:31:54 2009 +0200
@@ -404,12 +404,14 @@
             for subjeid, objeid in used:
                 if subjcard in '?1' and subjeid in subjeids:
                     subjeids.remove(subjeid)
-                    if objeid in objeids:
-                        objeids.remove(objeid)
+                    # XXX why?
+                    #if objeid in objeids:
+                    #    objeids.remove(objeid)
                 if objcard in '?1' and objeid in objeids:
                     objeids.remove(objeid)
-                    if subjeid in subjeids:
-                        subjeids.remove(subjeid)
+                    # XXX why?
+                    #if subjeid in subjeids:
+                    #    subjeids.remove(subjeid)
         if not subjeids:
             check_card_satisfied(objcard, objeids, subj, rschema, obj)
             return
--- a/doc/book/en/development/testing/index.rst	Tue Oct 13 18:21:24 2009 +0200
+++ b/doc/book/en/development/testing/index.rst	Thu Oct 15 10:31:54 2009 +0200
@@ -56,7 +56,7 @@
     # the default admin connection and one may be tempted to close it
     self.restore_connection()
 
-Take care of the references kept to the entities created with a connection or the other.
+Do not use the references kept to the entities created with a connection from another.
 
 
 Email notifications tests
--- a/entities/wfobjs.py	Tue Oct 13 18:21:24 2009 +0200
+++ b/entities/wfobjs.py	Thu Oct 15 10:31:54 2009 +0200
@@ -417,17 +417,11 @@
     def cwetype_workflow(self):
         """return the default workflow for entities of this type"""
         # XXX CWEType method
-        wfrset = self._cw.execute('Any WF WHERE X is ET, X eid %(x)s, '
-                                  'WF workflow_of ET', {'x': self.eid}, 'x')
-        if len(wfrset) == 1:
+        wfrset = self._cw.execute('Any WF WHERE ET default_workflow WF, '
+                                  'ET name %(et)s', {'et': self.id})
+        if wfrset:
             return wfrset.get_entity(0, 0)
-        if len(wfrset) > 1:
-            for wf in wfrset.entities():
-                if wf.is_default_workflow_of(self.__regid__):
-                    return wf
-            self.warning("can't find default workflow for %s", self.__regid__)
-        else:
-            self.warning("can't find any workflow for %s", self.__regid__)
+        self.warning("can't find any workflow for %s", self.id)
         return None
 
     def possible_transitions(self, type='normal'):
--- a/entity.py	Tue Oct 13 18:21:24 2009 +0200
+++ b/entity.py	Thu Oct 15 10:31:54 2009 +0200
@@ -18,7 +18,7 @@
 from rql import parse
 from rql.utils import rqlvar_maker
 
-from cubicweb import Unauthorized
+from cubicweb import Unauthorized, typed_eid
 from cubicweb.rset import ResultSet
 from cubicweb.selectors import yes
 from cubicweb.appobject import AppObject
@@ -232,7 +232,7 @@
         meaning that the entity has to be created
         """
         try:
-            int(self.eid)
+            typed_eid(self.eid)
             return True
         except (ValueError, TypeError):
             return False
@@ -587,14 +587,18 @@
     def related_rql(self, rtype, role='subject', targettypes=None):
         rschema = self._cw.vreg.schema[rtype]
         if role == 'subject':
+            restriction = 'E eid %%(x)s, E %s X' % rtype
             if targettypes is None:
                 targettypes = rschema.objects(self.e_schema)
-            restriction = 'E eid %%(x)s, E %s X' % rtype
+            else:
+                restriction += 'E is IN (%s)' % ','.join(targettypes)
             card = greater_card(rschema, (self.e_schema,), targettypes, 0)
         else:
+            restriction = 'E eid %%(x)s, X %s E' % rtype
             if targettypes is None:
                 targettypes = rschema.subjects(self.e_schema)
-            restriction = 'E eid %%(x)s, X %s E' % rtype
+            else:
+                restriction += 'E is IN (%s)' % ','.join(targettypes)
             card = greater_card(rschema, targettypes, (self.e_schema,), 1)
         if len(targettypes) > 1:
             fetchattrs_list = []
@@ -741,9 +745,14 @@
             self._related_cache.pop('%s_%s' % (rtype, role), None)
 
     def clear_all_caches(self):
+        haseid = 'eid' in self
         self.clear()
         for rschema, _, role in self.e_schema.relation_definitions():
             self.clear_related_cache(rschema.type, role)
+        # set eid if it was in, else we may get nasty error while editing this
+        # entity if it's bound to a repo session
+        if haseid:
+            self['eid'] = self.eid
 
     # raw edition utilities ###################################################
 
@@ -763,6 +772,20 @@
             self._cw.execute('SET %s WHERE X eid %%(x)s' % ','.join(relations),
                              kwargs, 'x')
 
+    def set_relations(self, _cw_unsafe=False, **kwargs):
+        if _cw_unsafe:
+            execute = self.req.unsafe_execute
+        else:
+            execute = self.req.execute
+        for attr, values in kwargs.iteritems():
+            if attr.startswith('reverse_'):
+                restr = 'Y %s X' % attr[len('reverse_'):]
+            else:
+                restr = 'X %s Y' % attr
+            execute('SET %s WHERE X eid %%(x)s, Y eid IN (%s)' % (
+                restr, ','.join(str(r.eid) for r in values)),
+                    {'x': self.eid}, 'x')
+
     def delete(self):
         assert self.has_eid(), self.eid
         self._cw.execute('DELETE %s X WHERE X eid %%(x)s' % self.e_schema,
--- a/etwist/server.py	Tue Oct 13 18:21:24 2009 +0200
+++ b/etwist/server.py	Thu Oct 15 10:31:54 2009 +0200
@@ -180,8 +180,8 @@
 
     def render(self, request):
         """Render a page from the root resource"""
-        # reload modified files (only in development or debug mode)
-        if self.config.mode == 'dev' or self.debugmode:
+        # reload modified files in debug mode
+        if self.debugmode:
             self.appli.vreg.register_objects(self.config.vregistry_path())
         if self.config['profile']: # default profiler don't trace threads
             return self.render_request(request)
--- a/etwist/twconfig.py	Tue Oct 13 18:21:24 2009 +0200
+++ b/etwist/twconfig.py	Thu Oct 15 10:31:54 2009 +0200
@@ -47,7 +47,7 @@
           'default': None,
           'help': 'if this option is set, use the specified user to start \
 the repository rather than the user running the command',
-          'group': 'main', 'inputlevel': (WebConfiguration.mode == 'installed') and 0 or 1,
+          'group': 'main', 'inputlevel': WebConfiguration.mode == 'system'
           }),
         ('session-time',
          {'type' : 'int',
--- a/hooks/workflow.py	Tue Oct 13 18:21:24 2009 +0200
+++ b/hooks/workflow.py	Thu Oct 15 10:31:54 2009 +0200
@@ -106,6 +106,34 @@
             outputs.add(ep.subwf_state.eid)
 
 
+class _SubWorkflowExitOp(PreCommitOperation):
+
+    def precommit_event(self):
+        session = self.session
+        forentity = self.forentity
+        trinfo = self.trinfo
+        # we're in a subworkflow, check if we've reached an exit point
+        wftr = forentity.subworkflow_input_transition()
+        if wftr is None:
+            # inconsistency detected
+            msg = session._("state doesn't belong to entity's current workflow")
+            raise ValidationError(self.trinfo.eid, {'to_state': msg})
+        tostate = wftr.get_exit_point(forentity, trinfo['to_state'])
+        if tostate is not None:
+            # reached an exit point
+            msg = session._('exiting from subworkflow %s')
+            msg %= session._(forentity.current_workflow.name)
+            session.transaction_data[(forentity.eid, 'subwfentrytr')] = True
+            # XXX iirk
+            req = forentity.req
+            forentity.req = session.super_session
+            try:
+                trinfo = forentity.change_state(tostate, msg, u'text/plain',
+                                                tr=wftr)
+            finally:
+                forentity.req = req
+
+
 # hooks ########################################################################
 
 class WorkflowHook(hook.Hook):
@@ -229,33 +257,13 @@
     events = ('after_add_entity',)
 
     def __call__(self):
-        session = self._cw
-        entity = self.entity
-        _change_state(session, entity['wf_info_for'],
-                      entity['from_state'], entity['to_state'])
-        forentity = session.entity_from_eid(entity['wf_info_for'])
-        assert forentity.current_state.eid == entity['to_state']
+        trinfo = self.entity
+        _change_state(self._cw, trinfo['wf_info_for'],
+                      trinfo['from_state'], trinfo['to_state'])
+        forentity = self._cw.entity_from_eid(trinfo['wf_info_for'])
+        assert forentity.current_state.eid == trinfo['to_state']
         if forentity.main_workflow.eid != forentity.current_workflow.eid:
-            # we're in a subworkflow, check if we've reached an exit point
-            wftr = forentity.subworkflow_input_transition()
-            if wftr is None:
-                # inconsistency detected
-                msg = session._("state doesn't belong to entity's current workflow")
-                raise ValidationError(entity.eid, {'to_state': msg})
-            tostate = wftr.get_exit_point(forentity, entity['to_state'])
-            if tostate is not None:
-                # reached an exit point
-                msg = session._('exiting from subworkflow %s')
-                msg %= session._(forentity.current_workflow.name)
-                session.transaction_data[(forentity.eid, 'subwfentrytr')] = True
-                # XXX iirk
-                req = forentity._cw
-                forentity._cw = session.super_session
-                try:
-                    trinfo = forentity.change_state(tostate, msg, u'text/plain',
-                                                    tr=wftr)
-                finally:
-                    forentity._cw = req
+            _SubWorkflowExitOp(self._cw, forentity=forentity, trinfo=trinfo)
 
 
 class CheckInStateChangeAllowed(WorkflowHook):
--- a/interfaces.py	Tue Oct 13 18:21:24 2009 +0200
+++ b/interfaces.py	Thu Oct 15 10:31:54 2009 +0200
@@ -127,7 +127,7 @@
     def children_rql(self):
         """XXX returns RQL to get children"""
 
-    def __iter__(self):
+    def iterchildren(self):
         """iterates over the item's children"""
 
     def is_leaf(self):
--- a/misc/migration/3.5.3_Any.py	Tue Oct 13 18:21:24 2009 +0200
+++ b/misc/migration/3.5.3_Any.py	Thu Oct 15 10:31:54 2009 +0200
@@ -1,7 +1,7 @@
 # type attribute might already be there if migrating from
 # version < 3.5 to version >= 3.5.3, BaseTransition being added
 # in bootstrap_migration
-if versions_map['cubicweb'][0] < (3, 5, 0):
+if versions_map['cubicweb'][0] >= (3, 5, 0):
     add_attribute('BaseTransition', 'type')
     sync_schema_props_perms('state_of')
     sync_schema_props_perms('transition_of')
--- a/req.py	Tue Oct 13 18:21:24 2009 +0200
+++ b/req.py	Thu Oct 15 10:31:54 2009 +0200
@@ -108,31 +108,55 @@
 
     # XXX move to CWEntityManager or even better as factory method (unclear
     # where yet...)
-    def create_entity(self, etype, *args, **kwargs):
+    def create_entity(self, etype, **kwargs):
         """add a new entity of the given type
 
         Example (in a shell session):
 
-        c = create_entity('Company', name='Logilab')
-        create_entity('Person', ('works_for', 'Y'), Y=c.eid, firstname='John', lastname='Doe')
+        c = create_entity('Company', name=u'Logilab')
+        create_entity('Person', works_for=c, firstname=u'John', lastname=u'Doe')
+
         """
         rql = 'INSERT %s X' % etype
         relations = []
-        restrictions = []
+        restrictions = set()
         cachekey = []
-        for rtype, rvar in args:
-            relations.append('X %s %s' % (rtype, rvar))
-            restrictions.append('%s eid %%(%s)s' % (rvar, rvar))
-            cachekey.append(rvar)
-        for attr in kwargs:
-            if attr in cachekey:
-                continue
-            relations.append('X %s %%(%s)s' % (attr, attr))
+        pending_relations = []
+        for attr, value in kwargs.iteritems():
+            if isinstance(value, (tuple, list, set, frozenset)):
+                if len(value) == 1:
+                    value = iter(value).next()
+                else:
+                    pending_relations.append( (attr, value) )
+                    continue
+            if hasattr(value, 'eid'): # non final relation
+                rvar = attr.upper()
+                # XXX safer detection of object relation
+                if attr.startswith('reverse_'):
+                    relations.append('%s %s X' % (rvar, attr[len('reverse_'):]))
+                else:
+                    relations.append('X %s %s' % (attr, rvar))
+                restriction = '%s eid %%(%s)s' % (rvar, attr)
+                if not restriction in restrictions:
+                    restrictions.add(restriction)
+                cachekey.append(attr)
+                kwargs[attr] = value.eid
+            else: # attribute
+                relations.append('X %s %%(%s)s' % (attr, attr))
         if relations:
             rql = '%s: %s' % (rql, ', '.join(relations))
         if restrictions:
             rql = '%s WHERE %s' % (rql, ', '.join(restrictions))
-        return self.execute(rql, kwargs, cachekey).get_entity(0, 0)
+        created = self.execute(rql, kwargs, cachekey).get_entity(0, 0)
+        for attr, values in pending_relations:
+            if attr.startswith('reverse_'):
+                restr = 'Y %s X' % attr[len('reverse_'):]
+            else:
+                restr = 'X %s Y' % attr
+            self.execute('SET %s WHERE X eid %%(x)s, Y eid IN (%s)' % (
+                restr, ','.join(str(r.eid) for r in values)),
+                         {'x': created.eid}, 'x')
+        return created
 
     def ensure_ro_rql(self, rql):
         """raise an exception if the given rql is not a select query"""
--- a/selectors.py	Tue Oct 13 18:21:24 2009 +0200
+++ b/selectors.py	Thu Oct 15 10:31:54 2009 +0200
@@ -43,7 +43,7 @@
 __docformat__ = "restructuredtext en"
 
 import logging
-from warnings import warn
+from warnings import warn, filterwarnings
 
 from logilab.common.compat import all
 from logilab.common.interface import implements as implements_iface
@@ -64,7 +64,7 @@
 
 def lltrace(selector):
     # don't wrap selectors if not in development mode
-    if CubicWebConfiguration.mode == 'installed':
+    if CubicWebConfiguration.mode == 'system': # XXX config.debug
         return selector
     def traced(cls, *args, **kwargs):
         # /!\ lltrace decorates pure function or __call__ method, this
--- a/server/serverconfig.py	Tue Oct 13 18:21:24 2009 +0200
+++ b/server/serverconfig.py	Thu Oct 15 10:31:54 2009 +0200
@@ -15,7 +15,7 @@
 from logilab.common.decorators import wproperty, cached, clear_cache
 
 from cubicweb import CW_SOFTWARE_ROOT, RegistryNotFound
-from cubicweb.toolsutils import env_path, read_config, restrict_perms_to_user
+from cubicweb.toolsutils import read_config, restrict_perms_to_user
 from cubicweb.cwconfig import CubicWebConfiguration, merge_options
 from cubicweb.server import SOURCE_TYPES
 
@@ -75,12 +75,6 @@
 class ServerConfiguration(CubicWebConfiguration):
     """standalone RQL server"""
     name = 'repository'
-    if os.environ.get('APYCOT_ROOT'):
-        root = os.environ['APYCOT_ROOT']
-    elif CubicWebConfiguration.mode == 'dev':
-        BACKUP_DIR = CubicWebConfiguration.RUNTIME_DIR
-    else:
-        BACKUP_DIR = '/var/lib/cubicweb/backup/'
 
     cubicweb_appobject_path = CubicWebConfiguration.cubicweb_appobject_path | set(['sobjects', 'hooks'])
     cube_appobject_path = CubicWebConfiguration.cube_appobject_path | set(['sobjects', 'hooks'])
--- a/server/sources/__init__.py	Tue Oct 13 18:21:24 2009 +0200
+++ b/server/sources/__init__.py	Thu Oct 15 10:31:54 2009 +0200
@@ -191,6 +191,9 @@
         * if this source doesn't support the relation, can be crossed unless
           explicitly specified in .dont_cross_relations
         """
+        # XXX find a way to have relation such as state_of in dont cross
+        #     relation (eg composite relation without both end type available?
+        #     card 1 relation ? ...)
         if self.support_relation(rtype):
             return rtype in self.cross_relations
         return rtype not in self.dont_cross_relations
--- a/server/test/unittest_msplanner.py	Tue Oct 13 18:21:24 2009 +0200
+++ b/server/test/unittest_msplanner.py	Thu Oct 15 10:31:54 2009 +0200
@@ -37,7 +37,7 @@
     support_entities = {'Card': True, 'Note': True, 'State': True}
     support_relations = {'in_state': True, 'multisource_rel': True, 'multisource_inlined_rel': True,
                          'multisource_crossed_rel': True}
-    dont_cross_relations = set(('fiche', 'in_state'))
+    dont_cross_relations = set(('fiche', 'state_of'))
     cross_relations = set(('multisource_crossed_rel',))
 
     def syntax_tree_search(self, *args, **kwargs):
@@ -1977,6 +1977,14 @@
                      None, None, [self.system], {}, [])],
                    {'x': 999999, 'u': 999998})
 
+    def test_state_of_cross(self):
+        self._test('DELETE State X WHERE NOT X state_of Y',
+                   [('DeleteEntitiesStep',
+                     [('OneFetchStep',
+                       [('Any X WHERE NOT X state_of Y, X is State, Y is Workflow',
+                         [{'X': 'State', 'Y': 'Workflow'}])],
+                       None, None, [self.system], {}, [])])]
+                   )
 
 class MSPlannerTwoSameExternalSourcesTC(BasePlannerTC):
     """test planner related feature on a 3-sources repository:
--- a/server/test/unittest_rql2sql.py	Tue Oct 13 18:21:24 2009 +0200
+++ b/server/test/unittest_rql2sql.py	Thu Oct 15 10:31:54 2009 +0200
@@ -567,6 +567,15 @@
 FROM cw_EmailAddress AS O
 WHERE NOT EXISTS(SELECT 1 FROM use_email_relation AS rel_use_email0 WHERE rel_use_email0.eid_from=1 AND rel_use_email0.eid_to=O.cw_eid) AND EXISTS(SELECT 1 FROM use_email_relation AS rel_use_email1 WHERE rel_use_email1.eid_to=O.cw_eid AND EXISTS(SELECT 1 FROM cw_CWGroup AS D WHERE rel_use_email1.eid_from=2 AND NOT EXISTS(SELECT 1 FROM in_group_relation AS rel_in_group2 WHERE rel_in_group2.eid_from=2 AND rel_in_group2.eid_to=D.cw_eid) AND D.cw_name=guests))
 ORDER BY 4 DESC'''),
+
+
+    ("Any X WHERE X eid 0, X eid 0",
+     '''SELECT 0'''),
+
+    ("Any X WHERE X eid 0, X eid 0, X test TRUE",
+     '''SELECT X.cw_eid
+FROM cw_Personne AS X
+WHERE X.cw_eid=0 AND X.cw_eid=0 AND X.cw_test='''),
     ]
 
 MULTIPLE_SEL = [
--- a/test/unittest_entity.py	Tue Oct 13 18:21:24 2009 +0200
+++ b/test/unittest_entity.py	Thu Oct 15 10:31:54 2009 +0200
@@ -185,7 +185,19 @@
         Personne.fetch_attrs, Personne.fetch_order = fetch_config(('nom', ))
         # XXX
         self.assertEquals(p.related_rql('evaluee'),
-                          'Any X,AA ORDERBY Z DESC WHERE X modification_date Z, E eid %(x)s, E evaluee X, X modification_date AA')
+                          'Any X,AA ORDERBY Z DESC '
+                          'WHERE X modification_date Z, E eid %(x)s, E evaluee X, '
+                          'X modification_date AA')
+
+        tag = self.vreg['etypes'].etype_class('Tag')(self.request())
+        self.assertEquals(tag.related_rql('tags', 'subject'),
+                          'Any X,AA ORDERBY Z DESC '
+                          'WHERE X modification_date Z, E eid %(x)s, E tags X, '
+                          'X modification_date AA')
+        self.assertEquals(tag.related_rql('tags', 'subject', ('Personne',)),
+                          'Any X,AA,AB ORDERBY AA ASC '
+                          'WHERE E eid %(x)s, E tags XE is IN (Personne), X nom AA, '
+                          'X modification_date AB')
 
     def test_unrelated_rql_security_1(self):
         user = self.request().user
@@ -460,6 +472,21 @@
         self.assertEquals(card.absolute_url(),
                           'http://testing.fr/cubicweb/card/eid/%s' % card.eid)
 
+    def test_create_entity(self):
+        p1 = self.add_entity('Personne', nom=u'fayolle', prenom=u'alexandre')
+        p2 = self.add_entity('Personne', nom=u'campeas', prenom=u'aurelien')
+        note = self.add_entity('Note', type=u'z')
+        req = self.request()
+        p = req.create_entity('Personne', nom=u'di mascio', prenom=u'adrien',
+                              connait=p1, evaluee=[p1, p2],
+                              reverse_ecrit_par=note)
+        self.assertEquals(p.nom, 'di mascio')
+        self.assertEquals([c.nom for c in p.connait], ['fayolle'])
+        self.assertEquals(sorted([c.nom for c in p.evaluee]), ['campeas', 'fayolle'])
+        self.assertEquals([c.type for c in p.reverse_ecrit_par], ['z'])
+
+
+
 if __name__ == '__main__':
     from logilab.common.testlib import unittest_main
     unittest_main()
--- a/vregistry.py	Tue Oct 13 18:21:24 2009 +0200
+++ b/vregistry.py	Thu Oct 15 10:31:54 2009 +0200
@@ -202,7 +202,7 @@
                                      % (args, kwargs.keys(),
                                         [repr(v) for v in appobjects]))
         if len(winners) > 1:
-            if self.config.mode == 'installed':
+            if self.config.debugmode:
                 self.error('select ambiguity, args: %s\nkwargs: %s %s',
                            args, kwargs.keys(), [repr(v) for v in winners])
             else:
--- a/web/views/editcontroller.py	Tue Oct 13 18:21:24 2009 +0200
+++ b/web/views/editcontroller.py	Thu Oct 15 10:31:54 2009 +0200
@@ -157,7 +157,7 @@
             todelete = self._cw.list_form_param('__delete', formparams, pop=True)
             self.delete_relations(parse_relations_descr(todelete))
         if formparams.has_key('__cloned_eid'):
-            entity.copy_relations(formparams['__cloned_eid'])
+            entity.copy_relations(typed_eid(formparams['__cloned_eid']))
         if formparams.has_key('__insert'):
             toinsert = self._cw.list_form_param('__insert', formparams, pop=True)
             self.insert_relations(parse_relations_descr(toinsert))
--- a/web/views/editforms.py	Tue Oct 13 18:21:24 2009 +0200
+++ b/web/views/editforms.py	Thu Oct 15 10:31:54 2009 +0200
@@ -177,6 +177,7 @@
             form = self._build_form(
                 entity, rtype, role, default, onsubmit, reload)
             if not self.should_edit_attribute(entity, rschema, role, form):
+                self.w(entity.printable_value(rtype))
                 return
             value = entity.printable_value(rtype) or default
             self.attribute_form(lzone, value, form,
@@ -184,13 +185,15 @@
         else:
             if rvid is None:
                 rvid = self._compute_best_vid(entity.e_schema, rschema, role)
-            if not self.should_edit_relation(entity, rschema, role, rvid):
-                return
             rset = entity.related(rtype, role)
             if rset:
                 value = self._cw.view(rvid, rset)
             else:
                 value = default
+            if not self.should_edit_relation(entity, rschema, role, rvid):
+                if rset:
+                    self.w(value)
+                return
             onsubmit = ("return inlineValidateRelationForm('%(rtype)s', '%(role)s', '%(eid)s', "
                         "'%(divid)s', %(reload)s, '%(vid)s', '%(default)s', '%(lzone)s');")
             form = self._build_form(
--- a/web/webconfig.py	Tue Oct 13 18:21:24 2009 +0200
+++ b/web/webconfig.py	Thu Oct 15 10:31:54 2009 +0200
@@ -172,7 +172,7 @@
 
         ('print-traceback',
          {'type' : 'yn',
-          'default': not CubicWebConfiguration.mode == 'installed',
+          'default': CubicWebConfiguration.mode != 'system',
           'help': 'print the traceback on the error page when an error occured',
           'group': 'web', 'inputlevel': 2,
           }),