backport stable
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Tue, 22 Dec 2009 10:27:49 +0100
changeset 4181 c79135c217df
parent 4173 cfd5d3270f99 (current diff)
parent 4148 748454627176 (diff)
child 4182 b7cf0ebdd8b6
backport stable
cwconfig.py
cwctl.py
ext/rest.py
hooks/integrity.py
hooks/syncschema.py
hooks/workflow.py
schema.py
selectors.py
server/sources/native.py
test/unittest_schema.py
view.py
web/formfields.py
web/views/formrenderers.py
web/views/forms.py
--- a/bin/cubicweb-ctl.bat	Tue Dec 22 09:27:28 2009 +0100
+++ b/bin/cubicweb-ctl.bat	Tue Dec 22 10:27:49 2009 +0100
@@ -7,8 +7,10 @@
  
 """
 # -------------------- Python section --------------------
+import sys
+from os.path import join, dirname
+sys.path.insert(0, join(dirname(__file__), '..', '..'))
 from cubicweb.cwctl import run
-import sys
 run(sys.argv[1:])
 
 DosExitLabel = """
--- a/cwconfig.py	Tue Dec 22 09:27:28 2009 +0100
+++ b/cwconfig.py	Tue Dec 22 10:27:49 2009 +0100
@@ -477,7 +477,7 @@
                 try:
                     load_module_from_file(join(CW_SOFTWARE_ROOT, ctlfile))
                 except ImportError, err:
