8 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
8 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
9 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS |
9 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS |
10 # FOR A PARTICULAR PURPOSE. |
10 # FOR A PARTICULAR PURPOSE. |
11 # |
11 # |
12 |
12 |
13 __docformat__ = 'restructuredtext' |
13 """PyAMS_form.form module |
14 |
14 |
|
15 This module is the core of PyAMS_form package; it provides additions to form management as |
|
16 provided by z3c.form package. |
|
17 """ |
|
18 |
|
19 import json |
15 import logging |
20 import logging |
16 logger = logging.getLogger('PyAMS (form)') |
21 |
17 |
|
18 import json |
|
19 import transaction |
22 import transaction |
20 import venusian |
23 import venusian |
21 from persistent import IPersistent |
24 from persistent import IPersistent |
22 from pyramid.decorator import reify |
25 from pyramid.decorator import reify |
23 from pyramid.events import subscriber |
26 from pyramid.events import subscriber |
24 from pyramid.response import Response |
27 from pyramid.response import Response |
25 from pyramid_chameleon.interfaces import IChameleonTranslate |
28 from pyramid_chameleon.interfaces import IChameleonTranslate |
26 from pyramid_zope_request import PyramidPublisherRequest, PyramidToPublisher |
29 from pyramid_zope_request import PyramidPublisherRequest, PyramidToPublisher |
27 from z3c.form.button import Buttons |
30 from z3c.form.button import Buttons |
28 from z3c.form.form import AddForm as BaseAddForm, DisplayForm as BaseDisplayForm, EditForm as BaseEditForm, Form, \ |
31 from z3c.form.form import AddForm as BaseAddForm, DisplayForm as BaseDisplayForm, \ |
29 applyChanges |
32 EditForm as BaseEditForm, Form, applyChanges |
30 from z3c.form.interfaces import DISPLAY_MODE, IErrorViewSnippet, IMultipleErrors |
33 from z3c.form.interfaces import DISPLAY_MODE, IErrorViewSnippet, IMultipleErrors |
31 from zope.component import queryUtility |
34 from zope.component import queryUtility |
32 from zope.interface import Interface, alsoProvides, classImplements, implementer, Invalid |
35 from zope.interface import Interface, Invalid, alsoProvides, classImplements, implementer |
33 from zope.lifecycleevent import Attributes, ObjectCreatedEvent |
36 from zope.lifecycleevent import Attributes, ObjectCreatedEvent |
34 from zope.location import locate |
37 from zope.location import locate |
35 from zope.publisher.interfaces.browser import IBrowserRequest |
38 from zope.publisher.interfaces.browser import IBrowserRequest |
36 from zope.schema.fieldproperty import FieldProperty |
39 from zope.schema.fieldproperty import FieldProperty |
37 from zope.schema.interfaces import ValidationError |
40 from zope.schema.interfaces import ValidationError |
38 |
41 |
39 from pyams_form.group import GroupsBasedForm |
42 from pyams_form.group import GroupsBasedForm |
40 from pyams_form.interfaces import get_form_weight |
43 from pyams_form.interfaces import get_form_weight |
41 from pyams_form.interfaces.form import FormCreatedEvent, IAJAXForm, ICustomUpdateSubForm, IForm, \ |
44 from pyams_form.interfaces.form import FormCreatedEvent, IAJAXForm, ICustomUpdateSubForm, IForm, \ |
42 IFormContextPermissionChecker, IFormCreatedEvent, IFormLayer, IInnerForm, IInnerSubForm, IInnerTabForm |
45 IFormContextPermissionChecker, IFormCreatedEvent, IFormLayer, IInnerForm, IInnerSubForm, \ |
|
46 IInnerTabForm |
43 from pyams_form.interfaces.form import FormObjectCreatedEvent, FormObjectModifiedEvent |
47 from pyams_form.interfaces.form import FormObjectCreatedEvent, FormObjectModifiedEvent |
44 from pyams_form.interfaces.form import IAddFormButtons, IEditFormButtons, IModalAddFormButtons, \ |
48 from pyams_form.interfaces.form import IAddFormButtons, IEditFormButtons, IModalAddFormButtons, \ |
45 IModalDisplayFormButtons, IModalEditFormButtons |
49 IModalDisplayFormButtons, IModalEditFormButtons |
46 from pyams_i18n.interfaces import II18n |
50 from pyams_i18n.interfaces import II18n |
47 from pyams_pagelet.interfaces import PageletCreatedEvent |
51 from pyams_pagelet.interfaces import PageletCreatedEvent |
50 from pyams_template.interfaces import IContentTemplate, ILayoutTemplate |
54 from pyams_template.interfaces import IContentTemplate, ILayoutTemplate |
51 from pyams_utils.adapter import ContextRequestViewAdapter, adapter_config |
55 from pyams_utils.adapter import ContextRequestViewAdapter, adapter_config |
52 from pyams_utils.interfaces import FORBIDDEN_PERMISSION, ICacheKeyValue |
56 from pyams_utils.interfaces import FORBIDDEN_PERMISSION, ICacheKeyValue |
53 from pyams_utils.url import absolute_url |
57 from pyams_utils.url import absolute_url |
54 |
58 |
55 from pyams_form import _ |
59 |
56 |
60 __docformat__ = 'restructuredtext' |
|
61 |
|
62 from pyams_form import _ # pylint: disable=ungrouped-imports |
|
63 |
|
64 |
|
65 LOGGER = logging.getLogger('PyAMS (form)') |
57 |
66 |
58 REDIRECT_STATUS_CODES = (300, 301, 302, 303, 304, 305, 307) |
67 REDIRECT_STATUS_CODES = (300, 301, 302, 303, 304, 305, 307) |
59 |
68 |
60 |
69 |
61 @PyramidToPublisher(IBrowserRequest) |
70 @PyramidToPublisher(IBrowserRequest) |
94 alsoProvides(req, self.layer) |
103 alsoProvides(req, self.layer) |
95 request.registry.notify(FormCreatedEvent(self)) |
104 request.registry.notify(FormCreatedEvent(self)) |
96 |
105 |
97 @property |
106 @property |
98 def title(self): |
107 def title(self): |
|
108 """Get form's title""" |
99 registry = self.request.registry |
109 registry = self.request.registry |
100 adapter = registry.queryMultiAdapter((self.context, self.request, self), IContentTitle) |
110 adapter = registry.queryMultiAdapter((self.context, self.request, self), IContentTitle) |
101 if adapter is None: |
111 if adapter is None: |
102 adapter = registry.queryAdapter(self.context, IContentTitle) |
112 adapter = registry.queryAdapter(self.context, IContentTitle) |
103 if adapter is not None: |
113 if adapter is not None: |
104 return adapter.title |
114 return adapter.title |
105 else: |
115 return II18n(self.context).query_attribute('title', request=self.request) |
106 return II18n(self.context).query_attribute('title', request=self.request) |
|
107 |
116 |
108 def check_mode(self): |
117 def check_mode(self): |
|
118 """Check form's mode according to context's permissions""" |
109 content = self.getContent() |
119 content = self.getContent() |
110 # check form permission to get form mode |
120 # check form permission to get form mode |
111 if self.edit_permission and not self.request.has_permission(self.edit_permission, content): |
121 if self.edit_permission and not self.request.has_permission(self.edit_permission, content): |
112 self.mode = DISPLAY_MODE |
122 self.mode = DISPLAY_MODE |
113 return |
123 return |
114 # check form mode based on context checker |
124 # check form mode based on context checker |
115 registry = self.request.registry |
125 registry = self.request.registry |
116 permission = None |
126 permission = None |
117 checker = registry.queryMultiAdapter((content, self.request, self), IFormContextPermissionChecker) |
127 checker = registry.queryMultiAdapter((content, self.request, self), |
|
128 IFormContextPermissionChecker) |
118 if checker is None: |
129 if checker is None: |
119 checker = registry.queryAdapter(content, IFormContextPermissionChecker) |
130 checker = registry.queryAdapter(content, IFormContextPermissionChecker) |
120 if checker is not None: |
131 if checker is not None: |
121 permission = checker.edit_permission |
132 permission = checker.edit_permission |
122 if permission and (permission != self.edit_permission): |
133 if permission and (permission != self.edit_permission): |
123 if (permission == FORBIDDEN_PERMISSION) or not self.request.has_permission(permission, content): |
134 if (permission == FORBIDDEN_PERMISSION) or not self.request.has_permission(permission, |
|
135 content): |
124 self.mode = DISPLAY_MODE |
136 self.mode = DISPLAY_MODE |
125 |
137 |
126 def update(self): |
138 def update(self): |
|
139 """Update form and all it's subforms""" |
127 # check form mode |
140 # check form mode |
128 self.check_mode() |
141 self.check_mode() |
129 # update form and sub-forms |
142 # update form and sub-forms |
130 [subform.update() for subform in self.subforms] |
143 [subform.update() for subform in self.subforms] # pylint: disable=expression-not-assigned |
131 [tabform.update() for tabform in self.tabforms] |
144 [tabform.update() for tabform in self.tabforms] # pylint: disable=expression-not-assigned |
132 Form.update(self) |
145 Form.update(self) |
133 # savepoint is required for each inner component to be persisted!! |
146 # savepoint is required for each inner component to be persisted!! |
134 transaction.savepoint() |
147 transaction.savepoint() |
135 |
148 |
136 def updateWidgets(self, prefix=None): |
149 def updateWidgets(self, prefix=None): |
|
150 """Update form's widgets""" |
137 super(BaseForm, self).updateWidgets(prefix) |
151 super(BaseForm, self).updateWidgets(prefix) |
138 if not self._groups: |
152 if not self._groups: |
139 self.updateGroups() |
153 self.updateGroups() |
140 |
154 |
141 def get_form_action(self): |
155 def get_form_action(self): |
|
156 """Get action associated with form""" |
142 return self.action |
157 return self.action |
143 |
158 |
144 @reify |
159 @reify |
145 def subforms(self): |
160 def subforms(self): |
|
161 """Get subforms adapters associated with this form""" |
146 registry = self.request.registry |
162 registry = self.request.registry |
147 return sorted((adapter[1] |
163 return sorted((adapter[1] |
148 for adapter in registry.getAdapters((self.context, self.request, self), IInnerSubForm)), |
164 for adapter in |
|
165 registry.getAdapters((self.context, self.request, self), IInnerSubForm)), |
149 key=get_form_weight) |
166 key=get_form_weight) |
150 |
167 |
151 @reify |
168 @reify |
152 def tabforms(self): |
169 def tabforms(self): |
|
170 """Get tabforms adapters associated with this form""" |
153 registry = self.request.registry |
171 registry = self.request.registry |
154 return sorted((adapter[1] |
172 return sorted((adapter[1] |
155 for adapter in registry.getAdapters((self.context, self.request, self), IInnerTabForm)), |
173 for adapter in |
|
174 registry.getAdapters((self.context, self.request, self), IInnerTabForm)), |
156 key=get_form_weight) |
175 key=get_form_weight) |
157 |
176 |
158 @property |
177 @property |
159 def forms(self): |
178 def forms(self): |
|
179 """Get all forms associated with this form, including the form itself""" |
160 return [self, ] + self.subforms + self.tabforms |
180 return [self, ] + self.subforms + self.tabforms |
161 |
181 |
162 def get_forms(self, include_self=True): |
182 def get_forms(self, include_self=True): |
|
183 """Get forms associated with this form; if *include_self* argument is True, self is also |
|
184 included into results list |
|
185 """ |
163 if include_self: |
186 if include_self: |
164 yield self |
187 yield self |
165 for group in self.groups: |
188 for group in self.groups: |
166 for subform in group.get_forms(): |
189 for subform in group.get_forms(): |
167 yield subform |
190 yield subform |
171 for form in self.tabforms: |
194 for form in self.tabforms: |
172 yield form |
195 yield form |
173 |
196 |
174 @reify |
197 @reify |
175 def warn_on_change(self): |
198 def warn_on_change(self): |
|
199 """JSON boolean value specifying "warn on change" flag""" |
176 if self._warn_on_change is True: |
200 if self._warn_on_change is True: |
177 return 'true' |
201 return 'true' |
178 elif self._warn_on_change is False: |
202 if self._warn_on_change is False: |
179 return 'false' |
203 return 'false' |
180 else: |
204 return None |
181 return None |
|
182 |
205 |
183 @property |
206 @property |
184 def is_dialog(self): |
207 def is_dialog(self): |
|
208 """Boolean flag specifying if current form should be displayed in a modal dialog box""" |
185 return IDialog.providedBy(self) |
209 return IDialog.providedBy(self) |
186 |
210 |
187 def get_widget_callback(self, widget): |
211 def get_widget_callback(self, widget): |
|
212 """Get callback associated with a given widget""" |
188 return (self.callbacks or {}).get(widget) |
213 return (self.callbacks or {}).get(widget) |
189 |
214 |
190 # Default z3c.form methods |
215 # Default z3c.form methods |
191 |
216 |
192 @property |
217 @property |
193 def errors(self): |
218 def errors(self): |
|
219 """Get form's errors""" |
194 result = [] |
220 result = [] |
195 for form in self.forms: |
221 for form in self.forms: |
196 result.extend(form.widgets.errors) |
222 result.extend(form.widgets.errors) |
197 return result |
223 return result |
198 |
224 |
199 def add_error(self, error, widget, status=None): |
225 def add_error(self, error, widget, status=None): |
|
226 """Add error to current list of form's errors""" |
200 if isinstance(error, str): |
227 if isinstance(error, str): |
201 error = Invalid(error) |
228 error = Invalid(error) |
202 if isinstance(widget, str): |
229 if isinstance(widget, str): |
203 widget = self.widgets[widget] |
230 widget = self.widgets[widget] |
204 snippet = self.request.registry.getMultiAdapter((error, self.request, widget, |
231 snippet = self.request.registry.getMultiAdapter((error, self.request, widget, |
211 if not self.status: |
238 if not self.status: |
212 self.status = translate(status or self.formErrorsMessage) |
239 self.status = translate(status or self.formErrorsMessage) |
213 self.status += '\n{0}'.format(translate(error.args[0])) |
240 self.status += '\n{0}'.format(translate(error.args[0])) |
214 |
241 |
215 def update_content(self, content, data): |
242 def update_content(self, content, data): |
|
243 """Update content properties with given data""" |
216 changes = applyChanges(self, content, data.get(self, data)) |
244 changes = applyChanges(self, content, data.get(self, data)) |
217 for subform in self.get_forms(include_self=False): |
245 for subform in self.get_forms(include_self=False): |
218 if subform.mode == DISPLAY_MODE: |
246 if subform.mode == DISPLAY_MODE: |
219 continue |
247 continue |
220 subform_update = ICustomUpdateSubForm(subform, None) |
248 subform_update = ICustomUpdateSubForm(subform, None) |
221 if subform_update is not None: |
249 if subform_update is not None: |
222 updates = subform_update.update_content(subform.getContent(), data.get(subform, data)) |
250 # pylint: disable=assignment-from-no-return |
|
251 updates = subform_update.update_content(subform.getContent(), |
|
252 data.get(subform, data)) |
223 if isinstance(updates, dict): |
253 if isinstance(updates, dict): |
224 changes.update(updates) |
254 changes.update(updates) |
225 else: |
255 else: |
226 changes.update(applyChanges(subform, subform.getContent(), data.get(subform, data))) |
256 changes.update(applyChanges(subform, subform.getContent(), data.get(subform, data))) |
227 return changes |
257 return changes |
228 |
258 |
229 def render(self): |
259 def render(self): |
|
260 """Render form using content template |
|
261 |
|
262 Several default templates are defined for all base forms interfaces. |
|
263 """ |
230 request = self.request |
264 request = self.request |
231 if isinstance(request, PyramidPublisherRequest): |
265 if isinstance(request, PyramidPublisherRequest): |
232 request = request._request |
266 request = request._request # pylint: disable=protected-access |
233 cdict = { |
267 cdict = { |
234 'context': self.context, |
268 'context': self.context, |
235 'request': request, |
269 'request': request, |
236 'view': self, |
270 'view': self, |
237 'translate': queryUtility(IChameleonTranslate) |
271 'translate': queryUtility(IChameleonTranslate) |
264 layout = registry.queryMultiAdapter((self, request, self.context), |
298 layout = registry.queryMultiAdapter((self, request, self.context), |
265 ILayoutTemplate) |
299 ILayoutTemplate) |
266 if layout is None: |
300 if layout is None: |
267 layout = registry.getMultiAdapter((self, request), ILayoutTemplate) |
301 layout = registry.getMultiAdapter((self, request), ILayoutTemplate) |
268 return Response(layout(**cdict)) |
302 return Response(layout(**cdict)) |
269 return Response(self.layout(**cdict)) |
303 return Response(self.layout(**cdict)) # pylint: disable=not-callable |
270 |
304 |
271 def get_skin(self, request=None): |
305 def get_skin(self, request=None): |
|
306 """Get current skin applied to request""" |
|
307 if request is None: |
|
308 request = self.request |
272 return request.annotations.get('__skin__') |
309 return request.annotations.get('__skin__') |
273 |
310 |
274 |
311 |
275 @implementer(IAJAXForm) |
312 @implementer(IAJAXForm) |
276 class AJAXForm(BaseForm): |
313 class AJAXForm(BaseForm): |
277 """AJAX form base class""" |
314 # pylint: disable=abstract-method |
|
315 """AJAX form base class |
|
316 |
|
317 An AJAX form is a form which is submitted through an AJAX call; submit response is |
|
318 returned as a JSON object which is handled by MyAMS. |
|
319 """ |
278 |
320 |
279 ajax_handler = FieldProperty(IAJAXForm['ajax_handler']) |
321 ajax_handler = FieldProperty(IAJAXForm['ajax_handler']) |
280 form_options = FieldProperty(IAJAXForm['form_options']) |
322 form_options = FieldProperty(IAJAXForm['form_options']) |
281 form_target = FieldProperty(IAJAXForm['form_target']) |
323 form_target = FieldProperty(IAJAXForm['form_target']) |
282 ajax_callback = FieldProperty(IAJAXForm['ajax_callback']) |
324 ajax_callback = FieldProperty(IAJAXForm['ajax_callback']) |
283 |
325 |
284 def get_form_action(self): |
326 def get_form_action(self): |
|
327 """Get form's target action URL""" |
285 return absolute_url(self.context, self.request, self.request.view_name) |
328 return absolute_url(self.context, self.request, self.request.view_name) |
286 |
329 |
287 def get_form_options(self): |
330 def get_form_options(self): |
|
331 """Get form's options""" |
288 return json.dumps(self.form_options) if self.form_options else None |
332 return json.dumps(self.form_options) if self.form_options else None |
289 |
333 |
290 def get_ajax_handler(self): |
334 def get_ajax_handler(self): |
|
335 """Get form's AJAX handler""" |
291 return absolute_url(self.context, self.request, self.ajax_handler) |
336 return absolute_url(self.context, self.request, self.ajax_handler) |
292 |
337 |
293 def get_ajax_errors(self, ajax_errors=None): |
338 def get_ajax_errors(self, ajax_errors=None): |
294 """Extract form errors in AJAX format""" |
339 """Extract form errors in JSON format""" |
295 translate = self.request.localizer.translate |
340 translate = self.request.localizer.translate |
296 errors = { |
341 errors = { |
297 'status': u'error', |
342 'status': u'error', |
298 'error_message': translate(self.status) |
343 'error_message': translate(self.status) |
299 } |
344 } |
300 registry = self.request.registry |
345 registry = self.request.registry |
301 for error in (ajax_errors or self.errors): |
346 for error in (ajax_errors or self.errors): |
302 if isinstance(error, Exception): |
347 if isinstance(error, Exception): |
303 error = registry.getMultiAdapter((error, self.request, None, None, self, self.request), |
348 error = registry.getMultiAdapter( |
304 IErrorViewSnippet) |
349 (error, self.request, None, None, self, self.request), |
|
350 IErrorViewSnippet) |
305 error.update() |
351 error.update() |
306 if IMultipleErrors.providedBy(error.error): |
352 if IMultipleErrors.providedBy(error.error): |
307 for inner_error in error.error.errors: |
353 for inner_error in error.error.errors: |
308 if hasattr(inner_error, 'widget'): |
354 if hasattr(inner_error, 'widget'): |
309 widget = inner_error.widget |
355 widget = inner_error.widget |
371 self.actions['add'].addClass('btn-primary') |
418 self.actions['add'].addClass('btn-primary') |
372 |
419 |
373 def createAndAdd(self, data): |
420 def createAndAdd(self, data): |
374 registry = self.request.registry |
421 registry = self.request.registry |
375 # create object |
422 # create object |
376 object = self.create(data.get(self, data)) |
423 obj = self.create(data.get(self, data)) |
377 if IPersistent.providedBy(object): |
424 if IPersistent.providedBy(obj): |
378 registry.notify(ObjectCreatedEvent(object)) |
425 registry.notify(ObjectCreatedEvent(obj)) |
379 # set parent temporarily to avoid NotYet exceptions |
426 # set parent temporarily to avoid NotYet exceptions |
380 locate(object, self.context) |
427 locate(obj, self.context) |
381 # update object properties before adding it |
428 # update object properties before adding it |
382 self.update_content(object, data) |
429 self.update_content(obj, data) |
383 self.add(object) |
430 self.add(obj) |
384 registry.notify(FormObjectCreatedEvent(object, self)) |
431 registry.notify(FormObjectCreatedEvent(obj, self)) |
385 return object |
432 return obj |
386 |
433 |
387 def update_content(self, content, data): |
434 def update_content(self, content, data): |
388 changes = applyChanges(self, content, data.get(self, data)) |
435 changes = applyChanges(self, content, data.get(self, data)) |
389 for subform in self.get_forms(include_self=False): |
436 for subform in self.get_forms(include_self=False): |
390 if subform.mode == DISPLAY_MODE: |
437 if subform.mode == DISPLAY_MODE: |
391 continue |
438 continue |
392 subform_update = ICustomUpdateSubForm(subform, None) |
439 subform_update = ICustomUpdateSubForm(subform, None) |
393 if subform_update is not None: |
440 if subform_update is not None: |
|
441 # pylint: disable=assignment-from-no-return |
394 updates = subform_update.update_content(content, data.get(subform, data)) |
442 updates = subform_update.update_content(content, data.get(subform, data)) |
395 if isinstance(updates, dict): |
443 if isinstance(updates, dict): |
396 changes.update(updates) |
444 changes.update(updates) |
397 else: |
445 else: |
398 changes.update(applyChanges(subform, content, data.get(subform, data))) |
446 changes.update(applyChanges(subform, content, data.get(subform, data))) |
455 for form in self.get_forms(include_self=False): |
506 for form in self.get_forms(include_self=False): |
456 try: |
507 try: |
457 form_output = form.get_ajax_output(changes) |
508 form_output = form.get_ajax_output(changes) |
458 if form_output: |
509 if form_output: |
459 for key, value in form_output.items(): |
510 for key, value in form_output.items(): |
460 if isinstance(value, (list, tuple)) and (key in output): # concatenate lists |
511 if isinstance(value, (list, tuple)) and ( |
|
512 key in output): # concatenate lists |
461 form_output[key] += output[key] |
513 form_output[key] += output[key] |
462 output.update(form_output) |
514 output.update(form_output) |
463 except NotImplementedError: |
515 except NotImplementedError: |
464 pass |
516 pass |
465 if output: |
517 if output: |
466 return output |
518 return output |
467 else: |
519 return { |
468 return { |
520 'status': 'reload', |
469 'status': 'reload', |
521 'location': self.nextURL() |
470 'location': self.nextURL() |
522 } |
471 } |
|
472 |
523 |
473 |
524 |
474 @implementer(IDialog) |
525 @implementer(IDialog) |
475 class DialogAddForm(AddForm): |
526 class DialogAddForm(AddForm): |
|
527 # pylint: disable=abstract-method |
476 """Modal dialog add form""" |
528 """Modal dialog add form""" |
477 |
529 |
478 buttons = Buttons(IModalAddFormButtons) |
530 buttons = Buttons(IModalAddFormButtons) |
479 dialog_class = 'modal-medium' |
531 dialog_class = 'modal-medium' |
480 |
532 |
481 |
533 |
482 @implementer(IInnerForm) |
534 @implementer(IInnerForm) |
483 class InnerAddForm(AddForm): |
535 class InnerAddForm(AddForm): |
|
536 # pylint: disable=abstract-method |
484 """Inner add form""" |
537 """Inner add form""" |
485 |
538 |
486 css_class = 'inner' |
539 css_class = 'inner' |
487 |
540 |
488 buttons = Buttons(Interface) |
541 buttons = Buttons(Interface) |
665 # Form events subscribers |
720 # Form events subscribers |
666 # |
721 # |
667 |
722 |
668 @subscriber(IFormCreatedEvent, context_selector=ISkinnable) |
723 @subscriber(IFormCreatedEvent, context_selector=ISkinnable) |
669 def handle_form_skin(event): |
724 def handle_form_skin(event): |
|
725 """Apply skin on form's creation event""" |
670 request = _request = event.object.request |
726 request = _request = event.object.request |
671 if isinstance(request, PyramidPublisherRequest): |
727 if isinstance(request, PyramidPublisherRequest): |
672 _request = request._request |
728 _request = request._request # pylint: disable=protected-access |
673 skin = ISkinnable(event.object).get_skin(_request) |
729 skin = ISkinnable(event.object).get_skin(_request) # pylint: disable=assignment-from-no-return |
674 if skin is not None: |
730 if skin is not None: |
675 apply_skin(request, skin) |
731 apply_skin(request, skin) |
676 |
732 |
677 |
733 |
678 class FormSelector(object): |
734 class FormSelector: |
679 """Form event selector |
735 """Form event selector |
680 |
736 |
681 This selector can be used by subscriber to filter form events |
737 This selector can be used by subscriber to filter form events |
682 """ |
738 """ |
683 |
739 |
684 def __init__(self, ifaces, config): |
740 def __init__(self, ifaces, config): # pylint: disable=unused-argument |
685 if not isinstance(ifaces, (list, tuple)): |
741 if not isinstance(ifaces, (list, tuple)): |
686 ifaces = (ifaces,) |
742 ifaces = (ifaces,) |
687 self.interfaces = ifaces |
743 self.interfaces = ifaces |
688 |
744 |
689 def text(self): |
745 def text(self): |
|
746 """Form's selector text""" |
690 return 'form_selector = %s' % str(self.interfaces) |
747 return 'form_selector = %s' % str(self.interfaces) |
691 |
748 |
692 phash = text |
749 phash = text |
693 |
750 |
694 def __call__(self, event): |
751 def __call__(self, event): |
700 if isinstance(event.form, intf): |
757 if isinstance(event.form, intf): |
701 return True |
758 return True |
702 return False |
759 return False |
703 |
760 |
704 |
761 |
705 class WidgetSelector(object): |
762 class WidgetSelector: |
706 """Widget event selector |
763 """Widget event selector |
707 |
764 |
708 This selector can be used by subscribers to filter widgets events |
765 This selector can be used by subscribers to filter widgets events |
709 """ |
766 """ |
710 |
767 |
711 def __init__(self, ifaces, config): |
768 def __init__(self, ifaces, config): # pylint: disable=unused-argument |
712 if not isinstance(ifaces, (list, tuple)): |
769 if not isinstance(ifaces, (list, tuple)): |
713 ifaces = (ifaces,) |
770 ifaces = (ifaces,) |
714 self.interfaces = ifaces |
771 self.interfaces = ifaces |
715 |
772 |
716 def text(self): |
773 def text(self): |
|
774 """Widget's selector text""" |
717 return 'widget_selector = %s' % str(self.interfaces) |
775 return 'widget_selector = %s' % str(self.interfaces) |
718 |
776 |
719 phash = text |
777 phash = text |
720 |
778 |
721 def __call__(self, event): |
779 def __call__(self, event): |
727 if isinstance(event.widget, intf): |
785 if isinstance(event.widget, intf): |
728 return True |
786 return True |
729 return False |
787 return False |
730 |
788 |
731 |
789 |
732 class ajax_config(object): |
790 class ajax_config: # pylint: disable=invalid-name |
733 """Class decorator used to declare AJAX settings for a form. |
791 """Class decorator used to declare AJAX settings for a form. |
734 |
792 |
735 When decorating a form class, this decorator create a new subclass which will handle AJAX queries |
793 When decorating a form class, this decorator create a new subclass which will handle AJAX |
736 executed when submitting the form, and register this class as Pyramid's view. |
794 queries executed when submitting the form, and register this class as Pyramid's view. |
737 |
795 |
738 Decorator arguments (all optional) are: |
796 Decorator arguments (all optional) are: |
739 |
797 |
740 - **name**: AJAX view name |
798 - :param name: AJAX view name |
741 - **context** (or **for_**): view context type |
799 - :param context: (or **for_**): view context type |
742 - **layer** (or **request_type**): request type for which view is registered |
800 - :param layer: (or **request_type**): request type for which view is registered |
743 - **permission**: permission required to call the view; if not set, permission is extracted from form's |
801 - :param permission: permission required to call the view; if not set, permission is |
744 "edit_permission" attribute |
802 extracted from form's "edit_permission" attribute |
745 - **base**: base class for newly created AJAX form; if not set, inherits from :py:class:`AJAXEditForm` |
803 - :param base: base class for newly created AJAX form; if not set, inherits from |
746 - **implementer**: list of interfaces implemented by the new class |
804 :py:class:`AJAXEditForm` |
747 - **method** (or **request_method**): HTTP method name; if not defined, view is restricted to "POST" requests |
805 - :param implementer: list of interfaces implemented by the new class |
748 - **renderer**: name of Pyramid renderer used to return view output; defaults to 'json' |
806 - :param method: (or **request_method**): HTTP method name; if not defined, view is |
749 - **xhr**: Pyramid's view's "xhr" predicate; **True** by default |
807 restricted to "POST" requests |
|
808 - :param renderer: name of Pyramid renderer used to return view output; defaults to 'json' |
|
809 - :param xhr: Pyramid's view's "xhr" predicate; **True** by default |
750 """ |
810 """ |
751 |
811 |
752 venusian = venusian # for testing injection |
812 venusian = venusian # for testing injection |
753 |
813 |
754 def __init__(self, **settings): |
814 def __init__(self, **settings): |
771 |
831 |
772 def __call__(self, wrapped): |
832 def __call__(self, wrapped): |
773 settings = self.__dict__.copy() |
833 settings = self.__dict__.copy() |
774 depth = settings.pop('_depth', 0) |
834 depth = settings.pop('_depth', 0) |
775 |
835 |
776 def callback(context, name, ob): |
836 def callback(context, name, obj): # pylint: disable=unused-argument |
777 cdict = { |
837 cdict = { |
778 '__name__': settings.get('name'), |
838 '__name__': settings.get('name'), |
779 '__module__': ob.__module__, |
839 '__module__': obj.__module__, |
780 'permission': settings.get('permission') or ob.edit_permission |
840 'permission': settings.get('permission') or obj.edit_permission |
781 } |
841 } |
782 |
842 |
783 # Set form's AJAX handler |
843 # Set form's AJAX handler |
784 ob.ajax_handler = settings.get('name') |
844 obj.ajax_handler = settings.get('name') |
785 |
845 |
786 # Create new AJAX form and register view |
846 # Create new AJAX form and register view |
787 base = settings.pop('base') |
847 base = settings.pop('base') |
788 new_class = type('AJAX' + ob.__name__, (base, ob), cdict) |
848 new_class = type('AJAX' + obj.__name__, (base, obj), cdict) |
789 try: |
849 try: |
790 # check if current form is overriding "get_ajax_output" method |
850 # check if current form is overriding "get_ajax_output" method |
791 if base not in (AJAXAddForm, AJAXEditForm): # custom base |
851 if base not in (AJAXAddForm, AJAXEditForm): # custom base |
792 ob_ajax_parent, _ = ob.get_ajax_output.__qualname__.split('.') |
852 ob_ajax_parent, _ = obj.get_ajax_output.__qualname__.split('.') |
793 base_ajax_parent, _ = base.get_ajax_output.__qualname__.split('.') |
853 base_ajax_parent, _ = base.get_ajax_output.__qualname__.split('.') |
794 if (ob_ajax_parent != base_ajax_parent) and \ |
854 if (ob_ajax_parent != base_ajax_parent) and \ |
795 (ob_ajax_parent in ('AJAXForm', 'AJAXAddForm', 'AJAXEditForm')): |
855 (ob_ajax_parent in ('AJAXForm', 'AJAXAddForm', 'AJAXEditForm')): |
796 new_class.get_ajax_output = base.get_ajax_output |
856 new_class.get_ajax_output = base.get_ajax_output |
797 else: |
857 else: |
798 new_class.get_ajax_output = ob.get_ajax_output |
858 new_class.get_ajax_output = obj.get_ajax_output |
799 else: |
859 else: |
800 base_ajax_parent, _ = ob.get_ajax_output.__qualname__.split('.') |
860 base_ajax_parent, _ = obj.get_ajax_output.__qualname__.split('.') |
801 if base_ajax_parent not in ('AJAXForm', 'AJAXAddForm', 'AJAXEditForm'): # overriden method |
861 if base_ajax_parent not in ('AJAXForm', 'AJAXAddForm', 'AJAXEditForm'): |
802 new_class.get_ajax_output = ob.get_ajax_output |
862 # overriden method |
|
863 new_class.get_ajax_output = obj.get_ajax_output |
803 except AttributeError: |
864 except AttributeError: |
804 pass |
865 pass |
805 |
866 |
806 if 'implementer' in settings: |
867 if 'implementer' in settings: |
807 implementer = settings.pop('implementer') |
868 impl = settings.pop('implementer') |
808 if not isinstance(implementer, (list, tuple, set)): |
869 if not isinstance(impl, (list, tuple, set)): |
809 implementer = (implementer,) |
870 impl = (impl,) |
810 classImplements(new_class, *implementer) |
871 classImplements(new_class, *impl) |
811 |
872 |
812 logger.debug('Registering AJAX view "{0}" for {1} ({2})'.format(settings.get('name'), |
873 LOGGER.debug('Registering AJAX view "{0}" for {1} ({2})'.format( |
813 str(settings.get('context', Interface)), |
874 settings.get('name'), str(settings.get('context', Interface)), str(new_class))) |
814 str(new_class))) |
875 |
815 |
876 config = context.config.with_package(info.module) # pylint: disable=no-member |
816 config = context.config.with_package(info.module) |
|
817 config.add_view(view=new_class, **settings) |
877 config.add_view(view=new_class, **settings) |
818 |
878 |
819 info = self.venusian.attach(wrapped, callback, category='pyams_form', |
879 info = self.venusian.attach(wrapped, callback, category='pyams_form', depth=depth + 1) |
820 depth=depth + 1) |
880 |
821 |
881 if info.scope == 'class': # pylint: disable=no-member |
822 if info.scope == 'class': |
|
823 # if the decorator was attached to a method in a class, or |
882 # if the decorator was attached to a method in a class, or |
824 # otherwise executed at class scope, we need to set an |
883 # otherwise executed at class scope, we need to set an |
825 # 'attr' into the settings if one isn't already in there |
884 # 'attr' into the settings if one isn't already in there |
826 if settings.get('attr') is None: |
885 if settings.get('attr') is None: |
827 settings['attr'] = wrapped.__name__ |
886 settings['attr'] = wrapped.__name__ |
828 |
887 |
829 settings['_info'] = info.codeinfo # fbo "action_method" |
888 settings['_info'] = info.codeinfo # pylint: disable=no-member |
830 return wrapped |
889 return wrapped |