37 if card in '+*': |
39 if card in '+*': |
38 return card |
40 return card |
39 return '1' |
41 return '1' |
40 |
42 |
41 |
43 |
42 class RelationTags(object): |
44 MODE_TAGS = set(('link', 'create')) |
43 |
45 CATEGORY_TAGS = set(('primary', 'secondary', 'generic', 'generated')) # , 'metadata')) |
44 MODE_TAGS = frozenset(('link', 'create')) |
46 |
45 CATEGORY_TAGS = frozenset(('primary', 'secondary', 'generic', 'generated', |
47 try: |
46 'inlineview')) |
48 from cubicweb.web.views.editforms import AutomaticEntityForm |
47 |
49 from cubicweb.web.views.boxes import EditBox |
48 def __init__(self, eclass, tagdefs): |
50 |
49 # XXX if a rtag is redefined in a subclass, |
51 def dispatch_rtags(tags, rtype, role, stype, otype): |
50 # the rtag of the base class overwrite the rtag of the subclass |
52 for tag in tags: |
51 self.eclass = eclass |
53 if tag in MODE_TAGS: |
52 self._tagdefs = {} |
54 EditBox.rmode.set_rtag(tag, rtype, role, stype, otype) |
53 for relation, tags in tagdefs.iteritems(): |
55 elif tag in CATEGORY_TAGS: |
54 # tags must become a set |
56 AutomaticEntityForm.rcategories.set_rtag(tag, rtype, role, stype, otype) |
55 if isinstance(tags, basestring): |
57 elif tag == 'inlined': |
56 tags = set((tags,)) |
58 AutomaticEntityForm.rinline.set_rtag(True, rtype, role, stype, otype) |
57 elif not isinstance(tags, set): |
|
58 tags = set(tags) |
|
59 # relation must become a 3-uple (rtype, targettype, role) |
|
60 if isinstance(relation, basestring): |
|
61 self._tagdefs[(relation, '*', 'subject')] = tags |
|
62 self._tagdefs[(relation, '*', 'object')] = tags |
|
63 elif len(relation) == 1: # useful ? |
|
64 self._tagdefs[(relation[0], '*', 'subject')] = tags |
|
65 self._tagdefs[(relation[0], '*', 'object')] = tags |
|
66 elif len(relation) == 2: |
|
67 rtype, ttype = relation |
|
68 ttype = bw_normalize_etype(ttype) # XXX bw compat |
|
69 self._tagdefs[rtype, ttype, 'subject'] = tags |
|
70 self._tagdefs[rtype, ttype, 'object'] = tags |
|
71 elif len(relation) == 3: |
|
72 relation = list(relation) # XXX bw compat |
|
73 relation[1] = bw_normalize_etype(relation[1]) |
|
74 self._tagdefs[tuple(relation)] = tags |
|
75 else: |
59 else: |
76 raise ValueError('bad rtag definition (%r)' % (relation,)) |
60 raise ValueError(tag) |
77 |
61 |
78 |
62 except ImportError: |
79 def __initialize__(self): |
63 AutomaticEntityForm = None |
80 # eclass.[*]schema are only set when registering |
64 |
81 self.schema = self.eclass.schema |
65 def dispatch_rtags(*args): |
82 eschema = self.eschema = self.eclass.e_schema |
66 pass |
83 rtags = self._tagdefs |
67 |
84 # expand wildcards in rtags and add automatic tags |
|
85 for rschema, tschemas, role in sorted(eschema.relation_definitions(True)): |
|
86 rtype = rschema.type |
|
87 star_tags = rtags.pop((rtype, '*', role), set()) |
|
88 for tschema in tschemas: |
|
89 tags = rtags.setdefault((rtype, tschema.type, role), set(star_tags)) |
|
90 if role == 'subject': |
|
91 X, Y = eschema, tschema |
|
92 card = rschema.rproperty(X, Y, 'cardinality')[0] |
|
93 composed = rschema.rproperty(X, Y, 'composite') == 'object' |
|
94 else: |
|
95 X, Y = tschema, eschema |
|
96 card = rschema.rproperty(X, Y, 'cardinality')[1] |
|
97 composed = rschema.rproperty(X, Y, 'composite') == 'subject' |
|
98 # set default category tags if needed |
|
99 if not tags & self.CATEGORY_TAGS: |
|
100 if card in '1+': |
|
101 if not rschema.is_final() and composed: |
|
102 category = 'generated' |
|
103 elif rschema.is_final() and ( |
|
104 rschema.type.endswith('_format') |
|
105 or rschema.type.endswith('_encoding')): |
|
106 category = 'generated' |
|
107 else: |
|
108 category = 'primary' |
|
109 elif rschema.is_final(): |
|
110 if (rschema.type.endswith('_format') |
|
111 or rschema.type.endswith('_encoding')): |
|
112 category = 'generated' |
|
113 else: |
|
114 category = 'secondary' |
|
115 else: |
|
116 category = 'generic' |
|
117 tags.add(category) |
|
118 if not tags & self.MODE_TAGS: |
|
119 if card in '?1': |
|
120 # by default, suppose link mode if cardinality doesn't allow |
|
121 # more than one relation |
|
122 mode = 'link' |
|
123 elif rschema.rproperty(X, Y, 'composite') == role: |
|
124 # if self is composed of the target type, create mode |
|
125 mode = 'create' |
|
126 else: |
|
127 # link mode by default |
|
128 mode = 'link' |
|
129 tags.add(mode) |
|
130 |
|
131 def _default_target(self, rschema, role='subject'): |
|
132 eschema = self.eschema |
|
133 if role == 'subject': |
|
134 return eschema.subject_relation(rschema).objects(eschema)[0] |
|
135 else: |
|
136 return eschema.object_relation(rschema).subjects(eschema)[0] |
|
137 |
|
138 # dict compat |
|
139 def __getitem__(self, key): |
|
140 if isinstance(key, basestring): |
|
141 key = (key,) |
|
142 return self.get_tags(*key) |
|
143 |
|
144 __contains__ = __getitem__ |
|
145 |
|
146 def get_tags(self, rtype, targettype=None, role='subject'): |
|
147 rschema = self.schema.rschema(rtype) |
|
148 if targettype is None: |
|
149 tschema = self._default_target(rschema, role) |
|
150 else: |
|
151 tschema = self.schema.eschema(targettype) |
|
152 return self._tagdefs[(rtype, tschema.type, role)] |
|
153 |
|
154 __call__ = get_tags |
|
155 |
|
156 def get_mode(self, rtype, targettype=None, role='subject'): |
|
157 # XXX: should we make an assertion on rtype not being final ? |
|
158 # assert not rschema.is_final() |
|
159 tags = self.get_tags(rtype, targettype, role) |
|
160 # do not change the intersection order ! |
|
161 modes = tags & self.MODE_TAGS |
|
162 assert len(modes) == 1 |
|
163 return modes.pop() |
|
164 |
|
165 def get_category(self, rtype, targettype=None, role='subject'): |
|
166 tags = self.get_tags(rtype, targettype, role) |
|
167 categories = tags & self.CATEGORY_TAGS |
|
168 assert len(categories) == 1 |
|
169 return categories.pop() |
|
170 |
|
171 def is_inlined(self, rtype, targettype=None, role='subject'): |
|
172 # return set(('primary', 'secondary')) & self.get_tags(rtype, targettype) |
|
173 return 'inlineview' in self.get_tags(rtype, targettype, role) |
|
174 |
|
175 |
|
176 class metaentity(type): |
68 class metaentity(type): |
177 """this metaclass sets the relation tags on the entity class |
69 """this metaclass sets the relation tags on the entity class |
178 and deals with the `widgets` attribute |
70 and deals with the `widgets` attribute |
179 """ |
71 """ |
180 def __new__(mcs, name, bases, classdict): |
72 def __new__(mcs, name, bases, classdict): |
181 # collect baseclass' rtags |
73 # collect baseclass' rtags |
182 tagdefs = {} |
74 if '__rtags__' in classdict: |
183 widgets = {} |
75 etype = classdict['id'] |
184 for base in bases: |
76 warn('%s: __rtags__ is deprecated' % name, DeprecationWarning) |
185 tagdefs.update(getattr(base, '__rtags__', {})) |
77 for relation, tags in classdict.pop('__rtags__').iteritems(): |
186 widgets.update(getattr(base, 'widgets', {})) |
78 # tags must become an iterable |
187 # update with the class' own rtgas |
79 if isinstance(tags, basestring): |
188 tagdefs.update(classdict.get('__rtags__', {})) |
80 tags = (tags,) |
189 widgets.update(classdict.get('widgets', {})) |
81 # relation must become a 3-uple (rtype, targettype, role) |
190 # XXX decide whether or not it's a good idea to replace __rtags__ |
82 if isinstance(relation, basestring): |
191 # good point: transparent support for inheritance levels >= 2 |
83 dispatch_rtags(tags, relation, 'subject', etype, '*') |
192 # bad point: we loose the information of which tags are specific |
84 dispatch_rtags(tags, relation, 'object', '*', etype) |
193 # to this entity class |
85 elif len(relation) == 1: # useful ? |
194 classdict['__rtags__'] = tagdefs |
86 dispatch_rtags(tags, relation[0], 'subject', etype, '*') |
195 classdict['widgets'] = widgets |
87 dispatch_rtags(tags, relation[0], 'object', '*', etype) |
196 eclass = super(metaentity, mcs).__new__(mcs, name, bases, classdict) |
88 elif len(relation) == 2: |
197 # adds the "rtags" attribute |
89 rtype, ttype = relation |
198 eclass.rtags = RelationTags(eclass, tagdefs) |
90 ttype = bw_normalize_etype(ttype) # XXX bw compat |
199 return eclass |
91 dispatch_rtags(tags, rtype, 'subject', etype, ttype) |
|
92 dispatch_rtags(tags, rtype, 'object', ttype, etype) |
|
93 elif len(relation) == 3: |
|
94 rtype, ttype, role = relation |
|
95 ttype = bw_normalize_etype(ttype) |
|
96 if role == 'subject': |
|
97 dispatch_rtags(tags, rtype, 'subject', etype, ttype) |
|
98 else: |
|
99 dispatch_rtags(tags, rtype, 'object', ttype, etype) |
|
100 else: |
|
101 raise ValueError('bad rtag definition (%r)' % (relation,)) |
|
102 if 'widgets' in classdict and AutomaticEntityForm is not None: |
|
103 etype = classdict['id'] |
|
104 warn('%s: widgets is deprecated' % name, DeprecationWarning) |
|
105 for relation, wdgname in classdict.pop('widgets').iteritems(): |
|
106 AutomaticEntityForm.rwidgets.set_rtag(wdgname, rtype, 'subject', etype) |
|
107 return super(metaentity, mcs).__new__(mcs, name, bases, classdict) |
200 |
108 |
201 |
109 |
202 class Entity(AppRsetObject, dict): |
110 class Entity(AppRsetObject, dict): |
203 """an entity instance has e_schema automagically set on |
111 """an entity instance has e_schema automagically set on |
204 the class and instances has access to their issuing cursor. |
112 the class and instances has access to their issuing cursor. |