-                    cls.critical('could not import the command provider %s (cause : %s)' %
+                    cls.info('could not import the command provider %s (cause : %s)' %
                                 (ctlfile, err))
                 cls.info('loaded cubicweb-ctl plugin %s', ctlfile)
         for cube in cls.available_cubes():
@@ -571,6 +571,15 @@
         self.adjust_sys_path()
         self.load_defaults()
         self.translations = {}
+        # don't register ReStructured Text directives by simple import, avoid pb
+        # with eg sphinx.
+        # XXX should be done properly with a function from cw.uicfg
+        try:
+            from cubicweb.ext.rest import cw_rest_init
+        except ImportError:
+            pass
+        else:
+            cw_rest_init()
 
     def adjust_sys_path(self):
         self.cls_adjust_sys_path()
@@ -867,12 +876,12 @@
                     self.warning('site_erudi.py is deprecated, should be renamed to site_cubicweb.py')
 
     def _load_site_cubicweb(self, sitefile):
-        context = {'__file__': sitefile}
-        execfile(sitefile, context, context)
+        from logilab.common.modutils import load_module_from_file
+        module = load_module_from_file(sitefile)
         self.info('%s loaded', sitefile)
         # cube specific options
-        if context.get('options'):
-            self.register_options(context['options'])
+        if getattr(module, 'options', None):
+            self.register_options(module.options)
             self.load_defaults()
 
     def load_configuration(self):
--- a/cwctl.py	Tue Dec 22 09:27:28 2009 +0100
+++ b/cwctl.py	Tue Dec 22 10:27:49 2009 +0100
@@ -12,7 +12,7 @@
     def kill(*args): pass
     def getpgid(): pass
 
-from os.path import exists, join, isfile, isdir
+from os.path import exists, join, isfile, isdir, dirname, abspath
 
 from logilab.common.clcommands import register_commands, pop_arg
 from logilab.common.shellutils import ASK
@@ -307,6 +307,7 @@
         helper.bootstrap(cubes, self.config.config_level)
         # write down configuration
         config.save()
+        self._handle_win32(config, appid)
         print '-> generated %s' % config.main_config_file()
         # handle i18n files structure
         # in the first cube given
@@ -332,6 +333,29 @@
         print '\n-> creation done for %r.\n' % config.apphome
         helper.postcreate()
 
+    def _handle_win32(self, config, appid):
+        if sys.platform != 'win32':
+            return
+        service_template = """
+import sys
+import win32serviceutil
+sys.path.insert(0, r"%(CWPATH)s")
+
+from cubicweb.etwist.service import CWService
+
+classdict = {'_svc_name_': 'cubicweb-%(APPID)s',
+             '_svc_display_name_': 'CubicWeb ' + '%(CNAME)s',
+             'instance': '%(APPID)s'}
+%(CNAME)sService = type('%(CNAME)sService', (CWService,), classdict)
+
+if __name__ == '__main__':
+    win32serviceutil.HandleCommandLine(%(CNAME)sService)
+"""
+        open(join(config.apphome, 'win32svc.py'), 'wb').write(
+            service_template % {'APPID': appid,
+                                'CNAME': appid.capitalize(),
+                                'CWPATH': abspath(join(dirname(__file__), '..'))})
+
 
 class DeleteInstanceCommand(Command):
     """Delete an instance. Will remove instance's files and
--- a/doc/book/en/admin/setup.rst	Tue Dec 22 09:27:28 2009 +0100
+++ b/doc/book/en/admin/setup.rst	Tue Dec 22 10:27:49 2009 +0100
@@ -226,6 +226,23 @@
 
 ... and get a meaningful output.
 
+Running an instance as a service
+--------------------------------
+
+This currently assumes that the instances configurations is located
+at C:\etc\cubicweb.d.
+
+For a cube 'my_cube', you will then find C:\etc\cubicweb.d\my_cube\win32svc.py
+that has to be used thusly::
+
+  win32svc install
+
+This should just register your instance as a windows service. A simple::
+
+  net start cubicweb-my_cube
+
+should start the service.
+
 
 PostgreSQL installation
 ```````````````````````
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/etwist/service.py	Tue Dec 22 10:27:49 2009 +0100
@@ -0,0 +1,63 @@
+import os
+import os.path as osp
+import sys
+
+import win32serviceutil
+import win32service
+import win32event
+
+from cubicweb.etwist.server import (CubicWebRootResource, reactor, server,
+                                    parsePOSTData, channel)
+
+from logging import getLogger, handlers
+from cubicweb import set_log_methods
+from cubicweb.cwconfig import CubicWebConfiguration as cwcfg
+
+logger = getLogger('cubicweb.twisted')
+logger.handlers = [handlers.NTEventLogHandler('cubicweb')]
+
+os.environ['CW_INSTANCES_DIR'] = r'C:\etc\cubicweb.d'
+os.environ['USERNAME'] = 'cubicweb'
+
+
+class CWService(object, win32serviceutil.ServiceFramework):
+    _svc_name_ = None
+    _svc_display_name_ = None
+    instance = None
+
+    def __init__(self, *args, **kwargs):
+        win32serviceutil.ServiceFramework.__init__(self, *args, **kwargs)
+        self._stop_event = win32event.CreateEvent(None, 0, 0, None)
+        cwcfg.load_cwctl_plugins()
+        set_log_methods(CubicWebRootResource, logger)
+        server.parsePOSTData = parsePOSTData
+
+    def SvcStop(self):
+        self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
+        logger.info('stopping %s service' % self.instance)
+        win32event.SetEvent(self._stop_event)
+        self.ReportServiceStatus(win32service.SERVICE_STOPPED)
+
+    def SvcDoRun(self):
+        self.ReportServiceStatus(win32service.SERVICE_START_PENDING)
+        logger = getLogger('cubicweb.twisted')
+        logger.info('starting %s service' % self.instance)
+        try:
+            # create the site
+            config = cwcfg.config_for(self.instance)
+            root_resource = CubicWebRootResource(config, False)
+            website = server.Site(root_resource)
+            # serve it via standard HTTP on port set in the configuration
+            port = config['port'] or 8080
+            logger.info('listening on port %s' % port)
+            reactor.listenTCP(port, channel.HTTPFactory(website))
+            root_resource.init_publisher()
+            root_resource.start_service()
+            logger.info('instance started on %s', root_resource.base_url)
+            self.ReportServiceStatus(win32service.SERVICE_RUNNING)
+            reactor.run()
+        except Exception, e:
+            logger.error('service %s stopped (cause: %s)' % (self.instance, e))
+            logger.exception('what happened ...')
+            self.SvcStop()
+
--- a/ext/rest.py	Tue Dec 22 09:27:28 2009 +0100
+++ b/ext/rest.py	Tue Dec 22 10:27:49 2009 +0100
@@ -80,8 +80,6 @@
     return [nodes.reference(rawtext, utils.unescape(rest), refuri=ref,
                             **options)], []
 
-register_canonical_role('eid', eid_reference_role)
-
 
 def winclude_directive(name, arguments, options, content, lineno,
                        content_offset, block_text, state, state_machine):
@@ -146,14 +144,13 @@
 winclude_directive.arguments = (1, 0, 1)
 winclude_directive.options = {'literal': directives.flag,
                               'encoding': directives.encoding}
-directives.register_directive('winclude', winclude_directive)
 
 try:
     from pygments import highlight
     from pygments.lexers import get_lexer_by_name, LEXERS
     from pygments.formatters import HtmlFormatter
 except ImportError:
-    pass
+    pygments_directive = None
 else:
     _PYGMENTS_FORMATTER = HtmlFormatter()
 
@@ -174,7 +171,6 @@
 
     pygments_directive.arguments = (1, 0, 1)
     pygments_directive.content = 1
-    directives.register_directive('sourcecode', pygments_directive)
 
 
 class CubicWebReSTParser(Parser):
@@ -244,3 +240,15 @@
             data = unicode(data, encoding, 'replace')
         return xml_escape(req._('error while publishing ReST text')
                            + '\n\n' + data)
+
+
+_INITIALIZED = False
+def cw_rest_init():
+    global _INITIALIZED
+    if _INITIALIZED:
+        return
+    _INITIALIZED = True
+    register_canonical_role('eid', eid_reference_role)
+    directives.register_directive('winclude', winclude_directive)
+    if pygments_directive is not None:
+        directives.register_directive('sourcecode', pygments_directive)
--- a/hooks/integrity.py	Tue Dec 22 09:27:28 2009 +0100
+++ b/hooks/integrity.py	Tue Dec 22 10:27:49 2009 +0100
@@ -164,15 +164,14 @@
     events = ('after_add_entity', 'after_update_entity')
 
     def __call__(self):
-        schema = self._cw.vreg.schema
-        entity = self.entity
-        for attr in entity.edited_attributes:
-            if schema.rschema(attr).final:
-                constraints = [c for c in entity.e_schema.rdef(attr).constraints
+        eschema = entity.eschema
+        for attr in self.entity.edited_attributes:
+            if eschema.subjrels[attr].final:
+                constraints = [c for c in eschema.rdef(attr).constraints
                                if isinstance(c, (RQLUniqueConstraint, RQLConstraint))]
                 if constraints:
                     _CheckConstraintsOp(self._cw, constraints=constraints,
-                                        rdef=(entity.eid, attr, None))
+                                        rdef=(self.entity.eid, attr, None))
 
 
 class CheckUniqueHook(UserIntegrityHook):
--- a/hooks/syncschema.py	Tue Dec 22 09:27:28 2009 +0100
+++ b/hooks/syncschema.py	Tue Dec 22 10:27:49 2009 +0100
@@ -939,7 +939,6 @@
     if errors:
         raise ValidationError(entity.eid, errors)
 
-# relation_type hooks ##########################################################
 
 class AfterDelRelationTypeHook(SyncSchemaHook):
     """before deleting a CWAttribute or CWRelation entity:
--- a/hooks/workflow.py	Tue Dec 22 09:27:28 2009 +0100
+++ b/hooks/workflow.py	Tue Dec 22 10:27:49 2009 +0100
@@ -224,8 +224,8 @@
                     msg = session._("transition doesn't belong to entity's workflow")
                     raise ValidationError(entity.eid, {'by_transition': msg})
                 if not tr.has_input_state(fromstate):
-                    msg = session._("transition %s isn't allowed from %s") % (
-                        _(tr.name), _(fromstate.name))
+                    msg = session._("transition %(tr)s isn't allowed from %(st)s") % {
+                        'tr': session._(tr.name), 'st': session._(fromstate.name)}
                     raise ValidationError(entity.eid, {'by_transition': msg})
                 if not tr.may_be_fired(foreid):
                     msg = session._("transition may not be fired")
--- a/schema.py	Tue Dec 22 09:27:28 2009 +0100
+++ b/schema.py	Tue Dec 22 10:27:49 2009 +0100
@@ -138,8 +138,9 @@
 
 def split_expression(rqlstring):
     for expr in rqlstring.split(','):
-        for word in expr.split():
-            yield word
+        for noparen in expr.split('('):
+            for word in noparen.split():
+                yield word
 
 def normalize_expression(rqlstring):
     """normalize an rql expression to ease schema synchronization (avoid
--- a/schemas/_regproc.postgres.sql	Tue Dec 22 09:27:28 2009 +0100
+++ b/schemas/_regproc.postgres.sql	Tue Dec 22 10:27:49 2009 +0100
@@ -5,10 +5,12 @@
 
 */
 
+DROP FUNCTION IF EXISTS comma_join (anyarray) CASCADE;
 CREATE FUNCTION comma_join (anyarray) RETURNS text AS $$
     SELECT array_to_string($1, ', ')
 $$ LANGUAGE SQL;;
 
+DROP AGGREGATE IF EXISTS group_concat (anyelement) CASCADE;
 CREATE AGGREGATE group_concat (
   basetype = anyelement,
   sfunc = array_append,
@@ -19,6 +21,7 @@
 
 
 
+DROP FUNCTION IF EXISTS limit_size (fulltext text, format text, maxsize integer);
 CREATE FUNCTION limit_size (fulltext text, format text, maxsize integer) RETURNS text AS $$
 DECLARE
     plaintext text;
@@ -39,7 +42,7 @@
 END
 $$ LANGUAGE plpgsql;;
 
-
+DROP FUNCTION IF EXISTS text_limit_size (fulltext text, maxsize integer);
 CREATE FUNCTION text_limit_size (fulltext text, maxsize integer) RETURNS text AS $$
 BEGIN
     RETURN limit_size(fulltext, 'text/plain', maxsize);
--- a/selectors.py	Tue Dec 22 09:27:28 2009 +0100
+++ b/selectors.py	Tue Dec 22 10:27:49 2009 +0100
@@ -628,7 +628,12 @@
                     req.form['etype'] = etype
                 except KeyError:
                     return 0
-        return self.score_class(req.vreg['etypes'].etype_class(etype), req)
+        score = self.score_class(req.vreg['etypes'].etype_class(etype), req)
+        if score:
+            eschema = req.vreg.schema.eschema(etype)
+            if eschema.has_local_role('add') or eschema.has_perm(req, 'add'):
+                return score
+        return 0
 
 
 class relation_possible(EClassSelector):
--- a/server/sources/native.py	Tue Dec 22 09:27:28 2009 +0100
+++ b/server/sources/native.py	Tue Dec 22 10:27:49 2009 +0100
@@ -579,6 +579,7 @@
 
 def sql_schema(driver):
     helper = get_adv_func_helper(driver)
+    tstamp_col_type = helper.TYPE_MAPPING.get('TIMESTAMP', 'TIMESTAMP')
     schema = """
 /* Create the repository's system database */
 
@@ -588,7 +589,7 @@
   eid INTEGER PRIMARY KEY NOT NULL,
   type VARCHAR(64) NOT NULL,
   source VARCHAR(64) NOT NULL,
-  mtime TIMESTAMP NOT NULL,
+  mtime %s NOT NULL,
   extid VARCHAR(256)
 );
 CREATE INDEX entities_type_idx ON entities(type);
