--- 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)