14 from cubicweb.web import stdmsgs, uicfg |
14 from cubicweb.web import stdmsgs, uicfg |
15 from cubicweb.web import form, formwidgets as fwdgs |
15 from cubicweb.web import form, formwidgets as fwdgs |
16 from cubicweb.web.formfields import guess_field |
16 from cubicweb.web.formfields import guess_field |
17 from cubicweb.web.views import forms, editforms |
17 from cubicweb.web.views import forms, editforms |
18 |
18 |
|
19 _afs = uicfg.autoform_section |
19 |
20 |
20 class AutomaticEntityForm(forms.EntityFieldsForm): |
21 class AutomaticEntityForm(forms.EntityFieldsForm): |
21 """base automatic form to edit any entity. |
22 """base automatic form to edit any entity. |
22 |
23 |
23 Designed to be fully generated from schema but highly configurable through: |
24 Designed to be fully generated from schema but highly configurable through: |
24 * rtags (rcategories, rfields, rwidgets, inlined, rpermissions) |
25 |
|
26 * uicfg (autoform_* relation tags) |
25 * various standard form parameters |
27 * various standard form parameters |
26 |
28 * overriding |
27 XXX s/rtags/uicfg/ ? |
|
28 |
29 |
29 You can also easily customise it by adding/removing fields in |
30 You can also easily customise it by adding/removing fields in |
30 AutomaticEntityForm instances. |
31 AutomaticEntityForm instances or by inheriting from it. |
31 """ |
32 """ |
32 __regid__ = 'edition' |
33 __regid__ = 'edition' |
33 |
34 |
34 cwtarget = 'eformframe' |
35 cwtarget = 'eformframe' |
35 cssclass = 'entityForm' |
36 cssclass = 'entityForm' |
36 copy_nav_params = True |
37 copy_nav_params = True |
37 form_buttons = [fwdgs.SubmitButton(), |
38 form_buttons = [fwdgs.SubmitButton(), |
38 fwdgs.Button(stdmsgs.BUTTON_APPLY, cwaction='apply'), |
39 fwdgs.Button(stdmsgs.BUTTON_APPLY, cwaction='apply'), |
39 fwdgs.Button(stdmsgs.BUTTON_CANCEL, cwaction='cancel')] |
40 fwdgs.Button(stdmsgs.BUTTON_CANCEL, cwaction='cancel')] |
40 attrcategories = ('primary', 'secondary') |
41 # for attributes selection when searching in uicfg.autoform_section |
|
42 formtype = 'main' |
|
43 # set this to a list of [(relation, role)] if you want to explictily tell |
|
44 # which relations should be edited |
|
45 display_fields = None |
41 # class attributes below are actually stored in the uicfg module since we |
46 # class attributes below are actually stored in the uicfg module since we |
42 # don't want them to be reloaded |
47 # don't want them to be reloaded |
43 rcategories = uicfg.autoform_section |
|
44 rfields = uicfg.autoform_field |
48 rfields = uicfg.autoform_field |
45 rfields_kwargs = uicfg.autoform_field_kwargs |
49 rfields_kwargs = uicfg.autoform_field_kwargs |
46 rinlined = uicfg.autoform_is_inlined |
|
47 rpermissions_overrides = uicfg.autoform_permissions_overrides |
|
48 |
50 |
49 # class methods mapping schema relations to fields in the form ############ |
51 # class methods mapping schema relations to fields in the form ############ |
50 |
|
51 @classmethod |
|
52 def erelations_by_category(cls, entity, categories=None, permission=None, |
|
53 rtags=None, strict=False): |
|
54 """return a list of (relation schema, target schemas, role) matching |
|
55 categories and permission |
|
56 |
|
57 `strict`: |
|
58 bool telling if having local role is enough (strict = False) or not |
|
59 """ |
|
60 if categories is not None: |
|
61 if not isinstance(categories, (list, tuple, set, frozenset)): |
|
62 categories = (categories,) |
|
63 if not isinstance(categories, (set, frozenset)): |
|
64 categories = frozenset(categories) |
|
65 eschema = entity.e_schema |
|
66 if rtags is None: |
|
67 rtags = cls.rcategories |
|
68 permsoverrides = cls.rpermissions_overrides |
|
69 if entity.has_eid(): |
|
70 eid = entity.eid |
|
71 else: |
|
72 eid = None |
|
73 strict = False |
|
74 for rschema, targetschemas, role in eschema.relation_definitions(True): |
|
75 # check category first, potentially lower cost than checking |
|
76 # permission which may imply rql queries |
|
77 if categories is not None: |
|
78 targetschemas = [tschema for tschema in targetschemas |
|
79 if rtags.etype_get(eschema, rschema, role, tschema) in categories] |
|
80 if not targetschemas: |
|
81 continue |
|
82 if permission is not None: |
|
83 # tag allowing to hijack the permission machinery when |
|
84 # permission is not verifiable until the entity is actually |
|
85 # created... |
|
86 if eid is None and '%s_on_new' % permission in permsoverrides.etype_get(eschema, rschema, role): |
|
87 yield (rschema, targetschemas, role) |
|
88 continue |
|
89 if rschema.is_final(): |
|
90 if not rschema.has_perm(entity._cw, permission, eid): |
|
91 continue |
|
92 elif role == 'subject': |
|
93 if not ((not strict and rschema.has_local_role(permission)) or |
|
94 rschema.has_perm(entity._cw, permission, fromeid=eid)): |
|
95 continue |
|
96 # on relation with cardinality 1 or ?, we need delete perm as well |
|
97 # if the relation is already set |
|
98 if (permission == 'add' |
|
99 and rschema.cardinality(eschema, targetschemas[0], role) in '1?' |
|
100 and eid and entity.related(rschema.type, role) |
|
101 and not rschema.has_perm(entity._cw, 'delete', fromeid=eid, |
|
102 toeid=entity.related(rschema.type, role)[0][0])): |
|
103 continue |
|
104 elif role == 'object': |
|
105 if not ((not strict and rschema.has_local_role(permission)) or |
|
106 rschema.has_perm(entity._cw, permission, toeid=eid)): |
|
107 continue |
|
108 # on relation with cardinality 1 or ?, we need delete perm as well |
|
109 # if the relation is already set |
|
110 if (permission == 'add' |
|
111 and rschema.cardinality(targetschemas[0], eschema, role) in '1?' |
|
112 and eid and entity.related(rschema.type, role) |
|
113 and not rschema.has_perm(entity._cw, 'delete', toeid=eid, |
|
114 fromeid=entity.related(rschema.type, role)[0][0])): |
|
115 continue |
|
116 yield (rschema, targetschemas, role) |
|
117 |
|
118 @classmethod |
|
119 def esrelations_by_category(cls, entity, categories=None, permission=None, |
|
120 strict=False): |
|
121 """filter out result of relations_by_category(categories, permission) by |
|
122 removing final relations |
|
123 |
|
124 return a sorted list of (relation's label, relation'schema, role) |
|
125 """ |
|
126 result = [] |
|
127 for rschema, ttypes, role in cls.erelations_by_category( |
|
128 entity, categories, permission, strict=strict): |
|
129 if rschema.is_final(): |
|
130 continue |
|
131 result.append((rschema.display_name(entity._cw, role), rschema, role)) |
|
132 return sorted(result) |
|
133 |
52 |
134 @iclassmethod |
53 @iclassmethod |
135 def field_by_name(cls_or_self, name, role='subject', eschema=None): |
54 def field_by_name(cls_or_self, name, role='subject', eschema=None): |
136 """return field with the given name and role. If field is not explicitly |
55 """return field with the given name and role. If field is not explicitly |
137 defined for the form but `eclass` is specified, guess_field will be |
56 defined for the form but `eclass` is specified, guess_field will be |
232 |
151 |
233 action = property(action, set_action) |
152 action = property(action, set_action) |
234 |
153 |
235 # methods mapping edited entity relations to fields in the form ############ |
154 # methods mapping edited entity relations to fields in the form ############ |
236 |
155 |
237 def relations_by_category(self, categories=None, permission=None): |
156 def _relations_by_section(self, section, permission='add', strict=False): |
238 """return a list of (relation schema, target schemas, role) matching |
157 """return a list of (relation schema, target schemas, role) matching |
239 given category(ies) and permission |
158 given category(ies) and permission |
240 """ |
159 """ |
241 return self.erelations_by_category(self.edited_entity, categories, |
160 return _afs.relations_by_section( |
242 permission) |
161 self.edited_entity, self.formtype, section, permission, strict) |
|
162 |
|
163 def editable_attributes(self, strict=False): |
|
164 """return a list of (relation schema, role) to edit for the entity""" |
|
165 if self.display_fields is not None: |
|
166 return self.display_fields |
|
167 # XXX we should simply put eid in the generated section, no? |
|
168 return [(rtype, role) for rtype, _, role in self._relations_by_section( |
|
169 'attributes', strict=strict) if rtype != 'eid'] |
|
170 |
|
171 def editable_relations(self): |
|
172 """return a sorted list of (relation's label, relation'schema, role) for |
|
173 relations in the 'relations' section |
|
174 """ |
|
175 result = [] |
|
176 for rschema, _, role in self._relations_by_section('relations', |
|
177 strict=True): |
|
178 result.append( (rschema.display_name(entity._cw, role, |
|
179 entity.__regid__), |
|
180 rschema, role) ) |
|
181 return sorted(result) |
243 |
182 |
244 def inlined_relations(self): |
183 def inlined_relations(self): |
245 """return a list of (relation schema, target schemas, role) matching |
184 """return a list of (relation schema, target schemas, role) matching |
246 given category(ies) and permission |
185 given category(ies) and permission |
247 """ |
186 """ |
248 return self.erelations_by_category(self.edited_entity, True, 'add', |
187 return self._relations_by_section('inlined') |
249 self.rinlined) |
|
250 |
|
251 def srelations_by_category(self, categories=None, permission=None, |
|
252 strict=False): |
|
253 """filter out result of relations_by_category(categories, permission) by |
|
254 removing final relations |
|
255 |
|
256 return a sorted list of (relation's label, relation'schema, role) |
|
257 """ |
|
258 return self.esrelations_by_category(self.edited_entity, categories, |
|
259 permission, strict=strict) |
|
260 |
|
261 def editable_attributes(self): |
|
262 """return a list of (relation schema, role) to edit for the entity""" |
|
263 return [(rschema, role) for rschema, _, role in self.relations_by_category( |
|
264 self.attrcategories, 'add') if rschema != 'eid'] |
|
265 |
188 |
266 # generic relations modifier ############################################### |
189 # generic relations modifier ############################################### |
267 |
190 |
268 def relations_table(self): |
191 def relations_table(self): |
269 """yiels 3-tuples (rtype, target, related_list) |
192 """yiels 3-tuples (rtype, target, related_list) |