@@ -599,13 +600,13 @@
   eid INTEGER PRIMARY KEY NOT NULL,
   type VARCHAR(64) NOT NULL,
   source VARCHAR(64) NOT NULL,
-  dtime TIMESTAMP NOT NULL,
+  dtime %s NOT NULL,
   extid VARCHAR(256)
 );
 CREATE INDEX deleted_entities_type_idx ON deleted_entities(type);
 CREATE INDEX deleted_entities_dtime_idx ON deleted_entities(dtime);
 CREATE INDEX deleted_entities_extid_idx ON deleted_entities(extid);
-""" % helper.sql_create_sequence('entities_id_seq')
+""" % (helper.sql_create_sequence('entities_id_seq'), tstamp_col_type, tstamp_col_type)
     return schema
 
 
--- a/test/unittest_schema.py	Tue Dec 22 09:27:28 2009 +0100
+++ b/test/unittest_schema.py	Tue Dec 22 10:27:49 2009 +0100
@@ -22,7 +22,7 @@
     CubicWebSchema, CubicWebEntitySchema, CubicWebSchemaLoader,
     RQLConstraint, RQLUniqueConstraint, RQLVocabularyConstraint,
     RQLExpression, ERQLExpression, RRQLExpression,
-    normalize_expression, order_eschemas)
+    normalize_expression, order_eschemas, guess_rrqlexpr_mainvars)
 from cubicweb.devtools import TestServerConfiguration as TestConfiguration
 
 DATADIR = join(dirname(__file__), 'data')
