# HG changeset patch # User Sylvain Thénault # Date 1261474069 -3600 # Node ID c79135c217df33ed72a76f46642257c6a0d0a432 # Parent cfd5d3270f991fbaf90da8dbef6180fa8a8c335e# Parent 7484546271766cbc3dc1c35acaab95e6b65e75dc backport stable diff -r cfd5d3270f99 -r c79135c217df bin/cubicweb-ctl.bat --- 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 = """ diff -r cfd5d3270f99 -r c79135c217df cwconfig.py --- 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): diff -r cfd5d3270f99 -r c79135c217df cwctl.py --- 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 diff -r cfd5d3270f99 -r c79135c217df doc/book/en/admin/setup.rst --- 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 ``````````````````````` diff -r cfd5d3270f99 -r c79135c217df etwist/service.py --- /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() + diff -r cfd5d3270f99 -r c79135c217df ext/rest.py --- 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) diff -r cfd5d3270f99 -r c79135c217df hooks/integrity.py --- 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): diff -r cfd5d3270f99 -r c79135c217df hooks/syncschema.py --- 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: diff -r cfd5d3270f99 -r c79135c217df hooks/workflow.py --- 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") diff -r cfd5d3270f99 -r c79135c217df schema.py --- 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 diff -r cfd5d3270f99 -r c79135c217df schemas/_regproc.postgres.sql --- 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); diff -r cfd5d3270f99 -r c79135c217df selectors.py --- 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): diff -r cfd5d3270f99 -r c79135c217df server/sources/native.py --- 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 diff -r cfd5d3270f99 -r c79135c217df server/sources/rql2sql.py diff -r cfd5d3270f99 -r c79135c217df test/unittest_schema.py --- 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() diff -r cfd5d3270f99 -r c79135c217df view.py --- 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): diff -r cfd5d3270f99 -r c79135c217df web/formfields.py --- 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): diff -r cfd5d3270f99 -r c79135c217df web/views/formrenderers.py --- 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'') w(u'' % eid) w(u'') - w(u'%s' % _('add relation')) w(u'