|
1 # copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved. |
|
2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr |
|
3 # |
|
4 # This file is part of CubicWeb. |
|
5 # |
|
6 # CubicWeb is free software: you can redistribute it and/or modify it under the |
|
7 # terms of the GNU Lesser General Public License as published by the Free |
|
8 # Software Foundation, either version 2.1 of the License, or (at your option) |
|
9 # any later version. |
|
10 # |
|
11 # CubicWeb is distributed in the hope that it will be useful, but WITHOUT |
|
12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
|
13 # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more |
|
14 # details. |
|
15 # |
|
16 # You should have received a copy of the GNU Lesser General Public License along |
|
17 # with CubicWeb. If not, see <http://www.gnu.org/licenses/>. |
|
18 """ |
|
19 A RelationTag object is an object which allows to link a configuration |
|
20 information to a relation definition. For instance, the standard |
|
21 primary view uses a RelationTag object (uicfg.primaryview_section) to |
|
22 get the section to display relations. |
|
23 |
|
24 .. sourcecode:: python |
|
25 |
|
26 # display ``entry_of`` relations in the ``relations`` section in the ``BlogEntry`` primary view |
|
27 uicfg.primaryview_section.tag_subject_of(('BlogEntry', 'entry_of', '*'), |
|
28 'relations') |
|
29 |
|
30 # hide every relation ``entry_of`` in the ``Blog`` primary view |
|
31 uicfg.primaryview_section.tag_object_of(('*', 'entry_of', 'Blog'), 'hidden') |
|
32 |
|
33 Three primitives are defined: |
|
34 * ``tag_subject_of`` tag a relation in the subject's context |
|
35 * ``tag_object_of`` tag a relation in the object's context |
|
36 * ``tag_attribute`` shortcut for tag_subject_of |
|
37 """ |
|
38 __docformat__ = "restructuredtext en" |
|
39 |
|
40 import logging |
|
41 from warnings import warn |
|
42 |
|
43 from six import string_types |
|
44 |
|
45 from logilab.common.logging_ext import set_log_methods |
|
46 from logilab.common.registry import RegistrableInstance, yes |
|
47 |
|
48 def _ensure_str_key(key): |
|
49 return tuple(str(k) for k in key) |
|
50 |
|
51 class RegistrableRtags(RegistrableInstance): |
|
52 __registry__ = 'uicfg' |
|
53 __select__ = yes() |
|
54 |
|
55 |
|
56 class RelationTags(RegistrableRtags): |
|
57 """a tag store for full relation definitions : |
|
58 |
|
59 (subject type, relation type, object type, tagged) |
|
60 |
|
61 allowing to set tags using wildcard (eg '*') as subject type / object type |
|
62 |
|
63 This class associates a single tag to each key. |
|
64 """ |
|
65 _allowed_values = None |
|
66 # _init expected to be a method (introduced in 3.17), while _initfunc a |
|
67 # function given as __init__ argument and kept for bw compat |
|
68 _init = _initfunc = None |
|
69 |
|
70 def __init__(self): |
|
71 self._tagdefs = {} |
|
72 |
|
73 def __repr__(self): |
|
74 # find a way to have more infos but keep it readable |
|
75 # (in error messages in case of an ambiguity for instance) |
|
76 return '%s (%s): %s' % (id(self), self.__regid__, self.__class__) |
|
77 |
|
78 # dict compat |
|
79 def __getitem__(self, key): |
|
80 return self.get(*key) |
|
81 __contains__ = __getitem__ |
|
82 |
|
83 def clear(self): |
|
84 self._tagdefs.clear() |
|
85 |
|
86 def _get_keys(self, stype, rtype, otype, tagged): |
|
87 keys = [] |
|
88 if '*' not in (stype, otype): |
|
89 keys.append(('*', rtype, '*', tagged)) |
|
90 if '*' != stype: |
|
91 keys.append(('*', rtype, otype, tagged)) |
|
92 if '*' != otype: |
|
93 keys.append((stype, rtype, '*', tagged)) |
|
94 keys.append((stype, rtype, otype, tagged)) |
|
95 return keys |
|
96 |
|
97 def init(self, schema, check=True): |
|
98 # XXX check existing keys against schema |
|
99 if check: |
|
100 for (stype, rtype, otype, tagged), value in list(self._tagdefs.items()): |
|
101 for ertype in (stype, rtype, otype): |
|
102 if ertype != '*' and not ertype in schema: |
|
103 self.warning('removing rtag %s: %s, %s undefined in schema', |
|
104 (stype, rtype, otype, tagged), value, ertype) |
|
105 self.del_rtag(stype, rtype, otype, tagged) |
|
106 break |
|
107 if self._init is not None: |
|
108 self.apply(schema, self._init) |
|
109 |
|
110 def apply(self, schema, func): |
|
111 for eschema in schema.entities(): |
|
112 if eschema.final: |
|
113 continue |
|
114 for rschema, tschemas, role in eschema.relation_definitions(True): |
|
115 for tschema in tschemas: |
|
116 if role == 'subject': |
|
117 sschema, oschema = eschema, tschema |
|
118 else: |
|
119 sschema, oschema = tschema, eschema |
|
120 func(sschema, rschema, oschema, role) |
|
121 |
|
122 # rtag declaration api #################################################### |
|
123 |
|
124 def tag_attribute(self, key, *args, **kwargs): |
|
125 key = list(key) |
|
126 key.append('*') |
|
127 key.append('subject') |
|
128 self.tag_relation(key, *args, **kwargs) |
|
129 |
|
130 def tag_subject_of(self, key, *args, **kwargs): |
|
131 key = list(key) |
|
132 key.append('subject') |
|
133 self.tag_relation(key, *args, **kwargs) |
|
134 |
|
135 def tag_object_of(self, key, *args, **kwargs): |
|
136 key = list(key) |
|
137 key.append('object') |
|
138 self.tag_relation(key, *args, **kwargs) |
|
139 |
|
140 def tag_relation(self, key, tag): |
|
141 assert len(key) == 4, 'bad key: %s' % list(key) |
|
142 if self._allowed_values is not None: |
|
143 assert tag in self._allowed_values, \ |
|
144 '%r is not an allowed tag (should be in %s)' % ( |
|
145 tag, self._allowed_values) |
|
146 self._tagdefs[_ensure_str_key(key)] = tag |
|
147 return tag |
|
148 |
|
149 def _tag_etype_attr(self, etype, attr, desttype='*', *args, **kwargs): |
|
150 if isinstance(attr, string_types): |
|
151 attr, role = attr, 'subject' |
|
152 else: |
|
153 attr, role = attr |
|
154 if role == 'subject': |
|
155 self.tag_subject_of((etype, attr, desttype), *args, **kwargs) |
|
156 else: |
|
157 self.tag_object_of((desttype, attr, etype), *args, **kwargs) |
|
158 |
|
159 |
|
160 # rtag runtime api ######################################################## |
|
161 |
|
162 def del_rtag(self, *key): |
|
163 del self._tagdefs[key] |
|
164 |
|
165 def get(self, *key): |
|
166 for key in reversed(self._get_keys(*key)): |
|
167 try: |
|
168 return self._tagdefs[key] |
|
169 except KeyError: |
|
170 continue |
|
171 return None |
|
172 |
|
173 def etype_get(self, etype, rtype, role, ttype='*'): |
|
174 if role == 'subject': |
|
175 return self.get(etype, rtype, ttype, role) |
|
176 return self.get(ttype, rtype, etype, role) |
|
177 |
|
178 # these are overridden by set_log_methods below |
|
179 # only defining here to prevent pylint from complaining |
|
180 info = warning = error = critical = exception = debug = lambda msg,*a,**kw: None |
|
181 |
|
182 |
|
183 class RelationTagsSet(RelationTags): |
|
184 """This class associates a set of tags to each key. |
|
185 """ |
|
186 tag_container_cls = set |
|
187 |
|
188 def tag_relation(self, key, tag): |
|
189 rtags = self._tagdefs.setdefault(_ensure_str_key(key), |
|
190 self.tag_container_cls()) |
|
191 rtags.add(tag) |
|
192 return rtags |
|
193 |
|
194 def get(self, stype, rtype, otype, tagged): |
|
195 rtags = self.tag_container_cls() |
|
196 for key in self._get_keys(stype, rtype, otype, tagged): |
|
197 try: |
|
198 rtags.update(self._tagdefs[key]) |
|
199 except KeyError: |
|
200 continue |
|
201 return rtags |
|
202 |
|
203 |
|
204 class RelationTagsDict(RelationTagsSet): |
|
205 """This class associates a set of tags to each key.""" |
|
206 tag_container_cls = dict |
|
207 |
|
208 def tag_relation(self, key, tag): |
|
209 key = _ensure_str_key(key) |
|
210 try: |
|
211 rtags = self._tagdefs[key] |
|
212 rtags.update(tag) |
|
213 return rtags |
|
214 except KeyError: |
|
215 self._tagdefs[key] = tag |
|
216 return tag |
|
217 |
|
218 def setdefault(self, key, tagkey, tagvalue): |
|
219 key = _ensure_str_key(key) |
|
220 try: |
|
221 rtags = self._tagdefs[key] |
|
222 rtags.setdefault(tagkey, tagvalue) |
|
223 return rtags |
|
224 except KeyError: |
|
225 self._tagdefs[key] = {tagkey: tagvalue} |
|
226 return self._tagdefs[key] |
|
227 |
|
228 |
|
229 class RelationTagsBool(RelationTags): |
|
230 _allowed_values = frozenset((True, False)) |
|
231 |
|
232 |
|
233 class NoTargetRelationTagsDict(RelationTagsDict): |
|
234 |
|
235 @property |
|
236 def name(self): |
|
237 return self.__class__.name |
|
238 |
|
239 # tag_subject_of / tag_object_of issue warning if '*' is not given as target |
|
240 # type, while tag_relation handle it silently since it may be used during |
|
241 # initialization |
|
242 def tag_subject_of(self, key, tag): |
|
243 subj, rtype, obj = key |
|
244 if obj != '*': |
|
245 self.warning('using explict target type in %s.tag_subject_of() ' |
|
246 'has no effect, use (%s, %s, "*") instead of (%s, %s, %s)', |
|
247 self.name, subj, rtype, subj, rtype, obj) |
|
248 super(NoTargetRelationTagsDict, self).tag_subject_of((subj, rtype, '*'), tag) |
|
249 |
|
250 def tag_object_of(self, key, tag): |
|
251 subj, rtype, obj = key |
|
252 if subj != '*': |
|
253 self.warning('using explict subject type in %s.tag_object_of() ' |
|
254 'has no effect, use ("*", %s, %s) instead of (%s, %s, %s)', |
|
255 self.name, rtype, obj, subj, rtype, obj) |
|
256 super(NoTargetRelationTagsDict, self).tag_object_of(('*', rtype, obj), tag) |
|
257 |
|
258 def tag_relation(self, key, tag): |
|
259 if key[-1] == 'subject' and key[-2] != '*': |
|
260 if isinstance(key, tuple): |
|
261 key = list(key) |
|
262 key[-2] = '*' |
|
263 elif key[-1] == 'object' and key[0] != '*': |
|
264 if isinstance(key, tuple): |
|
265 key = list(key) |
|
266 key[0] = '*' |
|
267 super(NoTargetRelationTagsDict, self).tag_relation(key, tag) |
|
268 |
|
269 |
|
270 set_log_methods(RelationTags, logging.getLogger('cubicweb.rtags')) |