@@ -278,5 +278,12 @@
     def test_comparison(self):
         self.assertEquals(ERQLExpression('X is CWUser', 'X', 0), ERQLExpression('X is CWUser', 'X', 0))
         self.assertNotEquals(ERQLExpression('X is CWUser', 'X', 0), ERQLExpression('X is CWGroup', 'X', 0))
+
+class GuessRrqlExprMainVarsTC(TestCase):
+    def test_exists(self):
+        mainvars = guess_rrqlexpr_mainvars(normalize_expression('NOT EXISTS(O team_competition C, C level < 3)'))
+        self.assertEquals(mainvars, 'O')
+
+
 if __name__ == '__main__':
     unittest_main()
--- a/view.py	Tue Dec 22 09:27:28 2009 +0100
+++ b/view.py	Tue Dec 22 10:27:49 2009 +0100
@@ -471,7 +471,7 @@
     def build_update_js_call(self, cbname, msg):
         rql = self.cw_rset.printable_rql()
         return "javascript:userCallbackThenUpdateUI('%s', '%s', %s, %s, '%s', '%s')" % (
-            cbname, self.__regid__, dumps(rql), dumps(msg),
+            cbname, self.id, dumps(rql), dumps(msg),
             self.__registry__, self.div_id())
 
     def build_reload_js_call(self, cbname, msg):
