schema.py
branchstable
changeset 3978 2c95e3033f64
parent 3968 e8dbad65a7a2
child 3979 8f1b3fbb158f
equal deleted inserted replaced
3977:5c84c6df6798 3978:2c95e3033f64
   116         return unicode(req.pgettext(context, key)).lower()
   116         return unicode(req.pgettext(context, key)).lower()
   117     else:
   117     else:
   118         return unicode(req._(key)).lower()
   118         return unicode(req._(key)).lower()
   119 
   119 
   120 __builtins__['display_name'] = deprecated('display_name should be imported from cubicweb.schema')(display_name)
   120 __builtins__['display_name'] = deprecated('display_name should be imported from cubicweb.schema')(display_name)
       
   121 
       
   122 
       
   123 # rql expression utilities function ############################################
       
   124 
       
   125 def guess_rrqlexpr_mainvars(expression):
       
   126     defined = set(split_expression(expression))
       
   127     mainvars = []
       
   128     if 'S' in defined:
       
   129         mainvars.append('S')
       
   130     if 'O' in defined:
       
   131         mainvars.append('O')
       
   132     if 'U' in defined:
       
   133         mainvars.append('U')
       
   134     if not mainvars:
       
   135         raise Exception('unable to guess selection variables')
       
   136     return ','.join(mainvars)
       
   137 
       
   138 def split_expression(rqlstring):
       
   139     for expr in rqlstring.split(','):
       
   140         for word in expr.split():
       
   141             yield word
       
   142 
       
   143 def normalize_expression(rqlstring):
       
   144     """normalize an rql expression to ease schema synchronization (avoid
       
   145     suppressing and reinserting an expression if only a space has been added/removed
       
   146     for instance)
       
   147     """
       
   148     return u', '.join(' '.join(expr.split()) for expr in rqlstring.split(','))
       
   149 
       
   150 
       
   151 # Schema objects definition ###################################################
   121 
   152 
   122 def ERSchema_display_name(self, req, form=''):
   153 def ERSchema_display_name(self, req, form=''):
   123     """return a internationalized string for the entity/relation type name in
   154     """return a internationalized string for the entity/relation type name in
   124     a given form
   155     a given form
   125     """
   156     """
   541         return self._eid_index[eid]
   572         return self._eid_index[eid]
   542 
   573 
   543 
   574 
   544 # Possible constraints ########################################################
   575 # Possible constraints ########################################################
   545 
   576 
   546 class RQLVocabularyConstraint(BaseConstraint):
   577 class BaseRQLConstraint(BaseConstraint):
   547     """the rql vocabulary constraint :
   578     """base class for rql constraints
   548 
       
   549     limit the proposed values to a set of entities returned by a rql query,
       
   550     but this is not enforced at the repository level
       
   551 
       
   552      restriction is additional rql restriction that will be added to
       
   553      a predefined query, where the S and O variables respectivly represent
       
   554      the subject and the object of the relation
       
   555 
       
   556      mainvars is a string that should be used as selection variable (eg
       
   557      `'Any %s WHERE ...' % mainvars`). If not specified, an attempt will be
       
   558      done to guess it according to variable used in the expression.
       
   559     """
   579     """
   560 
   580 
   561     def __init__(self, restriction, mainvars=None):
   581     def __init__(self, restriction, mainvars=None):
   562         self.restriction = normalize_expression(restriction)
   582         self.restriction = normalize_expression(restriction)
   563         if mainvars is None:
   583         if mainvars is None:
   601 
   621 
   602     def __repr__(self):
   622     def __repr__(self):
   603         return '<%s @%#x>' % (self.__str__(), id(self))
   623         return '<%s @%#x>' % (self.__str__(), id(self))
   604 
   624 
   605 
   625 
   606 class RQLConstraint(RQLVocabularyConstraint):
   626 class RQLVocabularyConstraint(BaseRQLConstraint):
   607     """the rql constraint is similar to the RQLVocabularyConstraint but
   627     """the rql vocabulary constraint :
   608     are also enforced at the repository level
   628 
   609     """
   629     limit the proposed values to a set of entities returned by a rql query,
   610     distinct_query = False
   630     but this is not enforced at the repository level
       
   631 
       
   632      restriction is additional rql restriction that will be added to
       
   633      a predefined query, where the S and O variables respectivly represent
       
   634      the subject and the object of the relation
       
   635 
       
   636      mainvars is a string that should be used as selection variable (eg
       
   637      `'Any %s WHERE ...' % mainvars`). If not specified, an attempt will be
       
   638      done to guess it according to variable used in the expression.
       
   639     """
       
   640 
       
   641 
       
   642 class RepoEnforcedRQLConstraintMixIn(object):
   611 
   643 
   612     def __init__(self, restriction, mainvars=None, msg=None):
   644     def __init__(self, restriction, mainvars=None, msg=None):
   613         super(RQLConstraint, self).__init__(restriction, mainvars)
   645         super(RepoEnforcedRQLConstraintMixIn, self).__init__(restriction, mainvars)
   614         self.msg = msg
   646         self.msg = msg
   615 
   647 
   616     def serialize(self):
   648     def serialize(self):
   617         # start with a semicolon for bw compat, see below
   649         # start with a semicolon for bw compat, see below
   618         return ';%s;%s\n%s' % (self.mainvars, self.restriction,
   650         return ';%s;%s\n%s' % (self.mainvars, self.restriction,
   625         value, msg = value.split('\n', 1)
   657         value, msg = value.split('\n', 1)
   626         _, mainvars, restriction = value.split(';', 2)
   658         _, mainvars, restriction = value.split(';', 2)
   627         return cls(restriction, mainvars, msg)
   659         return cls(restriction, mainvars, msg)
   628     deserialize = classmethod(deserialize)
   660     deserialize = classmethod(deserialize)
   629 
   661 
   630     def exec_query(self, session, eidfrom, eidto):
       
   631         if eidto is None:
       
   632             # checking constraint for an attribute relation
       
   633             restriction = 'S eid %(s)s, ' + self.restriction
       
   634             args, ck = {'s': eidfrom}, 's'
       
   635         else:
       
   636             restriction = 'S eid %(s)s, O eid %(o)s, ' + self.restriction
       
   637             args, ck = {'s': eidfrom, 'o': eidto}, ('s', 'o')
       
   638         rql = 'Any %s WHERE %s' % (self.mainvars,  restriction)
       
   639         if self.distinct_query:
       
   640             rql = 'DISTINCT ' + rql
       
   641         return session.unsafe_execute(rql, args, ck, build_descr=False)
       
   642 
       
   643     def repo_check(self, session, eidfrom, rtype, eidto=None):
   662     def repo_check(self, session, eidfrom, rtype, eidto=None):
   644         """raise ValidationError if the relation doesn't satisfy the constraint
   663         """raise ValidationError if the relation doesn't satisfy the constraint
   645         """
   664         """
   646         if not self.exec_query(session, eidfrom, eidto):
   665         if not self.match_condition(session, eidfrom, eidto):
   647             # XXX at this point if both or neither of S and O are in mainvar we
   666             # XXX at this point if both or neither of S and O are in mainvar we
   648             # dunno if the validation error `occured` on eidfrom or eidto (from
   667             # dunno if the validation error `occured` on eidfrom or eidto (from
   649             # user interface point of view)
   668             # user interface point of view)
   650             if eidto is None or 'S' in self.mainvars or not 'O' in self.mainvars:
   669             if eidto is None or 'S' in self.mainvars or not 'O' in self.mainvars:
   651                 maineid = eidfrom
   670                 maineid = eidfrom
   657                 msg = '%(constraint)s %(restriction)s failed' % {
   676                 msg = '%(constraint)s %(restriction)s failed' % {
   658                     'constraint':  session._(self.type()),
   677                     'constraint':  session._(self.type()),
   659                     'restriction': self.restriction}
   678                     'restriction': self.restriction}
   660             raise ValidationError(maineid, {rtype: msg})
   679             raise ValidationError(maineid, {rtype: msg})
   661 
   680 
   662 
   681     def exec_query(self, session, eidfrom, eidto):
   663 class RQLUniqueConstraint(RQLConstraint):
   682         if eidto is None:
       
   683             # checking constraint for an attribute relation
       
   684             restriction = 'S eid %(s)s, ' + self.restriction
       
   685             args, ck = {'s': eidfrom}, 's'
       
   686         else:
       
   687             restriction = 'S eid %(s)s, O eid %(o)s, ' + self.restriction
       
   688             args, ck = {'s': eidfrom, 'o': eidto}, ('s', 'o')
       
   689         rql = 'Any %s WHERE %s' % (self.mainvars,  restriction)
       
   690         if self.distinct_query:
       
   691             rql = 'DISTINCT ' + rql
       
   692         return session.unsafe_execute(rql, args, ck, build_descr=False)
       
   693 
       
   694 
       
   695 class RQLConstraint(RepoEnforcedRQLConstraintMixIn, RQLVocabularyConstraint):
       
   696     """the rql constraint is similar to the RQLVocabularyConstraint but
       
   697     are also enforced at the repository level
       
   698     """
       
   699     distinct_query = False
       
   700 
       
   701     def match_condition(self, session, eidfrom, eidto):
       
   702         return self.exec_query(session, eidfrom, eidto)
       
   703 
       
   704 
       
   705 class RQLUniqueConstraint(RepoEnforcedRQLConstraintMixIn, BaseRQLConstraint):
   664     """the unique rql constraint check that the result of the query isn't
   706     """the unique rql constraint check that the result of the query isn't
   665     greater than one
   707     greater than one
   666     """
   708     """
   667     distinct_query = True
   709     distinct_query = True
   668 
   710 
   669     def exec_query(self, session, eidfrom, eidto):
   711     def match_condition(self, session, eidfrom, eidto):
   670         rset = super(RQLUniqueConstraint, self).exec_query(session, eidfrom, eidto)
   712         return len(self.exec_query(session, eidfrom, eidto)) <= 1
   671         return len(rset) <= 1
       
   672 
       
   673 
       
   674 def split_expression(rqlstring):
       
   675     for expr in rqlstring.split(','):
       
   676         for word in expr.split():
       
   677             yield word
       
   678 
       
   679 def normalize_expression(rqlstring):
       
   680     """normalize an rql expression to ease schema synchronization (avoid
       
   681     suppressing and reinserting an expression if only a space has been added/removed
       
   682     for instance)
       
   683     """
       
   684     return u', '.join(' '.join(expr.split()) for expr in rqlstring.split(','))
       
   685 
   713 
   686 
   714 
   687 class RQLExpression(object):
   715 class RQLExpression(object):
   688     def __init__(self, expression, mainvars, eid):
   716     def __init__(self, expression, mainvars, eid):
   689         self.eid = eid # eid of the entity representing this rql expression
   717         self.eid = eid # eid of the entity representing this rql expression
   846             if eid is None:
   874             if eid is None:
   847                 return False
   875                 return False
   848             return self._check(session, x=eid)
   876             return self._check(session, x=eid)
   849         return self._check(session)
   877         return self._check(session)
   850 
   878 
   851 PyFileReader.context['ERQLExpression'] = yobsolete(ERQLExpression)
       
   852 
       
   853 def guess_rrqlexpr_mainvars(expression):
       
   854     defined = set(split_expression(expression))
       
   855     mainvars = []
       
   856     if 'S' in defined:
       
   857         mainvars.append('S')
       
   858     if 'O' in defined:
       
   859         mainvars.append('O')
       
   860     if 'U' in defined:
       
   861         mainvars.append('U')
       
   862     if not mainvars:
       
   863         raise Exception('unable to guess selection variables')
       
   864     return ','.join(mainvars)
       
   865 
   879 
   866 class RRQLExpression(RQLExpression):
   880 class RRQLExpression(RQLExpression):
   867     def __init__(self, expression, mainvars=None, eid=None):
   881     def __init__(self, expression, mainvars=None, eid=None):
   868         if mainvars is None:
   882         if mainvars is None:
   869             mainvars = guess_rrqlexpr_mainvars(expression)
   883             mainvars = guess_rrqlexpr_mainvars(expression)
   907             if toeid is None:
   921             if toeid is None:
   908                 return False
   922                 return False
   909             kwargs['o'] = toeid
   923             kwargs['o'] = toeid
   910         return self._check(session, **kwargs)
   924         return self._check(session, **kwargs)
   911 
   925 
   912 PyFileReader.context['RRQLExpression'] = yobsolete(RRQLExpression)
       
   913 
   926 
   914 # workflow extensions #########################################################
   927 # workflow extensions #########################################################
   915 
   928 
   916 from yams.buildobjs import _add_relation as yams_add_relation
   929 from yams.buildobjs import _add_relation as yams_add_relation
   917 
   930 
   944 
   957 
   945 class WorkflowableEntityType(ybo.EntityType):
   958 class WorkflowableEntityType(ybo.EntityType):
   946     __metaclass__ = workflowable_definition
   959     __metaclass__ = workflowable_definition
   947     __abstract__ = True
   960     __abstract__ = True
   948 
   961 
   949 PyFileReader.context['WorkflowableEntityType'] = WorkflowableEntityType
       
   950 
   962 
   951 # schema loading ##############################################################
   963 # schema loading ##############################################################
   952 
   964 
   953 CONSTRAINTS['RQLConstraint'] = RQLConstraint
   965 CONSTRAINTS['RQLConstraint'] = RQLConstraint
   954 CONSTRAINTS['RQLUniqueConstraint'] = RQLUniqueConstraint
   966 CONSTRAINTS['RQLUniqueConstraint'] = RQLUniqueConstraint
  1065 def bw_set_statement_type(self, etype):
  1077 def bw_set_statement_type(self, etype):
  1066     return orig_set_statement_type(self, bw_normalize_etype(etype))
  1078     return orig_set_statement_type(self, bw_normalize_etype(etype))
  1067 stmts.Select.set_statement_type = bw_set_statement_type
  1079 stmts.Select.set_statement_type = bw_set_statement_type
  1068 
  1080 
  1069 # XXX deprecated
  1081 # XXX deprecated
       
  1082 
  1070 from yams.constraints import format_constraint
  1083 from yams.constraints import format_constraint
  1071 from yams.buildobjs import RichString
  1084 from yams.buildobjs import RichString
       
  1085 
       
  1086 PyFileReader.context['ERQLExpression'] = yobsolete(ERQLExpression)
       
  1087 PyFileReader.context['RRQLExpression'] = yobsolete(RRQLExpression)
       
  1088 PyFileReader.context['WorkflowableEntityType'] = WorkflowableEntityType
  1072 PyFileReader.context['format_constraint'] = format_constraint
  1089 PyFileReader.context['format_constraint'] = format_constraint