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) |
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 |