cubicweb/schema.py
branch3.25
changeset 12210 3fa6c9ef2f51
parent 12086 39c9e548f0ce
child 12258 46a8146f9703
equal deleted inserted replaced
12207:2fc04786dd36 12210:3fa6c9ef2f51
     1 # copyright 2003-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
     1 # copyright 2003 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
     2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
     2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
     3 #
     3 #
     4 # This file is part of CubicWeb.
     4 # This file is part of CubicWeb.
     5 #
     5 #
     6 # CubicWeb is free software: you can redistribute it and/or modify it under the
     6 # CubicWeb is free software: you can redistribute it and/or modify it under the
    17 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
    17 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
    18 """classes to define schemas for CubicWeb"""
    18 """classes to define schemas for CubicWeb"""
    19 
    19 
    20 from __future__ import print_function
    20 from __future__ import print_function
    21 
    21 
    22 import pkgutil
       
    23 import re
    22 import re
    24 from os.path import join, basename
    23 from os.path import join
    25 from hashlib import md5
    24 from hashlib import md5
    26 from logging import getLogger
    25 from logging import getLogger
    27 from warnings import warn
    26 from warnings import warn
    28 
    27 
    29 from six import PY2, text_type, string_types, add_metaclass
    28 from six import PY2, text_type, string_types, add_metaclass
    30 from six.moves import range
    29 from six.moves import range
    31 
    30 
    32 from logilab.common import tempattr
       
    33 from logilab.common.decorators import cached, clear_cache, monkeypatch, cachedproperty
    31 from logilab.common.decorators import cached, clear_cache, monkeypatch, cachedproperty
    34 from logilab.common.logging_ext import set_log_methods
    32 from logilab.common.logging_ext import set_log_methods
    35 from logilab.common.deprecation import deprecated
    33 from logilab.common.deprecation import deprecated
    36 from logilab.common.textutils import splitstrip
    34 from logilab.common.textutils import splitstrip
    37 from logilab.common.graph import get_cycles
    35 from logilab.common.graph import get_cycles
    42     RelationDefinitionSchema, PermissionMixIn, role_name
    40     RelationDefinitionSchema, PermissionMixIn, role_name
    43 from yams.constraints import (BaseConstraint, FormatConstraint,
    41 from yams.constraints import (BaseConstraint, FormatConstraint,
    44                               cstr_json_dumps, cstr_json_loads)
    42                               cstr_json_dumps, cstr_json_loads)
    45 from yams.reader import (CONSTRAINTS, PyFileReader, SchemaLoader,
    43 from yams.reader import (CONSTRAINTS, PyFileReader, SchemaLoader,
    46                          cleanup_sys_modules, fill_schema_from_namespace)
    44                          cleanup_sys_modules, fill_schema_from_namespace)
    47 
    45 from yams.buildobjs import _add_relation as yams_add_relation
    48 from rql import parse, nodes, RQLSyntaxError, TypeResolverException
    46 
       
    47 from rql import parse, nodes, stmts, RQLSyntaxError, TypeResolverException
    49 from rql.analyze import ETypeResolver
    48 from rql.analyze import ETypeResolver
    50 
    49 
    51 import cubicweb
    50 import cubicweb
       
    51 from cubicweb import server
    52 from cubicweb import ETYPE_NAME_MAP, ValidationError, Unauthorized, _
    52 from cubicweb import ETYPE_NAME_MAP, ValidationError, Unauthorized, _
    53 
    53 
    54 from cubicweb import server
       
    55 
    54 
    56 PURE_VIRTUAL_RTYPES = set(('identity', 'has_text',))
    55 PURE_VIRTUAL_RTYPES = set(('identity', 'has_text',))
    57 VIRTUAL_RTYPES = set(('eid', 'identity', 'has_text',))
    56 VIRTUAL_RTYPES = set(('eid', 'identity', 'has_text',))
    58 
    57 
    59 # set of meta-relations available for every entity types
    58 # set of meta-relations available for every entity types
   600 def ERSchema_display_name(self, req, form='', context=None):
   599 def ERSchema_display_name(self, req, form='', context=None):
   601     """return a internationalized string for the entity/relation type name in
   600     """return a internationalized string for the entity/relation type name in
   602     a given form
   601     a given form
   603     """
   602     """
   604     return display_name(req, self.type, form, context)
   603     return display_name(req, self.type, form, context)
       
   604 
       
   605 
   605 ERSchema.display_name = ERSchema_display_name
   606 ERSchema.display_name = ERSchema_display_name
   606 
   607 
   607 
   608 
   608 @cached
   609 @cached
   609 def get_groups(self, action):
   610 def get_groups(self, action):
   619     assert action in self.ACTIONS, action
   620     assert action in self.ACTIONS, action
   620     try:
   621     try:
   621         return frozenset(g for g in self.permissions[action] if isinstance(g, string_types))
   622         return frozenset(g for g in self.permissions[action] if isinstance(g, string_types))
   622     except KeyError:
   623     except KeyError:
   623         return ()
   624         return ()
       
   625 
       
   626 
   624 PermissionMixIn.get_groups = get_groups
   627 PermissionMixIn.get_groups = get_groups
   625 
   628 
   626 
   629 
   627 @cached
   630 @cached
   628 def get_rqlexprs(self, action):
   631 def get_rqlexprs(self, action):
   638     assert action in self.ACTIONS, action
   641     assert action in self.ACTIONS, action
   639     try:
   642     try:
   640         return tuple(g for g in self.permissions[action] if not isinstance(g, string_types))
   643         return tuple(g for g in self.permissions[action] if not isinstance(g, string_types))
   641     except KeyError:
   644     except KeyError:
   642         return ()
   645         return ()
       
   646 
       
   647 
   643 PermissionMixIn.get_rqlexprs = get_rqlexprs
   648 PermissionMixIn.get_rqlexprs = get_rqlexprs
   644 
   649 
   645 
   650 
   646 def set_action_permissions(self, action, permissions):
   651 def set_action_permissions(self, action, permissions):
   647     """set the groups and rql expressions allowing to perform <action> on
   652     """set the groups and rql expressions allowing to perform <action> on
   654     :param permissions: the groups and rql expressions allowing the given action
   659     :param permissions: the groups and rql expressions allowing the given action
   655     """
   660     """
   656     orig_set_action_permissions(self, action, tuple(permissions))
   661     orig_set_action_permissions(self, action, tuple(permissions))
   657     clear_cache(self, 'get_rqlexprs')
   662     clear_cache(self, 'get_rqlexprs')
   658     clear_cache(self, 'get_groups')
   663     clear_cache(self, 'get_groups')
       
   664 
       
   665 
   659 orig_set_action_permissions = PermissionMixIn.set_action_permissions
   666 orig_set_action_permissions = PermissionMixIn.set_action_permissions
   660 PermissionMixIn.set_action_permissions = set_action_permissions
   667 PermissionMixIn.set_action_permissions = set_action_permissions
   661 
   668 
   662 
   669 
   663 def has_local_role(self, action):
   670 def has_local_role(self, action):
   671     if self.get_rqlexprs(action):
   678     if self.get_rqlexprs(action):
   672         return True
   679         return True
   673     if action in ('update', 'delete'):
   680     if action in ('update', 'delete'):
   674         return 'owners' in self.get_groups(action)
   681         return 'owners' in self.get_groups(action)
   675     return False
   682     return False
       
   683 
       
   684 
   676 PermissionMixIn.has_local_role = has_local_role
   685 PermissionMixIn.has_local_role = has_local_role
   677 
   686 
   678 
   687 
   679 def may_have_permission(self, action, req):
   688 def may_have_permission(self, action, req):
   680     if action != 'read' and not (self.has_local_role('read') or
   689     if action != 'read' and not (self.has_local_role('read') or
   681                                  self.has_perm(req, 'read')):
   690                                  self.has_perm(req, 'read')):
   682         return False
   691         return False
   683     return self.has_local_role(action) or self.has_perm(req, action)
   692     return self.has_local_role(action) or self.has_perm(req, action)
       
   693 
       
   694 
   684 PermissionMixIn.may_have_permission = may_have_permission
   695 PermissionMixIn.may_have_permission = may_have_permission
   685 
   696 
   686 
   697 
   687 def has_perm(self, _cw, action, **kwargs):
   698 def has_perm(self, _cw, action, **kwargs):
   688     """return true if the action is granted globally or locally"""
   699     """return true if the action is granted globally or locally"""
   689     try:
   700     try:
   690         self.check_perm(_cw, action, **kwargs)
   701         self.check_perm(_cw, action, **kwargs)
   691         return True
   702         return True
   692     except Unauthorized:
   703     except Unauthorized:
   693         return False
   704         return False
       
   705 
       
   706 
   694 PermissionMixIn.has_perm = has_perm
   707 PermissionMixIn.has_perm = has_perm
   695 
   708 
   696 
   709 
   697 def check_perm(self, _cw, action, **kwargs):
   710 def check_perm(self, _cw, action, **kwargs):
   698     # NB: _cw may be a server transaction or a request object.
   711     # NB: _cw may be a server transaction or a request object.
   732                                    for rqlexpr in self.get_rqlexprs(action)]))
   745                                    for rqlexpr in self.get_rqlexprs(action)]))
   733     if any(rqlexpr.check(_cw, **kwargs)
   746     if any(rqlexpr.check(_cw, **kwargs)
   734            for rqlexpr in self.get_rqlexprs(action)):
   747            for rqlexpr in self.get_rqlexprs(action)):
   735         return
   748         return
   736     raise Unauthorized(action, str(self))
   749     raise Unauthorized(action, str(self))
       
   750 
       
   751 
   737 PermissionMixIn.check_perm = check_perm
   752 PermissionMixIn.check_perm = check_perm
   738 
   753 
   739 
   754 
   740 CubicWebRelationDefinitionSchema._RPROPERTIES['eid'] = None
   755 CubicWebRelationDefinitionSchema._RPROPERTIES['eid'] = None
   741 # remember rproperties defined at this point. Others will have to be serialized in
   756 # remember rproperties defined at this point. Others will have to be serialized in
   816 
   831 
   817     def main_attribute(self):
   832     def main_attribute(self):
   818         """convenience method that returns the *main* (i.e. the first non meta)
   833         """convenience method that returns the *main* (i.e. the first non meta)
   819         attribute defined in the entity schema
   834         attribute defined in the entity schema
   820         """
   835         """
   821         for rschema, _ in self.attribute_definitions():
   836         for rschema, __ in self.attribute_definitions():
   822             if not (rschema in META_RTYPES
   837             if not (rschema in META_RTYPES
   823                     or self.is_metadata(rschema)):
   838                     or self.is_metadata(rschema)):
   824                 return rschema
   839                 return rschema
   825 
   840 
   826     def add_subject_relation(self, rschema):
   841     def add_subject_relation(self, rschema):
  1260             # dunno if the validation error `occurred` on eidfrom or eidto (from
  1275             # dunno if the validation error `occurred` on eidfrom or eidto (from
  1261             # user interface point of view)
  1276             # user interface point of view)
  1262             #
  1277             #
  1263             # possible enhancement: check entity being created, it's probably
  1278             # possible enhancement: check entity being created, it's probably
  1264             # the main eid unless this is a composite relation
  1279             # the main eid unless this is a composite relation
  1265             if eidto is None or 'S' in self.mainvars or not 'O' in self.mainvars:
  1280             if eidto is None or 'S' in self.mainvars or 'O' not in self.mainvars:
  1266                 maineid = eidfrom
  1281                 maineid = eidfrom
  1267                 qname = role_name(rtype, 'subject')
  1282                 qname = role_name(rtype, 'subject')
  1268             else:
  1283             else:
  1269                 maineid = eidto
  1284                 maineid = eidto
  1270                 qname = role_name(rtype, 'object')
  1285                 qname = role_name(rtype, 'object')
  1271             if self.msg:
  1286             if self.msg:
  1272                 msg = session._(self.msg)
  1287                 msg = session._(self.msg)
  1273             else:
  1288             else:
  1274                 msg = '%(constraint)s %(expression)s failed' % {
  1289                 msg = '%(constraint)s %(expression)s failed' % {
  1275                     'constraint':  session._(self.type()),
  1290                     'constraint': session._(self.type()),
  1276                     'expression': self.expression}
  1291                     'expression': self.expression}
  1277             raise ValidationError(maineid, {qname: msg})
  1292             raise ValidationError(maineid, {qname: msg})
  1278 
  1293 
  1279     def exec_query(self, _cw, eidfrom, eidto):
  1294     def exec_query(self, _cw, eidfrom, eidto):
  1280         if eidto is None:
  1295         if eidto is None:
  1318         return len(self.exec_query(session, eidfrom, eidto)) <= 1
  1333         return len(self.exec_query(session, eidfrom, eidto)) <= 1
  1319 
  1334 
  1320 
  1335 
  1321 # workflow extensions #########################################################
  1336 # workflow extensions #########################################################
  1322 
  1337 
  1323 from yams.buildobjs import _add_relation as yams_add_relation
       
  1324 
       
  1325 
       
  1326 class workflowable_definition(ybo.metadefinition):
  1338 class workflowable_definition(ybo.metadefinition):
  1327     """extends default EntityType's metaclass to add workflow relations
  1339     """extends default EntityType's metaclass to add workflow relations
  1328     (i.e. in_state, wf_info_for and custom_workflow). This is the default
  1340     (i.e. in_state, wf_info_for and custom_workflow). This is the default
  1329     metaclass for WorkflowableEntityType.
  1341     metaclass for WorkflowableEntityType.
  1330     """
  1342     """
  1407         """return a Schema instance from the schema definition read
  1419         """return a Schema instance from the schema definition read
  1408         from <directory>
  1420         from <directory>
  1409         """
  1421         """
  1410         self.info('loading %s schemas', ', '.join(config.cubes()))
  1422         self.info('loading %s schemas', ', '.join(config.cubes()))
  1411         try:
  1423         try:
  1412             return super(CubicWebSchemaLoader, self).load(config, config.schema_modnames(), **kwargs)
  1424             return super(CubicWebSchemaLoader, self).load(
       
  1425                 config, config.schema_modnames(), **kwargs)
  1413         finally:
  1426         finally:
  1414             # we've to cleanup modules imported from cubicweb.schemas as well
  1427             # we've to cleanup modules imported from cubicweb.schemas as well
  1415             cleanup_sys_modules([join(cubicweb.CW_SOFTWARE_ROOT, 'schemas')])
  1428             cleanup_sys_modules([join(cubicweb.CW_SOFTWARE_ROOT, 'schemas')])
  1416 
  1429 
  1417     # these are overridden by set_log_methods below
  1430     # these are overridden by set_log_methods below
  1446             hasperm = cw.user.matching_groups(MAY_USE_TEMPLATE_FORMAT)
  1459             hasperm = cw.user.matching_groups(MAY_USE_TEMPLATE_FORMAT)
  1447         if hasperm:
  1460         if hasperm:
  1448             return self.regular_formats + tuple(NEED_PERM_FORMATS)
  1461             return self.regular_formats + tuple(NEED_PERM_FORMATS)
  1449     return self.regular_formats
  1462     return self.regular_formats
  1450 
  1463 
       
  1464 
  1451 # XXX itou for some Statement methods
  1465 # XXX itou for some Statement methods
  1452 from rql import stmts
       
  1453 
       
  1454 
  1466 
  1455 def bw_get_etype(self, name):
  1467 def bw_get_etype(self, name):
  1456     return orig_get_etype(self, bw_normalize_etype(name))
  1468     return orig_get_etype(self, bw_normalize_etype(name))
       
  1469 
       
  1470 
  1457 orig_get_etype = stmts.ScopeNode.get_etype
  1471 orig_get_etype = stmts.ScopeNode.get_etype
  1458 stmts.ScopeNode.get_etype = bw_get_etype
  1472 stmts.ScopeNode.get_etype = bw_get_etype
  1459 
  1473 
  1460 
  1474 
  1461 def bw_add_main_variable_delete(self, etype, vref):
  1475 def bw_add_main_variable_delete(self, etype, vref):
  1462     return orig_add_main_variable_delete(self, bw_normalize_etype(etype), vref)
  1476     return orig_add_main_variable_delete(self, bw_normalize_etype(etype), vref)
       
  1477 
       
  1478 
  1463 orig_add_main_variable_delete = stmts.Delete.add_main_variable
  1479 orig_add_main_variable_delete = stmts.Delete.add_main_variable
  1464 stmts.Delete.add_main_variable = bw_add_main_variable_delete
  1480 stmts.Delete.add_main_variable = bw_add_main_variable_delete
  1465 
  1481 
  1466 
  1482 
  1467 def bw_add_main_variable_insert(self, etype, vref):
  1483 def bw_add_main_variable_insert(self, etype, vref):
  1468     return orig_add_main_variable_insert(self, bw_normalize_etype(etype), vref)
  1484     return orig_add_main_variable_insert(self, bw_normalize_etype(etype), vref)
       
  1485 
       
  1486 
  1469 orig_add_main_variable_insert = stmts.Insert.add_main_variable
  1487 orig_add_main_variable_insert = stmts.Insert.add_main_variable
  1470 stmts.Insert.add_main_variable = bw_add_main_variable_insert
  1488 stmts.Insert.add_main_variable = bw_add_main_variable_insert
  1471 
  1489 
  1472 
  1490 
  1473 def bw_set_statement_type(self, etype):
  1491 def bw_set_statement_type(self, etype):
  1474     return orig_set_statement_type(self, bw_normalize_etype(etype))
  1492     return orig_set_statement_type(self, bw_normalize_etype(etype))
       
  1493 
       
  1494 
  1475 orig_set_statement_type = stmts.Select.set_statement_type
  1495 orig_set_statement_type = stmts.Select.set_statement_type
  1476 stmts.Select.set_statement_type = bw_set_statement_type
  1496 stmts.Select.set_statement_type = bw_set_statement_type