# HG changeset patch # User Sylvain Thénault # Date 1249639970 -7200 # Node ID 03e7a6efd960b1cf82345b3de4c6bfa919a4a409 # Parent e39982748753371131ff8d467485741c71e23fae# Parent 6d0acd812d986f2dd11a1f3c3a438ce4e4f8caf3 backport stable branch diff -r 6d0acd812d98 -r 03e7a6efd960 README --- a/README Fri Aug 07 12:12:20 2009 +0200 +++ b/README Fri Aug 07 12:12:50 2009 +0200 @@ -1,18 +1,27 @@ -CubicWeb semantic web framework +CubicWeb semantic web framework =============================== - + Install ------- -From the source distribution, extract the tarball and run :: - - python setup.py install - -For deb and rpm packages, use the tools recommended by your distribution. + +More details at http://www.cubicweb.org/doc/en/admin/setup + +Getting started +--------------- + +Execute: - + apt-get install cubicweb cubicweb-dev cubicweb-blog + cubicweb-ctl create blog myblog + cubicweb-ctl start -D myblog + sensible-browser http://localhost:8080/ + +Details at http://www.cubicweb.org/doc/en/intro/tutorial/blog-in-five-minutes + Documentation ------------- -Look in the doc/ subdirectory. + +Look in the doc/ subdirectory or read http://www.cubicweb.org/doc/en/ diff -r 6d0acd812d98 -r 03e7a6efd960 __init__.py --- a/__init__.py Fri Aug 07 12:12:20 2009 +0200 +++ b/__init__.py Fri Aug 07 12:12:50 2009 +0200 @@ -100,20 +100,39 @@ [(etype,)]) return self.decorate_rset(rset) + def empty_rset(self): + """return a result set for the given eid without doing actual query + (we have the eid, we can suppose it exists and user has access to the + entity) + """ + from cubicweb.rset import ResultSet + return self.decorate_rset(ResultSet([], 'Any X WHERE X eid -1')) + def entity_from_eid(self, eid, etype=None): - rset = self.eid_rset(eid, etype) - if rset: - return rset.get_entity(0, 0) - else: - return None + try: + return self.entity_cache(eid) + except KeyError: + rset = self.eid_rset(eid, etype) + entity = rset.get_entity(0, 0) + self.set_entity_cache(entity) + return entity + def entity_cache(self, eid): + raise KeyError + def set_entity_cache(self, entity): + pass # url generation methods ################################################## - def build_url(self, method, base_url=None, **kwargs): + def build_url(self, *args, **kwargs): """return an absolute URL using params dictionary key/values as URL parameters. Values are automatically URL quoted, and the publishing method to use may be specified or will be guessed. """ + # use *args since we don't want first argument to be "anonymous" to + # avoid potential clash with kwargs + assert len(args) == 1, 'only 0 or 1 non-named-argument expected' + method = args[0] + base_url = kwargs.pop('base_url', None) if base_url is None: base_url = self.base_url() if '_restpath' in kwargs: @@ -193,7 +212,7 @@ # abstract methods to override according to the web front-end ############# def base_url(self): - """return the root url of the application""" + """return the root url of the instance""" raise NotImplementedError def decorate_rset(self, rset): @@ -298,3 +317,49 @@ def underline_title(title, car='-'): return title+'\n'+(car*len(title)) + +class CubicWebEventManager(object): + """simple event / callback manager. + + Typical usage to register a callback:: + + >>> from cubicweb import CW_EVENT_MANAGER + >>> CW_EVENT_MANAGER.bind('after-registry-reload', mycallback) + + Typical usage to emit an event:: + + >>> from cubicweb import CW_EVENT_MANAGER + >>> CW_EVENT_MANAGER.emit('after-registry-reload') + + emit() accepts an additional context parameter that will be passed + to the callback if specified (and only in that case) + """ + def __init__(self): + self.callbacks = {} + + def bind(self, event, callback, *args, **kwargs): + self.callbacks.setdefault(event, []).append( (callback, args, kwargs) ) + + def emit(self, event, context=None): + for callback, args, kwargs in self.callbacks.get(event, ()): + if context is None: + callback(*args, **kwargs) + else: + callback(context, *args, **kwargs) + +CW_EVENT_MANAGER = CubicWebEventManager() + +def onevent(event): + """decorator to ease event / callback binding + + >>> from cubicweb import onevent + >>> @onevent('before-registry-reload') + ... def mycallback(): + ... print 'hello' + ... + >>> + """ + def _decorator(func): + CW_EVENT_MANAGER.bind(event, func) + return func + return _decorator diff -r 6d0acd812d98 -r 03e7a6efd960 __pkginfo__.py --- a/__pkginfo__.py Fri Aug 07 12:12:20 2009 +0200 +++ b/__pkginfo__.py Fri Aug 07 12:12:50 2009 +0200 @@ -7,7 +7,7 @@ distname = "cubicweb" modname = "cubicweb" -numversion = (3, 3, 5) +numversion = (3, 4, 0) version = '.'.join(str(num) for num in numversion) license = 'LGPL v2' diff -r 6d0acd812d98 -r 03e7a6efd960 _exceptions.py --- a/_exceptions.py Fri Aug 07 12:12:20 2009 +0200 +++ b/_exceptions.py Fri Aug 07 12:12:50 2009 +0200 @@ -129,6 +129,9 @@ class UnknownProperty(RegistryException): """property found in database but unknown in registry""" +class RegistryOutOfDate(RegistryException): + """raised when a source file modification is detected""" + # query exception ############################################################# class QueryError(CubicWebRuntimeError): diff -r 6d0acd812d98 -r 03e7a6efd960 appobject.py --- a/appobject.py Fri Aug 07 12:12:20 2009 +0200 +++ b/appobject.py Fri Aug 07 12:12:50 2009 +0200 @@ -1,4 +1,6 @@ -"""Base class for dynamically loaded objects manipulated in the web interface +"""Base class for dynamically loaded objects accessible through the vregistry. + +You'll also find some convenience classes to build selectors. :organization: Logilab :copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. @@ -7,20 +9,23 @@ """ __docformat__ = "restructuredtext en" +import types +from logging import getLogger from datetime import datetime, timedelta, time from logilab.common.decorators import classproperty -from logilab.common.deprecation import obsolete +from logilab.common.deprecation import deprecated +from logilab.common.logging_ext import set_log_methods from rql.nodes import VariableRef, SubQuery from rql.stmts import Union, Select from cubicweb import Unauthorized, NoSelectableObject -from cubicweb.vregistry import VObject, AndSelector -from cubicweb.selectors import yes from cubicweb.utils import UStringIO, ustrftime, strptime, todate, todatetime ONESECOND = timedelta(0, 1, 0) +CACHE_REGISTRY = {} + class Cache(dict): def __init__(self): @@ -29,37 +34,267 @@ self.cache_creation_date = _now self.latest_cache_lookup = _now -CACHE_REGISTRY = {} + +# selector base classes and operations ######################################## + +def objectify_selector(selector_func): + """convenience decorator for simple selectors where a class definition + would be overkill:: + + @objectify_selector + def yes(cls, *args, **kwargs): + return 1 + + """ + return type(selector_func.__name__, (Selector,), + {'__call__': lambda self, *args, **kwargs: selector_func(*args, **kwargs)}) + + +def _instantiate_selector(selector): + """ensures `selector` is a `Selector` instance + + NOTE: This should only be used locally in build___select__() + XXX: then, why not do it ?? + """ + if isinstance(selector, types.FunctionType): + return objectify_selector(selector)() + if isinstance(selector, type) and issubclass(selector, Selector): + return selector() + return selector + + +class Selector(object): + """base class for selector classes providing implementation + for operators ``&`` and ``|`` + + This class is only here to give access to binary operators, the + selector logic itself should be implemented in the __call__ method + + + a selector is called to help choosing the correct object for a + particular context by returning a score (`int`) telling how well + the class given as first argument apply to the given context. + + 0 score means that the class doesn't apply. + """ + + @property + def func_name(self): + # backward compatibility + return self.__class__.__name__ + + def search_selector(self, selector): + """search for the given selector or selector instance in the selectors + tree. Return it of None if not found + """ + if self is selector: + return self + if isinstance(selector, type) and isinstance(self, selector): + return self + return None + + def __str__(self): + return self.__class__.__name__ + + def __and__(self, other): + return AndSelector(self, other) + def __rand__(self, other): + return AndSelector(other, self) + + def __or__(self, other): + return OrSelector(self, other) + def __ror__(self, other): + return OrSelector(other, self) + + def __invert__(self): + return NotSelector(self) + + # XXX (function | function) or (function & function) not managed yet + + def __call__(self, cls, *args, **kwargs): + return NotImplementedError("selector %s must implement its logic " + "in its __call__ method" % self.__class__) + + +class MultiSelector(Selector): + """base class for compound selector classes""" + + def __init__(self, *selectors): + self.selectors = self.merge_selectors(selectors) + + def __str__(self): + return '%s(%s)' % (self.__class__.__name__, + ','.join(str(s) for s in self.selectors)) + + @classmethod + def merge_selectors(cls, selectors): + """deal with selector instanciation when necessary and merge + multi-selectors if possible: -class AppRsetObject(VObject): - """This is the base class for CubicWeb application objects - which are selected according to a request and result set. + AndSelector(AndSelector(sel1, sel2), AndSelector(sel3, sel4)) + ==> AndSelector(sel1, sel2, sel3, sel4) + """ + merged_selectors = [] + for selector in selectors: + try: + selector = _instantiate_selector(selector) + except: + pass + #assert isinstance(selector, Selector), selector + if isinstance(selector, cls): + merged_selectors += selector.selectors + else: + merged_selectors.append(selector) + return merged_selectors + + def search_selector(self, selector): + """search for the given selector or selector instance in the selectors + tree. Return it of None if not found + """ + for childselector in self.selectors: + if childselector is selector: + return childselector + found = childselector.search_selector(selector) + if found is not None: + return found + return None + + +class AndSelector(MultiSelector): + """and-chained selectors (formerly known as chainall)""" + def __call__(self, cls, *args, **kwargs): + score = 0 + for selector in self.selectors: + partscore = selector(cls, *args, **kwargs) + if not partscore: + return 0 + score += partscore + return score + - Classes are kept in the vregistry and instantiation is done at selection - time. +class OrSelector(MultiSelector): + """or-chained selectors (formerly known as chainfirst)""" + def __call__(self, cls, *args, **kwargs): + for selector in self.selectors: + partscore = selector(cls, *args, **kwargs) + if partscore: + return partscore + return 0 + +class NotSelector(Selector): + """negation selector""" + def __init__(self, selector): + self.selector = selector + + def __call__(self, cls, *args, **kwargs): + score = self.selector(cls, *args, **kwargs) + return int(not score) + + def __str__(self): + return 'NOT(%s)' % super(NotSelector, self).__str__() + + +class yes(Selector): + """return arbitrary score + + default score of 0.5 so any other selector take precedence + """ + def __init__(self, score=0.5): + self.score = score + + def __call__(self, *args, **kwargs): + return self.score + + +# the base class for all appobjects ############################################ + +class AppObject(object): + """This is the base class for CubicWeb application objects which are + selected according to a context (usually at least a request and a result + set). + + Concrete application objects classes are designed to be loaded by the + vregistry and should be accessed through it, not by direct instantiation. + + The following attributes should be set on concret appobject classes: + :__registry__: + name of the registry for this object (string like 'views', + 'templates'...) + :id: + object's identifier in the registry (string like 'main', + 'primary', 'folder_box') + :__select__: + class'selector + + Moreover, the `__abstract__` attribute may be set to True to indicate + that a appobject is abstract and should not be registered. At registration time, the following attributes are set on the class: :vreg: - the application's registry + the instance's registry :schema: - the application's schema + the instance's schema :config: - the application's configuration + the instance's configuration - At instantiation time, the following attributes are set on the instance: + At selection time, the following attributes are set on the instance: :req: current request :rset: - result set on which the object is applied + context result set or None + :row: + if a result set is set and the context is about a particular cell in the + result set, and not the result set as a whole, specify the row number we + are interested in, else None + :col: + if a result set is set and the context is about a particular cell in the + result set, and not the result set as a whole, specify the col number we + are interested in, else None """ + __registry__ = None + id = None __select__ = yes() @classmethod - def registered(cls, vreg): - super(AppRsetObject, cls).registered(vreg) - cls.vreg = vreg - cls.schema = vreg.schema - cls.config = vreg.config + def classid(cls): + """returns a unique identifier for the appobject""" + return '%s.%s' % (cls.__module__, cls.__name__) + + # XXX bw compat code + @classmethod + def build___select__(cls): + for klass in cls.mro(): + if klass.__name__ == 'AppObject': + continue # the bw compat __selector__ is there + klassdict = klass.__dict__ + if ('__select__' in klassdict and '__selectors__' in klassdict + and '__selgenerated__' not in klassdict): + raise TypeError("__select__ and __selectors__ can't be used together on class %s" % cls) + if '__selectors__' in klassdict and '__selgenerated__' not in klassdict: + cls.__selgenerated__ = True + # case where __selectors__ is defined locally (but __select__ + # is in a parent class) + selectors = klassdict['__selectors__'] + if len(selectors) == 1: + # micro optimization: don't bother with AndSelector if there's + # only one selector + select = _instantiate_selector(selectors[0]) + else: + select = AndSelector(*selectors) + cls.__select__ = select + + @classmethod + def registered(cls, registry): + """called by the registry when the appobject has been registered. + + It must return the object that will be actually registered (this may be + the right hook to create an instance for example). By default the + appobject is returned without any transformation. + """ + cls.build___select__() + cls.vreg = registry.vreg + cls.schema = registry.schema + cls.config = registry.config cls.register_properties() return cls @@ -67,15 +302,6 @@ def vreg_initialization_completed(cls): pass - @classmethod - def selected(cls, *args, **kwargs): - """by default web app objects are usually instantiated on - selection according to a request, a result set, and optional - row and col - """ - assert len(args) <= 2 - return cls(*args, **kwargs) - # Eproperties definition: # key: id of the property (the actual CWProperty key is build using # .. @@ -101,7 +327,7 @@ return '%s.%s.%s' % (cls.__registry__, cls.id, propid) @classproperty - @obsolete('use __select__ and & or | operators') + @deprecated('use __select__ and & or | operators') def __selectors__(cls): selector = cls.__select__ if isinstance(selector, AndSelector): @@ -111,7 +337,7 @@ return selector def __init__(self, req=None, rset=None, row=None, col=None, **extra): - super(AppRsetObject, self).__init__() + super(AppObject, self).__init__() self.req = req self.rset = rset self.row = row @@ -151,7 +377,8 @@ # try to get page boundaries from the navigation component # XXX we should probably not have a ref to this component here (eg in # cubicweb.common) - nav = self.vreg.select_component('navigation', self.req, self.rset) + nav = self.vreg['components'].select_object('navigation', self.req, + rset=self.rset) if nav: start, stop = nav.page_boundaries() rql = self._limit_offset_rql(stop - start, start) @@ -187,9 +414,11 @@ rqlst.parent = None return rql - def view(self, __vid, rset=None, __fallback_vid=None, **kwargs): + def view(self, __vid, rset=None, __fallback_oid=None, __registry='views', + **kwargs): """shortcut to self.vreg.view method avoiding to pass self.req""" - return self.vreg.view(__vid, self.req, rset, __fallback_vid, **kwargs) + return self.vreg[__registry].render(__vid, self.req, __fallback_oid, + rset=rset, **kwargs) def initialize_varmaker(self): varmaker = self.req.get_page_data('rql_varmaker') @@ -202,11 +431,18 @@ controller = 'view' - def build_url(self, method=None, **kwargs): + def build_url(self, *args, **kwargs): """return an absolute URL using params dictionary key/values as URL parameters. Values are automatically URL quoted, and the publishing method to use may be specified or will be guessed. """ + # use *args since we don't want first argument to be "anonymous" to + # avoid potential clash with kwargs + if args: + assert len(args) == 1, 'only 0 or 1 non-named-argument expected' + method = args[0] + else: + method = None # XXX I (adim) think that if method is passed explicitly, we should # not try to process it and directly call req.build_url() if method is None: @@ -267,7 +503,7 @@ return output.getvalue() def format_date(self, date, date_format=None, time=False): - """return a string for a date time according to application's + """return a string for a date time according to instance's configuration """ if date: @@ -280,7 +516,7 @@ return u'' def format_time(self, time): - """return a string for a time according to application's + """return a string for a time according to instance's configuration """ if time: @@ -288,7 +524,7 @@ return u'' def format_float(self, num): - """return a string for floating point number according to application's + """return a string for floating point number according to instance's configuration """ if num: @@ -332,22 +568,4 @@ if first in ('insert', 'set', 'delete'): raise Unauthorized(self.req._('only select queries are authorized')) - -class AppObject(AppRsetObject): - """base class for application objects which are not selected - according to a result set, only by their identifier. - - Those objects may not have req, rset and cursor set. - """ - - @classmethod - def selected(cls, *args, **kwargs): - """by default web app objects are usually instantiated on - selection - """ - return cls(*args, **kwargs) - - def __init__(self, req=None, rset=None, **kwargs): - self.req = req - self.rset = rset - self.__dict__.update(kwargs) +set_log_methods(AppObject, getLogger('cubicweb.appobject')) diff -r 6d0acd812d98 -r 03e7a6efd960 common/i18n.py --- a/common/i18n.py Fri Aug 07 12:12:20 2009 +0200 +++ b/common/i18n.py Fri Aug 07 12:12:50 2009 +0200 @@ -73,7 +73,7 @@ pofiles = [pof for pof in pofiles if exists(pof)] mergedpo = join(destdir, '%s_merged.po' % lang) try: - # merge application messages' catalog with the stdlib's one + # merge instance/cubes messages catalogs with the stdlib's one execute('msgcat --use-first --sort-output --strict %s > %s' % (' '.join(pofiles), mergedpo)) # make sure the .mo file is writeable and compile with *msgfmt* diff -r 6d0acd812d98 -r 03e7a6efd960 common/mail.py --- a/common/mail.py Fri Aug 07 12:12:20 2009 +0200 +++ b/common/mail.py Fri Aug 07 12:12:50 2009 +0200 @@ -18,7 +18,7 @@ def addrheader(uaddr, uname=None): # even if an email address should be ascii, encode it using utf8 since - # application tests may generate non ascii email address + # automatic tests may generate non ascii email address addr = uaddr.encode('UTF-8') if uname: return '%s <%s>' % (header(uname).encode(), addr) diff -r 6d0acd812d98 -r 03e7a6efd960 common/migration.py --- a/common/migration.py Fri Aug 07 12:12:20 2009 +0200 +++ b/common/migration.py Fri Aug 07 12:12:50 2009 +0200 @@ -1,5 +1,4 @@ -"""utility to ease migration of application version to newly installed -version +"""utilities for instances migration :organization: Logilab :copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. @@ -11,11 +10,12 @@ import sys import os import logging -from tempfile import mktemp +import tempfile from os.path import exists, join, basename, splitext from logilab.common.decorators import cached from logilab.common.configuration import REQUIRED, read_old_config +from logilab.common.shellutils import ASK from cubicweb import ConfigurationError @@ -70,11 +70,10 @@ ability to show the script's content """ while True: - confirm = raw_input('** execute %r (Y/n/s[how]) ?' % scriptpath) - confirm = confirm.strip().lower() - if confirm in ('n', 'no'): + answer = ASK.ask('Execute %r ?' % scriptpath, ('Y','n','show'), 'Y') + if answer == 'n': return False - elif confirm in ('s', 'show'): + elif answer == 'show': stream = open(scriptpath) scriptcontent = stream.read() stream.close() @@ -153,11 +152,15 @@ migrdir = self.config.cube_migration_scripts_dir(cube) scripts = filter_scripts(self.config, migrdir, fromversion, toversion) if scripts: + prevversion = None for version, script in scripts: + # take care to X.Y.Z_Any.py / X.Y.Z_common.py: we've to call + # cube_upgraded once all script of X.Y.Z have been executed + if prevversion is not None and version != prevversion: + self.cube_upgraded(cube, version) + prevversion = version self.process_script(script) - self.cube_upgraded(cube, version) - if version != toversion: - self.cube_upgraded(cube, toversion) + self.cube_upgraded(cube, toversion) else: self.cube_upgraded(cube, toversion) @@ -169,7 +172,7 @@ def interact(self, args, kwargs, meth): """execute the given method according to user's confirmation""" - msg = 'execute command: %s(%s) ?' % ( + msg = 'Execute command: %s(%s) ?' % ( meth.__name__[4:], ', '.join([repr(arg) for arg in args] + ['%s=%r' % (n,v) for n,v in kwargs.items()])) @@ -185,26 +188,24 @@ if `retry` is true the r[etry] answer may return 2 """ - print question, - possibleanswers = 'Y/n' + possibleanswers = ['Y','n'] if abort: - possibleanswers += '/a[bort]' + possibleanswers.append('abort') if shell: - possibleanswers += '/s[hell]' + possibleanswers.append('shell') if retry: - possibleanswers += '/r[etry]' + possibleanswers.append('retry') try: - confirm = raw_input('(%s): ' % ( possibleanswers, )) - answer = confirm.strip().lower() + answer = ASK.ask(question, possibleanswers, 'Y') except (EOFError, KeyboardInterrupt): answer = 'abort' - if answer in ('n', 'no'): + if answer == 'n': return False - if answer in ('r', 'retry'): + if answer == 'retry': return 2 - if answer in ('a', 'abort'): + if answer == 'abort': raise SystemExit(1) - if shell and answer in ('s', 'shell'): + if shell and answer == 'shell': self.interactive_shell() return self.confirm(question) return True @@ -337,7 +338,7 @@ configfile = self.config.main_config_file() if self._option_changes: read_old_config(self.config, self._option_changes, configfile) - newconfig = mktemp() + _, newconfig = tempfile.mkstemp() for optdescr in self._option_changes: if optdescr[0] == 'added': optdict = self.config.get_option_def(optdescr[1]) diff -r 6d0acd812d98 -r 03e7a6efd960 common/mixins.py --- a/common/mixins.py Fri Aug 07 12:12:20 2009 +0200 +++ b/common/mixins.py Fri Aug 07 12:12:50 2009 +0200 @@ -8,7 +8,7 @@ """ __docformat__ = "restructuredtext en" -from logilab.common.deprecation import obsolete +from logilab.common.deprecation import deprecated from logilab.common.decorators import cached from cubicweb import typed_eid @@ -155,7 +155,7 @@ def root(self): """return the root object""" - return self.req.eid_rset(self.path()[0]).get_entity(0, 0) + return self.req.entity_from_eid(self.path()[0]) class WorkflowableMixIn(object): @@ -242,9 +242,9 @@ # specific vocabulary methods ############################################# - @obsolete('use EntityFieldsForm.subject_in_state_vocabulary') + @deprecated('use EntityFieldsForm.subject_in_state_vocabulary') def subject_in_state_vocabulary(self, rschema, limit=None): - form = self.vreg.select_object('forms', 'edition', self.req, entity=self) + form = self.vreg.select('forms', 'edition', self.req, entity=self) return form.subject_in_state_vocabulary(rschema, limit) diff -r 6d0acd812d98 -r 03e7a6efd960 common/mttransforms.py --- a/common/mttransforms.py Fri Aug 07 12:12:20 2009 +0200 +++ b/common/mttransforms.py Fri Aug 07 12:12:50 2009 +0200 @@ -46,8 +46,8 @@ from cubicweb.ext.tal import compile_template except ImportError: HAS_TAL = False - from cubicweb.schema import FormatConstraint - FormatConstraint.need_perm_formats.remove('text/cubicweb-page-template') + from cubicweb import schema + schema.NEED_PERM_FORMATS.remove('text/cubicweb-page-template') else: HAS_TAL = True diff -r 6d0acd812d98 -r 03e7a6efd960 common/tags.py --- a/common/tags.py Fri Aug 07 12:12:20 2009 +0200 +++ b/common/tags.py Fri Aug 07 12:12:50 2009 +0200 @@ -7,7 +7,7 @@ """ __docformat__ = "restructuredtext en" -from cubicweb.common.uilib import simple_sgml_tag +from cubicweb.common.uilib import simple_sgml_tag, sgml_attributes class tag(object): def __init__(self, name, escapecontent=True): @@ -38,8 +38,7 @@ if id: attrs['id'] = id attrs['name'] = name - html = [u'' % sgml_attributes(attrs)] html += options html.append(u'') return u'\n'.join(html) diff -r 6d0acd812d98 -r 03e7a6efd960 common/test/unittest_migration.py --- a/common/test/unittest_migration.py Fri Aug 07 12:12:20 2009 +0200 +++ b/common/test/unittest_migration.py Fri Aug 07 12:12:50 2009 +0200 @@ -37,8 +37,8 @@ self.config = MigrTestConfig('data') from yams.schema import Schema self.config.load_schema = lambda expand_cubes=False: Schema('test') - self.config.__class__.cubicweb_vobject_path = frozenset() - self.config.__class__.cube_vobject_path = frozenset() + self.config.__class__.cubicweb_appobject_path = frozenset() + self.config.__class__.cube_appobject_path = frozenset() def test_filter_scripts_base(self): self.assertListEquals(filter_scripts(self.config, SMIGRDIR, (2,3,0), (2,4,0)), diff -r 6d0acd812d98 -r 03e7a6efd960 common/uilib.py --- a/common/uilib.py Fri Aug 07 12:12:20 2009 +0200 +++ b/common/uilib.py Fri Aug 07 12:12:50 2009 +0200 @@ -212,10 +212,15 @@ HTML4_EMPTY_TAGS = frozenset(('base', 'meta', 'link', 'hr', 'br', 'param', 'img', 'area', 'input', 'col')) +def sgml_attributes(attrs): + return u' '.join(u'%s="%s"' % (attr, xml_escape(unicode(value))) + for attr, value in sorted(attrs.items()) + if value is not None) + def simple_sgml_tag(tag, content=None, escapecontent=True, **attrs): """generation of a simple sgml tag (eg without children tags) easier - content and attributes will be escaped + content and attri butes will be escaped """ value = u'<%s' % tag if attrs: @@ -223,9 +228,7 @@ attrs['class'] = attrs.pop('klass') except KeyError: pass - value += u' ' + u' '.join(u'%s="%s"' % (attr, xml_escape(unicode(value))) - for attr, value in sorted(attrs.items()) - if value is not None) + value += u' ' + sgml_attributes(attrs) if content: if escapecontent: content = xml_escape(unicode(content)) diff -r 6d0acd812d98 -r 03e7a6efd960 cwconfig.py --- a/cwconfig.py Fri Aug 07 12:12:20 2009 +0200 +++ b/cwconfig.py Fri Aug 07 12:12:50 2009 +0200 @@ -22,6 +22,7 @@ from os.path import exists, join, expanduser, abspath, normpath, basename, isdir from logilab.common.decorators import cached +from logilab.common.deprecation import deprecated from logilab.common.logging_ext import set_log_methods, init_log from logilab.common.configuration import (Configuration, Method, ConfigurationMixIn, merge_options) @@ -141,7 +142,7 @@ name = None # log messages format (see logging module documentation for available keys) log_format = '%(asctime)s - (%(name)s) %(levelname)s: %(message)s' - # nor remove vobjects based on unused interface + # nor remove appobjects based on unused interface cleanup_interface_sobjects = True if os.environ.get('APYCOT_ROOT'): @@ -149,7 +150,7 @@ 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')): + elif exists(join(CW_SOFTWARE_ROOT, '.hg')) or os.environ.get('CW_MODE') == 'user': mode = 'dev' CUBES_DIR = abspath(normpath(join(CW_SOFTWARE_ROOT, '../cubes'))) else: @@ -168,14 +169,7 @@ {'type' : 'string', 'default': '', 'help': 'Pyro name server\'s host. If not set, will be detected by a \ -broadcast query', - 'group': 'pyro-name-server', 'inputlevel': 1, - }), - ('pyro-ns-port', - {'type' : 'int', - 'default': None, - 'help': 'Pyro name server\'s listening port. If not set, default \ -port will be used.', +broadcast query. It may contains port information using : notation.', 'group': 'pyro-name-server', 'inputlevel': 1, }), ('pyro-ns-group', @@ -221,7 +215,7 @@ 'group': 'appobjects', 'inputlevel': 2, }), ) - # static and class methods used to get application independant resources ## + # static and class methods used to get instance independant resources ## @staticmethod def cubicweb_version(): @@ -247,7 +241,7 @@ @classmethod def i18n_lib_dir(cls): - """return application's i18n directory""" + """return instance's i18n directory""" if cls.mode in ('dev', 'test') and not os.environ.get('APYCOT_ROOT'): return join(CW_SOFTWARE_ROOT, 'i18n') return join(cls.shared_dir(), 'i18n') @@ -418,24 +412,24 @@ except Exception, ex: cls.warning("can't init cube %s: %s", cube, ex) - cubicweb_vobject_path = set(['entities']) - cube_vobject_path = set(['entities']) + cubicweb_appobject_path = set(['entities']) + cube_appobject_path = set(['entities']) @classmethod def build_vregistry_path(cls, templpath, evobjpath=None, tvobjpath=None): """given a list of directories, return a list of sub files and - directories that should be loaded by the application objects registry. + directories that should be loaded by the instance objects registry. :param evobjpath: optional list of sub-directories (or files without the .py ext) of the cubicweb library that should be tested and added to the output list - if they exists. If not give, default to `cubicweb_vobject_path` class + if they exists. If not give, default to `cubicweb_appobject_path` class attribute. :param tvobjpath: optional list of sub-directories (or files without the .py ext) of directories given in `templpath` that should be tested and added to the output list if they exists. If not give, default to - `cube_vobject_path` class attribute. + `cube_appobject_path` class attribute. """ vregpath = cls.build_vregistry_cubicweb_path(evobjpath) vregpath += cls.build_vregistry_cube_path(templpath, tvobjpath) @@ -445,7 +439,7 @@ def build_vregistry_cubicweb_path(cls, evobjpath=None): vregpath = [] if evobjpath is None: - evobjpath = cls.cubicweb_vobject_path + evobjpath = cls.cubicweb_appobject_path for subdir in evobjpath: path = join(CW_SOFTWARE_ROOT, subdir) if exists(path): @@ -456,7 +450,7 @@ def build_vregistry_cube_path(cls, templpath, tvobjpath=None): vregpath = [] if tvobjpath is None: - tvobjpath = cls.cube_vobject_path + tvobjpath = cls.cube_appobject_path for directory in templpath: for subdir in tvobjpath: path = join(directory, subdir) @@ -539,8 +533,10 @@ # for some commands (creation...) we don't want to initialize gettext set_language = True - # set this to true to avoid false error message while creating an application + # set this to true to avoid false error message while creating an instance creating = False + # set this to true to allow somethings which would'nt be possible + repairing = False options = CubicWebNoAppConfiguration.options + ( ('log-file', @@ -564,7 +560,7 @@ }), ('sender-name', {'type' : 'string', - 'default': Method('default_application_id'), + 'default': Method('default_instance_id'), 'help': 'name used as HELO name for outgoing emails from the \ repository.', 'group': 'email', 'inputlevel': 2, @@ -581,49 +577,49 @@ @classmethod def runtime_dir(cls): """run time directory for pid file...""" - return env_path('CW_RUNTIME', cls.RUNTIME_DIR, 'run time') + return env_path('CW_RUNTIME_DIR', cls.RUNTIME_DIR, 'run time') @classmethod def registry_dir(cls): """return the control directory""" - return env_path('CW_REGISTRY', cls.REGISTRY_DIR, 'registry') + return env_path('CW_INSTANCES_DIR', cls.REGISTRY_DIR, 'registry') @classmethod def instance_data_dir(cls): """return the instance data directory""" - return env_path('CW_INSTANCE_DATA', + return env_path('CW_INSTANCES_DATA_DIR', cls.INSTANCE_DATA_DIR or cls.REGISTRY_DIR, 'additional data') @classmethod def migration_scripts_dir(cls): """cubicweb migration scripts directory""" - return env_path('CW_MIGRATION', cls.MIGRATION_DIR, 'migration') + return env_path('CW_MIGRATION_DIR', cls.MIGRATION_DIR, 'migration') @classmethod def config_for(cls, appid, config=None): - """return a configuration instance for the given application identifier + """return a configuration instance for the given instance identifier """ - config = config or guess_configuration(cls.application_home(appid)) + config = config or guess_configuration(cls.instance_home(appid)) configcls = configuration_cls(config) return configcls(appid) @classmethod def possible_configurations(cls, appid): """return the name of possible configurations for the given - application id + instance id """ - home = cls.application_home(appid) + home = cls.instance_home(appid) return possible_configurations(home) @classmethod - def application_home(cls, appid): - """return the home directory of the application with the given - application id + def instance_home(cls, appid): + """return the home directory of the instance with the given + instance id """ home = join(cls.registry_dir(), appid) if not exists(home): - raise ConfigurationError('no such application %s (check it exists with "cubicweb-ctl list")' % appid) + raise ConfigurationError('no such instance %s (check it exists with "cubicweb-ctl list")' % appid) return home MODES = ('common', 'repository', 'Any', 'web') @@ -637,14 +633,14 @@ # default configuration methods ########################################### - def default_application_id(self): - """return the application identifier, useful for option which need this + def default_instance_id(self): + """return the instance identifier, useful for option which need this as default value """ return self.appid def default_log_file(self): - """return default path to the log file of the application'server""" + """return default path to the log file of the instance'server""" if self.mode == 'dev': basepath = '/tmp/%s-%s' % (basename(self.appid), self.name) path = basepath + '.log' @@ -660,10 +656,10 @@ return '/var/log/cubicweb/%s-%s.log' % (self.appid, self.name) def default_pid_file(self): - """return default path to the pid file of the application'server""" + """return default path to the pid file of the instance'server""" return join(self.runtime_dir(), '%s-%s.pid' % (self.appid, self.name)) - # instance methods used to get application specific resources ############# + # instance methods used to get instance specific resources ############# def __init__(self, appid): self.appid = appid @@ -722,7 +718,7 @@ self._cubes = self.reorder_cubes(list(self._cubes) + cubes) def main_config_file(self): - """return application's control configuration file""" + """return instance's control configuration file""" return join(self.apphome, '%s.conf' % self.name) def save(self): @@ -739,7 +735,7 @@ return md5.new(';'.join(infos)).hexdigest() def load_site_cubicweb(self): - """load (web?) application's specific site_cubicweb file""" + """load instance's specific site_cubicweb file""" for path in reversed([self.apphome] + self.cubes_path()): sitefile = join(path, 'site_cubicweb.py') if exists(sitefile) and not sitefile in self._site_loaded: @@ -762,7 +758,7 @@ self.load_defaults() def load_configuration(self): - """load application's configuration files""" + """load instance's configuration files""" super(CubicWebConfiguration, self).load_configuration() if self.apphome and self.set_language: # init gettext @@ -774,14 +770,14 @@ return self._logging_initialized = True CubicWebNoAppConfiguration.init_log(self, logthreshold, debug, - logfile=self.get('log-file')) + logfile=self.get('log-file')) # read a config file if it exists logconfig = join(self.apphome, 'logging.conf') if exists(logconfig): logging.fileConfig(logconfig) def available_languages(self, *args): - """return available translation for an application, by looking for + """return available translation for an instance, by looking for compiled catalog take *args to be usable as a vocabulary method @@ -861,6 +857,6 @@ set_log_methods(CubicWebConfiguration, logging.getLogger('cubicweb.configuration')) -# alias to get a configuration instance from an application id -application_configuration = CubicWebConfiguration.config_for - +# alias to get a configuration instance from an instance id +instance_configuration = CubicWebConfiguration.config_for +application_configuration = deprecated('use instance_configuration')(instance_configuration) diff -r 6d0acd812d98 -r 03e7a6efd960 cwctl.py --- a/cwctl.py Fri Aug 07 12:12:20 2009 +0200 +++ b/cwctl.py Fri Aug 07 12:12:50 2009 +0200 @@ -1,6 +1,6 @@ """%%prog %s [options] %s -CubicWeb main applications controller. +CubicWeb main instances controller. :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses %s""" @@ -9,10 +9,11 @@ from os.path import exists, join, isfile, isdir from logilab.common.clcommands import register_commands, pop_arg +from logilab.common.shellutils import ASK from cubicweb import ConfigurationError, ExecutionError, BadCommandUsage, underline_title from cubicweb.cwconfig import CubicWebConfiguration as cwcfg, CONFIGURATIONS -from cubicweb.toolsutils import Command, main_run, rm, create_dir, confirm +from cubicweb.toolsutils import Command, main_run, rm, create_dir def wait_process_end(pid, maxtry=10, waittime=1): """wait for a process to actually die""" @@ -45,11 +46,11 @@ return modes -class ApplicationCommand(Command): - """base class for command taking 0 to n application id as arguments - (0 meaning all registered applications) +class InstanceCommand(Command): + """base class for command taking 0 to n instance id as arguments + (0 meaning all registered instances) """ - arguments = '[...]' + arguments = '[...]' options = ( ("force", {'short': 'f', 'action' : 'store_true', @@ -84,7 +85,7 @@ return allinstances def run(self, args): - """run the _method on each argument (a list of application + """run the _method on each argument (a list of instance identifiers) """ if not args: @@ -102,29 +103,29 @@ for appid in args: if askconfirm: print '*'*72 - if not confirm('%s application %r ?' % (self.name, appid)): + if not confirm('%s instance %r ?' % (self.name, appid)): continue self.run_arg(appid) def run_arg(self, appid): - cmdmeth = getattr(self, '%s_application' % self.name) + cmdmeth = getattr(self, '%s_instance' % self.name) try: cmdmeth(appid) except (KeyboardInterrupt, SystemExit): print >> sys.stderr, '%s aborted' % self.name sys.exit(2) # specific error code except (ExecutionError, ConfigurationError), ex: - print >> sys.stderr, 'application %s not %s: %s' % ( + print >> sys.stderr, 'instance %s not %s: %s' % ( appid, self.actionverb, ex) except Exception, ex: import traceback traceback.print_exc() - print >> sys.stderr, 'application %s not %s: %s' % ( + print >> sys.stderr, 'instance %s not %s: %s' % ( appid, self.actionverb, ex) -class ApplicationCommandFork(ApplicationCommand): - """Same as `ApplicationCommand`, but command is forked in a new environment +class InstanceCommandFork(InstanceCommand): + """Same as `InstanceCommand`, but command is forked in a new environment for each argument """ @@ -136,7 +137,7 @@ for appid in args: if askconfirm: print '*'*72 - if not confirm('%s application %r ?' % (self.name, appid)): + if not confirm('%s instance %r ?' % (self.name, appid)): continue if forkcmd: status = system('%s %s' % (forkcmd, appid)) @@ -148,10 +149,9 @@ # base commands ############################################################### class ListCommand(Command): - """List configurations, componants and applications. + """List configurations, cubes and instances. - list available configurations, installed web and server componants, and - registered applications + list available configurations, installed cubes, and registered instances """ name = 'list' options = ( @@ -206,30 +206,30 @@ try: regdir = cwcfg.registry_dir() except ConfigurationError, ex: - print 'No application available:', ex + print 'No instance available:', ex print return instances = list_instances(regdir) if instances: - print 'Available applications (%s):' % regdir + print 'Available instances (%s):' % regdir for appid in instances: modes = cwcfg.possible_configurations(appid) if not modes: - print '* %s (BROKEN application, no configuration found)' % appid + print '* %s (BROKEN instance, no configuration found)' % appid continue print '* %s (%s)' % (appid, ', '.join(modes)) try: config = cwcfg.config_for(appid, modes[0]) except Exception, exc: - print ' (BROKEN application, %s)' % exc + print ' (BROKEN instance, %s)' % exc continue else: - print 'No application available in %s' % regdir + print 'No instance available in %s' % regdir print -class CreateApplicationCommand(Command): - """Create an application from a cube. This is an unified +class CreateInstanceCommand(Command): + """Create an instance from a cube. This is an unified command which can handle web / server / all-in-one installation according to available parts of the software library and of the desired cube. @@ -238,11 +238,11 @@ the name of cube to use (list available cube names using the "list" command). You can use several cubes by separating them using comma (e.g. 'jpl,eemail') - - an identifier for the application to create + + an identifier for the instance to create """ name = 'create' - arguments = ' ' + arguments = ' ' options = ( ("config-level", {'short': 'l', 'type' : 'int', 'metavar': '', @@ -255,7 +255,7 @@ {'short': 'c', 'type' : 'choice', 'metavar': '', 'choices': ('all-in-one', 'repository', 'twisted'), 'default': 'all-in-one', - 'help': 'installation type, telling which part of an application \ + 'help': 'installation type, telling which part of an instance \ should be installed. You can list available configurations using the "list" \ command. Default to "all-in-one", e.g. an installation embedding both the RQL \ repository and the web server.', @@ -265,9 +265,9 @@ def run(self, args): """run the command with its specific arguments""" - from logilab.common.textutils import get_csv + from logilab.common.textutils import splitstrip configname = self.config.config - cubes = get_csv(pop_arg(args, 1)) + cubes = splitstrip(pop_arg(args, 1)) appid = pop_arg(args) # get the configuration and helper cwcfg.creating = True @@ -285,13 +285,13 @@ print '\navailable cubes:', print ', '.join(cwcfg.available_cubes()) return - # create the registry directory for this application - print '\n'+underline_title('Creating the application %s' % appid) + # create the registry directory for this instance + print '\n'+underline_title('Creating the instance %s' % appid) create_dir(config.apphome) # load site_cubicweb from the cubes dir (if any) config.load_site_cubicweb() # cubicweb-ctl configuration - print '\n'+underline_title('Configuring the application (%s.conf)' % configname) + print '\n'+underline_title('Configuring the instance (%s.conf)' % configname) config.input_config('main', self.config.config_level) # configuration'specific stuff print @@ -311,9 +311,10 @@ 'continue anyway ?'): print 'creation not completed' return - # create the additional data directory for this application + # create the additional data directory for this instance if config.appdatahome != config.apphome: # true in dev mode create_dir(config.appdatahome) + create_dir(join(config.appdatahome, 'backup')) if config['uid']: from logilab.common.shellutils import chown # this directory should be owned by the uid of the server process @@ -323,18 +324,18 @@ helper.postcreate() -class DeleteApplicationCommand(Command): - """Delete an application. Will remove application's files and +class DeleteInstanceCommand(Command): + """Delete an instance. Will remove instance's files and unregister it. """ name = 'delete' - arguments = '' + arguments = '' options = () def run(self, args): """run the command with its specific arguments""" - appid = pop_arg(args, msg="No application specified !") + appid = pop_arg(args, msg="No instance specified !") configs = [cwcfg.config_for(appid, configname) for configname in cwcfg.possible_configurations(appid)] if not configs: @@ -353,16 +354,16 @@ if ex.errno != errno.ENOENT: raise confignames = ', '.join([config.name for config in configs]) - print 'application %s (%s) deleted' % (appid, confignames) + print '-> instance %s (%s) deleted.' % (appid, confignames) -# application commands ######################################################## +# instance commands ######################################################## -class StartApplicationCommand(ApplicationCommand): - """Start the given applications. If no application is given, start them all. +class StartInstanceCommand(InstanceCommand): + """Start the given instances. If no instance is given, start them all. - ... - identifiers of the applications to start. If no application is + ... + identifiers of the instances to start. If no instance is given, start them all. """ name = 'start' @@ -374,22 +375,32 @@ ("force", {'short': 'f', 'action' : 'store_true', 'default': False, - 'help': 'start the application even if it seems to be already \ + 'help': 'start the instance even if it seems to be already \ running.'}), ('profile', {'short': 'P', 'type' : 'string', 'metavar': '', 'default': None, 'help': 'profile code and use the specified file to store stats', }), + ('loglevel', + {'short': 'l', 'type' : 'choice', 'metavar': '', + 'default': None, 'choices': ('debug', 'info', 'warning', 'error'), + 'help': 'debug if -D is set, error otherwise', + }), ) - def start_application(self, appid): - """start the application's server""" + def start_instance(self, appid): + """start the instance's server""" # use get() since start may be used from other commands (eg upgrade) # without all options defined debug = self.get('debug') force = self.get('force') + loglevel = self.get('loglevel') config = cwcfg.config_for(appid) + if loglevel is not None: + loglevel = 'LOG_%s' % loglevel.upper() + config.global_set_option('log-threshold', loglevel) + config.init_log(loglevel, debug=debug, force=True) if self.get('profile'): config.global_set_option('profile', self.config.profile) helper = self.config_helper(config, cmdname='start') @@ -398,36 +409,27 @@ msg = "%s seems to be running. Remove %s by hand if necessary or use \ the --force option." raise ExecutionError(msg % (appid, pidf)) - command = helper.start_command(config, debug) - if debug: - print "starting server with command :" - print command - if system(command): - print 'an error occured while starting the application, not started' - print - return False - if not debug: - print 'application %s started' % appid + helper.start_command(config, debug) return True -class StopApplicationCommand(ApplicationCommand): - """Stop the given applications. +class StopInstanceCommand(InstanceCommand): + """Stop the given instances. - ... - identifiers of the applications to stop. If no application is + ... + identifiers of the instances to stop. If no instance is given, stop them all. """ name = 'stop' actionverb = 'stopped' def ordered_instances(self): - instances = super(StopApplicationCommand, self).ordered_instances() + instances = super(StopInstanceCommand, self).ordered_instances() instances.reverse() return instances - def stop_application(self, appid): - """stop the application's server""" + def stop_instance(self, appid): + """stop the instance's server""" config = cwcfg.config_for(appid) helper = self.config_helper(config, cmdname='stop') helper.poststop() # do this anyway @@ -458,15 +460,15 @@ except OSError: # already removed by twistd pass - print 'application %s stopped' % appid + print 'instance %s stopped' % appid -class RestartApplicationCommand(StartApplicationCommand, - StopApplicationCommand): - """Restart the given applications. +class RestartInstanceCommand(StartInstanceCommand, + StopInstanceCommand): + """Restart the given instances. - ... - identifiers of the applications to restart. If no application is + ... + identifiers of the instances to restart. If no instance is given, restart them all. """ name = 'restart' @@ -476,18 +478,18 @@ regdir = cwcfg.registry_dir() if not isfile(join(regdir, 'startorder')) or len(args) <= 1: # no specific startorder - super(RestartApplicationCommand, self).run_args(args, askconfirm) + super(RestartInstanceCommand, self).run_args(args, askconfirm) return print ('some specific start order is specified, will first stop all ' - 'applications then restart them.') + 'instances then restart them.') # get instances in startorder stopped = [] for appid in args: if askconfirm: print '*'*72 - if not confirm('%s application %r ?' % (self.name, appid)): + if not confirm('%s instance %r ?' % (self.name, appid)): continue - self.stop_application(appid) + self.stop_instance(appid) stopped.append(appid) forkcmd = [w for w in sys.argv if not w in args] forkcmd[1] = 'start' @@ -497,46 +499,46 @@ if status: sys.exit(status) - def restart_application(self, appid): - self.stop_application(appid) - if self.start_application(appid): - print 'application %s %s' % (appid, self.actionverb) + def restart_instance(self, appid): + self.stop_instance(appid) + if self.start_instance(appid): + print 'instance %s %s' % (appid, self.actionverb) -class ReloadConfigurationCommand(RestartApplicationCommand): - """Reload the given applications. This command is equivalent to a +class ReloadConfigurationCommand(RestartInstanceCommand): + """Reload the given instances. This command is equivalent to a restart for now. - ... - identifiers of the applications to reload. If no application is + ... + identifiers of the instances to reload. If no instance is given, reload them all. """ name = 'reload' - def reload_application(self, appid): - self.restart_application(appid) + def reload_instance(self, appid): + self.restart_instance(appid) -class StatusCommand(ApplicationCommand): - """Display status information about the given applications. +class StatusCommand(InstanceCommand): + """Display status information about the given instances. - ... - identifiers of the applications to status. If no application is - given, get status information about all registered applications. + ... + identifiers of the instances to status. If no instance is + given, get status information about all registered instances. """ name = 'status' options = () @staticmethod - def status_application(appid): - """print running status information for an application""" + def status_instance(appid): + """print running status information for an instance""" for mode in cwcfg.possible_configurations(appid): config = cwcfg.config_for(appid, mode) print '[%s-%s]' % (appid, mode), try: pidf = config['pid-file'] except KeyError: - print 'buggy application, pid file not specified' + print 'buggy instance, pid file not specified' continue if not exists(pidf): print "doesn't seem to be running" @@ -551,22 +553,22 @@ print "running with pid %s" % (pid) -class UpgradeApplicationCommand(ApplicationCommandFork, - StartApplicationCommand, - StopApplicationCommand): - """Upgrade an application after cubicweb and/or component(s) upgrade. +class UpgradeInstanceCommand(InstanceCommandFork, + StartInstanceCommand, + StopInstanceCommand): + """Upgrade an instance after cubicweb and/or component(s) upgrade. For repository update, you will be prompted for a login / password to use to connect to the system database. For some upgrades, the given user should have create or alter table permissions. - ... - identifiers of the applications to upgrade. If no application is + ... + identifiers of the instances to upgrade. If no instance is given, upgrade them all. """ name = 'upgrade' actionverb = 'upgraded' - options = ApplicationCommand.options + ( + options = InstanceCommand.options + ( ('force-componant-version', {'short': 't', 'type' : 'csv', 'metavar': 'cube1=X.Y.Z,cube2=X.Y.Z', 'default': None, @@ -584,7 +586,7 @@ ('nostartstop', {'short': 'n', 'action' : 'store_true', 'default': False, - 'help': 'don\'t try to stop application before migration and to restart it after.'}), + 'help': 'don\'t try to stop instance before migration and to restart it after.'}), ('verbosity', {'short': 'v', 'type' : 'int', 'metavar': '<0..2>', @@ -595,7 +597,7 @@ ('backup-db', {'short': 'b', 'type' : 'yn', 'metavar': '', 'default': None, - 'help': "Backup the application database before upgrade.\n"\ + 'help': "Backup the instance database before upgrade.\n"\ "If the option is ommitted, confirmation will be ask.", }), @@ -610,25 +612,24 @@ ) def ordered_instances(self): - # need this since mro return StopApplicationCommand implementation - return ApplicationCommand.ordered_instances(self) + # need this since mro return StopInstanceCommand implementation + return InstanceCommand.ordered_instances(self) - def upgrade_application(self, appid): + def upgrade_instance(self, appid): + print '\n' + underline_title('Upgrading the instance %s' % appid) from logilab.common.changelog import Version config = cwcfg.config_for(appid) - config.creating = True # notice we're not starting the server + config.repairing = True # notice we're not starting the server config.verbosity = self.config.verbosity try: config.set_sources_mode(self.config.ext_sources or ('migration',)) except AttributeError: # not a server config pass - # get application and installed versions for the server and the componants - print 'getting versions configuration from the repository...' + # get instance and installed versions for the server and the componants mih = config.migration_handler() repo = mih.repo_connect() vcconf = repo.get_versions() - print 'done' if self.config.force_componant_version: packversions = {} for vdef in self.config.force_componant_version: @@ -654,13 +655,13 @@ if cubicwebversion > applcubicwebversion: toupgrade.append(('cubicweb', applcubicwebversion, cubicwebversion)) if not self.config.fs_only and not toupgrade: - print 'no software migration needed for application %s' % appid + print '-> no software migration needed for instance %s.' % appid return for cube, fromversion, toversion in toupgrade: - print '**** %s migration %s -> %s' % (cube, fromversion, toversion) + 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): - self.stop_application(appid) + self.stop_instance(appid) # run cubicweb/componants migration scripts mih.migrate(vcconf, reversed(toupgrade), self.config) # rewrite main configuration file @@ -675,15 +676,15 @@ errors = config.i18ncompile(langs) if errors: print '\n'.join(errors) - if not confirm('error while compiling message catalogs, ' + if not confirm('Error while compiling message catalogs, ' 'continue anyway ?'): - print 'migration not completed' + print '-> migration not completed.' return mih.shutdown() print - print 'application migrated' + print '-> instance migrated.' if not (cwcfg.mode == 'dev' or self.config.nostartstop): - self.start_application(appid) + self.start_instance(appid) print @@ -693,11 +694,11 @@ argument may be given corresponding to a file containing commands to execute in batch mode. - - the identifier of the application to connect. + + the identifier of the instance to connect. """ name = 'shell' - arguments = ' [batch command file]' + arguments = ' [batch command file]' options = ( ('system-only', {'short': 'S', 'action' : 'store_true', @@ -717,7 +718,7 @@ ) def run(self, args): - appid = pop_arg(args, 99, msg="No application specified !") + appid = pop_arg(args, 99, msg="No instance specified !") config = cwcfg.config_for(appid) if self.config.ext_sources: assert not self.config.system_only @@ -736,18 +737,18 @@ mih.shutdown() -class RecompileApplicationCatalogsCommand(ApplicationCommand): - """Recompile i18n catalogs for applications. +class RecompileInstanceCatalogsCommand(InstanceCommand): + """Recompile i18n catalogs for instances. - ... - identifiers of the applications to consider. If no application is - given, recompile for all registered applications. + ... + identifiers of the instances to consider. If no instance is + given, recompile for all registered instances. """ name = 'i18ninstance' @staticmethod - def i18ninstance_application(appid): - """recompile application's messages catalogs""" + def i18ninstance_instance(appid): + """recompile instance's messages catalogs""" config = cwcfg.config_for(appid) try: config.bootstrap_cubes() @@ -756,8 +757,8 @@ if ex.errno != errno.ENOENT: raise # bootstrap_cubes files doesn't exist - # set creating to notify this is not a regular start - config.creating = True + # notify this is not a regular start + config.repairing = True # create an in-memory repository, will call config.init_cubes() config.repository() except AttributeError: @@ -791,16 +792,16 @@ print cube register_commands((ListCommand, - CreateApplicationCommand, - DeleteApplicationCommand, - StartApplicationCommand, - StopApplicationCommand, - RestartApplicationCommand, + CreateInstanceCommand, + DeleteInstanceCommand, + StartInstanceCommand, + StopInstanceCommand, + RestartInstanceCommand, ReloadConfigurationCommand, StatusCommand, - UpgradeApplicationCommand, + UpgradeInstanceCommand, ShellCommand, - RecompileApplicationCatalogsCommand, + RecompileInstanceCatalogsCommand, ListInstancesCommand, ListCubesCommand, )) diff -r 6d0acd812d98 -r 03e7a6efd960 cwvreg.py --- a/cwvreg.py Fri Aug 07 12:12:20 2009 +0200 +++ b/cwvreg.py Fri Aug 07 12:12:50 2009 +0200 @@ -8,12 +8,17 @@ __docformat__ = "restructuredtext en" _ = unicode -from logilab.common.decorators import cached, clear_cache +from logilab.common.decorators import cached, clear_cache, monkeypatch +from logilab.common.deprecation import deprecated +from logilab.common.modutils import cleanup_sys_modules from rql import RQLHelper -from cubicweb import ETYPE_NAME_MAP, Binary, UnknownProperty, UnknownEid -from cubicweb.vregistry import VRegistry, ObjectNotFound, NoSelectableObject +from cubicweb import (ETYPE_NAME_MAP, Binary, UnknownProperty, UnknownEid, + ObjectNotFound, NoSelectableObject, RegistryNotFound, + RegistryOutOfDate, CW_EVENT_MANAGER) +from cubicweb.utils import dump_class +from cubicweb.vregistry import VRegistry, Registry from cubicweb.rtags import RTAGS @@ -31,47 +36,233 @@ if impl: return sorted(impl.expected_ifaces) except AttributeError: - pass # old-style vobject classes with no accepts_interfaces + pass # old-style appobject classes with no accepts_interfaces except: print 'bad selector %s on %s' % (obj.__select__, obj) raise return () -class CubicWebRegistry(VRegistry): - """extend the generic VRegistry with some cubicweb specific stuff""" +class CWRegistry(Registry): + def __init__(self, vreg): + super(CWRegistry, self).__init__(vreg.config) + self.vreg = vreg + self.schema = vreg.schema + + def initialization_completed(self): + # call vreg_initialization_completed on appobjects and print + # registry content + for appobjects in self.itervalues(): + for appobject in appobjects: + appobject.vreg_initialization_completed() + + def render(self, __oid, req, __fallback_oid=None, rset=None, **kwargs): + """select object, or fallback object if specified and the first one + isn't selectable, then render it + """ + try: + obj = self.select(__oid, req, rset=rset, **kwargs) + except NoSelectableObject: + if __fallback_oid is None: + raise + obj = self.select(__fallback_oid, req, **kwargs) + return obj.render(**kwargs) + + def select_vobject(self, oid, *args, **kwargs): + selected = self.select_object(oid, *args, **kwargs) + if selected and selected.propval('visible'): + return selected + return None + + def possible_vobjects(self, *args, **kwargs): + """return an ordered list of possible app objects in a given registry, + supposing they support the 'visible' and 'order' properties (as most + visualizable objects) + """ + return sorted([x for x in self.possible_objects(*args, **kwargs) + if x.propval('visible')], + key=lambda x: x.propval('order')) + + +VRegistry.REGISTRY_FACTORY[None] = CWRegistry + + +class ETypeRegistry(CWRegistry): + + def initialization_completed(self): + """on registration completed, clear etype_class internal cache + """ + super(ETypeRegistry, self).initialization_completed() + # clear etype cache if you don't want to run into deep weirdness + clear_cache(self, 'etype_class') + + def register(self, obj, **kwargs): + oid = kwargs.get('oid') or obj.id + if oid != 'Any' and not oid in self.schema: + self.error('don\'t register %s, %s type not defined in the ' + 'schema', obj, obj.id) + return + kwargs['clear'] = True + super(ETypeRegistry, self).register(obj, **kwargs) + + @cached + def etype_class(self, etype): + """return an entity class for the given entity type. + + Try to find out a specific class for this kind of entity or default to a + dump of the nearest parent class (in yams inheritance) registered. + + Fall back to 'Any' if not yams parent class found. + """ + etype = str(etype) + if etype == 'Any': + return self.select('Any', 'Any') + eschema = self.schema.eschema(etype) + baseschemas = [eschema] + eschema.ancestors() + # browse ancestors from most specific to most generic and try to find an + # associated custom entity class + for baseschema in baseschemas: + try: + btype = ETYPE_NAME_MAP[baseschema] + except KeyError: + btype = str(baseschema) + try: + objects = self[btype] + assert len(objects) == 1, objects + cls = objects[0] + break + except ObjectNotFound: + pass + else: + # no entity class for any of the ancestors, fallback to the default + # one + objects = self['Any'] + assert len(objects) == 1, objects + cls = objects[0] + if cls.id == etype: + cls.__initialize__() + return cls + cls = dump_class(cls, etype) + cls.id = etype + cls.__initialize__() + return cls + +VRegistry.REGISTRY_FACTORY['etypes'] = ETypeRegistry + + +class ViewsRegistry(CWRegistry): + + def main_template(self, req, oid='main-template', **kwargs): + """display query by calling the given template (default to main), + and returning the output as a string instead of requiring the [w]rite + method as argument + """ + res = self.render(oid, req, **kwargs) + if isinstance(res, unicode): + return res.encode(req.encoding) + assert isinstance(res, str) + return res + + def possible_views(self, req, rset=None, **kwargs): + """return an iterator on possible views for this result set + + views returned are classes, not instances + """ + for vid, views in self.items(): + if vid[0] == '_': + continue + try: + view = self.select_best(views, req, rset=rset, **kwargs) + if view.linkable(): + yield view + except NoSelectableObject: + continue + except Exception: + self.exception('error while trying to select %s view for %s', + vid, rset) + +VRegistry.REGISTRY_FACTORY['views'] = ViewsRegistry + + +class ActionsRegistry(CWRegistry): + + def possible_actions(self, req, rset=None, **kwargs): + if rset is None: + actions = self.possible_vobjects(req, rset=rset, **kwargs) + else: + actions = rset.possible_actions(**kwargs) # cached implementation + result = {} + for action in actions: + result.setdefault(action.category, []).append(action) + return result + +VRegistry.REGISTRY_FACTORY['actions'] = ActionsRegistry + + + +class CubicWebVRegistry(VRegistry): + """Central registry for the cubicweb instance, extending the generic + VRegistry with some cubicweb specific stuff. + + This is one of the central object in cubicweb instance, coupling + dynamically loaded objects with the schema and the configuration objects. + + It specializes the VRegistry by adding some convenience methods to access to + stored objects. Currently we have the following registries of objects known + by the web instance (library may use some others additional registries): + + * etypes + * views + * components + * actions + * forms + * formrenderers + * controllers, which are directly plugged into the application + object to handle request publishing XXX to merge with views + * contentnavigation XXX to merge with components? to kill? + """ def __init__(self, config, debug=None, initlog=True): if initlog: # first init log service config.init_log(debug=debug) - super(CubicWebRegistry, self).__init__(config) + super(CubicWebVRegistry, self).__init__(config) self.schema = None self.reset() self.initialized = False + def setdefault(self, regid): + try: + return self[regid] + except RegistryNotFound: + self[regid] = self.registry_class(regid)(self) + return self[regid] + def items(self): - return [item for item in self._registries.items() + return [item for item in super(CubicWebVRegistry, self).items() if not item[0] in ('propertydefs', 'propertyvalues')] + def iteritems(self): + return (item for item in super(CubicWebVRegistry, self).iteritems() + if not item[0] in ('propertydefs', 'propertyvalues')) def values(self): - return [value for key, value in self._registries.items() - if not key in ('propertydefs', 'propertyvalues')] + return [value for key, value in self.items()] + def itervalues(self): + return (value for key, value in self.items()) def reset(self): - self._registries = {} - self._lastmodifs = {} + super(CubicWebVRegistry, self).reset() self._needs_iface = {} # two special registries, propertydefs which care all the property # definitions, and propertyvals which contains values for those # properties - self._registries['propertydefs'] = {} - self._registries['propertyvalues'] = self.eprop_values = {} + self['propertydefs'] = {} + self['propertyvalues'] = self.eprop_values = {} for key, propdef in self.config.eproperty_definitions(): self.register_property(key, **propdef) def set_schema(self, schema): - """set application'schema and load application objects""" + """set instance'schema and load application objects""" self.schema = schema clear_cache(self, 'rqlhelper') # now we can load application's web objects @@ -87,9 +278,7 @@ tests """ self.schema = schema - for registry, regcontent in self._registries.items(): - if registry in ('propertydefs', 'propertyvalues'): - continue + for registry, regcontent in self.items(): for objects in regcontent.values(): for obj in objects: obj.schema = schema @@ -105,13 +294,7 @@ self._needs_iface[obj] = ifaces def register(self, obj, **kwargs): - if kwargs.get('registryname', obj.__registry__) == 'etypes': - if obj.id != 'Any' and not obj.id in self.schema: - self.error('don\'t register %s, %s type not defined in the ' - 'schema', obj, obj.id) - return - kwargs['clear'] = True - super(CubicWebRegistry, self).register(obj, **kwargs) + super(CubicWebVRegistry, self).register(obj, **kwargs) # XXX bw compat ifaces = use_interfaces(obj) if ifaces: @@ -119,181 +302,108 @@ def register_objects(self, path, force_reload=None): """overriden to remove objects requiring a missing interface""" + try: + self._register_objects(path, force_reload) + except RegistryOutOfDate: + CW_EVENT_MANAGER.emit('before-registry-reload') + # modification detected, reset and reload + self.reset() + cleanup_sys_modules(path) + self._register_objects(path, force_reload) + CW_EVENT_MANAGER.emit('after-registry-reload') + + def _register_objects(self, path, force_reload=None): + """overriden to remove objects requiring a missing interface""" extrapath = {} for cubesdir in self.config.cubes_search_path(): if cubesdir != self.config.CUBES_DIR: extrapath[cubesdir] = 'cubes' - if super(CubicWebRegistry, self).register_objects(path, force_reload, + if super(CubicWebVRegistry, self).register_objects(path, force_reload, extrapath): self.initialization_completed() - # call vreg_initialization_completed on appobjects and print - # registry content - for registry, objects in self.items(): - self.debug('available in registry %s: %s', registry, - sorted(objects)) - for appobjects in objects.itervalues(): - for appobject in appobjects: - appobject.vreg_initialization_completed() # don't check rtags if we don't want to cleanup_interface_sobjects for rtag in RTAGS: rtag.init(self.schema, check=self.config.cleanup_interface_sobjects) def initialization_completed(self): - # clear etype cache if you don't want to run into deep weirdness - clear_cache(self, 'etype_class') + for regname, reg in self.items(): + self.debug('available in registry %s: %s', regname, sorted(reg)) + reg.initialization_completed() # we may want to keep interface dependent objects (e.g.for i18n # catalog generation) if self.config.cleanup_interface_sobjects: - # remove vobjects that don't support any available interface + # remove appobjects that don't support any available interface implemented_interfaces = set() if 'Any' in self.get('etypes', ()): for etype in self.schema.entities(): - cls = self.etype_class(etype) + cls = self['etypes'].etype_class(etype) for iface in cls.__implements__: implemented_interfaces.update(iface.__mro__) implemented_interfaces.update(cls.__mro__) for obj, ifaces in self._needs_iface.items(): ifaces = frozenset(isinstance(iface, basestring) and iface in self.schema - and self.etype_class(iface) + and self['etypes'].etype_class(iface) or iface for iface in ifaces) if not ('Any' in ifaces or ifaces & implemented_interfaces): - self.debug('kicking vobject %s (no implemented ' + self.debug('kicking appobject %s (no implemented ' 'interface among %s)', obj, ifaces) self.unregister(obj) # clear needs_iface so we don't try to remove some not-anymore-in # objects on automatic reloading self._needs_iface.clear() - @cached - def etype_class(self, etype): - """return an entity class for the given entity type. - Try to find out a specific class for this kind of entity or - default to a dump of the class registered for 'Any' - """ - etype = str(etype) - if etype == 'Any': - return self.select(self.registry_objects('etypes', 'Any'), 'Any') - eschema = self.schema.eschema(etype) - baseschemas = [eschema] + eschema.ancestors() - # browse ancestors from most specific to most generic and - # try to find an associated custom entity class - for baseschema in baseschemas: - try: - btype = ETYPE_NAME_MAP[baseschema] - except KeyError: - btype = str(baseschema) - try: - cls = self.select(self.registry_objects('etypes', btype), etype) - break - except ObjectNotFound: - pass - else: - # no entity class for any of the ancestors, fallback to the default - # one - cls = self.select(self.registry_objects('etypes', 'Any'), etype) - return cls - - def render(self, registry, oid, req, **context): - """select an object in a given registry and render it + def parse(self, session, rql, args=None): + rqlst = self.rqlhelper.parse(rql) + def type_from_eid(eid, session=session): + return session.describe(eid)[0] + try: + self.rqlhelper.compute_solutions(rqlst, {'eid': type_from_eid}, args) + except UnknownEid: + for select in rqlst.children: + select.solutions = [] + return rqlst - - registry: the registry's name - - oid : the view to call - - req : the HTTP request - """ - objclss = self.registry_objects(registry, oid) - try: - rset = context.pop('rset') - except KeyError: - rset = None - selected = self.select(objclss, req, rset, **context) - return selected.render(**context) + @property + @cached + def rqlhelper(self): + return RQLHelper(self.schema, + special_relations={'eid': 'uid', 'has_text': 'fti'}) - def main_template(self, req, oid='main-template', **context): - """display query by calling the given template (default to main), - and returning the output as a string instead of requiring the [w]rite - method as argument - """ - res = self.render('views', oid, req, **context) - if isinstance(res, unicode): - return res.encode(req.encoding) - assert isinstance(res, str) - return res - def possible_vobjects(self, registry, *args, **kwargs): - """return an ordered list of possible app objects in a given registry, - supposing they support the 'visible' and 'order' properties (as most - visualizable objects) - """ - return [x for x in sorted(self.possible_objects(registry, *args, **kwargs), - key=lambda x: x.propval('order')) - if x.propval('visible')] + @deprecated('use vreg["etypes"].etype_class(etype)') + def etype_class(self, etype): + return self["etypes"].etype_class(etype) - def possible_actions(self, req, rset, **kwargs): - if rset is None: - actions = self.possible_vobjects('actions', req, rset, **kwargs) - else: - actions = rset.possible_actions(**kwargs) # cached implementation - result = {} - for action in actions: - result.setdefault(action.category, []).append(action) - return result - - def possible_views(self, req, rset, **kwargs): - """return an iterator on possible views for this result set + @deprecated('use vreg["views"].main_template(*args, **kwargs)') + def main_template(self, req, oid='main-template', **context): + return self["views"].main_template(req, oid, **context) - views returned are classes, not instances - """ - for vid, views in self.registry('views').items(): - if vid[0] == '_': - continue - try: - view = self.select(views, req, rset, **kwargs) - if view.linkable(): - yield view - except NoSelectableObject: - continue - except Exception: - self.exception('error while trying to list possible %s views for %s', - vid, rset) + @deprecated('use vreg[registry].possible_vobjects(*args, **kwargs)') + def possible_vobjects(self, registry, *args, **kwargs): + return self[registry].possible_vobjects(*args, **kwargs) + + @deprecated('use vreg["actions"].possible_actions(*args, **kwargs)') + def possible_actions(self, req, rset=None, **kwargs): + return self["actions"].possible_actions(req, rest=rset, **kwargs) - def view(self, __vid, req, rset=None, __fallback_vid=None, **kwargs): - """shortcut to self.vreg.render method avoiding to pass self.req""" - try: - view = self.select_view(__vid, req, rset, **kwargs) - except NoSelectableObject: - if __fallback_vid is None: - raise - view = self.select_view(__fallback_vid, req, rset, **kwargs) - return view.render(**kwargs) + @deprecated("use vreg['boxes'].select_object(...)") + def select_box(self, oid, *args, **kwargs): + return self['boxes'].select_object(oid, *args, **kwargs) - def select_box(self, oid, *args, **kwargs): - """return the most specific view according to the result set""" - try: - return self.select_object('boxes', oid, *args, **kwargs) - except NoSelectableObject: - return + @deprecated("use vreg['components'].select_object(...)") + def select_component(self, cid, *args, **kwargs): + return self['components'].select_object(cid, *args, **kwargs) + @deprecated("use vreg['actions'].select_object(...)") def select_action(self, oid, *args, **kwargs): - """return the most specific view according to the result set""" - try: - return self.select_object('actions', oid, *args, **kwargs) - except NoSelectableObject: - return + return self['actions'].select_object(oid, *args, **kwargs) - def select_component(self, cid, *args, **kwargs): - """return the most specific component according to the result set""" - try: - return self.select_object('components', cid, *args, **kwargs) - except (NoSelectableObject, ObjectNotFound): - return - + @deprecated("use vreg['views'].select(...)") def select_view(self, __vid, req, rset=None, **kwargs): - """return the most specific view according to the result set""" - views = self.registry_objects('views', __vid) - return self.select(views, req, rset, **kwargs) + return self['views'].select(__vid, req, rset=rset, **kwargs) # properties handling ##################################################### @@ -307,7 +417,7 @@ def register_property(self, key, type, help, default=None, vocabulary=None, sitewide=False): """register a given property""" - properties = self._registries['propertydefs'] + properties = self['propertydefs'] assert type in YAMS_TO_PY properties[key] = {'type': type, 'vocabulary': vocabulary, 'default': default, 'help': help, @@ -319,7 +429,7 @@ boolean) """ try: - return self._registries['propertydefs'][key] + return self['propertydefs'][key] except KeyError: if key.startswith('system.version.'): soft = key.split('.')[-1] @@ -330,9 +440,9 @@ def property_value(self, key): try: - return self._registries['propertyvalues'][key] + return self['propertyvalues'][key] except KeyError: - return self._registries['propertydefs'][key]['default'] + return self['propertydefs'][key]['default'] def typed_value(self, key, value): """value is an unicode string, return it correctly typed. Let potential @@ -355,7 +465,7 @@ """init the property values registry using the given set of couple (key, value) """ self.initialized = True - values = self._registries['propertyvalues'] + values = self['propertyvalues'] for key, val in propvalues: try: values[key] = self.typed_value(key, val) @@ -366,59 +476,6 @@ self.warning('%s (you should probably delete that property ' 'from the database)', ex) - def parse(self, session, rql, args=None): - rqlst = self.rqlhelper.parse(rql) - def type_from_eid(eid, session=session): - return session.describe(eid)[0] - try: - self.rqlhelper.compute_solutions(rqlst, {'eid': type_from_eid}, args) - except UnknownEid: - for select in rqlst.children: - select.solutions = [] - return rqlst - - @property - @cached - def rqlhelper(self): - return RQLHelper(self.schema, - special_relations={'eid': 'uid', 'has_text': 'fti'}) - - -class MulCnxCubicWebRegistry(CubicWebRegistry): - """special registry to be used when an application has to deal with - connections to differents repository. This class add some additional wrapper - trying to hide buggy class attributes since classes are not designed to be - shared. - """ - def etype_class(self, etype): - """return an entity class for the given entity type. - Try to find out a specific class for this kind of entity or - default to a dump of the class registered for 'Any' - """ - usercls = super(MulCnxCubicWebRegistry, self).etype_class(etype) - if etype == 'Any': - return usercls - usercls.e_schema = self.schema.eschema(etype) - return usercls - - def select(self, vobjects, *args, **kwargs): - """return an instance of the most specific object according - to parameters - - raise NoSelectableObject if not object apply - """ - for vobject in vobjects: - vobject.vreg = self - vobject.schema = self.schema - vobject.config = self.config - selected = super(MulCnxCubicWebRegistry, self).select(vobjects, *args, - **kwargs) - # redo the same thing on the instance so it won't use equivalent class - # attributes (which may change) - selected.vreg = self - selected.schema = self.schema - selected.config = self.config - return selected from datetime import datetime, date, time, timedelta diff -r 6d0acd812d98 -r 03e7a6efd960 dbapi.py --- a/dbapi.py Fri Aug 07 12:12:20 2009 +0200 +++ b/dbapi.py Fri Aug 07 12:12:50 2009 +0200 @@ -13,14 +13,71 @@ from logging import getLogger from time import time, clock +from itertools import count from logilab.common.logging_ext import set_log_methods +from logilab.common.decorators import monkeypatch +from logilab.common.deprecation import deprecated + from cubicweb import ETYPE_NAME_MAP, ConnectionError, RequestSessionMixIn -from cubicweb.cwvreg import CubicWebRegistry, MulCnxCubicWebRegistry -from cubicweb.cwconfig import CubicWebNoAppConfiguration +from cubicweb import cwvreg, cwconfig _MARKER = object() +def _fake_property_value(self, name): + try: + return super(dbapi.DBAPIRequest, self).property_value(name) + except KeyError: + return '' + +def _fix_cls_attrs(reg, appobject): + appobject.vreg = reg.vreg + appobject.schema = reg.schema + appobject.config = reg.config + +def multiple_connections_fix(): + """some monkey patching necessary when an application has to deal with + several connections to different repositories. It tries to hide buggy class + attributes since classes are not designed to be shared among multiple + registries. + """ + defaultcls = cwvreg.VRegistry.REGISTRY_FACTORY[None] + orig_select_best = defaultcls.orig_select_best = defaultcls.select_best + @monkeypatch(defaultcls) + def select_best(self, appobjects, *args, **kwargs): + """return an instance of the most specific object according + to parameters + + raise NoSelectableObject if no object apply + """ + for appobjectcls in appobjects: + _fix_cls_attrs(self, appobjectcls) + selected = orig_select_best(self, appobjects, *args, **kwargs) + # redo the same thing on the instance so it won't use equivalent class + # attributes (which may change) + _fix_cls_attrs(self, selected) + return selected + + etypescls = cwvreg.VRegistry.REGISTRY_FACTORY['etypes'] + orig_etype_class = etypescls.orig_etype_class = etypescls.etype_class + @monkeypatch(defaultcls) + def etype_class(self, etype): + """return an entity class for the given entity type. + Try to find out a specific class for this kind of entity or + default to a dump of the class registered for 'Any' + """ + usercls = orig_etype_class(self, etype) + if etype == 'Any': + return usercls + usercls.e_schema = self.schema.eschema(etype) + return usercls + +def multiple_connections_unfix(): + defaultcls = cwvreg.VRegistry.REGISTRY_FACTORY[None] + defaultcls.select_best = defaultcls.orig_select_best + etypescls = cwvreg.VRegistry.REGISTRY_FACTORY['etypes'] + etypescls.etype_class = etypescls.orig_etype_class + class ConnectionProperties(object): def __init__(self, cnxtype=None, lang=None, close=True, log=False): self.cnxtype = cnxtype or 'pyro' @@ -44,24 +101,14 @@ from cubicweb.server.repository import Repository return Repository(config, vreg=vreg) else: # method == 'pyro' - from Pyro import core, naming - from Pyro.errors import NamingError, ProtocolError - core.initClient(banner=0) - nsid = ':%s.%s' % (config['pyro-ns-group'], database) - locator = naming.NameServerLocator() # resolve the Pyro object + from logilab.common.pyro_ext import ns_get_proxy try: - nshost, nsport = config['pyro-ns-host'], config['pyro-ns-port'] - uri = locator.getNS(nshost, nsport).resolve(nsid) - except ProtocolError: - raise ConnectionError('Could not connect to the Pyro name server ' - '(host: %s:%i)' % (nshost, nsport)) - except NamingError: - raise ConnectionError('Could not get repository for %s ' - '(not registered in Pyro), ' - 'you may have to restart your server-side ' - 'application' % nsid) - return core.getProxyForURI(uri) + return ns_get_proxy(database, + defaultnsgroup=config['pyro-ns-group'], + nshost=config['pyro-ns-host']) + except Exception, ex: + raise ConnectionError(str(ex)) def repo_connect(repo, login, password, cnxprops=None): """Constructor to create a new connection to the CubicWeb repository. @@ -75,22 +122,18 @@ cnx.vreg = repo.vreg return cnx -def connect(database=None, login=None, password=None, host=None, - group=None, cnxprops=None, port=None, setvreg=True, mulcnx=True, - initlog=True): +def connect(database=None, login=None, password=None, host=None, group=None, + cnxprops=None, setvreg=True, mulcnx=True, initlog=True): """Constructor for creating a connection to the CubicWeb repository. Returns a Connection object. - When method is 'pyro' and setvreg is True, use a special registry class - (MulCnxCubicWebRegistry) made to deal with connections to differents instances - in the same process unless specified otherwise by setting the mulcnx to - False. + When method is 'pyro', setvreg is True, try to deal with connections to + differents instances in the same process unless specified otherwise by + setting the mulcnx to False. """ - config = CubicWebNoAppConfiguration() + config = cwconfig.CubicWebNoAppConfiguration() if host: config.global_set_option('pyro-ns-host', host) - if port: - config.global_set_option('pyro-ns-port', port) if group: config.global_set_option('pyro-ns-group', group) cnxprops = cnxprops or ConnectionProperties() @@ -100,9 +143,8 @@ vreg = repo.vreg elif setvreg: if mulcnx: - vreg = MulCnxCubicWebRegistry(config, initlog=initlog) - else: - vreg = CubicWebRegistry(config, initlog=initlog) + multiple_connections_fix() + vreg = cwvreg.CubicWebVRegistry(config, initlog=initlog) schema = repo.get_schema() for oldetype, newetype in ETYPE_NAME_MAP.items(): if oldetype in schema: @@ -119,7 +161,7 @@ """usefull method for testing and scripting to get a dbapi.Connection object connected to an in-memory repository instance """ - if isinstance(config, CubicWebRegistry): + if isinstance(config, cwvreg.CubicWebVRegistry): vreg = config config = None else: @@ -394,7 +436,8 @@ raise ProgrammingError('Closed connection') return self._repo.get_schema() - def load_vobjects(self, cubes=_MARKER, subpath=None, expand=True, force_reload=None): + def load_appobjects(self, cubes=_MARKER, subpath=None, expand=True, + force_reload=None): config = self.vreg.config if cubes is _MARKER: cubes = self._repo.get_cubes() @@ -422,10 +465,37 @@ hm, config = self._repo.hm, self._repo.config hm.set_schema(hm.schema) # reset structure hm.register_system_hooks(config) - # application specific hooks - if self._repo.config.application_hooks: + # instance specific hooks + if self._repo.config.instance_hooks: hm.register_hooks(config.load_hooks(self.vreg)) + load_vobjects = deprecated()(load_appobjects) + + def use_web_compatible_requests(self, baseurl, sitetitle=None): + """monkey patch DBAPIRequest to fake a cw.web.request, so you should + able to call html views using rset from a simple dbapi connection. + + You should call `load_appobjects` at some point to register those views. + """ + from cubicweb.web.request import CubicWebRequestBase as cwrb + DBAPIRequest.build_ajax_replace_url = cwrb.build_ajax_replace_url.im_func + DBAPIRequest.list_form_param = cwrb.list_form_param.im_func + DBAPIRequest.property_value = _fake_property_value + DBAPIRequest.next_tabindex = count().next + DBAPIRequest.form = {} + DBAPIRequest.data = {} + fake = lambda *args, **kwargs: None + DBAPIRequest.relative_path = fake + DBAPIRequest.url = fake + DBAPIRequest.next_tabindex = fake + DBAPIRequest.add_js = fake #cwrb.add_js.im_func + DBAPIRequest.add_css = fake #cwrb.add_css.im_func + # XXX could ask the repo for it's base-url configuration + self.vreg.config.set_option('base-url', baseurl) + # XXX why is this needed? if really needed, could be fetched by a query + if sitetitle is not None: + self.vreg['propertydefs']['ui.site-title'] = {'default': sitetitle} + def source_defs(self): """Return the definition of sources used by the repository. @@ -443,8 +513,9 @@ if req is None: req = self.request() rset = req.eid_rset(eid, 'CWUser') - user = self.vreg.etype_class('CWUser')(req, rset, row=0, groups=groups, - properties=properties) + user = self.vreg['etypes'].etype_class('CWUser')(req, rset, row=0, + groups=groups, + properties=properties) user['login'] = login # cache login return user diff -r 6d0acd812d98 -r 03e7a6efd960 debian/changelog --- a/debian/changelog Fri Aug 07 12:12:20 2009 +0200 +++ b/debian/changelog Fri Aug 07 12:12:50 2009 +0200 @@ -1,3 +1,9 @@ +cubicweb (3.4.0-1) unstable; urgency=low + + * new upstream release + + -- Sylvain Thénault Fri, 07 Aug 2009 10:43:21 +0200 + cubicweb (3.3.5-1) unstable; urgency=low * new upstream release diff -r 6d0acd812d98 -r 03e7a6efd960 debian/control --- a/debian/control Fri Aug 07 12:12:20 2009 +0200 +++ b/debian/control Fri Aug 07 12:12:50 2009 +0200 @@ -62,7 +62,7 @@ Architecture: all XB-Python-Version: ${python:Versions} Depends: ${python:Depends}, cubicweb-common (= ${source:Version}), python-simplejson (>= 1.3), python-elementtree -Recommends: python-docutils, python-vobject, fckeditor +Recommends: python-docutils, python-vobject, fckeditor, python-fyzz Description: web interface library for the CubicWeb framework CubicWeb is a semantic web application framework. . @@ -76,8 +76,8 @@ Package: cubicweb-common Architecture: all XB-Python-Version: ${python:Versions} -Depends: ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.6.0), python-logilab-common (>= 0.43.0), python-yams (>= 0.23.0), python-rql (>= 0.22.1) -Recommends: python-simpletal (>= 4.0), python-lxml +Depends: ${python:Depends}, graphviz, gettext, python-logilab-mtconverter (>= 0.6.0), python-logilab-common (>= 0.44.0), python-yams (>= 0.24.0), python-rql (>= 0.22.1), python-lxml +Recommends: python-simpletal (>= 4.0) Conflicts: cubicweb-core Replaces: cubicweb-core Description: common library for the CubicWeb framework diff -r 6d0acd812d98 -r 03e7a6efd960 devtools/__init__.py --- a/devtools/__init__.py Fri Aug 07 12:12:20 2009 +0200 +++ b/devtools/__init__.py Fri Aug 07 12:12:50 2009 +0200 @@ -43,7 +43,7 @@ class TestServerConfiguration(ServerConfiguration): mode = 'test' set_language = False - read_application_schema = False + read_instance_schema = False bootstrap_schema = False init_repository = True options = merge_options(ServerConfiguration.options + ( @@ -77,12 +77,12 @@ def apphome(self): if exists(self.appid): return abspath(self.appid) - # application cube test + # cube test return abspath('..') appdatahome = apphome def main_config_file(self): - """return application's control configuration file""" + """return instance's control configuration file""" return join(self.apphome, '%s.conf' % self.name) def instance_md5_version(self): @@ -142,14 +142,14 @@ class BaseApptestConfiguration(TestServerConfiguration, TwistedConfiguration): repo_method = 'inmemory' options = merge_options(TestServerConfiguration.options + TwistedConfiguration.options) - cubicweb_vobject_path = TestServerConfiguration.cubicweb_vobject_path | TwistedConfiguration.cubicweb_vobject_path - cube_vobject_path = TestServerConfiguration.cube_vobject_path | TwistedConfiguration.cube_vobject_path + cubicweb_appobject_path = TestServerConfiguration.cubicweb_appobject_path | TwistedConfiguration.cubicweb_appobject_path + cube_appobject_path = TestServerConfiguration.cube_appobject_path | TwistedConfiguration.cube_appobject_path def available_languages(self, *args): return ('en', 'fr', 'de') def ext_resources_file(self): - """return application's external resources file""" + """return instance's external resources file""" return join(self.apphome, 'data', 'external_resources') def pyro_enabled(self): @@ -235,14 +235,14 @@ # XXX I'm afraid this test will prevent to run test from a production # environment self._sources = None - # application cube test + # instance cube test if cube is not None: self.apphome = self.cube_dir(cube) elif 'web' in os.getcwd().split(os.sep): # web test self.apphome = join(normpath(join(dirname(__file__), '..')), 'web') else: - # application cube test + # cube test self.apphome = abspath('..') self.sourcefile = sourcefile self.global_set_option('realm', '') diff -r 6d0acd812d98 -r 03e7a6efd960 devtools/_apptest.py --- a/devtools/_apptest.py Fri Aug 07 12:12:20 2009 +0200 +++ b/devtools/_apptest.py Fri Aug 07 12:12:50 2009 +0200 @@ -14,7 +14,7 @@ import yams.schema from cubicweb.dbapi import repo_connect, ConnectionProperties, ProgrammingError -from cubicweb.cwvreg import CubicWebRegistry +from cubicweb.cwvreg import CubicWebVRegistry from cubicweb.web.application import CubicWebPublisher from cubicweb.web import Redirect @@ -79,7 +79,7 @@ source = config.sources()['system'] if verbose: print "init test database ..." - self.vreg = vreg = CubicWebRegistry(config) + self.vreg = vreg = CubicWebVRegistry(config) self.admlogin = source['db-user'] # restore database <=> init database self.restore_database() @@ -177,7 +177,7 @@ self.create_request(rql=rql, **optional_args or {})) def check_view(self, rql, vid, optional_args, template='main'): - """checks if vreg.view() raises an exception in this environment + """checks if rendering view raises an exception in this environment If any exception is raised in this method, it will be considered as a TestFailure @@ -186,17 +186,16 @@ template=template, optional_args=optional_args) def call_view(self, vid, rql, template='main', optional_args=None): - """shortcut for self.vreg.view()""" assert template if optional_args is None: optional_args = {} optional_args['vid'] = vid req = self.create_request(rql=rql, **optional_args) - return self.vreg.main_template(req, template) + return self.vreg['views'].main_template(req, template) def call_edit(self, req): """shortcut for self.app.edit()""" - controller = self.app.select_controller('edit', req) + controller = self.vreg.select('controllers', 'edit', req) try: controller.publish() except Redirect: @@ -208,7 +207,7 @@ def iter_possible_views(self, req, rset): """returns a list of possible vids for """ - for view in self.vreg.possible_views(req, rset): + for view in self.vreg['views'].possible_views(req, rset): if view.category == 'startupview': continue yield view.id @@ -217,14 +216,14 @@ def iter_startup_views(self, req): """returns the list of startup views""" - for view in self.vreg.possible_views(req, None): + for view in self.vreg['views'].possible_views(req, None): if view.category != 'startupview': continue yield view.id def iter_possible_actions(self, req, rset): """returns a list of possible vids for """ - for action in self.vreg.possible_vobjects('actions', req, rset): + for action in self.vreg.possible_vobjects('actions', req, rset=rset): yield action class ExistingTestEnvironment(TestEnvironment): @@ -234,7 +233,7 @@ if verbose: print "init test database ..." source = config.sources()['system'] - self.vreg = CubicWebRegistry(config) + self.vreg = CubicWebVRegistry(config) self.cnx = init_test_database(driver=source['db-driver'], vreg=self.vreg)[1] if verbose: diff -r 6d0acd812d98 -r 03e7a6efd960 devtools/apptest.py --- a/devtools/apptest.py Fri Aug 07 12:12:20 2009 +0200 +++ b/devtools/apptest.py Fri Aug 07 12:12:50 2009 +0200 @@ -1,4 +1,4 @@ -"""This module provides misc utilities to test applications +"""This module provides misc utilities to test instances :organization: Logilab :copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. @@ -15,7 +15,7 @@ from logilab.common.pytest import nocoverage from logilab.common.umessage import message_from_string -from logilab.common.deprecation import deprecated_function +from logilab.common.deprecation import deprecated from cubicweb.devtools import init_test_database, TestServerConfiguration, ApptestConfiguration from cubicweb.devtools._apptest import TestEnvironment @@ -34,6 +34,14 @@ def message(self): return message_from_string(self.msg) + @property + def subject(self): + return self.message.get('Subject') + + @property + def content(self): + return self.message.get_payload(decode=True) + def __repr__(self): return '' % (','.join(self.recipients), self.message.get('Subject')) @@ -51,7 +59,7 @@ def get_versions(self, checkversions=False): - """return the a dictionary containing cubes used by this application + """return the a dictionary containing cubes used by this instance as key with their version as value, including cubicweb version. This is a public method, not requiring a session id. @@ -167,7 +175,7 @@ def etype_instance(self, etype, req=None): req = req or self.request() - e = self.env.vreg.etype_class(etype)(req, None, None) + e = self.env.vreg['etypes'].etype_class(etype)(req) e.eid = None return e @@ -216,21 +224,21 @@ self.vreg.config.global_set_option(optname, value) def pviews(self, req, rset): - return sorted((a.id, a.__class__) for a in self.vreg.possible_views(req, rset)) + return sorted((a.id, a.__class__) for a in self.vreg['views'].possible_views(req, rset=rset)) def pactions(self, req, rset, skipcategories=('addrelated', 'siteactions', 'useractions')): - return [(a.id, a.__class__) for a in self.vreg.possible_vobjects('actions', req, rset) + return [(a.id, a.__class__) for a in self.vreg['actions'].possible_vobjects(req, rset=rset) if a.category not in skipcategories] def pactions_by_cats(self, req, rset, categories=('addrelated',)): - return [(a.id, a.__class__) for a in self.vreg.possible_vobjects('actions', req, rset) + return [(a.id, a.__class__) for a in self.vreg['actions'].possible_vobjects(req, rset=rset) if a.category in categories] - paddrelactions = deprecated_function(pactions_by_cats) + paddrelactions = deprecated()(pactions_by_cats) def pactionsdict(self, req, rset, skipcategories=('addrelated', 'siteactions', 'useractions')): res = {} - for a in self.vreg.possible_vobjects('actions', req, rset): + for a in self.vreg['actions'].possible_vobjects(req, rset=rset): if a.category not in skipcategories: res.setdefault(a.category, []).append(a.__class__) return res @@ -241,7 +249,7 @@ dump = simplejson.dumps args = [dump(arg) for arg in args] req = self.request(fname=fname, pageid='123', arg=args) - ctrl = self.env.app.select_controller('json', req) + ctrl = self.vreg['controllers'].select('json', req) return ctrl.publish(), req # default test setup and teardown ######################################### @@ -286,7 +294,7 @@ def setUp(self): super(ControllerTC, self).setUp() self.req = self.request() - self.ctrl = self.env.app.select_controller('edit', self.req) + self.ctrl = self.vreg['controllers'].select('edit', self.req) def publish(self, req): assert req is self.ctrl.req @@ -300,7 +308,7 @@ def expect_redirect_publish(self, req=None): if req is not None: - self.ctrl = self.env.app.select_controller('edit', req) + self.ctrl = self.vreg['controllers'].select('edit', req) else: req = self.req try: @@ -398,7 +406,7 @@ rset.vreg = self.vreg rset.req = self.session # call to set_pool is necessary to avoid pb when using - # application entities for convenience + # instance entities for convenience self.session.set_pool() return rset @@ -449,7 +457,6 @@ pactionsdict = EnvBasedTC.pactionsdict.im_func # default test setup and teardown ######################################### - copy_schema = False def _prepare(self): MAILBOX[:] = [] # reset mailbox @@ -462,16 +469,6 @@ self.__close = repo.close self.cnxid = self.cnx.sessionid self.session = repo._sessions[self.cnxid] - if self.copy_schema: - # XXX copy schema since hooks may alter it and it may be not fully - # cleaned (missing some schema synchronization support) - try: - origschema = repo.__schema - except AttributeError: - repo.__schema = origschema = repo.schema - repo.schema = deepcopy(origschema) - repo.set_schema(repo.schema) # reset hooks - repo.vreg.update_schema(repo.schema) self.cnxs = [] # reset caches, they may introduce bugs among tests repo._type_source_cache = {} diff -r 6d0acd812d98 -r 03e7a6efd960 devtools/devctl.py --- a/devtools/devctl.py Fri Aug 07 12:12:20 2009 +0200 +++ b/devtools/devctl.py Fri Aug 07 12:12:50 2009 +0200 @@ -12,25 +12,25 @@ from datetime import datetime from os import mkdir, chdir from os.path import join, exists, abspath, basename, normpath, split, isdir - +from warnings import warn from logilab.common import STD_BLACKLIST from logilab.common.modutils import get_module_files -from logilab.common.textutils import get_csv +from logilab.common.textutils import splitstrip +from logilab.common.shellutils import ASK from logilab.common.clcommands import register_commands from cubicweb import CW_SOFTWARE_ROOT as BASEDIR, BadCommandUsage, underline_title from cubicweb.__pkginfo__ import version as cubicwebversion -from cubicweb.toolsutils import Command, confirm, copy_skeleton +from cubicweb.toolsutils import Command, copy_skeleton from cubicweb.web.webconfig import WebConfiguration from cubicweb.server.serverconfig import ServerConfiguration - class DevCubeConfiguration(ServerConfiguration, WebConfiguration): """dummy config to get full library schema and entities""" creating = True - cubicweb_vobject_path = ServerConfiguration.cubicweb_vobject_path | WebConfiguration.cubicweb_vobject_path - cube_vobject_path = ServerConfiguration.cube_vobject_path | WebConfiguration.cube_vobject_path + cubicweb_appobject_path = ServerConfiguration.cubicweb_appobject_path | WebConfiguration.cubicweb_appobject_path + cube_appobject_path = ServerConfiguration.cube_appobject_path | WebConfiguration.cube_appobject_path def __init__(self, cube): super(DevCubeConfiguration, self).__init__(cube) @@ -90,7 +90,7 @@ notice that relation definitions description and static vocabulary should be marked using '_' and extracted using xgettext """ - from cubicweb.cwvreg import CubicWebRegistry + from cubicweb.cwvreg import CubicWebVRegistry cube = cubedir and split(cubedir)[-1] libconfig = DevDepConfiguration(cube) libconfig.cleanup_interface_sobjects = False @@ -102,7 +102,7 @@ config = libconfig libconfig = None schema = config.load_schema(remove_unused_rtypes=False) - vreg = CubicWebRegistry(config) + vreg = CubicWebVRegistry(config) # set_schema triggers objects registrations vreg.set_schema(schema) w(DEFAULT_POT_HEAD) @@ -187,8 +187,8 @@ #cube = (cube and 'cubes.%s.' % cube or 'cubicweb.') done = set() if libconfig is not None: - from cubicweb.cwvreg import CubicWebRegistry - libvreg = CubicWebRegistry(libconfig) + from cubicweb.cwvreg import CubicWebVRegistry + libvreg = CubicWebVRegistry(libconfig) libvreg.set_schema(libschema) # trigger objects registration # prefill done set list(_iter_vreg_objids(libvreg, done)) @@ -254,14 +254,13 @@ if args: raise BadCommandUsage('Too much arguments') import shutil - from tempfile import mktemp + import tempfile import yams from logilab.common.fileutils import ensure_fs_mode from logilab.common.shellutils import globfind, find, rm from cubicweb.common.i18n import extract_from_tal, execute - tempdir = mktemp() - mkdir(tempdir) - potfiles = [join(I18NDIR, 'entities.pot')] + tempdir = tempfile.mkdtemp() + potfiles = [join(I18NDIR, 'static-messages.pot')] print '-> extract schema messages.' schemapot = join(tempdir, 'schema.pot') potfiles.append(schemapot) @@ -328,8 +327,8 @@ def update_cubes_catalogs(cubes): - toedit = [] for cubedir in cubes: + toedit = [] if not isdir(cubedir): print '-> ignoring %s that is not a directory.' % cubedir continue @@ -338,27 +337,34 @@ except Exception: import traceback traceback.print_exc() - print '-> Error while updating catalogs for cube', cubedir - # instructions pour la suite - print '-> regenerated this cube\'s .po catalogs.' - print '\nYou can now edit the following files:' - print '* ' + '\n* '.join(toedit) - print 'when you are done, run "cubicweb-ctl i18ninstance yourinstance".' + print '-> error while updating catalogs for cube', cubedir + else: + # instructions pour la suite + print '-> regenerated .po catalogs for cube %s.' % cubedir + print '\nYou can now edit the following files:' + print '* ' + '\n* '.join(toedit) + print ('When you are done, run "cubicweb-ctl i18ninstance ' + '" to see changes in your instances.') def update_cube_catalogs(cubedir): import shutil - from tempfile import mktemp + import tempfile from logilab.common.fileutils import ensure_fs_mode from logilab.common.shellutils import find, rm from cubicweb.common.i18n import extract_from_tal, execute toedit = [] cube = basename(normpath(cubedir)) - tempdir = mktemp() - mkdir(tempdir) + tempdir = tempfile.mkdtemp() print underline_title('Updating i18n catalogs for cube %s' % cube) chdir(cubedir) - potfiles = [join('i18n', scfile) for scfile in ('entities.pot',) - if exists(join('i18n', scfile))] + if exists(join('i18n', 'entities.pot')): + warn('entities.pot is deprecated, rename file to static-messages.pot (%s)' + % join('i18n', 'entities.pot'), DeprecationWarning) + potfiles = [join('i18n', 'entities.pot')] + elif exists(join('i18n', 'static-messages.pot')): + potfiles = [join('i18n', 'static-messages.pot')] + else: + potfiles = [] print '-> extract schema messages' schemapot = join(tempdir, 'schema.pot') potfiles.append(schemapot) @@ -477,7 +483,7 @@ " Please specify it using the --directory option") cubesdir = cubespath[0] if not isdir(cubesdir): - print "creating cubes directory", cubesdir + print "-> creating cubes directory", cubesdir try: mkdir(cubesdir) except OSError, err: @@ -486,19 +492,20 @@ if exists(cubedir): self.fail("%s already exists !" % (cubedir)) skeldir = join(BASEDIR, 'skeleton') + default_name = 'cubicweb-%s' % cubename.lower() if verbose: - distname = raw_input('Debian name for your cube (just type enter to use the cube name): ').strip() + distname = raw_input('Debian name for your cube ? [%s]): ' % default_name).strip() if not distname: - distname = 'cubicweb-%s' % cubename.lower() + distname = default_name elif not distname.startswith('cubicweb-'): - if confirm('do you mean cubicweb-%s ?' % distname): + if ASK.confirm('Do you mean cubicweb-%s ?' % distname): distname = 'cubicweb-' + distname else: - distname = 'cubicweb-%s' % cubename.lower() + distname = default_name longdesc = shortdesc = raw_input('Enter a short description for your cube: ') if verbose: - longdesc = raw_input('Enter a long description (or nothing if you want to reuse the short one): ') + longdesc = raw_input('Enter a long description (leave empty to reuse the short one): ') if verbose: includes = self._ask_for_dependancies() if len(includes) == 1: @@ -523,14 +530,14 @@ def _ask_for_dependancies(self): includes = [] for stdtype in ServerConfiguration.available_cubes(): - ans = raw_input("Depends on cube %s? (N/y/s(kip)/t(ype)" - % stdtype).lower().strip() - if ans == 'y': + answer = ASK.ask("Depends on cube %s? " % stdtype, + ('N','y','skip','type'), 'N') + if answer == 'y': includes.append(stdtype) - if ans == 't': - includes = get_csv(raw_input('type dependancies: ')) + if answer == 'type': + includes = splitstrip(raw_input('type dependancies: ')) break - elif ans == 's': + elif answer == 'skip': break return includes diff -r 6d0acd812d98 -r 03e7a6efd960 devtools/fake.py --- a/devtools/fake.py Fri Aug 07 12:12:20 2009 +0200 +++ b/devtools/fake.py Fri Aug 07 12:12:50 2009 +0200 @@ -35,27 +35,21 @@ def sources(self): return {} -class FakeVReg(object): +class FakeVReg(dict): def __init__(self, schema=None, config=None): self.schema = schema self.config = config or FakeConfig() self.properties = {'ui.encoding': 'UTF8', 'ui.language': 'en', } + self.update({ + 'controllers' : {'login': []}, + 'views' : {}, + }) def property_value(self, key): return self.properties[key] - _registries = { - 'controllers' : [Mock(id='view'), Mock(id='login'), - Mock(id='logout'), Mock(id='edit')], - 'views' : [Mock(id='primary'), Mock(id='secondary'), - Mock(id='oneline'), Mock(id='list')], - } - - def registry_objects(self, name, oid=None): - return self._registries[name] - def etype_class(self, etype): class Entity(dict): e_schema = self.schema[etype] @@ -88,12 +82,12 @@ return None def base_url(self): - """return the root url of the application""" + """return the root url of the instance""" return BASE_URL def relative_path(self, includeparams=True): """return the normalized path of the request (ie at least relative - to the application's root, but some other normalization may be needed + to the instance's root, but some other normalization may be needed so that the returned path may be used to compare to generated urls """ if self._url.startswith(BASE_URL): @@ -154,6 +148,25 @@ return self.execute(*args, **kwargs) +# class FakeRequestNoCnx(FakeRequest): +# def get_session_data(self, key, default=None, pop=False): +# """return value associated to `key` in session data""" +# if pop: +# return self._session_data.pop(key, default) +# else: +# return self._session_data.get(key, default) + +# def set_session_data(self, key, value): +# """set value associated to `key` in session data""" +# self._session_data[key] = value + +# def del_session_data(self, key): +# try: +# del self._session_data[key] +# except KeyError: +# pass + + class FakeUser(object): login = 'toto' eid = 0 @@ -174,7 +187,7 @@ def execute(self, *args): pass unsafe_execute = execute - + def commit(self, *args): self.transaction_data.clear() def close(self, *args): diff -r 6d0acd812d98 -r 03e7a6efd960 devtools/fill.py --- a/devtools/fill.py Fri Aug 07 12:12:20 2009 +0200 +++ b/devtools/fill.py Fri Aug 07 12:12:50 2009 +0200 @@ -225,7 +225,7 @@ :param etype: the entity's type :type schema: cubicweb.schema.Schema - :param schema: the application schema + :param schema: the instance schema :type entity_num: int :param entity_num: the number of entities to insert @@ -325,7 +325,7 @@ def make_relations_queries(schema, edict, cursor, ignored_relations=(), existingrels=None): """returns a list of generated RQL queries for relations - :param schema: The application schema + :param schema: The instance schema :param e_dict: mapping between etypes and eids diff -r 6d0acd812d98 -r 03e7a6efd960 devtools/pkginfo.py --- a/devtools/pkginfo.py Fri Aug 07 12:12:20 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,126 +0,0 @@ -"""distutils / __pkginfo__ helpers for cubicweb applications - -:organization: Logilab -:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. -:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr -:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses -""" - -import os -from os.path import isdir, join - - -def get_distutils_datafiles(cube, i18n=True, recursive=False): - """ - :param cube: application cube's name - """ - data_files = [] - data_files += get_basepyfiles(cube) - data_files += get_webdatafiles(cube) - if i18n: - data_files += get_i18nfiles(cube) - data_files += get_viewsfiles(cube, recursive=recursive) - data_files += get_migrationfiles(cube) - data_files += get_schemafiles(cube) - return data_files - - - -## listdir filter funcs ################################################ -def nopyc_and_nodir(fname): - if isdir(fname) or fname.endswith('.pyc') or fname.endswith('~'): - return False - return True - -def no_version_control(fname): - if fname in ('CVS', '.svn', '.hg'): - return False - if fname.endswith('~'): - return False - return True - -def basepy_files(fname): - if fname.endswith('.py') and fname != 'setup.py': - return True - return False - -def chain(*filters): - def newfilter(fname): - for filterfunc in filters: - if not filterfunc(fname): - return False - return True - return newfilter - -def listdir_with_path(path='.', filterfunc=None): - if filterfunc: - return [join(path, fname) for fname in os.listdir(path) if filterfunc(join(path, fname))] - else: - return [join(path, fname) for fname in os.listdir(path)] - - -## data_files helpers ################################################## -CUBES_DIR = join('share', 'cubicweb', 'cubes') - -def get_i18nfiles(cube): - """returns i18n files in a suitable format for distutils's - data_files parameter - """ - i18ndir = join(CUBES_DIR, cube, 'i18n') - potfiles = [(i18ndir, listdir_with_path('i18n', chain(no_version_control, nopyc_and_nodir)))] - return potfiles - - -def get_viewsfiles(cube, recursive=False): - """returns views files in a suitable format for distutils's - data_files parameter - - :param recursive: include views' subdirs recursively if True - """ - if recursive: - datafiles = [] - for dirpath, dirnames, filenames in os.walk('views'): - filenames = [join(dirpath, fname) for fname in filenames - if nopyc_and_nodir(join(dirpath, fname))] - dirpath = join(CUBES_DIR, cube, dirpath) - datafiles.append((dirpath, filenames)) - return datafiles - else: - viewsdir = join(CUBES_DIR, cube, 'views') - return [(viewsdir, - listdir_with_path('views', filterfunc=nopyc_and_nodir))] - - -def get_basepyfiles(cube): - """returns cube's base python scripts (tali18n.py, etc.) - in a suitable format for distutils's data_files parameter - """ - return [(join(CUBES_DIR, cube), - [fname for fname in os.listdir('.') - if fname.endswith('.py') and fname != 'setup.py'])] - - -def get_webdatafiles(cube): - """returns web's data files (css, png, js, etc.) in a suitable - format for distutils's data_files parameter - """ - return [(join(CUBES_DIR, cube, 'data'), - listdir_with_path('data', filterfunc=no_version_control))] - - -def get_migrationfiles(cube): - """returns cube's migration scripts - in a suitable format for distutils's data_files parameter - """ - return [(join(CUBES_DIR, cube, 'migration'), - listdir_with_path('migration', no_version_control))] - - -def get_schemafiles(cube): - """returns cube's schema files - in a suitable format for distutils's data_files parameter - """ - return [(join(CUBES_DIR, cube, 'schema'), - listdir_with_path('schema', no_version_control))] - - diff -r 6d0acd812d98 -r 03e7a6efd960 devtools/stresstester.py --- a/devtools/stresstester.py Fri Aug 07 12:12:20 2009 +0200 +++ b/devtools/stresstester.py Fri Aug 07 12:12:50 2009 +0200 @@ -1,4 +1,4 @@ -""" Usage: %s [OPTIONS] +""" Usage: %s [OPTIONS] Stress test a CubicWeb repository @@ -151,8 +151,8 @@ user = raw_input('login: ') if password is None: password = getpass('password: ') - from cubicweb.cwconfig import application_configuration - config = application_configuration(args[0]) + from cubicweb.cwconfig import instance_configuration + config = instance_configuration(args[0]) # get local access to the repository print "Creating repo", prof_file repo = Repository(config, prof_file) diff -r 6d0acd812d98 -r 03e7a6efd960 devtools/test/data/bootstrap_cubes --- a/devtools/test/data/bootstrap_cubes Fri Aug 07 12:12:20 2009 +0200 +++ b/devtools/test/data/bootstrap_cubes Fri Aug 07 12:12:50 2009 +0200 @@ -1,1 +1,1 @@ -person, comment +person diff -r 6d0acd812d98 -r 03e7a6efd960 devtools/test/data/schema.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/devtools/test/data/schema.py Fri Aug 07 12:12:50 2009 +0200 @@ -0,0 +1,20 @@ +""" + +:organization: Logilab +:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. +:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr +:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses +""" +from yams.buildobjs import EntityType, SubjectRelation, String, Int, Date + +from cubes.person.schema import Person + +Person.add_relation(Date(), 'birthday') + +class Bug(EntityType): + title = String(maxsize=64, required=True, fulltextindexed=True) + severity = String(vocabulary=('important', 'normal', 'minor'), default='normal') + cost = Int() + description = String(maxsize=4096, fulltextindexed=True) + identical_to = SubjectRelation('Bug', symetric=True) + diff -r 6d0acd812d98 -r 03e7a6efd960 devtools/test/data/schema/Bug.sql --- a/devtools/test/data/schema/Bug.sql Fri Aug 07 12:12:20 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,5 +0,0 @@ -title ivarchar(64) not null -state CHOICE('open', 'rejected', 'validation pending', 'resolved') default 'open' -severity CHOICE('important', 'normal', 'minor') default 'normal' -cost integer -description ivarchar(4096) diff -r 6d0acd812d98 -r 03e7a6efd960 devtools/test/data/schema/Project.sql --- a/devtools/test/data/schema/Project.sql Fri Aug 07 12:12:20 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,6 +0,0 @@ -name ivarchar(64) not null -summary ivarchar(128) -vcsurl varchar(256) -reporturl varchar(256) -description ivarchar(1024) -url varchar(128) diff -r 6d0acd812d98 -r 03e7a6efd960 devtools/test/data/schema/Story.sql --- a/devtools/test/data/schema/Story.sql Fri Aug 07 12:12:20 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,5 +0,0 @@ -title ivarchar(64) not null -state CHOICE('open', 'rejected', 'validation pending', 'resolved') default 'open' -priority CHOICE('minor', 'normal', 'important') default 'normal' -cost integer -description ivarchar(4096) diff -r 6d0acd812d98 -r 03e7a6efd960 devtools/test/data/schema/Version.sql --- a/devtools/test/data/schema/Version.sql Fri Aug 07 12:12:20 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,3 +0,0 @@ -num varchar(16) not null -diem date -status CHOICE('planned', 'dev', 'published') default 'planned' diff -r 6d0acd812d98 -r 03e7a6efd960 devtools/test/data/schema/custom.py --- a/devtools/test/data/schema/custom.py Fri Aug 07 12:12:20 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,9 +0,0 @@ -""" - -:organization: Logilab -:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. -:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr -:license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses -""" -Person = import_erschema('Person') -Person.add_relation(Date(), 'birthday') diff -r 6d0acd812d98 -r 03e7a6efd960 devtools/test/data/schema/relations.rel --- a/devtools/test/data/schema/relations.rel Fri Aug 07 12:12:20 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,33 +0,0 @@ -Bug concerns Project inline -Story concerns Project inline - -Bug corrected_in Version inline CONSTRAINT E concerns P, X version_of P -Story done_in Version inline CONSTRAINT E concerns P, X version_of P - -Bug identical_to Bug symetric -Bug identical_to Story symetric -Story identical_to Story symetric - -Story depends_on Story -Story depends_on Bug -Bug depends_on Story -Bug depends_on Bug - -Bug see_also Bug symetric -Bug see_also Story symetric -Bug see_also Project symetric -Story see_also Story symetric -Story see_also Project symetric -Project see_also Project symetric - -Project uses Project - -Version version_of Project inline -Version todo_by CWUser - -Comment about Bug inline -Comment about Story inline -Comment about Comment inline - -CWUser interested_in Project - diff -r 6d0acd812d98 -r 03e7a6efd960 devtools/testlib.py --- a/devtools/testlib.py Fri Aug 07 12:12:20 2009 +0200 +++ b/devtools/testlib.py Fri Aug 07 12:12:50 2009 +0200 @@ -44,7 +44,7 @@ # compute how many entities by type we need to be able to satisfy relation constraint relmap = {} for rschema in schema.relations(): - if rschema.meta or rschema.is_final(): # skip meta relations + if rschema.is_final(): continue for subj, obj in rschema.iter_rdefs(): card = rschema.rproperty(subj, obj, 'cardinality') @@ -172,7 +172,8 @@ return validator.parse_string(output.strip()) - def view(self, vid, rset, req=None, template='main-template', **kwargs): + def view(self, vid, rset=None, req=None, template='main-template', + **kwargs): """This method tests the view `vid` on `rset` using `template` If no error occured while rendering the view, the HTML is analyzed @@ -183,7 +184,9 @@ """ req = req or rset and rset.req or self.request() req.form['vid'] = vid - view = self.vreg.select_view(vid, req, rset, **kwargs) + kwargs['rset'] = rset + viewsreg = self.vreg['views'] + view = viewsreg.select(vid, req, **kwargs) # set explicit test description if rset is not None: self.set_description("testing %s, mod=%s (%s)" % ( @@ -194,9 +197,11 @@ if template is None: # raw view testing, no template viewfunc = view.render else: - templateview = self.vreg.select_view(template, req, rset, view=view, **kwargs) kwargs['view'] = view - viewfunc = lambda **k: self.vreg.main_template(req, template, **kwargs) + templateview = viewsreg.select(template, req, **kwargs) + viewfunc = lambda **k: viewsreg.main_template(req, template, + **kwargs) + kwargs.pop('rset') return self._test_view(viewfunc, view, template, kwargs) @@ -268,7 +273,8 @@ req = rset.req only_once_vids = ('primary', 'secondary', 'text') req.data['ex'] = ValueError("whatever") - for vid, views in self.vreg.registry('views').items(): + viewsvreg = self.vreg['views'] + for vid, views in viewsvreg.items(): if vid[0] == '_': continue if rset.rowcount > 1 and vid in only_once_vids: @@ -278,7 +284,7 @@ and not issubclass(view, NotificationView)] if views: try: - view = self.vreg.select(views, req, rset) + view = viewsvreg.select_best(views, req, rset=rset) if view.linkable(): yield view else: @@ -291,19 +297,19 @@ def list_actions_for(self, rset): """returns the list of actions that can be applied on `rset`""" req = rset.req - for action in self.vreg.possible_objects('actions', req, rset): + for action in self.vreg['actions'].possible_objects(req, rset=rset): yield action def list_boxes_for(self, rset): """returns the list of boxes that can be applied on `rset`""" req = rset.req - for box in self.vreg.possible_objects('boxes', req, rset): + for box in self.vreg['boxes'].possible_objects(req, rset=rset): yield box def list_startup_views(self): """returns the list of startup views""" req = self.request() - for view in self.vreg.possible_views(req, None): + for view in self.vreg['views'].possible_views(req, None): if view.category == 'startupview': yield view.id else: @@ -371,9 +377,9 @@ rset2 = rset.limit(limit=1, offset=row) yield rset2 -def not_selected(vreg, vobject): +def not_selected(vreg, appobject): try: - vreg._selected[vobject.__class__] -= 1 + vreg._selected[appobject.__class__] -= 1 except (KeyError, AttributeError): pass @@ -381,26 +387,29 @@ from cubicweb.devtools.apptest import TestEnvironment env = testclass._env = TestEnvironment('data', configcls=testclass.configcls, requestcls=testclass.requestcls) - vreg = env.vreg - vreg._selected = {} - orig_select = vreg.__class__.select - def instr_select(self, *args, **kwargs): - selected = orig_select(self, *args, **kwargs) + for reg in env.vreg.values(): + reg._selected = {} try: - self._selected[selected.__class__] += 1 - except KeyError: - self._selected[selected.__class__] = 1 - except AttributeError: - pass # occurs on vreg used to restore database - return selected - vreg.__class__.select = instr_select + orig_select_best = reg.__class__.__orig_select_best + except: + orig_select_best = reg.__class__.select_best + def instr_select_best(self, *args, **kwargs): + selected = orig_select_best(self, *args, **kwargs) + try: + self._selected[selected.__class__] += 1 + except KeyError: + self._selected[selected.__class__] = 1 + except AttributeError: + pass # occurs on reg used to restore database + return selected + reg.__class__.select_best = instr_select_best + reg.__class__.__orig_select_best = orig_select_best def print_untested_objects(testclass, skipregs=('hooks', 'etypes')): - vreg = testclass._env.vreg - for registry, vobjectsdict in vreg.items(): - if registry in skipregs: + for regname, reg in testclass._env.vreg.iteritems(): + if regname in skipregs: continue - for vobjects in vobjectsdict.values(): - for vobject in vobjects: - if not vreg._selected.get(vobject): - print 'not tested', registry, vobject + for appobjects in reg.itervalues(): + for appobject in appobjects: + if not reg._selected.get(appobject): + print 'not tested', regname, appobject diff -r 6d0acd812d98 -r 03e7a6efd960 doc/book/en/.templates/layout.html --- a/doc/book/en/.templates/layout.html Fri Aug 07 12:12:20 2009 +0200 +++ b/doc/book/en/.templates/layout.html Fri Aug 07 12:12:50 2009 +0200 @@ -4,7 +4,7 @@ {%- endblock %} {%- set reldelim1 = reldelim1 is not defined and ' »' or reldelim1 %} {%- set reldelim2 = reldelim2 is not defined and ' |' or reldelim2 %} -{%- macro relbar %} +{%- macro relbar() %} {%- endmacro %} -{%- macro sidebar %} +{%- macro sidebar() %} {%- if builder != 'htmlhelp' %}
diff -r 6d0acd812d98 -r 03e7a6efd960 doc/book/en/Z012-create-instance.en.txt --- a/doc/book/en/Z012-create-instance.en.txt Fri Aug 07 12:12:20 2009 +0200 +++ b/doc/book/en/Z012-create-instance.en.txt Fri Aug 07 12:12:50 2009 +0200 @@ -8,14 +8,14 @@ -------------------- A *CubicWeb* instance is a container that -refers to cubes and configuration parameters for your web application. +refers to cubes and configuration parameters for your web instance. Each instance is stored as a directory in ``~/etc/cubicweb.d`` which enables -us to run your application. +us to run your instance. What is a cube? --------------- -Cubes represent data and basic building bricks of your web applications : +Cubes represent data and basic building bricks of your web instances : blogs, person, date, addressbook and a lot more. .. XXX They related to each other by a 'Schema' which is also the PostGres representation. @@ -35,7 +35,7 @@ ------------------------------------ We can create an instance to view our -application in a web browser. :: +instance in a web browser. :: cubicweb-ctl create blog myblog @@ -52,17 +52,17 @@ sufficient. You can allways modify the parameters later by editing configuration files. When a user/psswd is requested to access the database please use the login you create at the time you configured the database -(:ref:`ConfigurationPostgres`). +(:ref:`ConfigurationPostgresql`). It is important to distinguish here the user used to access the database and -the user used to login to the cubicweb application. When a *CubicWeb* application +the user used to login to the cubicweb instance. When a *CubicWeb* instance starts, it uses the login/psswd for the database to get the schema and handle low level transaction. But, when ``cubicweb-ctl create`` asks for -a manager login/psswd of *CubicWeb*, it refers to an application user -to administrate your web application. +a manager login/psswd of *CubicWeb*, it refers to an instance user +to administrate your web instance. The configuration files are stored in *~/etc/cubicweb.d/myblog/*. -To launch the web application, you just type :: +To launch the web instance, you just type :: cubicweb-ctl start myblog diff -r 6d0acd812d98 -r 03e7a6efd960 doc/book/en/admin/create-instance.rst --- a/doc/book/en/admin/create-instance.rst Fri Aug 07 12:12:20 2009 +0200 +++ b/doc/book/en/admin/create-instance.rst Fri Aug 07 12:12:50 2009 +0200 @@ -6,9 +6,8 @@ Instance creation ----------------- -Now that we created our cube, we can create an instance to view our -application in a web browser. To do so we will use a `all-in-one` -configuration to simplify things :: +Now that we created a cube, we can create an instance and access it via a web +browser. We will use a `all-in-one` configuration to simplify things :: cubicweb-ctl create -c all-in-one mycube myinstance @@ -21,15 +20,15 @@ sufficient. You can anyway modify the configuration later on by editing configuration files. When a user/psswd is requested to access the database please use the login you create at the time you configured the database -(:ref:`ConfigurationPostgres`). +(:ref:`ConfigurationPostgresql`). It is important to distinguish here the user used to access the database and the -user used to login to the cubicweb application. When an instance starts, it uses +user used to login to the cubicweb instance. When an instance starts, it uses the login/psswd for the database to get the schema and handle low level transaction. But, when :command:`cubicweb-ctl create` asks for a manager login/psswd of *CubicWeb*, it refers to the user you will use during the -development to administrate your web application. It will be possible, later on, -to use this user to create others users for your final web application. +development to administrate your web instance. It will be possible, later on, +to use this user to create others users for your final web instance. Instance administration @@ -60,5 +59,9 @@ upgrade ~~~~~~~ +The command is:: + + cubicweb-ctl upgrade myinstance + XXX write me diff -r 6d0acd812d98 -r 03e7a6efd960 doc/book/en/admin/gae.rst --- a/doc/book/en/admin/gae.rst Fri Aug 07 12:12:20 2009 +0200 +++ b/doc/book/en/admin/gae.rst Fri Aug 07 12:12:50 2009 +0200 @@ -1,5 +1,7 @@ .. -*- coding: utf-8 -*- +.. _GoogleAppEngineSource: + CubicWeb in Google AppEngine ============================ @@ -26,7 +28,7 @@ Please follow instructions on how to install *CubicWeb* framework -(:ref:`CubicWebInstallation`). +(:ref:`SetUpEnv`). Installation ------------ @@ -126,14 +128,14 @@ 'cubicweb'". They disappear after the first run of i18ninstance. .. note:: The command myapp/bin/laxctl i18ncube needs to be executed - only if your application is using cubes from cubicweb-apps. + only if your instance is using cubes from cubicweb-apps. Otherwise, please skip it. -You will never need to add new entries in the translation catalog. Instead we would -recommand you to use ``self.req._("msgId")`` in your application code -to flag new message id to add to the catalog, where ``_`` refers to -xgettext that is used to collect new strings to translate. -While running ``laxctl i18ncube``, new string will be added to the catalogs. +You will never need to add new entries in the translation catalog. Instead we +would recommand you to use ``self.req._("msgId")`` in your code to flag new +message id to add to the catalog, where ``_`` refers to xgettext that is used to +collect new strings to translate. While running ``laxctl i18ncube``, new string +will be added to the catalogs. Generating the data directory ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -152,18 +154,18 @@ $ python myapp/bin/laxctl genschema -Application configuration +Instance configuration ------------------------- Authentication ~~~~~~~~~~~~~~ -You have the option of using or not google authentication for your application. +You have the option of using or not google authentication for your instance. This has to be define in ``app.conf`` and ``app.yaml``. In ``app.conf`` modify the following variable::   - # does this application rely on google authentication service or not. + # does this instance rely on google authentication service or not. use-google-auth=no In ``app.yaml`` comment the `login: required` set by default in the handler:: @@ -177,7 +179,7 @@ -Quickstart : launch the application +Quickstart : launch the instance ----------------------------------- On Mac OS X platforms, drag that directory on the @@ -189,7 +191,7 @@ Once the local server is started, visit `http://MYAPP_URL/_load `_ and sign in as administrator. This will initialize the repository and enable you to log in into -the application and continue the installation. +the instance and continue the installation. You should be redirected to a page displaying a message `content initialized`. diff -r 6d0acd812d98 -r 03e7a6efd960 doc/book/en/admin/index.rst --- a/doc/book/en/admin/index.rst Fri Aug 07 12:12:20 2009 +0200 +++ b/doc/book/en/admin/index.rst Fri Aug 07 12:12:50 2009 +0200 @@ -7,7 +7,7 @@ ------------------------- This part is for installation and administration of the *CubicWeb* framework and -applications based on that framework. +instances based on that framework. .. toctree:: :maxdepth: 1 @@ -25,11 +25,11 @@ RQL logs -------- -You can configure the *CubicWeb* application to keep a log +You can configure the *CubicWeb* instance to keep a log of the queries executed against your database. To do so, -edit the configuration file of your application +edit the configuration file of your instance ``.../etc/cubicweb.d/myapp/all-in-one.conf`` and uncomment the variable ``query-log-file``:: - # web application query log file + # web instance query log file query-log-file=/tmp/rql-myapp.log diff -r 6d0acd812d98 -r 03e7a6efd960 doc/book/en/admin/instance-config.rst --- a/doc/book/en/admin/instance-config.rst Fri Aug 07 12:12:20 2009 +0200 +++ b/doc/book/en/admin/instance-config.rst Fri Aug 07 12:12:50 2009 +0200 @@ -6,11 +6,11 @@ While creating an instance, a configuration file is generated in:: - $ (CW_REGISTRY) / / .conf + $ (CW_INSTANCES_DIR) / / .conf For example:: - /etc/cubicweb.d/JPL/all-in-one.conf + /etc/cubicweb.d/myblog/all-in-one.conf It is a simple text file format INI. In the following description, each option name is prefixed with its own section and followed by its @@ -22,7 +22,7 @@ :`web.auth-model` [cookie]: authentication mode, cookie or http :`web.realm`: - realm of the application in http authentication mode + realm of the instance in http authentication mode :`web.http-session-time` [0]: period of inactivity of an HTTP session before it closes automatically. Duration in seconds, 0 meaning no expiration (or more exactly at the @@ -70,7 +70,7 @@ regular expression matching sites which could be "embedded" in the site (controllers 'embed') :`web.submit-url`: - url where the bugs encountered in the application can be mailed to + url where the bugs encountered in the instance can be mailed to RQL server configuration @@ -92,7 +92,7 @@ ----------------------------------- Web server side: -:`pyro-client.pyro-application-id`: +:`pyro-client.pyro-instance-id`: pyro identifier of RQL server (e.g. the instance name) RQL server side: @@ -107,7 +107,7 @@ hostname hosting pyro server name. If no value is specified, it is located by a request from broadcast :`pyro-name-server.pyro-ns-group` [cubicweb]: - pyro group in which to save the application + pyro group in which to save the instance Configuring e-mail @@ -125,9 +125,9 @@ :`email.smtp-port [25]`: SMTP server port to use for outgoing mail :`email.sender-name`: - name to use for outgoing mail of the application + name to use for outgoing mail of the instance :`email.sender-addr`: - address for outgoing mail of the application + address for outgoing mail of the instance :`email.default dest-addrs`: destination addresses by default, if used by the configuration of the dissemination of the model (separated by commas) diff -r 6d0acd812d98 -r 03e7a6efd960 doc/book/en/admin/multisources.rst --- a/doc/book/en/admin/multisources.rst Fri Aug 07 12:12:20 2009 +0200 +++ b/doc/book/en/admin/multisources.rst Fri Aug 07 12:12:50 2009 +0200 @@ -1,4 +1,6 @@ -Integrating some data from another instance -=========================================== +Multiple sources of data +======================== -XXX feed me \ No newline at end of file +Data sources include SQL, LDAP, RQL, mercurial and subversion. + +XXX feed me diff -r 6d0acd812d98 -r 03e7a6efd960 doc/book/en/admin/setup.rst --- a/doc/book/en/admin/setup.rst Fri Aug 07 12:12:20 2009 +0200 +++ b/doc/book/en/admin/setup.rst Fri Aug 07 12:12:50 2009 +0200 @@ -39,7 +39,7 @@ apt-get install cubicweb cubicweb-dev `cubicweb` installs the framework itself, allowing you to create -new applications. +new instances. `cubicweb-dev` installs the development environment allowing you to develop new cubes. @@ -69,22 +69,29 @@ hg fclone http://www.logilab.org/hg/forests/cubicweb See :ref:`MercurialPresentation` for more details about Mercurial. +When cloning a repository, you might be set in a development branch +(the 'default' branch). You should check that the branches of the +repositories are set to 'stable' (using `hg up stable` for each one) +if you do not intend to develop the framework itself. In both cases, make sure you have installed the dependencies (see appendixes for the list). -Postgres installation -````````````````````` +PostgreSQL installation +``````````````````````` -Please refer to the `Postgresql project online documentation`_. +Please refer to the `PostgreSQL project online documentation`_. -.. _`Postgresql project online documentation`: http://www.postgresql.org/ +.. _`PostgreSQL project online documentation`: http://www.postgresql.org/ -You need to install the three following packages: `postgres-8.3`, -`postgres-contrib-8.3` and `postgresql-plpython-8.3`. +You need to install the three following packages: `postgresql-8.3`, +`postgresql-contrib-8.3` and `postgresql-plpython-8.3`. -Then you can install: +Other dependencies +`````````````````` + +You can also install: * `pyro` if you wish the repository to be accessible through Pyro or if the client and the server are not running on the same machine @@ -105,19 +112,20 @@ Add the following lines to either `.bashrc` or `.bash_profile` to configure your development environment :: - export PYTHONPATH=/full/path/to/cubicweb-forest + export PYTHONPATH=/full/path/to/cubicweb-forest -If you installed the debian packages, no configuration is required. -Your new cubes will be placed in `/usr/share/cubicweb/cubes` and -your applications will be placed in `/etc/cubicweb.d`. +If you installed *CubicWeb* with packages, no configuration is required and your +new cubes will be placed in `/usr/share/cubicweb/cubes` and your instances +will be placed in `/etc/cubicweb.d`. -To use others directories then you will have to configure the -following environment variables as follows:: +You may run a system-wide install of *CubicWeb* in "user mode" and use it for +development by setting the following environment variable:: + export CW_MODE=user export CW_CUBES_PATH=~/lib/cubes - export CW_REGISTRY=~/etc/cubicweb.d/ - export CW_INSTANCE_DATA=$CW_REGISTRY - export CW_RUNTIME=/tmp + export CW_INSTANCES_DIR=~/etc/cubicweb.d/ + export CW_INSTANCES_DATA_DIR=$CW_INSTANCES_DIR + export CW_RUNTIME_DIR=/tmp .. note:: The values given above are our suggestions but of course @@ -129,22 +137,22 @@ -.. _ConfigurationPostgres: +.. _ConfigurationPostgresql: -Postgres configuration -`````````````````````` +PostgreSQL configuration +```````````````````````` .. note:: - If you already have an existing cluster and postgres server + If you already have an existing cluster and PostgreSQL server running, you do not need to execute the initilization step - of your Postgres database. + of your PostgreSQL database. -* First, initialize the database Postgres with the command ``initdb``. +* First, initialize the database PostgreSQL with the command ``initdb``. :: $ initdb -D /path/to/pgsql - Once initialized, start the database server Postgres + Once initialized, start the database server PostgreSQL with the command:: $ postgres -D /path/to/psql @@ -176,7 +184,7 @@ This login/password will be requested when you will create an instance with `cubicweb-ctl create` to initialize the database of - your application. + your instance. .. note:: The authentication method can be configured in ``pg_hba.conf``. @@ -194,13 +202,17 @@ MySql configuration ``````````````````` -Yout must add the following lines in /etc/mysql/my.cnf file:: +Yout must add the following lines in ``/etc/mysql/my.cnf`` file:: transaction-isolation = READ-COMMITTED default-storage-engine=INNODB default-character-set=utf8 max_allowed_packet = 128M +.. note:: + It is unclear whether mysql supports indexed string of arbitrary lenght or + not. + Pyro configuration ------------------ diff -r 6d0acd812d98 -r 03e7a6efd960 doc/book/en/admin/site-config.rst --- a/doc/book/en/admin/site-config.rst Fri Aug 07 12:12:20 2009 +0200 +++ b/doc/book/en/admin/site-config.rst Fri Aug 07 12:12:50 2009 +0200 @@ -5,7 +5,7 @@ .. image:: ../images/lax-book.03-site-config-panel.en.png -This panel allows you to configure the appearance of your application site. +This panel allows you to configure the appearance of your instance site. Six menus are available and we will go through each of them to explain how to use them. @@ -65,9 +65,9 @@ Boxes ~~~~~ -The application has already a pre-defined set of boxes you can use right away. +The instance has already a pre-defined set of boxes you can use right away. This configuration section allows you to place those boxes where you want in the -application interface to customize it. +instance interface to customize it. The available boxes are : @@ -82,7 +82,7 @@ * search box : search box * startup views box : box listing the configuration options available for - the application site, such as `Preferences` and `Site Configuration` + the instance site, such as `Preferences` and `Site Configuration` Components ~~~~~~~~~~ diff -r 6d0acd812d98 -r 03e7a6efd960 doc/book/en/annexes/cookbook.rst --- a/doc/book/en/annexes/cookbook.rst Fri Aug 07 12:12:20 2009 +0200 +++ b/doc/book/en/annexes/cookbook.rst Fri Aug 07 12:12:50 2009 +0200 @@ -9,9 +9,12 @@ * How to import LDAP users in *CubicWeb*? + [XXX distribute this script with cubicweb instead] + Here is a very useful script which enables you to import LDAP users - into your *CubicWeb* application by running the following: :: + into your *CubicWeb* instance by running the following: +.. sourcecode:: python import os import pwd @@ -66,8 +69,10 @@ * How to load data from a script? The following script aims at loading data within a script assuming pyro-nsd is - running and your application is configured with ``pyro-server=yes``, otherwise - you would not be able to use dbapi. :: + running and your instance is configured with ``pyro-server=yes``, otherwise + you would not be able to use dbapi. + +.. sourcecode:: python from cubicweb import dbapi diff -r 6d0acd812d98 -r 03e7a6efd960 doc/book/en/annexes/cubicweb-ctl.rst --- a/doc/book/en/annexes/cubicweb-ctl.rst Fri Aug 07 12:12:20 2009 +0200 +++ b/doc/book/en/annexes/cubicweb-ctl.rst Fri Aug 07 12:12:50 2009 +0200 @@ -26,8 +26,8 @@ ------------------------ * ``newcube``, create a new cube on the file system based on the name - given in the parameters. This command create a cube from an application - skeleton that includes default files required for debian packaging. + given in the parameters. This command create a cube from a skeleton + that includes default files required for debian packaging. Command to create an instance @@ -69,7 +69,7 @@ * ``db-check``, checks data integrity of an instance. If the automatic correction is activated, it is recommanded to create a dump before this operation. * ``schema-sync``, synchronizes the persistent schema of an instance with - the application schema. It is recommanded to create a dump before this operation. + the instance schema. It is recommanded to create a dump before this operation. Commands to maintain i18n catalogs ---------------------------------- diff -r 6d0acd812d98 -r 03e7a6efd960 doc/book/en/annexes/depends.rst --- a/doc/book/en/annexes/depends.rst Fri Aug 07 12:12:20 2009 +0200 +++ b/doc/book/en/annexes/depends.rst Fri Aug 07 12:12:50 2009 +0200 @@ -44,5 +44,8 @@ * indexer - http://www.logilab.org/project/indexer - http://pypi.python.org/pypi/indexer - included in the forest +* fyzz - http://www.logilab.org/project/fyzz - http://pypi.python.org/pypi/fyzz + - included in the forest + Any help with the packaging of CubicWeb for more than Debian/Ubuntu (including eggs, buildouts, etc) will be greatly appreciated. diff -r 6d0acd812d98 -r 03e7a6efd960 doc/book/en/annexes/faq.rst --- a/doc/book/en/annexes/faq.rst Fri Aug 07 12:12:20 2009 +0200 +++ b/doc/book/en/annexes/faq.rst Fri Aug 07 12:12:50 2009 +0200 @@ -14,80 +14,79 @@ Why does not CubicWeb have a template language ? ------------------------------------------------ - There are enough template languages out there. You can use your - preferred template language if you want. [explain how to use a - template language] +There are enough template languages out there. You can use your +preferred template language if you want. [explain how to use a +template language] - *CubicWeb* does not define its own templating language as this was - not our goal. Based on our experience, we realized that - we could gain productivity by letting designers use design tools - and developpers develop without the use of the templating language - as an intermediary that could not be anyway efficient for both parties. - Python is the templating language that we use in *CubicWeb*, but again, - it does not prevent you from using a templating language. +*CubicWeb* does not define its own templating language as this was +not our goal. Based on our experience, we realized that +we could gain productivity by letting designers use design tools +and developpers develop without the use of the templating language +as an intermediary that could not be anyway efficient for both parties. +Python is the templating language that we use in *CubicWeb*, but again, +it does not prevent you from using a templating language. - The reason template languages are not used in this book is that - experience has proved us that using pure python was less cumbersome. +The reason template languages are not used in this book is that +experience has proved us that using pure python was less cumbersome. Why do you think using pure python is better than using a template language ? ----------------------------------------------------------------------------- - Python is an Object Oriented Programming language and as such it - already provides a consistent and strong architecture and syntax - a templating language would not reach. +Python is an Object Oriented Programming language and as such it +already provides a consistent and strong architecture and syntax +a templating language would not reach. - When doing development, you need a real language and template - languages are not real languages. +When doing development, you need a real language and template +languages are not real languages. - Using Python enables developing applications for which code is - easier to maintain with real functions/classes/contexts - without the need of learning a new dialect. By using Python, - we use standard OOP techniques and this is a key factor in a - robust application. +Using Python instead of a template langage for describing the user interface +makes it to maintain with real functions/classes/contexts without the need of +learning a new dialect. By using Python, we use standard OOP techniques and +this is a key factor in a robust application. Why do you use the LGPL license to prevent me from doing X ? ------------------------------------------------------------ - LGPL means that *if* you redistribute your application, you need to - redistribute the changes you made to CubicWeb under the LGPL licence. +LGPL means that *if* you redistribute your application, you need to +redistribute the changes you made to CubicWeb under the LGPL licence. - Publishing a web site has nothing to do with redistributing - source code. A fair amount of companies use modified LGPL code - for internal use. And someone could publish a *CubicWeb* component - under a BSD licence for others to plug into a LGPL framework without - any problem. The only thing we are trying to prevent here is someone - taking the framework and packaging it as closed source to his own - clients. +Publishing a web site has nothing to do with redistributing +source code. A fair amount of companies use modified LGPL code +for internal use. And someone could publish a *CubicWeb* component +under a BSD licence for others to plug into a LGPL framework without +any problem. The only thing we are trying to prevent here is someone +taking the framework and packaging it as closed source to his own +clients. CubicWeb looks pretty recent. Is it stable ? -------------------------------------------- - It is constantly evolving, piece by piece. The framework has evolved since - 2001 and data has been migrated from one schema to the other ever since. There - is a well-defined way to handle data and schema migration. +It is constantly evolving, piece by piece. The framework has evolved since +2001 and data has been migrated from one schema to the other ever since. There +is a well-defined way to handle data and schema migration. Why is the RQL query language looking similar to X ? ----------------------------------------------------- - It may remind you of SQL but it is higher level than SQL, more like - SPARQL. Except that SPARQL did not exist when we started the project. - Having SPARQL as a query language has been in our backlog for years. +It may remind you of SQL but it is higher level than SQL, more like +SPARQL. Except that SPARQL did not exist when we started the project. +With version 3.4, CubicWeb has support for SPARQL. - That RQL language is what is going to make a difference with django- - like frameworks for several reasons. +That RQL language is what is going to make a difference with django- +like frameworks for several reasons. - 1. accessing data is *much* easier with it. One can write complex - queries with RQL that would be tedious to define and hard to maintain - using an object/filter suite of method calls. +1. accessing data is *much* easier with it. One can write complex + queries with RQL that would be tedious to define and hard to maintain + using an object/filter suite of method calls. - 2. it offers an abstraction layer allowing your applications to run - on multiple back-ends. That means not only various SQL backends - (postgresql, sqlite, mysql), but also multiple databases at the - same time, and also non-SQL data stores like LDAP directories and - subversion/mercurial repositories (see the `vcsfile` - component). Google App Engine is yet another supported target for - RQL. +2. it offers an abstraction layer allowing your applications to run + on multiple back-ends. That means not only various SQL backends + (postgresql, sqlite, mysql), but also multiple databases at the + same time, and also non-SQL data stores like LDAP directories and + subversion/mercurial repositories (see the `vcsfile` + component). Google App Engine is yet another supported target for + RQL. [copy answer from forum, explain why similar to sparql and why better than django and SQL] @@ -101,7 +100,9 @@ How is security implemented ? ------------------------------ - This is an example of how it works in our framework:: +This is an example of how it works in our framework: + +.. sourcecode:: python class Version(EntityType): """a version is defining the content of a particular project's @@ -111,16 +112,17 @@ 'update': ('managers', 'logilab', 'owners',), 'delete': ('managers', ), 'add': ('managers', 'logilab', - ERQLExpression('X version_of PROJ, U in_group G, ' - 'PROJ require_permission P, ' - 'P name "add_version", P require_group G'),)} + ERQLExpression('X version_of PROJ, U in_group G, ' + 'PROJ require_permission P, ' + 'P name "add_version", P require_group G'),)} - The above means that permission to read a Version is granted to any - user that is part of one of the groups 'managers', 'users', 'guests'. - The 'add' permission is granted to users in group 'managers' or - 'logilab' and to users in group G, if G is linked by a permission - entity named "add_version" to the version's project. - :: +The above means that permission to read a Version is granted to any +user that is part of one of the groups 'managers', 'users', 'guests'. +The 'add' permission is granted to users in group 'managers' or +'logilab' and to users in group G, if G is linked by a permission +entity named "add_version" to the version's project. + +.. sourcecode:: python class version_of(RelationType): """link a version to its project. A version is necessarily linked @@ -129,20 +131,20 @@ permissions = {'read': ('managers', 'users', 'guests',), 'delete': ('managers', ), 'add': ('managers', 'logilab', - RRQLExpression('O require_permission P, P name "add_version", - 'U in_group G, P require_group G'),) } + RRQLExpression('O require_permission P, P name "add_version", ' + 'U in_group G, P require_group G'),) } - You can find additional information in the section :ref:`security`. +You can find additional information in the section :ref:`security`. - [XXX what does the second example means in addition to the first one?] +[XXX what does the second example means in addition to the first one?] What is `Error while publishing rest text ...` ? ------------------------------------------------ - While modifying the description of an entity, you get an error message in - the application `Error while publishing ...` for Rest text and plain text. - The server returns a traceback like as follows :: +While modifying the description of an entity, you get an error message in +the instance `Error while publishing ...` for Rest text and plain text. +The server returns a traceback like as follows :: 2008-10-06 15:05:08 - (cubicweb.rest) ERROR: error while publishing ReST text Traceback (most recent call last): @@ -151,73 +153,69 @@ file = __builtin__.open(filename, mode, buffering) TypeError: __init__() takes at most 3 arguments (4 given) - - This can be fixed by applying the patch described in : - http://code.google.com/p/googleappengine/issues/detail?id=48 +This can be fixed by applying the patch described in : +http://code.google.com/p/googleappengine/issues/detail?id=48 What are hooks used for ? ------------------------- - Hooks are executed around (actually before or after) events. The - most common events are data creation, update and deletion. They - permit additional constraint checking (those not expressible at the - schema level), pre and post computations depending on data - movements. +Hooks are executed around (actually before or after) events. The +most common events are data creation, update and deletion. They +permit additional constraint checking (those not expressible at the +schema level), pre and post computations depending on data +movements. - As such, they are a vital part of the framework. +As such, they are a vital part of the framework. - Other kinds of hooks, called Operations, are available - for execution just before commit. +Other kinds of hooks, called Operations, are available +for execution just before commit. When should you define an HTML template rather than define a graphical component ? ---------------------------------------------------------------------------------- - An HTML template cannot contain code, hence it is only about static - content. A component is made of code and operations that apply on a - well defined context (request, result set). It enables much more - dynamic views. +An HTML template cannot contain code, hence it is only about static +content. A component is made of code and operations that apply on a +well defined context (request, result set). It enables much more +dynamic views. What is the difference between `AppRsetObject` and `AppObject` ? ---------------------------------------------------------------- - `AppRsetObject` instances are selected on a request and a result - set. `AppObject` instances are directly selected by id. +`AppRsetObject` instances are selected on a request and a result +set. `AppObject` instances are directly selected by id. How to update a database after a schema modification ? ------------------------------------------------------ - It depends on what has been modified in the schema. - - * Update of an attribute permissions and properties: - ``synchronize_eschema('MyEntity')``. +It depends on what has been modified in the schema. - * Update of a relation permissions and properties: - ``synchronize_rschema('MyRelation')``. +* Update the permissions and properties of an entity or a relation: + ``sync_schema_props_perms('MyEntityOrRelation')``. - * Add an attribute: ``add_attribute('MyEntityType', 'myattr')``. +* Add an attribute: ``add_attribute('MyEntityType', 'myattr')``. - * Add a relation: ``add_relation_definition('SubjRelation', 'MyRelation', 'ObjRelation')``. +* Add a relation: ``add_relation_definition('SubjRelation', 'MyRelation', 'ObjRelation')``. How to create an anonymous user ? --------------------------------- - This allows to bypass authentication for your site. In the - ``all-in-one.conf`` file of your instance, define the anonymous user - as follows :: +This allows to bypass authentication for your site. In the +``all-in-one.conf`` file of your instance, define the anonymous user +as follows :: - # login of the CubicWeb user account to use for anonymous user (if you want to - # allow anonymous) - anonymous-user=anon + # login of the CubicWeb user account to use for anonymous user (if you want to + # allow anonymous) + anonymous-user=anon - # password of the CubicWeb user account matching login - anonymous-password=anon + # password of the CubicWeb user account matching login + anonymous-password=anon - You also must ensure that this `anon` user is a registered user of - the DB backend. If not, you can create through the administation - interface of your instance by adding a user with the role `guests`. - This could be the admin account (for development - purposes, of course). +You also must ensure that this `anon` user is a registered user of +the DB backend. If not, you can create through the administation +interface of your instance by adding a user with the role `guests`. +This could be the admin account (for development +purposes, of course). .. note:: While creating a new instance, you can decide to allow access @@ -225,58 +223,60 @@ decribed above. -How to change the application logo ? +How to change the instance logo ? ------------------------------------ - There are two ways of changing the logo. +There are two ways of changing the logo. - 1. The easiest way to use a different logo is to replace the existing - ``logo.png`` in ``myapp/data`` by your prefered icon and refresh. - By default all application will look for a ``logo.png`` to be - rendered in the logo section. +1. The easiest way to use a different logo is to replace the existing + ``logo.png`` in ``myapp/data`` by your prefered icon and refresh. + By default all instance will look for a ``logo.png`` to be + rendered in the logo section. - .. image:: ../images/lax-book.06-main-template-logo.en.png + .. image:: ../images/lax-book.06-main-template-logo.en.png - 2. In your cube directory, you can specify which file to use for the logo. - This is configurable in ``mycube/data/external_resources``: :: +2. In your cube directory, you can specify which file to use for the logo. + This is configurable in ``mycube/data/external_resources``: :: - LOGO = DATADIR/path/to/mylogo.gif + LOGO = DATADIR/path/to/mylogo.gif - where DATADIR is ``mycube/data``. + where DATADIR is ``mycube/data``. How to configure a LDAP source ? -------------------------------- - Your instance's sources are defined in ``/etc/cubicweb.d/myapp/sources``. - Configuring an LDAP source is about declaring that source in your - instance configuration file such as: :: +Your instance's sources are defined in ``/etc/cubicweb.d/myapp/sources``. +Configuring an LDAP source is about declaring that source in your +instance configuration file such as: :: - [ldapuser] - adapter=ldapuser - # ldap host - host=myhost - # base DN to lookup for usres - user-base-dn=ou=People,dc=mydomain,dc=fr - # user search scope - user-scope=ONELEVEL - # classes of user - user-classes=top,posixAccount - # attribute used as login on authentication - user-login-attr=uid - # name of a group in which ldap users will be by default - user-default-group=users - # map from ldap user attributes to cubicweb attributes - user-attrs-map=gecos:email,uid:login + [ldapuser] + adapter=ldapuser + # ldap host + host=myhost + # base DN to lookup for usres + user-base-dn=ou=People,dc=mydomain,dc=fr + # user search scope + user-scope=ONELEVEL + # classes of user + user-classes=top,posixAccount + # attribute used as login on authentication + user-login-attr=uid + # name of a group in which ldap users will be by default + user-default-group=users + # map from ldap user attributes to cubicweb attributes + user-attrs-map=gecos:email,uid:login - Any change applied to configuration file requires to restart your - application. +Any change applied to configuration file requires to restart your +instance. I get NoSelectableObject exceptions, how do I debug selectors ? --------------------------------------------------------------- - You just need to put the appropriate context manager around view/component - selection (one standard place in in vreg.py) : :: +You just need to put the appropriate context manager around view/component +selection (one standard place in in vreg.py): + +.. sourcecode:: python def possible_objects(self, registry, *args, **kwargs): """return an iterator on possible objects in a registry for this result set @@ -291,40 +291,43 @@ except NoSelectableObject: continue - Don't forget the 'from __future__ improt with_statement' at the - module top-level. +Don't forget the 'from __future__ import with_statement' at the module +top-level. - This will yield additional WARNINGs, like this: - :: +This will yield additional WARNINGs, like this:: 2009-01-09 16:43:52 - (cubicweb.selectors) WARNING: selector one_line_rset returned 0 for How to format an entity date attribute ? ---------------------------------------- - If your schema has an attribute of type Date or Datetime, you might - want to format it. First, you should define your preferred format using - the site configuration panel ``http://appurl/view?vid=systempropertiesform`` - and then set ``ui.date`` and/or ``ui.datetime``. - Then in the view code, use:: +If your schema has an attribute of type Date or Datetime, you might +want to format it. First, you should define your preferred format using +the site configuration panel ``http://appurl/view?vid=systempropertiesform`` +and then set ``ui.date`` and/or ``ui.datetime``. +Then in the view code, use: + +.. sourcecode:: python self.format_date(entity.date_attribute) Can PostgreSQL and CubicWeb authentication work with kerberos ? ---------------------------------------------------------------- - If you have PostgreSQL set up to accept kerberos authentication, you can set - the db-host, db-name and db-user parameters in the `sources` configuration - file while leaving the password blank. It should be enough for your - application to connect to postgresql with a kerberos ticket. +If you have PostgreSQL set up to accept kerberos authentication, you can set +the db-host, db-name and db-user parameters in the `sources` configuration +file while leaving the password blank. It should be enough for your +instance to connect to postgresql with a kerberos ticket. How to load data from a script ? -------------------------------- - The following script aims at loading data within a script assuming pyro-nsd is - running and your application is configured with ``pyro-server=yes``, otherwise - you would not be able to use dbapi. :: +The following script aims at loading data within a script assuming pyro-nsd is +running and your instance is configured with ``pyro-server=yes``, otherwise +you would not be able to use dbapi. + +.. sourcecode:: python from cubicweb import dbapi @@ -337,24 +340,30 @@ What is the CubicWeb datatype corresponding to GAE datastore's UserProperty ? ----------------------------------------------------------------------------- - If you take a look at your application schema and - click on "display detailed view of metadata" you will see that there - is a Euser entity in there. That's the one that is modeling users. The - thing that corresponds to a UserProperty is a relationship between - your entity and the Euser entity. As in :: +If you take a look at your instance schema and +click on "display detailed view of metadata" you will see that there +is a Euser entity in there. That's the one that is modeling users. The +thing that corresponds to a UserProperty is a relationship between +your entity and the Euser entity. As in: + +.. sourcecode:: python class TodoItem(EntityType): text = String() todo_by = SubjectRelation('Euser') - [XXX check that cw handle users better by - mapping Google Accounts to local Euser entities automatically] +[XXX check that cw handle users better by mapping Google Accounts to local Euser +entities automatically] How to reset the password for user joe ? ---------------------------------------- - You need to generate a new encrypted password:: +If you want to reset the admin password for ``myinstance``, do:: + + $ cubicweb-ctl reset-admin-pwd myinstance + +You need to generate a new encrypted password:: $ python >>> from cubicweb.server.utils import crypt_password @@ -362,8 +371,17 @@ 'qHO8282QN5Utg' >>> - and paste it in the database:: +and paste it in the database:: $ psql mydb mydb=> update cw_cwuser set cw_upassword='qHO8282QN5Utg' where cw_login='joe'; UPDATE 1 + +I've just created a user in a group and it doesn't work ! +--------------------------------------------------------- + +You are probably getting errors such as :: + + remove {'PR': 'Project', 'C': 'CWUser'} from solutions since your_user has no read access to cost + +This is because you have to put your user in the "users" group. The user has to be in both groups. diff -r 6d0acd812d98 -r 03e7a6efd960 doc/book/en/annexes/rql/index.rst --- a/doc/book/en/annexes/rql/index.rst Fri Aug 07 12:12:20 2009 +0200 +++ b/doc/book/en/annexes/rql/index.rst Fri Aug 07 12:12:50 2009 +0200 @@ -8,5 +8,4 @@ intro language - dbapi implementation diff -r 6d0acd812d98 -r 03e7a6efd960 doc/book/en/annexes/rql/intro.rst --- a/doc/book/en/annexes/rql/intro.rst Fri Aug 07 12:12:20 2009 +0200 +++ b/doc/book/en/annexes/rql/intro.rst Fri Aug 07 12:12:50 2009 +0200 @@ -5,41 +5,38 @@ Goals of RQL ~~~~~~~~~~~~ -The goal is to have a language emphasizing the way of browsing -relations. As such, attributes will be regarded as cases of -special relations (in terms of implementation, the language -user should see virtually no difference between an attribute and a +The goal is to have a language emphasizing the way of browsing relations. As +such, attributes will be regarded as cases of special relations (in terms of +implementation, the user should see no difference between an attribute and a relation). -RQL is inspired by SQL but is the highest level. A knowledge of the -*CubicWeb* schema defining the application is necessary. - Comparison with existing languages ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SQL ``` -RQL builds on the features of SQL but is at a higher level -(the current implementation of RQL generates SQL). For that it is limited -to the way of browsing relations and introduces variables. -The user does not need to know the model underlying SQL, but the *CubicWeb* -schema defining the application. + +RQL may remind of SQL but works at a higher abstraction level (the *CubicWeb* +framework generates SQL from RQL to fetch data from relation databases). RQL is +focused on browsing relations. The user needs only to know about the *CubicWeb* +data model he is querying, but not about the underlying SQL model. + +Sparql +`````` + +The query language most similar to RQL is SPARQL_, defined by the W3C to serve +for the semantic web. Versa ````` -We should look in more detail, but here are already some ideas for -the moment ... Versa_ is the language most similar to what we wanted -to do, but the model underlying data being RDF, there is some -number of things such as namespaces or handling of the RDF types which -does not interest us. On the functionality level, Versa_ is very comprehensive -including through many functions of conversion and basic types manipulation, -which may need to be guided at one time or another. -Finally, the syntax is a little esoteric. -Sparql -`````` -The query language most similar to RQL is SPARQL_, defined by the W3C to serve -for the semantic web. +We should look in more detail, but here are already some ideas for the moment +... Versa_ is the language most similar to what we wanted to do, but the model +underlying data being RDF, there is some number of things such as namespaces or +handling of the RDF types which does not interest us. On the functionality +level, Versa_ is very comprehensive including through many functions of +conversion and basic types manipulation, which may need to be guided at one time +or another. Finally, the syntax is a little esoteric. The different types of queries diff -r 6d0acd812d98 -r 03e7a6efd960 doc/book/en/conf.py --- a/doc/book/en/conf.py Fri Aug 07 12:12:20 2009 +0200 +++ b/doc/book/en/conf.py Fri Aug 07 12:12:50 2009 +0200 @@ -51,7 +51,7 @@ # The short X.Y version. version = '0.54' # The full version, including alpha/beta/rc tags. -release = '3.2' +release = '3.4' # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: diff -r 6d0acd812d98 -r 03e7a6efd960 doc/book/en/development/cubes/available-cubes.rst --- a/doc/book/en/development/cubes/available-cubes.rst Fri Aug 07 12:12:20 2009 +0200 +++ b/doc/book/en/development/cubes/available-cubes.rst Fri Aug 07 12:12:50 2009 +0200 @@ -2,7 +2,7 @@ Available cubes --------------- -An application is based on several basic cubes. In the set of available +An instance is based on several basic cubes. In the set of available basic cubes we can find for example : Base entity types diff -r 6d0acd812d98 -r 03e7a6efd960 doc/book/en/development/cubes/layout.rst --- a/doc/book/en/development/cubes/layout.rst Fri Aug 07 12:12:20 2009 +0200 +++ b/doc/book/en/development/cubes/layout.rst Fri Aug 07 12:12:50 2009 +0200 @@ -80,7 +80,7 @@ * ``entities`` contains the entities definition (server side and web interface) * ``sobjects`` contains hooks and/or views notifications (server side only) * ``views`` contains the web interface components (web interface only) -* ``test`` contains tests related to the application (not installed) +* ``test`` contains tests related to the cube (not installed) * ``i18n`` contains message catalogs for supported languages (server side and web interface) * ``data`` contains data files for static content (images, css, javascripts) diff -r 6d0acd812d98 -r 03e7a6efd960 doc/book/en/development/datamodel/baseschema.rst --- a/doc/book/en/development/datamodel/baseschema.rst Fri Aug 07 12:12:20 2009 +0200 +++ b/doc/book/en/development/datamodel/baseschema.rst Fri Aug 07 12:12:50 2009 +0200 @@ -1,9 +1,9 @@ -Pre-defined schemas in the library ----------------------------------- +Pre-defined entities in the library +----------------------------------- The library defines a set of entity schemas that are required by the system -or commonly used in *CubicWeb* applications. +or commonly used in *CubicWeb* instances. Entity types used to store the schema @@ -18,7 +18,7 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * `CWUser`, system users * `CWGroup`, users groups -* `CWPermission`, used to configure the security of the application +* `CWPermission`, used to configure the security of the instance Entity types used to manage workflows ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -28,11 +28,11 @@ Other entity types ~~~~~~~~~~~~~~~~~~ -* `CWCache` -* `CWProperty`, used to configure the application +* `CWCache`, cache entities used to improve performances +* `CWProperty`, used to configure the instance * `EmailAddress`, email address, used by the system to send notifications to the users and also used by others optionnals schemas * `Bookmark`, an entity type used to allow a user to customize his links within - the application + the instance diff -r 6d0acd812d98 -r 03e7a6efd960 doc/book/en/development/datamodel/define-workflows.rst --- a/doc/book/en/development/datamodel/define-workflows.rst Fri Aug 07 12:12:20 2009 +0200 +++ b/doc/book/en/development/datamodel/define-workflows.rst Fri Aug 07 12:12:50 2009 +0200 @@ -2,8 +2,8 @@ .. _Workflow: -An Example: Workflow definition -=============================== +Define a Workflow +================= General ------- @@ -20,7 +20,7 @@ ----------------- We want to create a workflow to control the quality of the BlogEntry -submitted on your application. When a BlogEntry is created by a user +submitted on your instance. When a BlogEntry is created by a user its state should be `submitted`. To be visible to all, it has to be in the state `published`. To move it from `submitted` to `published`, we need a transition that we can call `approve_blogentry`. diff -r 6d0acd812d98 -r 03e7a6efd960 doc/book/en/development/datamodel/definition.rst --- a/doc/book/en/development/datamodel/definition.rst Fri Aug 07 12:12:20 2009 +0200 +++ b/doc/book/en/development/datamodel/definition.rst Fri Aug 07 12:12:50 2009 +0200 @@ -3,7 +3,7 @@ Yams *schema* ------------- -The **schema** is the core piece of a *CubicWeb* application as it defines +The **schema** is the core piece of a *CubicWeb* instance as it defines the handled data model. It is based on entity types that are either already defined in the *CubicWeb* standard library; or more specific types, that *CubicWeb* expects to find in one or more Python files under the directory @@ -24,9 +24,8 @@ They are implicitely imported (as well as the special the function "_" for translation :ref:`internationalization`). -The instance schema of an application is defined on all appobjects by a .schema -class attribute set on registration. It's an instance of -:class:`yams.schema.Schema`. +The instance schema is defined on all appobjects by a .schema class attribute set +on registration. It's an instance of :class:`yams.schema.Schema`. Entity type ~~~~~~~~~~~ diff -r 6d0acd812d98 -r 03e7a6efd960 doc/book/en/development/datamodel/inheritance.rst --- a/doc/book/en/development/datamodel/inheritance.rst Fri Aug 07 12:12:20 2009 +0200 +++ b/doc/book/en/development/datamodel/inheritance.rst Fri Aug 07 12:12:50 2009 +0200 @@ -2,4 +2,7 @@ Inheritance ----------- +When describing a data model, entities can inherit from other entities as is +common in object-oriented programming. + XXX WRITME diff -r 6d0acd812d98 -r 03e7a6efd960 doc/book/en/development/devcore/appobject.rst --- a/doc/book/en/development/devcore/appobject.rst Fri Aug 07 12:12:20 2009 +0200 +++ b/doc/book/en/development/devcore/appobject.rst Fri Aug 07 12:12:50 2009 +0200 @@ -19,9 +19,9 @@ At the recording, the following attributes are dynamically added to the *subclasses*: -* `vreg`, the `vregistry` of the application -* `schema`, the application schema -* `config`, the application configuration +* `vreg`, the `vregistry` of the instance +* `schema`, the instance schema +* `config`, the instance configuration We also find on instances, the following attributes: @@ -36,7 +36,7 @@ can be specified through the special parameter `method` (the connection is theoretically done automatically :). - * `datadir_url()`, returns the directory of the application data + * `datadir_url()`, returns the directory of the instance data (contains static files such as images, css, js...) * `base_url()`, shortcut to `req.base_url()` @@ -57,9 +57,9 @@ :Data formatting: * `format_date(date, date_format=None, time=False)` returns a string for a - mx date time according to application's configuration + mx date time according to instance's configuration * `format_time(time)` returns a string for a mx date time according to - application's configuration + instance's configuration :And more...: diff -r 6d0acd812d98 -r 03e7a6efd960 doc/book/en/development/devcore/index.rst --- a/doc/book/en/development/devcore/index.rst Fri Aug 07 12:12:20 2009 +0200 +++ b/doc/book/en/development/devcore/index.rst Fri Aug 07 12:12:50 2009 +0200 @@ -5,7 +5,6 @@ :maxdepth: 1 vreg.rst - selection.rst appobject.rst selectors.rst dbapi.rst diff -r 6d0acd812d98 -r 03e7a6efd960 doc/book/en/development/devcore/vreg.rst --- a/doc/book/en/development/devcore/vreg.rst Fri Aug 07 12:12:20 2009 +0200 +++ b/doc/book/en/development/devcore/vreg.rst Fri Aug 07 12:12:50 2009 +0200 @@ -27,7 +27,7 @@ Examples: -.. code-block:: python +.. sourcecode:: python # web/views/basecomponents.py def registration_callback(vreg): @@ -45,7 +45,7 @@ API d'enregistrement des objets ``````````````````````````````` -.. code-block:: python +.. sourcecode:: python register(obj, registryname=None, oid=None, clear=False) diff -r 6d0acd812d98 -r 03e7a6efd960 doc/book/en/development/devrepo/sessions.rst --- a/doc/book/en/development/devrepo/sessions.rst Fri Aug 07 12:12:20 2009 +0200 +++ b/doc/book/en/development/devrepo/sessions.rst Fri Aug 07 12:12:50 2009 +0200 @@ -5,21 +5,22 @@ There are three kinds of sessions. -* user sessions are the most common: they are related to users and +* `user sessions` are the most common: they are related to users and carry security checks coming with user credentials -* super sessions are children of ordinary user sessions and allow to +* `super sessions` are children of ordinary user sessions and allow to bypass security checks (they are created by calling unsafe_execute on a user session); this is often convenient in hooks which may touch data that is not directly updatable by users -* internal sessions have all the powers; they are also used in only a +* `internal sessions` have all the powers; they are also used in only a few situations where you don't already have an adequate session at hand, like: user authentication, data synchronisation in multi-source contexts -Do not confuse the session type with their connection mode, for -instance : 'in memory' or 'pyro'. +.. note:: + Do not confuse the session type with their connection mode, for + instance : 'in memory' or 'pyro'. [WRITE ME] diff -r 6d0acd812d98 -r 03e7a6efd960 doc/book/en/development/devweb/index.rst --- a/doc/book/en/development/devweb/index.rst Fri Aug 07 12:12:20 2009 +0200 +++ b/doc/book/en/development/devweb/index.rst Fri Aug 07 12:12:50 2009 +0200 @@ -1,7 +1,7 @@ Web development =============== -In this chapter, we will core api for web development in the CubicWeb framework. +In this chapter, we will describe the core api for web development in the *CubicWeb* framework. .. toctree:: :maxdepth: 1 diff -r 6d0acd812d98 -r 03e7a6efd960 doc/book/en/development/devweb/internationalization.rst --- a/doc/book/en/development/devweb/internationalization.rst Fri Aug 07 12:12:20 2009 +0200 +++ b/doc/book/en/development/devweb/internationalization.rst Fri Aug 07 12:12:50 2009 +0200 @@ -1,12 +1,12 @@ .. -*- coding: utf-8 -*- -.. _internationalisation: +.. _internationalization: -Internationalisation +Internationalization --------------------- -Cubicweb fully supports the internalization of it's content and interface. +Cubicweb fully supports the internalization of its content and interface. Cubicweb's interface internationalization is based on the translation project `GNU gettext`_. @@ -16,7 +16,7 @@ * in your Python code and cubicweb-tal templates : mark translatable strings -* in your application : handle the translation catalog +* in your instance : handle the translation catalog String internationalization ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -66,16 +66,18 @@ .. note:: We dont need to mark the translation strings of entities/relations - used by a particular application's schema as they are generated + used by a particular instance's schema as they are generated automatically. +If you need to add messages on top of those that can be found in the source, +you can create a file named `i18n/static-messages.pot`. Handle the translation catalog ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Once the internationalization is done in your application's code, you need -to populate and update the translation catalog. Cubicweb provides the -following commands for this purpose: +Once the internationalization is done in your code, you need to populate and +update the translation catalog. Cubicweb provides the following commands for this +purpose: * `i18ncubicweb` updates Cubicweb framework's translation @@ -83,28 +85,28 @@ need to use this command. * `i18ncube` updates the translation catalogs of *one particular - component* (or of all components). After this command is + cube* (or of all cubes). After this command is executed you must update the translation files *.po* in the "i18n" directory of your template. This command will of course not remove existing translations still in use. * `i18ninstance` recompile the translation catalogs of *one particular instance* (or of all instances) after the translation catalogs of - its components have been updated. This command is automatically + its cubes have been updated. This command is automatically called every time you create or update your instance. The compiled catalogs (*.mo*) are stored in the i18n//LC_MESSAGES of - application where `lang` is the language identifier ('en' or 'fr' + instance where `lang` is the language identifier ('en' or 'fr' for exemple). Example ``````` -You have added and/or modified some translation strings in your application -(after creating a new view or modifying the application's schema for exemple). +You have added and/or modified some translation strings in your cube +(after creating a new view or modifying the cube's schema for exemple). To update the translation catalogs you need to do: -1. `cubicweb-ctl i18ncube ` -2. Edit the /xxx.po files and add missing translations (empty `msgstr`) +1. `cubicweb-ctl i18ncube ` +2. Edit the /i18n/xxx.po files and add missing translations (empty `msgstr`) 3. `hg ci -m "updated i18n catalogs"` -4. `cubicweb-ctl i18ninstance ` +4. `cubicweb-ctl i18ninstance ` diff -r 6d0acd812d98 -r 03e7a6efd960 doc/book/en/development/devweb/views.rst --- a/doc/book/en/development/devweb/views.rst Fri Aug 07 12:12:20 2009 +0200 +++ b/doc/book/en/development/devweb/views.rst Fri Aug 07 12:12:50 2009 +0200 @@ -1,3 +1,6 @@ + +.. _Views: + Views ----- @@ -76,8 +79,7 @@ - Using `templatable`, `content_type` and HTTP cache configuration -.. code-block:: python - +.. sourcecode:: python class RSSView(XMLView): id = 'rss' @@ -88,11 +90,9 @@ cache_max_age = 60*60*2 # stay in http cache for 2 hours by default - - Using custom selector -.. code-block:: python - +.. sourcecode:: python class SearchForAssociationView(EntityView): """view called by the edition view when the user asks @@ -112,9 +112,9 @@ We'll show you now an example of a ``primary`` view and how to customize it. If you want to change the way a ``BlogEntry`` is displayed, just override -the method ``cell_call()`` of the view ``primary`` in ``BlogDemo/views.py`` :: +the method ``cell_call()`` of the view ``primary`` in ``BlogDemo/views.py``: -.. code-block:: python +.. sourcecode:: python from cubicweb.view import EntityView from cubicweb.selectors import implements @@ -148,7 +148,7 @@ Let us now improve the primary view of a blog -.. code-block:: python +.. sourcecode:: python class BlogPrimaryView(EntityView): id = 'primary' @@ -215,9 +215,9 @@ [FILLME] - XML views, binaries... ---------------------- + For views generating other formats than HTML (an image generated dynamically for example), and which can not simply be included in the HTML page generated by the main template (see above), you have to: diff -r 6d0acd812d98 -r 03e7a6efd960 doc/book/en/development/entityclasses/data-as-objects.rst --- a/doc/book/en/development/entityclasses/data-as-objects.rst Fri Aug 07 12:12:20 2009 +0200 +++ b/doc/book/en/development/entityclasses/data-as-objects.rst Fri Aug 07 12:12:50 2009 +0200 @@ -61,10 +61,10 @@ Tne :class:`AnyEntity` class ---------------------------- -To provide a specific behavior for each entity, we have to define -a class inheriting from `cubicweb.entities.AnyEntity`. In general, we -define this class in a module of `mycube.entities` package of an application -so that it will be available on both server and client side. +To provide a specific behavior for each entity, we have to define a class +inheriting from `cubicweb.entities.AnyEntity`. In general, we define this class +in `mycube.entities` module (or in a submodule if we want to split code among +multiple files) so that it will be available on both server and client side. The class `AnyEntity` is loaded dynamically from the class `Entity` (`cubciweb.entity`). We define a sub-class to add methods or to diff -r 6d0acd812d98 -r 03e7a6efd960 doc/book/en/development/entityclasses/index.rst --- a/doc/book/en/development/entityclasses/index.rst Fri Aug 07 12:12:20 2009 +0200 +++ b/doc/book/en/development/entityclasses/index.rst Fri Aug 07 12:12:50 2009 +0200 @@ -2,7 +2,7 @@ =============== In this chapter, we will introduce the objects that are used to handle -the data stored in the database. +the logic associated to the data stored in the database. .. toctree:: :maxdepth: 1 @@ -10,4 +10,4 @@ data-as-objects load-sort interfaces - more \ No newline at end of file + more diff -r 6d0acd812d98 -r 03e7a6efd960 doc/book/en/development/entityclasses/interfaces.rst --- a/doc/book/en/development/entityclasses/interfaces.rst Fri Aug 07 12:12:20 2009 +0200 +++ b/doc/book/en/development/entityclasses/interfaces.rst Fri Aug 07 12:12:50 2009 +0200 @@ -1,16 +1,19 @@ Interfaces ---------- +Same thing as object-oriented programming interfaces. + XXX how to define a cw interface Declaration of interfaces implemented by a class ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + XXX __implements__ Interfaces defined in the library ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. automodule:: cubicweb.interface - :members: -````````````` +automodule:: cubicweb.interface :members: + + diff -r 6d0acd812d98 -r 03e7a6efd960 doc/book/en/development/entityclasses/load-sort.rst --- a/doc/book/en/development/entityclasses/load-sort.rst Fri Aug 07 12:12:20 2009 +0200 +++ b/doc/book/en/development/entityclasses/load-sort.rst Fri Aug 07 12:12:50 2009 +0200 @@ -1,15 +1,17 @@ + +.. _FetchAttrs: Loaded attributes and default sorting management ```````````````````````````````````````````````` -* The class attribute `fetch_attrs` allows to defined in an entity class - a list of names of attributes or relations that should be automatically - loaded when we recover the entities of this type. In the case of relations, +* The class attribute `fetch_attrs` allows to define in an entity class a list + of names of attributes or relations that should be automatically loaded when + entities of this type are fetched from the database. In the case of relations, we are limited to *subject of cardinality `?` or `1`* relations. * The class method `fetch_order(attr, var)` expects an attribute (or relation) name as a parameter and a variable name, and it should return a string - to use in the requirement `ORDER BY` of an RQL query to automatically + to use in the requirement `ORDERBY` of an RQL query to automatically sort the list of entities of such type according to this attribute, or `None` if we do not want to sort on the attribute given in the parameter. By default, the entities are sorted according to their creation date. diff -r 6d0acd812d98 -r 03e7a6efd960 doc/book/en/development/index.rst --- a/doc/book/en/development/index.rst Fri Aug 07 12:12:20 2009 +0200 +++ b/doc/book/en/development/index.rst Fri Aug 07 12:12:50 2009 +0200 @@ -18,3 +18,4 @@ testing/index migration/index webstdlib/index + profiling/index diff -r 6d0acd812d98 -r 03e7a6efd960 doc/book/en/development/migration/index.rst --- a/doc/book/en/development/migration/index.rst Fri Aug 07 12:12:20 2009 +0200 +++ b/doc/book/en/development/migration/index.rst Fri Aug 07 12:12:50 2009 +0200 @@ -5,19 +5,19 @@ Migration ========= -One of the main concept in *CubicWeb* is to create incremental applications. -For this purpose, multiple actions are provided to facilitate the improvement -of an application, and in particular to handle the changes to be applied -to the data model, without loosing existing data. +One of the main design goals of *CubicWeb* was to support iterative and agile +development. For this purpose, multiple actions are provided to facilitate the +improvement of an instance, and in particular to handle the changes to be +applied to the data model, without loosing existing data. -The current version of an application model is provided in the file +The current version of a cube (and of cubicweb itself) is provided in the file `__pkginfo__.py` as a tuple of 3 integers. Migration scripts management ---------------------------- Migration scripts has to be located in the directory `migration` of your -application and named accordingly: +cube and named accordingly: :: @@ -67,11 +67,8 @@ * `interactive_mode`, boolean indicating that the script is executed in an interactive mode or not -* `appltemplversion`, application model version of the instance - -* `templversion`, installed application model version - -* `cubicwebversion`, installed cubicweb version +* `versions_map`, dictionary of migrated versions (key are cubes + names, including 'cubicweb', values are (from version, to version) * `confirm(question)`, function asking the user and returning true if the user answers yes, false otherwise (always returns true in @@ -84,16 +81,14 @@ * `checkpoint`, request confirming and executing a "commit" at checking point -* `repo_schema`, instance persisting schema (e.g. instance schema of the - current migration) +* `schema`, instance schema (readen from the database) -* `newschema`, installed schema on the file system (e.g. schema of +* `fsschema`, installed schema on the file system (e.g. schema of the updated model and cubicweb) -* `sqlcursor`, SQL cursor for very rare cases where it is really - necessary or beneficial to go through the sql +* `repo`, repository object -* `repo`, repository object +* `session`, repository session object Schema migration @@ -134,18 +129,11 @@ * `drop_relation_definition(subjtype, rtype, objtype, commit=True)`, removes a relation definition. -* `synchronize_permissions(ertype, commit=True)`, synchronizes permissions on - an entity type or relation type. - -* `synchronize_rschema(rtype, commit=True)`, synchronizes properties and permissions - on a relation type. - -* `synchronize_eschema(etype, commit=True)`, synchronizes properties and persmissions - on an entity type. - -* `synchronize_schema(commit=True)`, synchronizes the persisting schema with the - updated schema (but without adding or removing new entity types, relations types - or even relations definitions). +* `sync_schema_props_perms(ertype=None, syncperms=True, syncprops=True, syncrdefs=True, commit=True)`, + synchronizes properties and/or permissions on: + - the whole schema if ertype is None + - an entity or relation type schema if ertype is a string + - a relation definition if ertype is a 3-uple (subject, relation, object) * `change_relation_props(subjtype, rtype, objtype, commit=True, **kwargs)`, changes properties of a relation definition by using the named parameters of the properties @@ -204,7 +192,7 @@ accomplished otherwise or to repair damaged databases during interactive session. They are available in `repository` scripts: -* `sqlexec(sql, args=None, ask_confirm=True)`, executes an arbitrary SQL query +* `sql(sql, args=None, ask_confirm=True)`, executes an arbitrary SQL query on the system source * `add_entity_type_table(etype, commit=True)` * `add_relation_type_table(rtype, commit=True)` * `uninline_relation(rtype, commit=True)` diff -r 6d0acd812d98 -r 03e7a6efd960 doc/book/en/development/profiling/index.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/doc/book/en/development/profiling/index.rst Fri Aug 07 12:12:50 2009 +0200 @@ -0,0 +1,55 @@ +Profiling and performance +========================= + +If you feel that one of your pages takes more time than it should to be +generated, chances are that you're making too many RQL queries. Obviously, +there are other reasons but experience tends to show this is the first thing to +track down. Luckily, CubicWeb provides a configuration option to log RQL +queries. In your ``all-in-one.conf`` file, set the **query-log-file** option:: + + # web application query log file + query-log-file=~/myapp-rql.log + +Then restart your application, reload your page and stop your application. +The file ``myapp-rql.log`` now contains the list of RQL queries that were +executed during your test. It's a simple text file containing lines such as:: + + Any A WHERE X eid %(x)s, X lastname A {'x': 448} -- (0.002 sec, 0.010 CPU sec) + Any A WHERE X eid %(x)s, X firstname A {'x': 447} -- (0.002 sec, 0.000 CPU sec) + +The structure of each line is:: + + --