Commit f1eceddb authored by Sylvain Thénault's avatar Sylvain Thénault
Browse files

backport stable

......@@ -92,7 +92,9 @@ class MigrationHelper(object):
def __init__(self, config, interactive=True, verbosity=1):
self.config = config
self.config.init_log(logthreshold=logging.ERROR, debug=True)
if config:
# no config on shell to a remote instance
self.config.init_log(logthreshold=logging.ERROR, debug=True)
# 0: no confirmation, 1: only main commands confirmed, 2 ask for everything
self.verbosity = verbosity
self.need_wrap = True
......@@ -144,7 +146,7 @@ class MigrationHelper(object):
ctx['versions_map'] = vmap
if self.config.accept_mode('Any') and 'cubicweb' in vmap:
migrdir = self.config.migration_scripts_dir()
self.process_script(join(migrdir, 'bootstrapmigration_repository.py'))
self.cmd_process_script(join(migrdir, 'bootstrapmigration_repository.py'))
for cube, fromversion, toversion in toupgrade:
if cube == 'cubicweb':
migrdir = self.config.migration_scripts_dir()
......@@ -159,7 +161,7 @@ class MigrationHelper(object):
if prevversion is not None and version != prevversion:
self.cube_upgraded(cube, prevversion)
prevversion = version
self.process_script(script)
self.cmd_process_script(script)
self.cube_upgraded(cube, toversion)
else:
self.cube_upgraded(cube, toversion)
......@@ -261,7 +263,7 @@ type "exit" or Ctrl-D to quit the shell and resume operation"""
context[attr[4:]] = getattr(self, attr)
return context
def process_script(self, migrscript, funcname=None, *args, **kwargs):
def cmd_process_script(self, migrscript, funcname=None, *args, **kwargs):
"""execute a migration script
in interactive mode, display the migration script path, ask for
confirmation and execute it if confirmed
......
......@@ -685,10 +685,16 @@ given, appropriate sources for migration will be automatically selected \
class ShellCommand(Command):
"""Run an interactive migration shell. This is a python shell with
enhanced migration commands predefined in the namespace. An additional
argument may be given corresponding to a file containing commands to
execute in batch mode.
"""Run an interactive migration shell on an instance. This is a python shell
with enhanced migration commands predefined in the namespace. An additional
argument may be given corresponding to a file containing commands to execute
in batch mode.
By default it will connect to a local instance using an in memory
connection, unless -P option is specified, in which case you will be
connected through pyro. In the later case, you won't have access to
repository internals (session, etc...) so most migration commands won't be
available.
<instance>
the identifier of the instance to connect.
......@@ -698,46 +704,87 @@ class ShellCommand(Command):
options = (
('system-only',
{'short': 'S', 'action' : 'store_true',
'default': False,
'help': 'only connect to the system source when the instance is '
'using multiple sources. You can\'t use this option and the '
'--ext-sources option at the same time.'}),
'--ext-sources option at the same time.',
'group': 'local'
}),
('ext-sources',
{'short': 'E', 'type' : 'csv', 'metavar': '<sources>',
'default': None,
'help': "For multisources instances, specify to which sources the \
repository should connect to for upgrading. When unspecified or 'all' given, \
will connect to all defined sources. If 'migration' is given, appropriate \
sources for migration will be automatically selected.",
'group': 'local'
}),
('force',
{'short': 'f', 'action' : 'store_true',
'default' : False,
'help': 'don\'t check instance is up to date.'}
),
'help': 'don\'t check instance is up to date.',
'group': 'local'
}),
('pyro',
{'short': 'P', 'action' : 'store_true',
'help': 'connect to a running instance through Pyro.',
'group': 'remote',
}),
('pyro-ns-host',
{'short': 'H', 'type' : 'string', 'metavar': '<host[:port]>',
'help': 'Pyro name server host. If not set, will be detected by '
'using a broadcast query.',
'group': 'remote'
}),
)
def run(self, args):
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
sources = self.config.ext_sources
elif self.config.system_only:
sources = ('system',)
else:
sources = ('all',)
config.set_sources_mode(sources)
config.repairing = self.config.force
mih = config.migration_handler()
if args:
for arg in args:
mih.process_script(arg)
if self.config.pyro:
from cubicweb import AuthenticationError
from cubicweb.dbapi import connect
from cubicweb.server.utils import manager_userpasswd
from cubicweb.server.migractions import ServerMigrationHelper
while True:
try:
login, pwd = manager_userpasswd(msg=None)
cnx = connect(appid, login=login, password=pwd,
host=self.config.pyro_ns_host, mulcnx=False)
except AuthenticationError, ex:
print ex
except (KeyboardInterrupt, EOFError):
print
sys.exit(0)
else:
break
cnx.load_appobjects()
repo = cnx._repo
mih = ServerMigrationHelper(None, repo=repo, cnx=cnx,
# hack so it don't try to load fs schema
schema=1)
else:
mih.interactive_shell()
mih.shutdown()
config = cwcfg.config_for(appid)
if self.config.ext_sources:
assert not self.config.system_only
sources = self.config.ext_sources
elif self.config.system_only:
sources = ('system',)
else:
sources = ('all',)
config.set_sources_mode(sources)
config.repairing = self.config.force
mih = config.migration_handler()
try:
if args:
for arg in args:
mih.cmd_process_script(arg)
else:
mih.interactive_shell()
finally:
if not self.config.pyro:
mih.shutdown()
else:
cnx.close()
class RecompileInstanceCatalogsCommand(InstanceCommand):
......
......@@ -29,7 +29,7 @@ def clear_rtag_objects():
def use_interfaces(obj):
"""return interfaces used by the given object by searchinf for implements
"""return interfaces used by the given object by searching for implements
selectors, with a bw compat fallback to accepts_interfaces attribute
"""
from cubicweb.selectors import implements
......@@ -62,16 +62,28 @@ class CWRegistry(Registry):
pass
@deprecated('[3.6] select object, then use obj.render()')
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
def render(self, __oid, req, __fallback_oid=None, rset=None, initargs=None,
**kwargs):
"""Select object with the given id (`__oid`) then render it. If the
object isn't selectable, try to select fallback object if
`__fallback_oid` is specified.
If specified `initargs` is expected to be a dictionnary containing
arguments that should be given to selection (hence to object's __init__
as well), but not to render(). Other arbitrary keyword arguments will be
given to selection *and* to render(), and so should be handled by
object's call or cell_call method..
"""
if initargs is None:
initargs = kwargs
else:
initargs.update(kwargs)
try:
obj = self.select(__oid, req, rset=rset, **kwargs)
obj = self.select(__oid, req, rset=rset, **initargs)
except NoSelectableObject:
if __fallback_oid is None:
raise
obj = self.select(__fallback_oid, req, rset=rset, **kwargs)
obj = self.select(__fallback_oid, req, rset=rset, **initargs)
return obj.render(**kwargs)
@deprecated('[3.6] use select_or_none and test for obj.cw_propval("visible")')
......@@ -305,9 +317,11 @@ class CubicWebVRegistry(VRegistry):
self.register_objects(searchpath, force_reload=False)
# map lowered entity type names to their actual name
self.case_insensitive_etypes = {}
for etype in self.schema.entities():
etype = str(etype)
for eschema in self.schema.entities():
etype = str(eschema)
self.case_insensitive_etypes[etype.lower()] = etype
clear_cache(eschema, 'ordered_relations')
clear_cache(eschema, 'meta_attributes')
def _set_schema(self, schema):
"""set instance'schema"""
......@@ -379,7 +393,7 @@ class CubicWebVRegistry(VRegistry):
implemented_interfaces = set()
if 'Any' in self.get('etypes', ()):
for etype in self.schema.entities():
if etype.is_final():
if etype.final:
continue
cls = self['etypes'].etype_class(etype)
for iface in cls.__implements__:
......
......@@ -448,7 +448,7 @@ class Connection(object):
return self._repo.get_schema()
def load_appobjects(self, cubes=_MARKER, subpath=None, expand=True,
force_reload=None):
force_reload=None):
config = self.vreg.config
if cubes is _MARKER:
cubes = self._repo.get_cubes()
......
......@@ -144,13 +144,13 @@ def _generate_schema_pot(w, vreg, schema, libconfig=None, cube=None):
if etype not in libschema:
add_msg(w, etype)
add_msg(w, '%s_plural' % etype)
if not eschema.is_final():
if not eschema.final:
add_msg(w, 'This %s' % etype)
add_msg(w, 'New %s' % etype)
if eschema.description and not eschema.description in done:
done.add(eschema.description)
add_msg(w, eschema.description)
if eschema.is_final():
if eschema.final:
continue
for rschema, targetschemas, role in eschema.relation_definitions(True):
for tschema in targetschemas:
......@@ -201,7 +201,7 @@ def _generate_schema_pot(w, vreg, schema, libconfig=None, cube=None):
for subjschema in rschema.subjects():
if not subjschema in libsubjects:
add_msg(w, rtype, subjschema.type)
if not (schema.rschema(rtype).is_final() or rschema.symetric):
if not (schema.rschema(rtype).final or rschema.symetric):
if rschema not in no_context_rtypes:
libobjects = librschema and librschema.objects() or ()
for objschema in rschema.objects():
......
......@@ -347,7 +347,7 @@ class RelationsQueriesGenerator(object):
queries = []
# 1/ skip final relations and explictly ignored relations
rels = [rschema for rschema in self.schema.relations()
if not (rschema.is_final() or rschema in ignored_relations)]
if not (rschema.final or rschema in ignored_relations)]
# for each relation
# 2/ take each possible couple (subj, obj)
# 3/ analyze cardinality of relation
......@@ -397,9 +397,12 @@ class RelationsQueriesGenerator(object):
restrictions = ', '.join(c.restriction for c in constraints)
q += ', %s' % restrictions
# restrict object eids if possible
# XXX the attempt to restrict below in completely wrong
# disabling it for now
objeids = select(restrictions, self.cursor)
else:
objeids = oedict.get(obj, frozenset())
## objeids = oedict.get(obj, frozenset())
if subjcard in '?1' or objcard in '?1':
for subjeid, objeid in used:
if subjcard in '?1' and subjeid in subjeids:
......
......@@ -45,7 +45,6 @@ class CubicWebDebugger(Debugger):
file('/tmp/toto.html', 'w').write(data)
webbrowser.open('file:///tmp/toto.html')
def line_context_filter(line_no, center, before=3, after=None):
"""return true if line are in context
......@@ -715,7 +714,7 @@ def how_many_dict(schema, cursor, how_many, skip):
# compute how many entities by type we need to be able to satisfy relation constraint
relmap = {}
for rschema in schema.relations():
if rschema.is_final():
if rschema.final:
continue
for subj, obj in rschema.iter_rdefs():
card = rschema.rproperty(subj, obj, 'cardinality')
......@@ -759,6 +758,7 @@ class AutoPopulateTest(CubicWebTC):
def post_populate(self, cursor):
pass
@nocoverage
def auto_populate(self, how_many):
"""this method populates the database with `how_many` entities
......@@ -779,9 +779,9 @@ class AutoPopulateTest(CubicWebTC):
rset = cu.execute('%s X' % etype)
edict[str(etype)] = set(row[0] for row in rset.rows)
existingrels = {}
ignored_relations = SYSTEM_RELATIONS | set(self.ignored_relations)
ignored_relations = SYSTEM_RELATIONS + self.ignored_relations
for rschema in self.schema.relations():
if rschema.is_final() or rschema in ignored_relations:
if rschema.final or rschema in ignored_relations:
continue
rset = cu.execute('DISTINCT Any X,Y WHERE X %s Y' % rschema)
existingrels.setdefault(rschema.type, set()).update((x, y) for x, y in rset)
......
......@@ -11,13 +11,9 @@ The keywords are not case sensitive.
::
DISTINCT, INSERT, SET, DELETE,
WHERE, AND, OR, NOT, EXISTS,
IN, LIKE, UNION, WITH, BEING,
TRUE, FALSE, NULL, TODAY, NOW,
LIMIT, OFFSET,
HAVING, GROUPBY, ORDERBY, ASC, DESC
AND, ASC, BEING, DELETE, DESC, DISTINCT, EXISTS, FALSE, GROUPBY,
HAVING, ILIKE, IN, INSERT, LIKE, LIMIT, NOT, NOW, NULL, OFFSET,
OR, ORDERBY, SET, TODAY, TRUE, UNION, WHERE, WITH
Variables and Typing
~~~~~~~~~~~~~~~~~~~~
......@@ -76,7 +72,7 @@ Comparison operators
````````````````````
::
=, <, <=, >=, >, ~=, IN, LIKE
=, <, <=, >=, >, ~=, IN, LIKE, ILIKE
* The operator `=` is the default operator.
......@@ -88,6 +84,8 @@ Comparison operators
Any X WHERE X name ~= 'Th%'
Any X WHERE X name LIKE '%lt'
* The operator `ILIKE` is the case insensitive version of `LIKE`.
* The operator `IN` provides a list of possible values:
::
......
......@@ -63,7 +63,7 @@ class AnyEntity(Entity):
def dc_description(self, format='text/plain'):
"""return a suitable description for this entity"""
if self.e_schema.has_subject_relation('description'):
if 'description' in self.e_schema.subjrels:
return self.printable_value('description', format=format)
return u''
......
......@@ -446,15 +446,12 @@ class WorkflowableMixIn(object):
kwargs['comment'] = comment
if commentformat is not None:
kwargs['comment_format'] = commentformat
args = [('wf_info_for', 'E')]
kwargs['E'] = self.eid
kwargs['wf_info_for'] = self
if treid is not None:
args.append( ('by_transition', 'T') )
kwargs['T'] = treid
kwargs['by_transition'] = self.req.entity_from_eid(treid)
if tseid is not None:
args.append( ('to_state', 'S') )
kwargs['S'] = tseid
return self._cw.create_entity('TrInfo', *args, **kwargs)
kwargs['to_state'] = self.req.entity_from_eid(tseid)
return self._cw.create_entity('TrInfo', **kwargs)
def fire_transition(self, tr, comment=None, commentformat=None):
"""change the entity's state by firing transition of the given name in
......
......@@ -139,7 +139,7 @@ class Entity(AppObject, dict):
_fetchattrs = []
for attr in fetchattrs:
try:
rschema = eschema.subject_relation(attr)
rschema = eschema.subjrels[attr]
except KeyError:
cls.warning('skipping fetch_attr %s defined in %s (not found in schema)',
attr, cls.__regid__)
......@@ -150,7 +150,7 @@ class Entity(AppObject, dict):
selection.append(var)
restriction = '%s %s %s' % (mainvar, attr, var)
restrictions.append(restriction)
if not rschema.is_final():
if not rschema.final:
# XXX this does not handle several destination types
desttype = rschema.objects(eschema.type)[0]
card = rschema.rproperty(eschema, desttype, 'cardinality')[0]
......@@ -184,7 +184,7 @@ class Entity(AppObject, dict):
needcheck = not cls.e_schema.has_unique_values(mainattr)
else:
for rschema in cls.e_schema.subject_relations():
if rschema.is_final() and rschema != 'eid' and cls.e_schema.has_unique_values(rschema):
if rschema.final and rschema != 'eid' and cls.e_schema.has_unique_values(rschema):
mainattr = str(rschema)
needcheck = False
break
......@@ -383,7 +383,7 @@ class Entity(AppObject, dict):
assert self.has_eid()
execute = self._cw.execute
for rschema in self.e_schema.subject_relations():
if rschema.is_final() or rschema.meta:
if rschema.final or rschema.meta:
continue
# skip already defined relations
if getattr(self, rschema.type):
......@@ -431,7 +431,7 @@ class Entity(AppObject, dict):
def to_complete_relations(self):
"""by default complete final relations to when calling .complete()"""
for rschema in self.e_schema.subject_relations():
if rschema.is_final():
if rschema.final:
continue
if len(rschema.objects(self.e_schema)) > 1:
# ambigous relations, the querier doesn't handle
......@@ -591,14 +591,14 @@ class Entity(AppObject, dict):
if targettypes is None:
targettypes = rschema.objects(self.e_schema)
else:
restriction += 'E is IN (%s)' % ','.join(targettypes)
restriction += ', X is IN (%s)' % ','.join(targettypes)
card = greater_card(rschema, (self.e_schema,), targettypes, 0)
else:
restriction = 'E eid %%(x)s, X %s E' % rtype
if targettypes is None:
targettypes = rschema.subjects(self.e_schema)
else:
restriction += 'E is IN (%s)' % ','.join(targettypes)
restriction += ', X is IN (%s)' % ','.join(targettypes)
card = greater_card(rschema, targettypes, (self.e_schema,), 1)
if len(targettypes) > 1:
fetchattrs_list = []
......@@ -706,7 +706,7 @@ class Entity(AppObject, dict):
else raise `KeyError`
"""
res = self._related_cache['%s_%s' % (rtype, role)][entities]
if limit is not None:
if limit is not None and limit < len(res):
if entities:
res = res[:limit]
else:
......
......@@ -65,7 +65,7 @@ else:
edef.name = edef.name.encode()
assert re.match(r'[A-Z][A-Za-z0-9]*[a-z]+[0-9]*$', edef.name), repr(edef.name)
eschema = super(CubicWebSchema, self).add_entity_type(edef)
if not eschema.is_final():
if not eschema.final:
# automatically add the eid relation to non final entity types
rdef = ybo.RelationDefinition(eschema.type, 'eid', 'Bytes',
cardinality='?1', uid=True)
......
......@@ -67,7 +67,7 @@ def entity_types_no_count(self, eschemas):
"""
req = self.req
for eschema in eschemas:
if eschema.is_final() or not (eschema.has_perm(req, 'read') or
if eschema.final or not (eschema.has_perm(req, 'read') or
eschema.has_local_role('read')):
continue
etype = eschema.type
......
......@@ -175,7 +175,7 @@ class ContentClear(StartupView):
# XXX should use unsafe_execute with all hooks deactivated
# XXX step by catching datastore errors?
for eschema in self.schema.entities():
if eschema.is_final() or eschema in self.skip_etypes:
if eschema.final or eschema in self.skip_etypes:
continue
self.req.execute('DELETE %s X' % eschema)
self.w(u'deleted all %s entities<br/>' % eschema)
......
......@@ -148,7 +148,7 @@ class Model(entities.AnyEntity):
def __initialize__(cls):
super(Model, cls).__initialize__()
cls._attributes = frozenset(rschema for rschema in cls.e_schema.subject_relations()
if rschema.is_final())
if rschema.final)
def __init__(self, *args, **kwargs):
# db.Model prototype:
......@@ -163,7 +163,7 @@ class Model(entities.AnyEntity):
super(Model, self).__init__(None, None)
# if Model instances are given in kwargs, turn them into db model
for key, val in kwargs.iteritems():
if key in self.e_schema.subject_relations() and not self.e_schema.schema[key].is_final():
if key in self.e_schema.subject_relations() and not self.e_schema.schema[key].final:
if isinstance(kwargs, (list, tuple)):
val = [isinstance(x, Model) and x._dbmodel or x for x in val]
elif isinstance(val, Model):
......
......@@ -90,7 +90,7 @@ def init_persistent_schema(ssession, schema):
eschema = schema.eschema('CWEType')
execute(rql, {'name': u'CWEType', 'descr': unicode(eschema.description)})
for eschema in schema.entities():
if eschema.is_final() or eschema == 'CWEType':
if eschema.final or eschema == 'CWEType':
continue
execute(rql, {'name': unicode(eschema),
'descr': unicode(eschema.description)})
......
......@@ -13,7 +13,7 @@ class SQLGenAnnotator(object):
def __init__(self, schema):
self.schema = schema
self.nfdomain = frozenset(eschema.type for eschema in schema.entities()
if not eschema.is_final())
if not eschema.final)
def annotate(self, rqlst):
rqlst.has_text_query = False
rqlst.need_distinct = False
......
......@@ -156,7 +156,7 @@ class VariableSelection(Restriction):
myvar = self.rhs.name
ovar = self.var.name
rtype = self.rtype
if self.schema.rschema(rtype).is_final():
if self.schema.rschema(rtype).final:
# should be detected by rql.stcheck: "Any C WHERE NOT X attr C" doesn't make sense
#if self._not:
# raise NotImplementedError()
......@@ -595,7 +595,7 @@ class RQLInterpreter(object):
# ok, we *may* process this Not node (not implemented error will be
# raised later if we can't)
extra[node.parent] = True
if rschema.is_final():
if rschema.final:
self._visit_final_relation(rschema, node, constraints, extra)
elif neged:
self._visit_non_final_neged_relation(rschema, node, constraints)
......
......@@ -63,8 +63,11 @@ class IntegrityHook(hook.Hook):
__abstract__ = True
category = 'integrity'
class UserIntegrityHook(IntegrityHook):
__select__ == IntegrityHook.__select__ & ~regular_session()
class CheckCardinalityHook(IntegrityHook):
class CheckCardinalityHook(UserIntegrityHook):
"""check cardinalities are satisfied"""
__regid__ = 'checkcard'
events = ('after_add_entity', 'before_delete_relation')
......@@ -139,7 +142,7 @@ class _CheckConstraintsOp(hook.LateOperation):
pass
class CheckConstraintHook(IntegrityHook):
class CheckConstraintHook(UserIntegrityHook):
"""check the relation satisfy its constraints
this is delayed to a precommit time operation since other relation which
......@@ -155,7 +158,7 @@ class CheckConstraintHook(IntegrityHook):
_CheckConstraintsOp(self._cw, constraints=constraints,
rdef=(self.eidfrom, self.rtype, self.eidto))
class CheckAttributeConstraintHook(IntegrityHook):
class CheckAttributeConstraintHook(UserIntegrityHook):
"""check the attribute relation satisfy its constraints
this is delayed to a precommit time operation since other relation which
......@@ -168,7 +171,7 @@ class CheckAttributeConstraintHook(IntegrityHook):
schema = self._cw.vreg.schema
entity = self.entity
for attr in entity.edited_attributes:
if schema.rschema(attr).is_final():
if schema.rschema(attr).final:
constraints = [c for c in entity.e_schema.constraints(attr)
if isinstance(c, RQLVocabularyConstraint)]
if constraints:
......@@ -176,7 +179,7 @@ class CheckAttributeConstraintHook(IntegrityHook):
rdef=(entity.eid, attr, None))
class CheckUniqueHook(IntegrityHook):
class CheckUniqueHook(UserIntegrityHook):
__regid__ = 'checkunique'
events = ('before_add_entity', 'before_update_entity')
......@@ -184,7 +187,7 @@ class CheckUniqueHook(IntegrityHook):
entity = self.entity
eschema = entity.e_schema
for attr in entity.edited_attributes:
if eschema.subject_relation(attr).is_final() and \
if eschema.subject_relation(attr).final and \
eschema.has_unique_values(attr):
val = entity[attr]
if val is None:
......@@ -253,7 +256,7 @@ class DontRemoveOwnersGroupHook(IntegrityHook):
self.entity['name'] = newname
class TidyHtmlFields(IntegrityHook):
class TidyHtmlFields(UserIntegrityHook):
"""tidy HTML in rich text strings"""
__regid__ = 'htmltidy'
events = ('before_add_entity', 'before_update_entity')
......
......@@ -80,14 +80,18 @@ class SetIsHook(MetaDataHook):