--- a/web/formfields.py	Tue Dec 22 09:27:28 2009 +0100
+++ b/web/formfields.py	Tue Dec 22 10:27:49 2009 +0100
@@ -618,7 +618,7 @@
 
     def vocabulary(self, form):
         if self.choices:
-            return self.choices
+            return super(BooleanField, self).vocabulary(form)
         return [(form._cw._('yes'), '1'), (form._cw._('no'), '')]
 
     def process_form_value(self, form):
--- a/web/views/formrenderers.py	Tue Dec 22 09:27:28 2009 +0100
+++ b/web/views/formrenderers.py	Tue Dec 22 10:27:49 2009 +0100
@@ -441,7 +441,6 @@
                 w(u'</tr>')
         w(u'<tr id="relationSelectorRow_%s" class="separator">' % eid)
         w(u'<th class="labelCol">')
-        w(u'<span>%s</span>' % _('add relation'))
         w(u'<select id="relationSelector_%s" tabindex="%s" '
           'onchange="javascript:showMatchingSelect(this.options[this.selectedIndex].value,%s);">'
           % (eid, req.next_tabindex(), xml_escape(dumps(eid))))
--- a/web/views/forms.py	Tue Dec 22 09:27:28 2009 +0100
+++ b/web/views/forms.py	Tue Dec 22 10:27:49 2009 +0100
@@ -223,14 +223,13 @@
         """return the key that may be used to store / retreive data about a
         previous post which failed because of a validation error
         """
-        try:
+        if self.force_session_key is not None:
             return self.force_session_key
-        except AttributeError:
-            # XXX if this is a json request, suppose we should redirect to the
-            # entity primary view
-            if self.req.json_request:
-                return '%s#%s' % (self.edited_entity.absolute_url(), self.domid)
-            return '%s#%s' % (self.req.url(), self.domid)
+        # XXX if this is a json request, suppose we should redirect to the
+        # entity primary view
+        if self.req.json_request and self.edited_entity.has_eid():
+            return '%s#%s' % (self.edited_entity.absolute_url(), self.domid)
+        return '%s#%s' % (self.req.url(), self.domid)
 
     def build_context(self, formvalues=None):
         super(EntityFieldsForm, self).build_context(formvalues)