--- a/src/pyams_form/__init__.py Tue Nov 19 16:30:58 2019 +0100
+++ b/src/pyams_form/__init__.py Wed Dec 04 11:05:25 2019 +0100
@@ -10,17 +10,23 @@
# FOR A PARTICULAR PURPOSE.
#
-__docformat__ = 'restructuredtext'
+"""PyAMS_form package
+
+This package is an extension to z3c.form.
+
+It allows to integrate z3c forms into Pyramid; it is adding some features like inner subforms
+(handled with adapters), form groups (which are ued to group form fields together inside a
+fieldset in a form), modal forms (which are displayed into a modal window), and is providing
+default templates for all these elements to be displayed correctly when using PyAMS_zmi package
+or any other Bootstrap-based skin.
+"""
+
+from pyramid.i18n import TranslationStringFactory
-# import standard library
-
-# import interfaces
+_ = TranslationStringFactory('pyams_form')
-# import packages
-
-from pyramid.i18n import TranslationStringFactory
-_ = TranslationStringFactory('pyams_form')
+__docformat__ = 'restructuredtext'
def includeme(config):
@@ -28,5 +34,5 @@
Split in another package to remove cyclic dependencies with TranslationStringFactory
"""
- from .include import include_package
+ from .include import include_package # pylint: disable=import-outside-toplevel
include_package(config)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_form/doctests/README.rst Wed Dec 04 11:05:25 2019 +0100
@@ -0,0 +1,11 @@
+==================
+pyams_form package
+==================
+
+ >>> from pyramid.testing import setUp, tearDown
+ >>> config = setUp()
+
+ >>> from pyams_form.interfaces.form import *
+ >>> from pyams_form.form import *
+
+ >>> tearDown()
--- a/src/pyams_form/doctests/README.txt Tue Nov 19 16:30:58 2019 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,3 +0,0 @@
-==================
-pyams_ package
-==================
--- a/src/pyams_form/form.py Tue Nov 19 16:30:58 2019 +0100
+++ b/src/pyams_form/form.py Wed Dec 04 11:05:25 2019 +0100
@@ -10,12 +10,15 @@
# FOR A PARTICULAR PURPOSE.
#
-__docformat__ = 'restructuredtext'
+"""PyAMS_form.form module
-import logging
-logger = logging.getLogger('PyAMS (form)')
+This module is the core of PyAMS_form package; it provides additions to form management as
+provided by z3c.form package.
+"""
import json
+import logging
+
import transaction
import venusian
from persistent import IPersistent
@@ -25,11 +28,11 @@
from pyramid_chameleon.interfaces import IChameleonTranslate
from pyramid_zope_request import PyramidPublisherRequest, PyramidToPublisher
from z3c.form.button import Buttons
-from z3c.form.form import AddForm as BaseAddForm, DisplayForm as BaseDisplayForm, EditForm as BaseEditForm, Form, \
- applyChanges
+from z3c.form.form import AddForm as BaseAddForm, DisplayForm as BaseDisplayForm, \
+ EditForm as BaseEditForm, Form, applyChanges
from z3c.form.interfaces import DISPLAY_MODE, IErrorViewSnippet, IMultipleErrors
from zope.component import queryUtility
-from zope.interface import Interface, alsoProvides, classImplements, implementer, Invalid
+from zope.interface import Interface, Invalid, alsoProvides, classImplements, implementer
from zope.lifecycleevent import Attributes, ObjectCreatedEvent
from zope.location import locate
from zope.publisher.interfaces.browser import IBrowserRequest
@@ -39,7 +42,8 @@
from pyams_form.group import GroupsBasedForm
from pyams_form.interfaces import get_form_weight
from pyams_form.interfaces.form import FormCreatedEvent, IAJAXForm, ICustomUpdateSubForm, IForm, \
- IFormContextPermissionChecker, IFormCreatedEvent, IFormLayer, IInnerForm, IInnerSubForm, IInnerTabForm
+ IFormContextPermissionChecker, IFormCreatedEvent, IFormLayer, IInnerForm, IInnerSubForm, \
+ IInnerTabForm
from pyams_form.interfaces.form import FormObjectCreatedEvent, FormObjectModifiedEvent
from pyams_form.interfaces.form import IAddFormButtons, IEditFormButtons, IModalAddFormButtons, \
IModalDisplayFormButtons, IModalEditFormButtons
@@ -52,8 +56,13 @@
from pyams_utils.interfaces import FORBIDDEN_PERMISSION, ICacheKeyValue
from pyams_utils.url import absolute_url
-from pyams_form import _
+
+__docformat__ = 'restructuredtext'
+from pyams_form import _ # pylint: disable=ungrouped-imports
+
+
+LOGGER = logging.getLogger('PyAMS (form)')
REDIRECT_STATUS_CODES = (300, 301, 302, 303, 304, 305, 307)
@@ -96,16 +105,17 @@
@property
def title(self):
+ """Get form's title"""
registry = self.request.registry
adapter = registry.queryMultiAdapter((self.context, self.request, self), IContentTitle)
if adapter is None:
adapter = registry.queryAdapter(self.context, IContentTitle)
if adapter is not None:
return adapter.title
- else:
- return II18n(self.context).query_attribute('title', request=self.request)
+ return II18n(self.context).query_attribute('title', request=self.request)
def check_mode(self):
+ """Check form's mode according to context's permissions"""
content = self.getContent()
# check form permission to get form mode
if self.edit_permission and not self.request.has_permission(self.edit_permission, content):
@@ -114,52 +124,65 @@
# check form mode based on context checker
registry = self.request.registry
permission = None
- checker = registry.queryMultiAdapter((content, self.request, self), IFormContextPermissionChecker)
+ checker = registry.queryMultiAdapter((content, self.request, self),
+ IFormContextPermissionChecker)
if checker is None:
checker = registry.queryAdapter(content, IFormContextPermissionChecker)
if checker is not None:
permission = checker.edit_permission
if permission and (permission != self.edit_permission):
- if (permission == FORBIDDEN_PERMISSION) or not self.request.has_permission(permission, content):
+ if (permission == FORBIDDEN_PERMISSION) or not self.request.has_permission(permission,
+ content):
self.mode = DISPLAY_MODE
def update(self):
+ """Update form and all it's subforms"""
# check form mode
self.check_mode()
# update form and sub-forms
- [subform.update() for subform in self.subforms]
- [tabform.update() for tabform in self.tabforms]
+ [subform.update() for subform in self.subforms] # pylint: disable=expression-not-assigned
+ [tabform.update() for tabform in self.tabforms] # pylint: disable=expression-not-assigned
Form.update(self)
# savepoint is required for each inner component to be persisted!!
transaction.savepoint()
def updateWidgets(self, prefix=None):
+ """Update form's widgets"""
super(BaseForm, self).updateWidgets(prefix)
if not self._groups:
self.updateGroups()
def get_form_action(self):
+ """Get action associated with form"""
return self.action
@reify
def subforms(self):
+ """Get subforms adapters associated with this form"""
registry = self.request.registry
return sorted((adapter[1]
- for adapter in registry.getAdapters((self.context, self.request, self), IInnerSubForm)),
+ for adapter in
+ registry.getAdapters((self.context, self.request, self), IInnerSubForm)),
key=get_form_weight)
@reify
def tabforms(self):
+ """Get tabforms adapters associated with this form"""
registry = self.request.registry
return sorted((adapter[1]
- for adapter in registry.getAdapters((self.context, self.request, self), IInnerTabForm)),
+ for adapter in
+ registry.getAdapters((self.context, self.request, self), IInnerTabForm)),
key=get_form_weight)
@property
def forms(self):
+ """Get all forms associated with this form, including the form itself"""
return [self, ] + self.subforms + self.tabforms
def get_forms(self, include_self=True):
+ """Get forms associated with this form; if *include_self* argument is True, self is also
+ included into results list
+ """
if include_self:
yield self
for group in self.groups:
@@ -173,30 +196,34 @@
@reify
def warn_on_change(self):
+ """JSON boolean value specifying "warn on change" flag"""
if self._warn_on_change is True:
return 'true'
- elif self._warn_on_change is False:
+ if self._warn_on_change is False:
return 'false'
- else:
- return None
+ return None
@property
def is_dialog(self):
+ """Boolean flag specifying if current form should be displayed in a modal dialog box"""
return IDialog.providedBy(self)
def get_widget_callback(self, widget):
+ """Get callback associated with a given widget"""
return (self.callbacks or {}).get(widget)
# Default z3c.form methods
@property
def errors(self):
+ """Get form's errors"""
result = []
for form in self.forms:
result.extend(form.widgets.errors)
return result
def add_error(self, error, widget, status=None):
+ """Add error to current list of form's errors"""
if isinstance(error, str):
error = Invalid(error)
if isinstance(widget, str):
@@ -213,13 +240,16 @@
self.status += '\n{0}'.format(translate(error.args[0]))
def update_content(self, content, data):
+ """Update content properties with given data"""
changes = applyChanges(self, content, data.get(self, data))
for subform in self.get_forms(include_self=False):
if subform.mode == DISPLAY_MODE:
continue
subform_update = ICustomUpdateSubForm(subform, None)
if subform_update is not None:
- updates = subform_update.update_content(subform.getContent(), data.get(subform, data))
+ # pylint: disable=assignment-from-no-return
+ updates = subform_update.update_content(subform.getContent(),
+ data.get(subform, data))
if isinstance(updates, dict):
changes.update(updates)
else:
@@ -227,9 +257,13 @@
return changes
def render(self):
+ """Render form using content template
+
+ Several default templates are defined for all base forms interfaces.
+ """
request = self.request
if isinstance(request, PyramidPublisherRequest):
- request = request._request
+ request = request._request # pylint: disable=protected-access
cdict = {
'context': self.context,
'request': request,
@@ -242,9 +276,9 @@
if template is None:
template = registry.getMultiAdapter((self, request), IContentTemplate)
return template(**cdict)
- return self.template(**cdict)
+ return self.template(**cdict) # pylint: disable=not-callable
- def __call__(self, **kwargs):
+ def __call__(self, **kwargs): # pylint: disable=arguments-differ
self.update()
if self.request.response.status_code in REDIRECT_STATUS_CODES:
return Response('')
@@ -266,15 +300,23 @@
if layout is None:
layout = registry.getMultiAdapter((self, request), ILayoutTemplate)
return Response(layout(**cdict))
- return Response(self.layout(**cdict))
+ return Response(self.layout(**cdict)) # pylint: disable=not-callable
def get_skin(self, request=None):
+ """Get current skin applied to request"""
+ if request is None:
+ request = self.request
return request.annotations.get('__skin__')
@implementer(IAJAXForm)
class AJAXForm(BaseForm):
- """AJAX form base class"""
+ # pylint: disable=abstract-method
+ """AJAX form base class
+
+ An AJAX form is a form which is submitted through an AJAX call; submit response is
+ returned as a JSON object which is handled by MyAMS.
+ """
ajax_handler = FieldProperty(IAJAXForm['ajax_handler'])
form_options = FieldProperty(IAJAXForm['form_options'])
@@ -282,16 +324,19 @@
ajax_callback = FieldProperty(IAJAXForm['ajax_callback'])
def get_form_action(self):
+ """Get form's target action URL"""
return absolute_url(self.context, self.request, self.request.view_name)
def get_form_options(self):
+ """Get form's options"""
return json.dumps(self.form_options) if self.form_options else None
def get_ajax_handler(self):
+ """Get form's AJAX handler"""
return absolute_url(self.context, self.request, self.ajax_handler)
def get_ajax_errors(self, ajax_errors=None):
- """Extract form errors in AJAX format"""
+ """Extract form errors in JSON format"""
translate = self.request.localizer.translate
errors = {
'status': u'error',
@@ -300,8 +345,9 @@
registry = self.request.registry
for error in (ajax_errors or self.errors):
if isinstance(error, Exception):
- error = registry.getMultiAdapter((error, self.request, None, None, self, self.request),
- IErrorViewSnippet)
+ error = registry.getMultiAdapter(
+ (error, self.request, None, None, self, self.request),
+ IErrorViewSnippet)
error.update()
if IMultipleErrors.providedBy(error.error):
for inner_error in error.error.errors:
@@ -356,6 +402,7 @@
#
class AddForm(AJAXForm, BaseAddForm):
+ # pylint: disable=abstract-method
"""Add form base class"""
prefix = 'add_form.'
@@ -373,16 +420,16 @@
def createAndAdd(self, data):
registry = self.request.registry
# create object
- object = self.create(data.get(self, data))
- if IPersistent.providedBy(object):
- registry.notify(ObjectCreatedEvent(object))
+ obj = self.create(data.get(self, data))
+ if IPersistent.providedBy(obj):
+ registry.notify(ObjectCreatedEvent(obj))
# set parent temporarily to avoid NotYet exceptions
- locate(object, self.context)
+ locate(obj, self.context)
# update object properties before adding it
- self.update_content(object, data)
- self.add(object)
- registry.notify(FormObjectCreatedEvent(object, self))
- return object
+ self.update_content(obj, data)
+ self.add(obj)
+ registry.notify(FormObjectCreatedEvent(obj, self))
+ return obj
def update_content(self, content, data):
changes = applyChanges(self, content, data.get(self, data))
@@ -391,6 +438,7 @@
continue
subform_update = ICustomUpdateSubForm(subform, None)
if subform_update is not None:
+ # pylint: disable=assignment-from-no-return
updates = subform_update.update_content(content, data.get(subform, data))
if isinstance(updates, dict):
changes.update(updates)
@@ -405,13 +453,15 @@
@property
def edit_permission(self):
+ """Get permission associated with this form's context"""
return self.view.edit_permission
class AJAXAddForm(AddForm):
+ # pylint: disable=abstract-method
"""AJAX add form"""
- def __call__(self):
+ def __call__(self): # pylint: disable=arguments-differ
self.request.registry.notify(PageletCreatedEvent(self))
data, errors = {}, ()
for form in self.get_forms():
@@ -435,8 +485,9 @@
except KeyError:
continue
else:
- view = registry.getMultiAdapter((error, self.request, widget, widget.field, self, self.context),
- IErrorViewSnippet)
+ view = registry.getMultiAdapter(
+ (error, self.request, widget, widget.field, self, self.context),
+ IErrorViewSnippet)
view.update()
widget.error = view
errors = (view,)
@@ -457,22 +508,23 @@
form_output = form.get_ajax_output(changes)
if form_output:
for key, value in form_output.items():
- if isinstance(value, (list, tuple)) and (key in output): # concatenate lists
+ if isinstance(value, (list, tuple)) and (
+ key in output): # concatenate lists
form_output[key] += output[key]
output.update(form_output)
except NotImplementedError:
pass
if output:
return output
- else:
- return {
- 'status': 'reload',
- 'location': self.nextURL()
- }
+ return {
+ 'status': 'reload',
+ 'location': self.nextURL()
+ }
@implementer(IDialog)
class DialogAddForm(AddForm):
+ # pylint: disable=abstract-method
"""Modal dialog add form"""
buttons = Buttons(IModalAddFormButtons)
@@ -481,6 +533,7 @@
@implementer(IInnerForm)
class InnerAddForm(AddForm):
+ # pylint: disable=abstract-method
"""Inner add form"""
css_class = 'inner'
@@ -528,7 +581,7 @@
class AJAXEditForm(EditForm):
"""AJAX edit form"""
- def __call__(self):
+ def __call__(self): # pylint: disable=arguments-differ,too-many-branches
# call form elements
self.request.registry.notify(PageletCreatedEvent(self))
data, errors = {}, ()
@@ -555,8 +608,9 @@
except KeyError:
continue
else:
- view = registry.getMultiAdapter((error, self.request, widget, widget.field, self, self.context),
- IErrorViewSnippet)
+ view = registry.getMultiAdapter(
+ (error, self.request, widget, widget.field, self, self.context),
+ IErrorViewSnippet)
view.update()
widget.error = view
errors = (view,)
@@ -593,7 +647,8 @@
form_output = form.get_ajax_output(changes)
if form_output:
for key, value in form_output.items():
- if isinstance(value, (list, tuple)) and (key in output): # concatenate lists
+ if isinstance(value, (list, tuple)) and (
+ key in output): # concatenate lists
form_output[key] += output[key]
output.update(form_output)
except NotImplementedError:
@@ -667,26 +722,28 @@
@subscriber(IFormCreatedEvent, context_selector=ISkinnable)
def handle_form_skin(event):
+ """Apply skin on form's creation event"""
request = _request = event.object.request
if isinstance(request, PyramidPublisherRequest):
- _request = request._request
- skin = ISkinnable(event.object).get_skin(_request)
+ _request = request._request # pylint: disable=protected-access
+ skin = ISkinnable(event.object).get_skin(_request) # pylint: disable=assignment-from-no-return
if skin is not None:
apply_skin(request, skin)
-class FormSelector(object):
+class FormSelector:
"""Form event selector
This selector can be used by subscriber to filter form events
"""
- def __init__(self, ifaces, config):
+ def __init__(self, ifaces, config): # pylint: disable=unused-argument
if not isinstance(ifaces, (list, tuple)):
ifaces = (ifaces,)
self.interfaces = ifaces
def text(self):
+ """Form's selector text"""
return 'form_selector = %s' % str(self.interfaces)
phash = text
@@ -702,18 +759,19 @@
return False
-class WidgetSelector(object):
+class WidgetSelector:
"""Widget event selector
This selector can be used by subscribers to filter widgets events
"""
- def __init__(self, ifaces, config):
+ def __init__(self, ifaces, config): # pylint: disable=unused-argument
if not isinstance(ifaces, (list, tuple)):
ifaces = (ifaces,)
self.interfaces = ifaces
def text(self):
+ """Widget's selector text"""
return 'widget_selector = %s' % str(self.interfaces)
phash = text
@@ -729,24 +787,26 @@
return False
-class ajax_config(object):
+class ajax_config: # pylint: disable=invalid-name
"""Class decorator used to declare AJAX settings for a form.
- When decorating a form class, this decorator create a new subclass which will handle AJAX queries
- executed when submitting the form, and register this class as Pyramid's view.
+ When decorating a form class, this decorator create a new subclass which will handle AJAX
+ queries executed when submitting the form, and register this class as Pyramid's view.
Decorator arguments (all optional) are:
- - **name**: AJAX view name
- - **context** (or **for_**): view context type
- - **layer** (or **request_type**): request type for which view is registered
- - **permission**: permission required to call the view; if not set, permission is extracted from form's
- "edit_permission" attribute
- - **base**: base class for newly created AJAX form; if not set, inherits from :py:class:`AJAXEditForm`
- - **implementer**: list of interfaces implemented by the new class
- - **method** (or **request_method**): HTTP method name; if not defined, view is restricted to "POST" requests
- - **renderer**: name of Pyramid renderer used to return view output; defaults to 'json'
- - **xhr**: Pyramid's view's "xhr" predicate; **True** by default
+ - :param name: AJAX view name
+ - :param context: (or **for_**): view context type
+ - :param layer: (or **request_type**): request type for which view is registered
+ - :param permission: permission required to call the view; if not set, permission is
+ extracted from form's "edit_permission" attribute
+ - :param base: base class for newly created AJAX form; if not set, inherits from
+ :py:class:`AJAXEditForm`
+ - :param implementer: list of interfaces implemented by the new class
+ - :param method: (or **request_method**): HTTP method name; if not defined, view is
+ restricted to "POST" requests
+ - :param renderer: name of Pyramid renderer used to return view output; defaults to 'json'
+ - :param xhr: Pyramid's view's "xhr" predicate; **True** by default
"""
venusian = venusian # for testing injection
@@ -773,58 +833,57 @@
settings = self.__dict__.copy()
depth = settings.pop('_depth', 0)
- def callback(context, name, ob):
+ def callback(context, name, obj): # pylint: disable=unused-argument
cdict = {
'__name__': settings.get('name'),
- '__module__': ob.__module__,
- 'permission': settings.get('permission') or ob.edit_permission
+ '__module__': obj.__module__,
+ 'permission': settings.get('permission') or obj.edit_permission
}
# Set form's AJAX handler
- ob.ajax_handler = settings.get('name')
+ obj.ajax_handler = settings.get('name')
# Create new AJAX form and register view
base = settings.pop('base')
- new_class = type('AJAX' + ob.__name__, (base, ob), cdict)
+ new_class = type('AJAX' + obj.__name__, (base, obj), cdict)
try:
# check if current form is overriding "get_ajax_output" method
if base not in (AJAXAddForm, AJAXEditForm): # custom base
- ob_ajax_parent, _ = ob.get_ajax_output.__qualname__.split('.')
+ ob_ajax_parent, _ = obj.get_ajax_output.__qualname__.split('.')
base_ajax_parent, _ = base.get_ajax_output.__qualname__.split('.')
if (ob_ajax_parent != base_ajax_parent) and \
- (ob_ajax_parent in ('AJAXForm', 'AJAXAddForm', 'AJAXEditForm')):
+ (ob_ajax_parent in ('AJAXForm', 'AJAXAddForm', 'AJAXEditForm')):
new_class.get_ajax_output = base.get_ajax_output
else:
- new_class.get_ajax_output = ob.get_ajax_output
+ new_class.get_ajax_output = obj.get_ajax_output
else:
- base_ajax_parent, _ = ob.get_ajax_output.__qualname__.split('.')
- if base_ajax_parent not in ('AJAXForm', 'AJAXAddForm', 'AJAXEditForm'): # overriden method
- new_class.get_ajax_output = ob.get_ajax_output
+ base_ajax_parent, _ = obj.get_ajax_output.__qualname__.split('.')
+ if base_ajax_parent not in ('AJAXForm', 'AJAXAddForm', 'AJAXEditForm'):
+ # overriden method
+ new_class.get_ajax_output = obj.get_ajax_output
except AttributeError:
pass
if 'implementer' in settings:
- implementer = settings.pop('implementer')
- if not isinstance(implementer, (list, tuple, set)):
- implementer = (implementer,)
- classImplements(new_class, *implementer)
+ impl = settings.pop('implementer')
+ if not isinstance(impl, (list, tuple, set)):
+ impl = (impl,)
+ classImplements(new_class, *impl)
- logger.debug('Registering AJAX view "{0}" for {1} ({2})'.format(settings.get('name'),
- str(settings.get('context', Interface)),
- str(new_class)))
+ LOGGER.debug('Registering AJAX view "{0}" for {1} ({2})'.format(
+ settings.get('name'), str(settings.get('context', Interface)), str(new_class)))
- config = context.config.with_package(info.module)
+ config = context.config.with_package(info.module) # pylint: disable=no-member
config.add_view(view=new_class, **settings)
- info = self.venusian.attach(wrapped, callback, category='pyams_form',
- depth=depth + 1)
+ info = self.venusian.attach(wrapped, callback, category='pyams_form', depth=depth + 1)
- if info.scope == 'class':
+ if info.scope == 'class': # pylint: disable=no-member
# if the decorator was attached to a method in a class, or
# otherwise executed at class scope, we need to set an
# 'attr' into the settings if one isn't already in there
if settings.get('attr') is None:
settings['attr'] = wrapped.__name__
- settings['_info'] = info.codeinfo # fbo "action_method"
+ settings['_info'] = info.codeinfo # pylint: disable=no-member
return wrapped
--- a/src/pyams_form/group.py Tue Nov 19 16:30:58 2019 +0100
+++ b/src/pyams_form/group.py Wed Dec 04 11:05:25 2019 +0100
@@ -10,28 +10,32 @@
# FOR A PARTICULAR PURPOSE.
#
-__docformat__ = 'restructuredtext'
-
+"""PyAMS_form.group module
-# import standard library
+This module provides groups management to forms. A group is a set of widgets which are displayed
+together inside a fieldset; widgets which are not explicitly associated with a group are affected
+to a "default" group.
+"""
-# import interfaces
+from pyramid.decorator import reify
+from zope.interface import implementer
+from zope.schema.fieldproperty import FieldProperty
+
from pyams_form.interfaces import get_form_weight
from pyams_form.interfaces.form import IFormWidgetsGroup, IGroupsBasedForm, IInnerSubForm
from pyams_i18n.interfaces.schema import II18nField
-# import packages
-from pyramid.decorator import reify
-from zope.interface import implementer
-from zope.schema.fieldproperty import FieldProperty
+
+__docformat__ = 'restructuredtext'
@implementer(IFormWidgetsGroup)
-class FormWidgetsGroup(object):
+class FormWidgetsGroup:
+ # pylint: disable=too-many-instance-attributes
"""Form widgets group"""
form = None
- id = FieldProperty(IFormWidgetsGroup['id'])
+ id = FieldProperty(IFormWidgetsGroup['id']) # pylint: disable=invalid-name
widgets = FieldProperty(IFormWidgetsGroup['widgets'])
bordered = FieldProperty(IFormWidgetsGroup['bordered'])
fieldset_class = FieldProperty(IFormWidgetsGroup['fieldset_class'])
@@ -45,10 +49,32 @@
display_mode = FieldProperty(IFormWidgetsGroup['display_mode'])
subforms_legend = FieldProperty(IFormWidgetsGroup['subforms_legend'])
- def __init__(self, form, id, widgets=None, bordered=True, fieldset_class=None, legend=None, help=None,
- css_class='', switch=False, checkbox_switch=False, checkbox_field=None, checkbox_mode='hide',
- display_mode='never'):
- assert (not checkbox_switch) or checkbox_field, "You must define checkbox field when using checkbox switch"
+ def __init__(self, form, id, widgets=None, bordered=True, fieldset_class=None, legend=None,
+ help=None, css_class='', switch=False, checkbox_switch=False, checkbox_field=None,
+ checkbox_mode='hide', display_mode='never'):
+ # pylint: disable=too-many-arguments,redefined-builtin,invalid-name
+ """Form widgets group initialization
+
+ :param form: the form containing the widgets and the group
+ :param id: the unique ID (inside the form) of the group
+ :param widgets: the widgets names list associated with the group
+ :param bordered: if True, the fieldset will have a border
+ :param fieldset_class: custom fieldset CSS class
+ :param legend: fieldset legend label
+ :param help: help text associated with the group
+ :param css_class: custom group CSS class
+ :param switch: if True, the fieldset will be switchable
+ :param checkbox_switch: if True, the switch will be using a checkbox
+ :param checkbox_field: if *checkbox_switch* is True, you must define the name of the
+ field which will store the checkbox value
+ :param checkbox_mode: if set to 'hide', the fieldset content is hidden when the checkbox
+ is switched; if set to 'disable', the fieldset contents are only disabled
+ :param display_mode: if set to 'never', fieldset content is never displayed; if set to
+ 'always', fieldset content is always displayed; if set to 'auto', fieldset content is
+ displayed when at least one widget value is not default
+ """
+ assert (not checkbox_switch) or checkbox_field, \
+ "You must define checkbox field when using checkbox switch"
self.form = form
self.id = id
self.widgets = widgets or []
@@ -65,6 +91,7 @@
@property
def css_class(self):
+ """Group's CSS class"""
css_class = self._css_class
if self.switch:
if self.checkbox_switch:
@@ -75,14 +102,18 @@
@reify
def checkbox_widget(self):
+ """Get widget associated with checkbox"""
if self.checkbox_field is None:
return None
for widget in self.widgets:
if widget.field is self.checkbox_field:
return widget
+ return None
@reify
def visible(self):
+ # pylint: disable=too-many-nested-blocks,too-many-branches
+ """Get visible status of group"""
if self.checkbox_switch:
widget = self.checkbox_widget
if self.form.ignoreContext:
@@ -92,81 +123,113 @@
name = widget.field.getName()
value = getattr(widget.field.interface(context), name, None)
return bool(value)
- else:
- if self.display_mode == 'never':
- return False
- elif (not self.switch) or (self.display_mode == 'always'):
- return True
- else: # no switch or auto mode
- for widget in self.widgets:
- if not widget.ignoreContext:
- field = widget.field
- if self.form.ignoreContext:
- value = field.default
- else:
- context = widget.context
- name = field.getName()
- value = getattr(field.interface(context), name, None)
- if value and (value != field.default):
- if II18nField.providedBy(field):
- for i18n_value in value.values():
- if i18n_value:
- return True
- else:
+ # else: no checkbox switch
+ if self.display_mode == 'never':
+ return False
+ if (not self.switch) or (self.display_mode == 'always'):
+ return True
+ # else: no switch or auto mode
+ for widget in self.widgets:
+ if not widget.ignoreContext:
+ field = widget.field
+ if self.form.ignoreContext:
+ value = field.default
+ else:
+ context = widget.context
+ name = field.getName()
+ value = getattr(field.interface(context), name, None)
+ if value and (value != field.default):
+ if II18nField.providedBy(field):
+ for i18n_value in value.values():
+ if i18n_value:
return True
- return False
+ return True
+ return False
@property
def visible_widgets(self):
+ """Get list of group's visible widgets"""
for widget in self.widgets:
if (self.checkbox_field is None) or (widget.field is not self.checkbox_field):
yield widget
@property
def switchable(self):
+ """Can fieldset be switched?"""
return self.switch or self.checkbox_switch
@property
def switcher_state(self):
+ """Get switcher state"""
return 'open' if self.visible else 'off'
@property
def checker_state(self):
+ """Get checker state"""
return 'on' if self.visible else 'off'
@reify
def subforms(self):
+ """Get list of subforms associated with this group"""
registry = self.form.request.registry
- return sorted((adapter[1]
- for adapter in registry.getAdapters((self.form.context, self.form.request, self),
- IInnerSubForm)),
+ return sorted((adapter[1] for adapter in
+ registry.getAdapters((self.form.context, self.form.request, self),
+ IInnerSubForm)),
key=get_form_weight)
def get_forms(self):
+ """Get list of subforms (including all sub-sub-forms) associated with this group"""
for form in self.subforms:
for subform in form.get_forms():
yield subform
def update(self):
+ """Update all subforms on group update"""
+ # pylint: disable=expression-not-assigned
[subform.update() for subform in self.subforms]
def update_content(self, content, data):
+ """Update content in all subforms"""
+ # pylint: disable=expression-not-assigned
[subform.update_content(content, data) for subform in self.subforms]
-def NamedWidgetsGroup(form, id, widgets, names=(), bordered=True, fieldset_class=None, legend=None, help=None,
- css_class='', switch=False, checkbox_switch=False, checkbox_field=None, checkbox_mode='hide',
- display_mode='never', factory=FormWidgetsGroup):
- """Create a widgets group based on widgets names"""
+def NamedWidgetsGroup(form, id, widgets, names=(), bordered=True, fieldset_class=None,
+ legend=None, help=None, css_class='', switch=False, checkbox_switch=False,
+ checkbox_field=None, checkbox_mode='hide', display_mode='never',
+ factory=FormWidgetsGroup):
+ # pylint: disable=too-many-arguments,redefined-builtin,invalid-name
+ """Create a widgets group based on widgets names
+
+ :param form: the form containing the widgets and the group
+ :param id: the unique ID (inside the form) of the group
+ :param widgets: the widgets names list associated with the group
+ :param bordered: if True, the fieldset will have a border
+ :param fieldset_class: custom fieldset CSS class
+ :param legend: fieldset legend label
+ :param help: help text associated with the group
+ :param css_class: custom group CSS class
+ :param switch: if True, the fieldset will be switchable
+ :param checkbox_switch: if True, the switch will be using a checkbox
+ :param checkbox_field: if *checkbox_switch* is True, you must define the name of the
+ field which will store the checkbox value
+ :param checkbox_mode: if set to 'hide', the fieldset content is hidden when the checkbox
+ is switched; if set to 'disable', the fieldset contents are only disabled
+ :param display_mode: if set to 'never', fieldset content is never displayed; if set to
+ 'always', fieldset content is always displayed; if set to 'auto', fieldset content is
+ displayed when at least one widget value is not default
+ :param factory: widgets group factory
+ """
if widgets is None:
form.updateWidgets()
widgets = form.widgets
- return factory(form, id, [widgets.get(name) for name in names], bordered, fieldset_class, legend, help,
- css_class, switch, checkbox_switch, checkbox_field, checkbox_mode, display_mode)
+ return factory(form, id, [widgets.get(name) for name in names], bordered, fieldset_class,
+ legend, help, css_class, switch, checkbox_switch, checkbox_field,
+ checkbox_mode, display_mode)
@implementer(IGroupsBasedForm)
-class GroupsBasedForm(object):
+class GroupsBasedForm:
"""Groups based form
Should be used as a base class for forms also implementing IForm
@@ -179,14 +242,20 @@
self._groups = []
def add_group(self, group):
+ """Add a group to current form's groups"""
self._groups.append(group)
@reify
def groups(self):
+ """Get list of groups associated with this form
+
+ If some widgets are associated with any group, a default group is created and located
+ at the end of groups list.
+ """
result = self._groups[:]
others = []
- if self.widgets:
- for widget in self.widgets.values():
+ if self.widgets: # pylint: disable=no-member
+ for widget in self.widgets.values(): # pylint: disable=no-member
found = False
for group in result:
if widget in group.widgets:
@@ -200,5 +269,7 @@
css_class=self.main_group_class))
return result
- def updateGroups(self):
+ def updateGroups(self): # pylint: disable=invalid-name
+ """Update form groups"""
+ # pylint: disable=expression-not-assigned
[group.update() for group in self.groups]
--- a/src/pyams_form/help.py Tue Nov 19 16:30:58 2019 +0100
+++ b/src/pyams_form/help.py Wed Dec 04 11:05:25 2019 +0100
@@ -10,37 +10,43 @@
# FOR A PARTICULAR PURPOSE.
#
-__docformat__ = 'restructuredtext'
-
+"""PyAMS_form.help module
-# import standard library
+This module provides a 'form_help' content provider, which can be used to insert help text
+inside any form using an :py:class:`IFormHelp <pyams_form.interfaces.form.IFormHlep>` adapter.
+"""
-# import interfaces
+from zope.interface import Interface, implementer
+from zope.schema.fieldproperty import FieldProperty
+
from pyams_form.interfaces.form import IFormHelp
from pyams_skin.layer import IPyAMSLayer
-from pyams_utils.interfaces.text import IHTMLRenderer
-
-# import packages
from pyams_template.template import template_config
from pyams_utils.adapter import ContextRequestViewAdapter, adapter_config
+from pyams_utils.interfaces.text import IHTMLRenderer
from pyams_utils.text import text_to_html
-from pyams_viewlet.viewlet import contentprovider_config
-from zope.interface import implementer, Interface
-from zope.schema.fieldproperty import FieldProperty
+from pyams_viewlet.viewlet import ViewContentProvider, contentprovider_config
+
+
+__docformat__ = 'restructuredtext'
@contentprovider_config(name='form_help', view=Interface, layer=IPyAMSLayer)
@template_config(template='templates/help.pt', layer=IPyAMSLayer)
-class HelpContentProvider(object):
- """Form help provider"""
+class HelpContentProvider(ViewContentProvider):
+ """Form help content provider"""
help = None
def update(self):
+ """Update content provider state"""
registry = self.request.registry
- help = self.help = registry.queryMultiAdapter((self.context, self.request, self.view), IFormHelp)
+ # pylint: disable=redefined-builtin
+ help = self.help = registry.queryMultiAdapter((self.context, self.request, self.view),
+ IFormHelp)
if help is not None:
- if help.permission and not self.request.has_permission(help.permission, context=self.context):
+ if help.permission and not self.request.has_permission(help.permission,
+ context=self.context):
self.help = None
elif help.mode and (self.view.mode != help.mode):
self.help = None
@@ -48,7 +54,7 @@
@implementer(IFormHelp)
class FormHelp(ContextRequestViewAdapter):
- """Form help"""
+ """Default form help base implementation"""
permission = FieldProperty(IFormHelp['permission'])
mode = FieldProperty(IFormHelp['mode'])
@@ -62,8 +68,9 @@
@adapter_config(context=(IFormHelp, IPyAMSLayer, Interface), provides=IHTMLRenderer)
class HelpRenderer(ContextRequestViewAdapter):
- """Help renderer"""
+ """Default form help renderer"""
- def render(self, **kwargs):
+ def render(self, **kwargs): # pylint: disable=unused-argument
+ """Render help text to HTML"""
message = self.request.localizer.translate(self.context.message)
return text_to_html(message, self.context.message_format)
--- a/src/pyams_form/include.py Tue Nov 19 16:30:58 2019 +0100
+++ b/src/pyams_form/include.py Wed Dec 04 11:05:25 2019 +0100
@@ -10,15 +10,15 @@
# FOR A PARTICULAR PURPOSE.
#
-__docformat__ = 'restructuredtext'
-
-# import standard library
+"""PyAMS_form.include package
-# import interfaces
+This package if used for Pyramid integration.
+"""
-# import packages
from pyams_form.form import FormSelector, WidgetSelector
+__docformat__ = 'restructuredtext'
+
def include_package(config):
"""Pyramid include"""
--- a/src/pyams_form/interfaces/__init__.py Tue Nov 19 16:30:58 2019 +0100
+++ b/src/pyams_form/interfaces/__init__.py Wed Dec 04 11:05:25 2019 +0100
@@ -10,15 +10,15 @@
# FOR A PARTICULAR PURPOSE.
#
-__docformat__ = 'restructuredtext'
+"""PyAMS_form.interfaces module
-# import standard library
+This namespace module is the base of PyAMS form interfaces.
+"""
-# import interfaces
from .form import IForm, IFormLayer
-# import packages
+__docformat__ = 'restructuredtext'
def get_form_weight(form):
"""Try to get form weight attribute"""
--- a/src/pyams_form/interfaces/form.py Tue Nov 19 16:30:58 2019 +0100
+++ b/src/pyams_form/interfaces/form.py Wed Dec 04 11:05:25 2019 +0100
@@ -10,6 +10,11 @@
# FOR A PARTICULAR PURPOSE.
#
+"""PyAMS_form.interfaces.form module
+
+This module provides all custom form-related interfaces.
+"""
+
from pyramid.interfaces import IView
from z3c.form import button
from z3c.form.interfaces import IButtonWidget, IFormLayer as IBaseFormLayer, INPUT_MODE, \
@@ -28,7 +33,7 @@
__docformat__ = 'restructuredtext'
-from pyams_form import _
+from pyams_form import _ # pylint: disable=ungrouped-imports
#
@@ -147,11 +152,8 @@
def get_widget_callback(self, widget):
"""Get submit callback associated with a given widget"""
- def update_content(self, object, data):
- """Update given object with form data"""
-
- def get_submit_output(self, writer, changes):
- """Get submit output"""
+ def update_content(self, content, data):
+ """Update given content with form data"""
class IAJAXForm(Interface):
@@ -192,6 +194,7 @@
form = Object(title="Parent form",
schema=IForm)
+ # pylint: disable=invalid-name
id = TextLine(title="Group ID",
required=False)
@@ -232,16 +235,16 @@
readonly=True)
checkbox_mode = Choice(title="Checkbox mode",
- description="""To indicate if fieldset content should be hidden or disabled """
- """when the checkbox if not checked""",
+ description="To indicate if fieldset content should be hidden or "
+ "disabled when the checkbox if not checked",
required=True,
values=('disable', 'hide'),
default='hide')
display_mode = Choice(title="Group display mode",
- description="""If 'auto', a switchable group containing only """
- """widgets with default values is hidden; if 'always', group is always
- visible; if 'never', group is always hidden""",
+ description="If 'auto', a switchable group containing only "
+ "widgets with default values is hidden; if 'always', group "
+ "is always visible; if 'never', group is always hidden",
values=('auto', 'never', 'always'),
required=True,
default='never')
@@ -283,7 +286,7 @@
def add_group(self, group):
"""Add given group to form groups"""
- def updateGroups(self):
+ def updateGroups(self): # pylint: disable=invalid-name
"""Update inner groups state"""
@@ -358,7 +361,7 @@
class ICustomUpdateSubForm(ISubForm):
"""SubForm interface with custom update method"""
- def update_content(self, object, data):
+ def update_content(self, object, data): # pylint: disable=redefined-builtin
"""Custom content update method"""
@@ -431,19 +434,19 @@
in some custom contexts to handle form custom settings.
"""
- def getFields(self):
+ def getFields(self): # pylint: disable=invalid-name
"""Get form fields"""
- def update(self):
+ def update(self): # pylint: disable=invalid-name
"""Update form"""
- def updateWidgets(self, prefix=None):
+ def updateWidgets(self, prefix=None): # pylint: disable=invalid-name
"""Update form widgets"""
- def updateActions(self):
+ def updateActions(self): # pylint: disable=invalid-name
"""Update form actions"""
- def updateGroups(self):
+ def updateGroups(self): # pylint: disable=invalid-name
"""Update form groups"""
@@ -459,7 +462,7 @@
if widget.mode == INPUT_MODE:
return True
if IForm.providedBy(form):
- for subform in (form.subforms + form.tabforms):
+ for subform in form.subforms + form.tabforms:
for widget in (subform.widgets or {}).values():
if widget.mode == INPUT_MODE:
return True
@@ -630,7 +633,7 @@
class FormObjectCreatedEvent(ObjectCreatedEvent):
"""Form object created event"""
- def __init__(self, object, form):
+ def __init__(self, object, form): # pylint: disable=redefined-builtin
super(FormObjectCreatedEvent, self).__init__(object)
self.form = form
@@ -643,6 +646,6 @@
class FormObjectModifiedEvent(ObjectModifiedEvent):
"""Form object modified event"""
- def __init__(self, object, form, *descriptions):
+ def __init__(self, object, form, *descriptions): # pylint: disable=redefined-builtin
ObjectModifiedEvent.__init__(self, object, *descriptions)
self.form = form
--- a/src/pyams_form/schema.py Tue Nov 19 16:30:58 2019 +0100
+++ b/src/pyams_form/schema.py Wed Dec 04 11:05:25 2019 +0100
@@ -10,19 +10,19 @@
# FOR A PARTICULAR PURPOSE.
#
-__docformat__ = 'restructuredtext'
+"""PyAMS_form.schema module
+
+This module provides an interface and a schema field for form buttons.
+"""
+
+from z3c.form.button import Button
+from z3c.form.interfaces import IButton
+from zope.interface import implementer
+from zope.schema import Bool, TextLine
+from zope.schema.fieldproperty import FieldProperty
-# import standard library
-
-# import interfaces
-from z3c.form.interfaces import IButton
-
-# import packages
-from z3c.form.button import Button
-from zope.interface import implementer
-from zope.schema import TextLine, Bool
-from zope.schema.fieldproperty import FieldProperty
+__docformat__ = 'restructuredtext'
class IResetButton(IButton):
@@ -44,7 +44,12 @@
class IActionButton(IButton):
- """Action button interface"""
+ """Action button interface
+
+ An action button is a form button which can be used to redirect the user to a "target" URL,
+ eventually opened in a modal window, or which can call a "click" handler which is the name of
+ a javascript function.
+ """
label = TextLine(title="Button label",
description="Button label displayed as hint",
--- a/src/pyams_form/search.py Tue Nov 19 16:30:58 2019 +0100
+++ b/src/pyams_form/search.py Wed Dec 04 11:05:25 2019 +0100
@@ -10,30 +10,31 @@
# FOR A PARTICULAR PURPOSE.
#
-__docformat__ = 'restructuredtext'
+"""PyAMS_form.search module
+This module provides a few helpers which can be used in search forms.
+"""
-# import standard library
+from pyramid.response import Response
+from z3c.form import button, field
+from z3c.table.interfaces import IValues
+from zope.interface import Interface, implementer
+from zope.schema import TextLine
-# import interfaces
-from pyams_form.interfaces.form import IWidgetForm, ISearchForm
+from pyams_form.form import AddForm
+from pyams_form.interfaces.form import ISearchForm, IWidgetForm
+from pyams_form.schema import ResetButton
from pyams_skin.interfaces import IContentSearch, IInnerPage, ISearchPage
from pyams_skin.layer import IPyAMSLayer
-from pyams_utils.interfaces import VIEW_SYSTEM_PERMISSION
-from z3c.table.interfaces import IValues
-
-# import packages
-from pyams_form.form import AddForm
-from pyams_form.schema import ResetButton
from pyams_skin.table import BaseTable
from pyams_template.template import template_config
from pyams_utils.adapter import ContextRequestViewAdapter, adapter_config
-from pyramid.response import Response
-from z3c.form import field, button
-from zope.interface import implementer, Interface
-from zope.schema import TextLine
+from pyams_utils.interfaces import VIEW_SYSTEM_PERMISSION
+
-from pyams_form import _
+__docformat__ = 'restructuredtext'
+
+from pyams_form import _ # pylint: disable=ungrouped-imports
class ISearchFields(Interface):
@@ -52,6 +53,7 @@
@implementer(IWidgetForm, ISearchForm)
class SearchForm(AddForm):
+ # pylint: disable=abstract-method
"""Base search form"""
legend = _("Search")
@@ -70,6 +72,9 @@
self.actions['search'].addClass('btn-primary')
def get_search_results(self):
+ """Get search results via an
+ :py:class:`IContentSearch <pyams_skin.interfaces.IContentSearch>` adapter
+ """
registry = self.request.registry
search = registry.queryMultiAdapter((self.context, self.request, self), IContentSearch)
if search is None:
@@ -78,18 +83,22 @@
search = IContentSearch(self.context, None)
if search is not None:
self.updateWidgets()
- data, errors = self.extractData()
+ data, errors = self.extractData() # pylint: disable=unused-variable
return search.get_search_results(data)
+ return None
@template_config(template='templates/search.pt', layer=IPyAMSLayer)
@implementer(IInnerPage, ISearchPage)
-class SearchView(object):
+class SearchView:
"""Base search view"""
search_form_factory = SearchForm
+ search_form = None
def update(self):
+ """Update search view"""
+ # pylint: disable=no-member
self.search_form = self.search_form_factory(self.context, self.request)
self.search_form.update()
@@ -110,5 +119,6 @@
@property
def values(self):
+ """Get view search results"""
form = self.view.search_form_factory(self.context, self.request)
return form.get_search_results()
--- a/src/pyams_form/security.py Tue Nov 19 16:30:58 2019 +0100
+++ b/src/pyams_form/security.py Wed Dec 04 11:05:25 2019 +0100
@@ -10,16 +10,15 @@
# FOR A PARTICULAR PURPOSE.
#
-__docformat__ = 'restructuredtext'
+"""PyAMS_form.security module
-
-# import standard library
+"""
-# import interfaces
-from pyams_form.interfaces.form import IFormSecurityContext, IFormContextPermissionChecker
+from pyramid.decorator import reify
-# import packages
-from pyramid.decorator import reify
+from pyams_form.interfaces.form import IFormContextPermissionChecker, IFormSecurityContext
+
+__docformat__ = 'restructuredtext'
def get_edit_permission(request, context=None):
@@ -32,22 +31,42 @@
checker = registry.queryAdapter(context, IFormContextPermissionChecker)
if checker is not None:
return checker.edit_permission
+ return None
-class ProtectedFormObjectMixin(object):
- """Form object protected by a permission"""
+class ProtectedFormObjectMixin:
+ """Form object protected by a permission
+
+ A "protected" form is a form on which you apply a permission; the context on which the security
+ applies can be provided by an :py:class:`IFormSecurityContext
+ <pyams_form.interfaces.IFormSecurityContext>` adapter, or will be extracted for the form
+ context itself.
+
+ The permission itself will be provided by an adapter to :py:class:`IFormContextPermissionChecker
+ <pyams_form.interfaces.IFormContextPermissionChecker>`
+ :
+
+ This class is a form mixin class which should be used for forms protected by a
+ security context.
+ """
@reify
def permission(self):
- registry = self.request.registry
+ """This permission is required to be able to edit the form context"""
+ request = self.request # pylint: disable=no-member
+ registry = request.registry
checker = None
context = IFormSecurityContext(self, None)
if context is None:
- context = self.context
- view = getattr(self, '__parent__', None) or getattr(self, 'view', None) or getattr(self, 'table', None)
+ context = self.context # pylint: disable=no-member
+ view = getattr(self, '__parent__', None) or \
+ getattr(self, 'view', None) or \
+ getattr(self, 'table', None)
if view is not None:
- checker = registry.queryMultiAdapter((context, self.request, view), IFormContextPermissionChecker)
+ checker = registry.queryMultiAdapter((context, request, view),
+ IFormContextPermissionChecker)
if checker is None:
checker = registry.queryAdapter(context, IFormContextPermissionChecker)
if checker is not None:
return checker.edit_permission
+ return None
--- a/src/pyams_form/terms.py Tue Nov 19 16:30:58 2019 +0100
+++ b/src/pyams_form/terms.py Wed Dec 04 11:05:25 2019 +0100
@@ -10,7 +10,11 @@
# FOR A PARTICULAR PURPOSE.
#
-__docformat__ = 'restructuredtext'
+"""PyAMS_form.terms module
+
+This module is used only to provide a custom ITerms adapter providing translations for
+boolean terms.
+"""
from z3c.form.interfaces import IBoolTerms, IFormLayer, ITerms, IWidget
from z3c.form.term import BoolTerms as BaseBoolTerms
@@ -19,6 +23,9 @@
from pyams_utils.adapter import adapter_config
+
+__docformat__ = 'restructuredtext'
+
from pyams_form import _
--- a/src/pyams_form/tests/__init__.py Tue Nov 19 16:30:58 2019 +0100
+++ b/src/pyams_form/tests/__init__.py Wed Dec 04 11:05:25 2019 +0100
@@ -1,1 +1,31 @@
+# -*- coding: utf-8 -*- ######################################################
+##############################################################################
+#
+# Copyright (c) 2008-2010 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+Generic Test case for pyams_form doctest
+"""
+__docformat__ = 'restructuredtext'
+
+import os
+import sys
+
+
+def get_package_dir(value):
+ """Get package directory"""
+
+ package_dir = os.path.split(value)[0]
+ if package_dir not in sys.path:
+ sys.path.append(package_dir)
+ return package_dir
--- a/src/pyams_form/tests/test_utilsdocs.py Tue Nov 19 16:30:58 2019 +0100
+++ b/src/pyams_form/tests/test_utilsdocs.py Wed Dec 04 11:05:25 2019 +0100
@@ -1,5 +1,7 @@
+# -*- coding: utf-8 -*- ######################################################
+##############################################################################
#
-# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# Copyright (c) 2008-2010 Thierry Florac <tflorac AT ulthar.net>
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
@@ -9,22 +11,26 @@
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
+##############################################################################
"""
-Generic Test case for pyams_form doctest
+Generic test case for pyams_form doctests
"""
+
__docformat__ = 'restructuredtext'
+import doctest
+import os
import unittest
-import doctest
-import sys
-import os
+
+from pyams_utils.tests import get_package_dir
-current_dir = os.path.dirname(__file__)
+CURRENT_DIR = os.path.abspath(os.path.dirname(__file__))
+
-def doc_suite(test_dir, setUp=None, tearDown=None, globs=None):
- """Returns a test suite, based on doctests found in /doctest."""
+def doc_suite(test_dir, setUp=None, tearDown=None, globs=None): # pylint: disable=invalid-name
+ """Returns a test suite, based on doctests found in /doctests"""
suite = []
if globs is None:
globs = globals()
@@ -32,15 +38,12 @@
flags = (doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE |
doctest.REPORT_ONLY_FIRST_FAILURE)
- package_dir = os.path.split(test_dir)[0]
- if package_dir not in sys.path:
- sys.path.append(package_dir)
-
+ package_dir = get_package_dir(test_dir)
doctest_dir = os.path.join(package_dir, 'doctests')
# filtering files on extension
docs = [os.path.join(doctest_dir, doc) for doc in
- os.listdir(doctest_dir) if doc.endswith('.txt')]
+ os.listdir(doctest_dir) if doc.endswith('.txt') or doc.endswith('.rst')]
for test in docs:
suite.append(doctest.DocFileSuite(test, optionflags=flags,
@@ -50,10 +53,11 @@
return unittest.TestSuite(suite)
+
def test_suite():
"""returns the test suite"""
- return doc_suite(current_dir)
+ return doc_suite(CURRENT_DIR)
+
if __name__ == '__main__':
unittest.main(defaultTest='test_suite')
-
--- a/src/pyams_form/tests/test_utilsdocstrings.py Tue Nov 19 16:30:58 2019 +0100
+++ b/src/pyams_form/tests/test_utilsdocstrings.py Wed Dec 04 11:05:25 2019 +0100
@@ -11,17 +11,20 @@
#
"""
-Generic Test case for pyams_form doc strings
+Generic test case for pyams_form docstrings
"""
+
__docformat__ = 'restructuredtext'
+import doctest
+import os
import unittest
-import doctest
-import sys
-import os
+
+from pyams_utils.tests import get_package_dir
-current_dir = os.path.abspath(os.path.dirname(__file__))
+CURRENT_DIR = os.path.abspath(os.path.dirname(__file__))
+
def doc_suite(test_dir, globs=None):
"""Returns a test suite, based on doc tests strings found in /*.py"""
@@ -32,9 +35,7 @@
flags = (doctest.ELLIPSIS | doctest.NORMALIZE_WHITESPACE |
doctest.REPORT_ONLY_FIRST_FAILURE)
- package_dir = os.path.split(test_dir)[0]
- if package_dir not in sys.path:
- sys.path.append(package_dir)
+ package_dir = get_package_dir(test_dir)
# filtering files on extension
docs = [doc for doc in
@@ -42,7 +43,7 @@
docs = [doc for doc in docs if not doc.startswith('__')]
for test in docs:
- fd = open(os.path.join(package_dir, test))
+ fd = open(os.path.join(package_dir, test)) # pylint: disable=invalid-name
content = fd.read()
fd.close()
if '>>> ' not in content:
@@ -54,9 +55,11 @@
return unittest.TestSuite(suite)
+
def test_suite():
"""returns the test suite"""
- return doc_suite(current_dir)
+ return doc_suite(CURRENT_DIR)
+
if __name__ == '__main__':
unittest.main(defaultTest='test_suite')
--- a/src/pyams_form/viewlet.py Tue Nov 19 16:30:58 2019 +0100
+++ b/src/pyams_form/viewlet.py Wed Dec 04 11:05:25 2019 +0100
@@ -10,20 +10,21 @@
# FOR A PARTICULAR PURPOSE.
#
-__docformat__ = 'restructuredtext'
+"""PyAMS_form.viewlet module
+
+This module provides several viewlet managers which are used into default forms templates.
+"""
+
+from zope.interface import implementer
+
+from pyams_form.interfaces.form import IFormHeaderViewletsManager, IFormLayer, \
+ IFormPrefixViewletsManager, IFormSuffixViewletsManager, IFormToolbarViewletsManager, \
+ IFormViewletsManager, IWidgetsPrefixViewletsManager, IWidgetsSuffixViewletsManager
+from pyams_template.template import get_view_template, template_config
+from pyams_viewlet.manager import WeightOrderedViewletManager, viewletmanager_config
-# import standard library
-
-# import interfaces
-from pyams_form.interfaces.form import IFormViewletsManager, IFormPrefixViewletsManager, IWidgetsPrefixViewletsManager, \
- IWidgetsSuffixViewletsManager, IFormSuffixViewletsManager, IFormLayer, IFormHeaderViewletsManager, \
- IFormToolbarViewletsManager
-
-# import packages
-from pyams_template.template import template_config, get_view_template
-from pyams_viewlet.manager import WeightOrderedViewletManager, viewletmanager_config
-from zope.interface import implementer
+__docformat__ = 'restructuredtext'
@implementer(IFormViewletsManager)
--- a/src/pyams_form/widget/__init__.py Tue Nov 19 16:30:58 2019 +0100
+++ b/src/pyams_form/widget/__init__.py Wed Dec 04 11:05:25 2019 +0100
@@ -10,6 +10,17 @@
# FOR A PARTICULAR PURPOSE.
#
+"""PyAMS_form.widget module
+
+This module provides several custom widgets used inside PyAMS, but also default templates
+customized for MyAMS.
+
+This module also provides several decorators, which can be used to define widgets templates
+in place of ZCML declarations.
+
+**WARNING**: please note that widgets templates used via z3c.form package use default
+"""
+
import inspect
import json
import locale
@@ -54,14 +65,14 @@
__docformat__ = 'restructuredtext'
-from pyams_form import _
+from pyams_form import _ # pylint: disable=ungrouped-imports
#
# Widget template configuration
#
-class widgettemplate_config(object):
+class widgettemplate_config: # pylint: disable=invalid-name
"""Class decorator used to declare a widget template"""
venusian = venusian # for testing injection
@@ -75,37 +86,40 @@
def __call__(self, wrapped):
settings = self.__dict__.copy()
- def callback(context, name, ob):
- template = os.path.join(os.path.dirname(inspect.getfile(inspect.getmodule(ob))),
+ def callback(context, name, obj): # pylint: disable=unused-argument
+ template = os.path.join(os.path.dirname(inspect.getfile(inspect.getmodule(obj))),
settings.get('template'))
if not os.path.isfile(template):
raise ConfigurationError("No such file", template)
- contentType = settings.get('contentType', 'text/html')
- factory = WidgetTemplateFactory(template, contentType)
+ content_type = settings.get('contentType', 'text/html')
+ factory = WidgetTemplateFactory(template, content_type)
provides = settings.get('provides', IPageTemplate)
directlyProvides(factory, provides)
- config = context.config.with_package(info.module)
- config.registry.registerAdapter(factory,
- (settings.get('context', Interface),
- settings.get('layer', IRequest),
- settings.get('view', None),
- settings.get('field', None),
- settings.get('widget', ob)),
- provides,
- settings.get('mode', INPUT_MODE))
+ registry = settings.get('registry')
+ if registry is None:
+ config = context.config.with_package(info.module) # pylint: disable=no-member
+ registry = config.registry
+ registry.registerAdapter(factory,
+ (settings.get('context', Interface),
+ settings.get('layer', IRequest),
+ settings.get('view', None),
+ settings.get('field', None),
+ settings.get('widget', obj)),
+ provides,
+ settings.get('mode', INPUT_MODE))
info = self.venusian.attach(wrapped, callback, category='pyams_form')
- if info.scope == 'class':
+ if info.scope == 'class': # pylint: disable=no-member
# if the decorator was attached to a method in a class, or
# otherwise executed at class scope, we need to set an
# 'attr' into the settings if one isn't already in there
if settings.get('attr') is None:
settings['attr'] = wrapped.__name__
- settings['_info'] = info.codeinfo # fbo "action_method"
+ settings['_info'] = info.codeinfo # pylint: disable=no-member
return wrapped
@@ -120,8 +134,8 @@
settings.get('template'))
if not os.path.isfile(template):
raise ConfigurationError("No such file", template)
- contentType = settings.get('contentType', 'text/html')
- factory = WidgetTemplateFactory(template, contentType)
+ content_type = settings.get('contentType', 'text/html')
+ factory = WidgetTemplateFactory(template, content_type)
provides = settings.get('provides', IPageTemplate)
directlyProvides(factory, provides)
registry = getGlobalSiteManager()
@@ -135,7 +149,7 @@
settings.get('mode', INPUT_MODE))
-class widgetlayout_config(object):
+class widgetlayout_config: # pylint: disable=invalid-name
"""Class decorator used to declare a widget layout"""
venusian = venusian # for testing injection
@@ -149,37 +163,40 @@
def __call__(self, wrapped):
settings = self.__dict__.copy()
- def callback(context, name, ob):
- template = os.path.join(os.path.dirname(inspect.getfile(inspect.getmodule(ob))),
+ def callback(context, name, obj): # pylint: disable=unused-argument
+ template = os.path.join(os.path.dirname(inspect.getfile(inspect.getmodule(obj))),
settings.get('template'))
if not os.path.isfile(template):
raise ConfigurationError("No such file", template)
- contentType = settings.get('contentType', 'text/html')
- factory = WidgetLayoutFactory(template, contentType)
+ content_type = settings.get('contentType', 'text/html')
+ factory = WidgetLayoutFactory(template, content_type)
provides = settings.get('provides', IWidgetLayoutTemplate)
directlyProvides(factory, provides)
- config = context.config.with_package(info.module)
- config.registry.registerAdapter(factory,
- (settings.get('context', Interface),
- settings.get('layer', IRequest),
- settings.get('view', None),
- settings.get('field', None),
- settings.get('widget', ob)),
- provides,
- settings.get('mode', INPUT_MODE))
+ registry = settings.get('registry')
+ if registry is None:
+ config = context.config.with_package(info.module) # pylint: disable=no-member
+ registry = config.registry
+ registry.registerAdapter(factory,
+ (settings.get('context', Interface),
+ settings.get('layer', IRequest),
+ settings.get('view', None),
+ settings.get('field', None),
+ settings.get('widget', obj)),
+ provides,
+ settings.get('mode', INPUT_MODE))
info = self.venusian.attach(wrapped, callback, category='pyams_form')
- if info.scope == 'class':
+ if info.scope == 'class': # pylint: disable=no-member
# if the decorator was attached to a method in a class, or
# otherwise executed at class scope, we need to set an
# 'attr' into the settings if one isn't already in there
if settings.get('attr') is None:
settings['attr'] = wrapped.__name__
- settings['_info'] = info.codeinfo # fbo "action_method"
+ settings['_info'] = info.codeinfo # pylint: disable=no-member
return wrapped
@@ -194,8 +211,8 @@
settings.get('template'))
if not os.path.isfile(template):
raise ConfigurationError("No such file", template)
- contentType = settings.get('contentType', 'text/html')
- factory = WidgetTemplateFactory(template, contentType)
+ content_type = settings.get('contentType', 'text/html')
+ factory = WidgetTemplateFactory(template, content_type)
provides = settings.get('provides', IWidgetLayoutTemplate)
directlyProvides(factory, provides)
registry = getGlobalSiteManager()
@@ -224,7 +241,8 @@
@adapter_config(context=(IResetButton, IFormLayer), provides=IFieldWidget)
-def ResetFieldWidget(field, request):
+def ResetFieldWidget(field, request): # pylint: disable=invalid-name
+ """Reset buton field widget factory"""
reset = FieldWidget(field, ResetWidget(request))
reset.value = field.title
return reset
@@ -235,8 +253,9 @@
"""Reset button action"""
def __init__(self, request, field):
- Action.__init__(self, request, field.title)
- ResetWidget.__init__(self, request)
+ # pylint: disable=super-init-not-called
+ Action.__init__(self, request, field.title) # pylint: disable=non-parent-init-called
+ ResetWidget.__init__(self, request) # pylint: disable=non-parent-init-called
self.field = field
@@ -255,7 +274,8 @@
@adapter_config(context=(ICloseButton, IFormLayer), provides=IFieldWidget)
-def CloseFieldWidget(field, request):
+def CloseFieldWidget(field, request): # pylint: disable=invalid-name
+ """Close button field widget factory"""
close = FieldWidget(field, CloseWidget(request))
close.value = field.title
return close
@@ -266,8 +286,9 @@
"""Close button action"""
def __init__(self, request, field):
- Action.__init__(self, request, field.title)
- CloseWidget.__init__(self, request)
+ # pylint: disable=super-init-not-called
+ Action.__init__(self, request, field.title) # pylint: disable=non-parent-init-called
+ CloseWidget.__init__(self, request) # pylint: disable=non-parent-init-called
self.field = field
@@ -288,7 +309,8 @@
@adapter_config(context=(IActionButton, IFormLayer), provides=IFieldWidget)
-def ActionFieldWidget(field, request):
+def ActionFieldWidget(field, request): # pylint: disable=invalid-name
+ """Action button field widget factory"""
action = FieldWidget(field, ActionWidget(request))
action.value = field.title
return action
@@ -299,8 +321,9 @@
"""Action button action"""
def __init__(self, request, field):
+ # pylint: disable=super-init-not-called
ActionWidget.__init__(self, request)
- Action.__init__(self, request, field.title)
+ Action.__init__(self, request, field.title) # pylint: disable=non-parent-init-called
self.field = field
self.label_css_class = field.label_css_class
self.click_handler = field.click_handler
@@ -319,9 +342,11 @@
error_message = _("Invalid UTF-8 encoded data")
def toWidgetValue(self, value):
+ """Convert bytes to string"""
return value.decode('utf-8') if isinstance(value, bytes) else value
def toFieldValue(self, value):
+ """Convert string to bytes"""
return value.encode('utf-8') if isinstance(value, str) else value
@@ -340,11 +365,13 @@
error_message = _("Invalid integer value")
def toWidgetValue(self, value):
+ """Convert integer value to string"""
if value is self.field.missing_value:
return ''
return locale.format_string('%d', value, grouping=True)
def toFieldValue(self, value):
+ """Convert string to integer"""
if not value:
return self.field.missing_value
try:
@@ -360,21 +387,25 @@
@property
def validate_rules(self):
+ """Add validation rules to JQuery-validate plug-in"""
conv = locale.localeconv()
allow_negative = True
if (self.field.min is not None) and (self.field.min >= 0):
allow_negative = False
- rules = {'pattern': '{}[\d{}]+{}'.format(
- '\{}?'.format(conv.get('negative_sign', '-'))
+ rules = {
+ 'pattern': r'{}[\d{}]+{}'.format(
+ r'\{}?'.format(conv.get('negative_sign', '-'))
if (allow_negative and (conv.get('n_sign_posn') in SIGN_BEFORE_VALUE)) else '',
- conv.get('thousands_sep', ''),
- '\{}?'.format(conv.get('negative_sign', '-'))
- if (allow_negative and (conv.get('n_sign_posn') in SIGN_AFTER_VALUE)) else '')}
+ conv.get('thousands_sep', ''),
+ r'\{}?'.format(conv.get('negative_sign', '-'))
+ if (allow_negative and (conv.get('n_sign_posn') in SIGN_AFTER_VALUE)) else '')
+ }
return json.dumps(rules)
@adapter_config(context=(IInt, IFormLayer), provides=IFieldWidget)
-def IntegerFieldWidget(field, request):
+def IntegerFieldWidget(field, request): # pylint: disable=invalid-name
+ """Integer field widget factory"""
return FieldWidget(field, IntegerWidget(request))
@@ -389,11 +420,13 @@
error_message = _("Invalid floating value")
def toWidgetValue(self, value):
+ """Convert float to string"""
if value is self.field.missing_value:
return ''
return locale.format_string('%.{0}f'.format(self.widget.precision), value, grouping=True)
def toFieldValue(self, value):
+ """Convert string to float"""
if not value:
return self.field.missing_value
try:
@@ -411,23 +444,27 @@
@property
def validate_rules(self):
+ """Add validation rules to JQuery-validate plug-in"""
conv = locale.localeconv()
allow_negative = True
if (self.field.min is not None) and (self.field.min >= 0):
allow_negative = False
- rules = {'pattern': '{}[\d{}]+{}?\d*{}'.format(
- '\{}?'.format(conv.get('negative_sign', '-'))
+ rules = {
+ 'pattern': r'{}[\d{}]+{}?\d*{}'.format(
+ r'\{}?'.format(conv.get('negative_sign', '-'))
if (allow_negative and (conv.get('n_sign_posn') in SIGN_BEFORE_VALUE)) else '',
- conv.get('thousands_sep', ''),
- conv.get('decimal_point', '\.'),
- '\{}?'.format(conv.get('negative_sign', '-'))
- if (allow_negative and (conv.get('n_sign_posn') in SIGN_AFTER_VALUE)) else '')}
+ conv.get('thousands_sep', ''),
+ conv.get('decimal_point', r'\.'),
+ r'\{}?'.format(conv.get('negative_sign', '-'))
+ if (allow_negative and (conv.get('n_sign_posn') in SIGN_AFTER_VALUE)) else '')
+ }
return json.dumps(rules)
@adapter_config(context=(IFloat, IFormLayer), provides=IFieldWidget)
@adapter_config(context=(IDecimal, IFormLayer), provides=IFieldWidget)
-def FloatFieldWidget(field, request):
+def FloatFieldWidget(field, request): # pylint: disable=invalid-name
+ """Float field widget factory"""
return FieldWidget(field, FloatWidget(request))
@@ -438,28 +475,33 @@
@property
def validate_rules(self):
+ """Add validation rules to JQuery-validate plug-in"""
conv = locale.localeconv()
allow_negative = True
if (self.field.min is not None) and (self.field.min >= 0):
allow_negative = False
- rules = {'pattern': '{}[\d{}]+{}?\d*{}'.format(
- '\{}?'.format(conv.get('negative_sign', '-'))
+ rules = {
+ 'pattern': r'{}[\d{}]+{}?\d*{}'.format(
+ r'\{}?'.format(conv.get('negative_sign', '-'))
if (allow_negative and (conv.get('n_sign_posn') in SIGN_BEFORE_VALUE)) else '',
- '', # no thousands separator
- '\.', # dot as decimal separator !!
- '\{}?'.format(conv.get('negative_sign', '-'))
- if (allow_negative and (conv.get('n_sign_posn') in SIGN_AFTER_VALUE)) else '')}
+ '', # no thousands separator
+ r'\.', # dot as decimal separator !!
+ r'\{}?'.format(conv.get('negative_sign', '-'))
+ if (allow_negative and (conv.get('n_sign_posn') in SIGN_AFTER_VALUE)) else '')
+ }
return json.dumps(rules)
@property
def validate_messages(self):
+ """Get validation message for pattern rule"""
return json.dumps({
'pattern': self.request.localizer.translate(DottedDecimalDataConverter.errorMessage)
})
@adapter_config(context=(IDottedDecimalField, IFormLayer), provides=IFieldWidget)
-def DottedDecimalFieldWidget(field, request):
+def DottedDecimalFieldWidget(field, request): # pylint: disable=invalid-name
+ """Dotted decimal field widget factory"""
return FieldWidget(field, DottedDecimalWidget(request))
@@ -474,7 +516,8 @@
@adapter_config(context=(IDate, IFormLayer), provides=IFieldWidget)
-def DateFieldWidget(field, request):
+def DateFieldWidget(field, request): # pylint: disable=invalid-name
+ """Date field widget factory"""
return FieldWidget(field, DateWidget(request))
@@ -487,10 +530,12 @@
"""Datetime field data converter"""
def toWidgetValue(self, value):
+ """Convert datetime to string"""
value = tztime(value)
return super(DatetimeDataConverter, self).toWidgetValue(value)
def toFieldValue(self, value):
+ """Convert string to local datetime"""
value = super(DatetimeDataConverter, self).toFieldValue(value)
return localgmtime(value)
@@ -502,7 +547,8 @@
@adapter_config(context=(IDatetime, IFormLayer), provides=IFieldWidget)
-def DatetimeFieldWidget(field, request):
+def DatetimeFieldWidget(field, request): # pylint: disable=invalid-name
+ """Datetime field widget factory"""
return FieldWidget(field, DatetimeWidget(request))
@@ -517,7 +563,8 @@
@adapter_config(context=(ITime, IFormLayer), provides=IFieldWidget)
-def TimeFieldWidget(field, request):
+def TimeFieldWidget(field, request): # pylint: disable=invalid-name
+ """Time field widget factory"""
return FieldWidget(field, TimeWidget(request))
@@ -530,12 +577,14 @@
"""Color field data converter"""
def toWidgetValue(self, value):
+ """Convert color to string"""
value = super(ColorDataConverter, self).toWidgetValue(value)
if value:
value = '#' + value
return value
def toFieldValue(self, value):
+ """Convert string to color value"""
if value and value.startswith('#'):
value = value[1:]
return super(ColorDataConverter, self).toFieldValue(value)
@@ -548,7 +597,8 @@
@adapter_config(context=(IColorField, IFormLayer), provides=IFieldWidget)
-def ColorFieldWidget(field, request):
+def ColorFieldWidget(field, request): # pylint: disable=invalid-name
+ """Color field widget factory"""
return FieldWidget(field, ColorWidget(request))
@@ -564,22 +614,26 @@
@property
def editor_data(self):
+ """Get editor data"""
registry = self.request.registry
- config = registry.queryMultiAdapter((self.form, self.request), ITinyMCEConfiguration, name=self.name)
+ config = registry.queryMultiAdapter((self.form, self.request), ITinyMCEConfiguration,
+ name=self.name)
if (config is None) and hasattr(self, 'basename'): # I18n widget
- config = registry.queryMultiAdapter((self.form, self.request), ITinyMCEConfiguration, name=self.basename)
+ config = registry.queryMultiAdapter((self.form, self.request), ITinyMCEConfiguration,
+ name=self.basename) # pylint: disable=no-member
if config is None:
config = registry.queryMultiAdapter((self.form, self.request), ITinyMCEConfiguration)
if config is None:
config = getattr(self, 'editor_config_data', None)
if config is not None:
return json.dumps(config)
- else:
- return json.dumps(config.configuration)
+ return None
+ return json.dumps(config.configuration)
@adapter_config(context=(IHTMLField, IFormLayer), provides=IFieldWidget)
-def HTMLFieldWidget(field, request):
+def HTMLFieldWidget(field, request): # pylint: disable=invalid-name
+ """HTML editor field widget factory"""
return FieldWidget(field, HTMLWidget(request))
@@ -592,9 +646,11 @@
"""JSON dict data converter"""
def toWidgetValue(self, value):
+ """Convert JSON dict to string"""
return value
def toFieldValue(self, value):
+ """Load JSON from string"""
return json.loads(value)
@@ -604,6 +660,7 @@
"""JSON dict field widget"""
def get_fields(self):
+ """Get fields from JSON mapping"""
form = self.__parent__.form
getter = self.request.registry.queryMultiAdapter((form.getContent(), self.request, form),
IJSONDictFieldsGetter)
@@ -614,7 +671,7 @@
@adapter_config(context=(IJSONDictField, IFormLayer), provides=IFieldWidget)
-def JSONDictFieldWidget(field, request):
+def JSONDictFieldWidget(field, request): # pylint: disable=invalid-name
"""JSON dict field widget factory"""
return FieldWidget(field, JSONDictWidget(request))
@@ -623,8 +680,10 @@
# Select2 widget
#
-@widgettemplate_config(mode=INPUT_MODE, template='templates/select-input.pt', layer=IFormLayer)
-@widgettemplate_config(mode=DISPLAY_MODE, template='templates/select-display.pt', layer=IFormLayer)
+@widgettemplate_config(mode=INPUT_MODE, template='templates/select-input.pt',
+ layer=IFormLayer)
+@widgettemplate_config(mode=DISPLAY_MODE, template='templates/select-display.pt',
+ layer=IFormLayer)
@implementer_only(ISelect2Widget)
class Select2Widget(SelectWidget):
"""Select2 widget"""
@@ -632,17 +691,21 @@
noValueMessage = _("(no selected value)")
def get_content(self, entry):
+ """Get translated entry content"""
translate = self.request.localizer.translate
return translate(entry['content'])
@adapter_config(context=(IChoice, IFormLayer), provides=IFieldWidget)
-def ChoiceFieldWidget(field, request):
+def ChoiceFieldWidget(field, request): # pylint: disable=invalid-name
+ """Choice field widget factory"""
return FieldWidget(field, Select2Widget(request))
-@widgettemplate_config(mode=INPUT_MODE, template='templates/hidden-select-input.pt', layer=IFormLayer)
-@widgettemplate_config(mode=DISPLAY_MODE, template='templates/hidden-select-display.pt', layer=IFormLayer)
+@widgettemplate_config(mode=INPUT_MODE, template='templates/hidden-select-input.pt',
+ layer=IFormLayer)
+@widgettemplate_config(mode=DISPLAY_MODE, template='templates/hidden-select-display.pt',
+ layer=IFormLayer)
@implementer(IObjectData)
class HiddenSelect2Widget(Select2Widget):
"""Hidden select2 widget
@@ -651,6 +714,7 @@
"""
def extract(self, default=NO_VALUE):
+ """Extract terms from input value"""
if self.name not in self.request and self.name + '-empty-marker' in self.request:
return []
value = self.request.get(self.name, default)
@@ -669,17 +733,21 @@
@property
def values(self):
+ """Convert values list to string"""
return '|'.join(self.value or '')
@property
def values_map(self):
+ """Create JSON mapping from selected values"""
result = {}
terms = self.terms
+ # pylint: disable=expression-not-assigned
[result.update({value: terms.getTermByToken(value).title}) for value in self.value or ()]
return json.dumps(result)
-def HiddenSelect2FieldWidget(field, request):
+def HiddenSelect2FieldWidget(field, request): # pylint: disable=invalid-name
+ """Hidden Select2 field widget factory"""
return FieldWidget(field, HiddenSelect2Widget(request))
@@ -690,9 +758,11 @@
"""Hidden select2 data converter"""
def toWidgetValue(self, value):
+ """Convert selection to string"""
return value or ()
def toFieldValue(self, value):
+ """Create set from selected values"""
return set(value or ())
@@ -705,25 +775,31 @@
"""Text line list field data converter"""
def toWidgetValue(self, value):
+ """Convert values list to string"""
return '|'.join(value or [])
def toFieldValue(self, value):
+ """Create list from string"""
return value.split('|') if value else None
-@widgettemplate_config(mode=INPUT_MODE, template='templates/textlinelist-input.pt', layer=IFormLayer)
-@widgettemplate_config(mode=DISPLAY_MODE, template='templates/textlinelist-display.pt', layer=IFormLayer)
+@widgettemplate_config(mode=INPUT_MODE, template='templates/textlinelist-input.pt',
+ layer=IFormLayer)
+@widgettemplate_config(mode=DISPLAY_MODE, template='templates/textlinelist-display.pt',
+ layer=IFormLayer)
@implementer_only(ITextLineListWidget)
class TextLineListWidget(TextWidget):
"""Text line list widget"""
@property
def tags(self):
+ """Create string from input value"""
return json.dumps((self.value or '').split('|'))
@adapter_config(context=(ITextLineListField, IFormLayer), provides=IFieldWidget)
-def TextLineListFieldWidget(field, request):
+def TextLineListFieldWidget(field, request): # pylint: disable=invalid-name
+ """Textlines list field widget"""
return FieldWidget(field, TextLineListWidget(request))
@@ -731,17 +807,22 @@
# TextLine widget with SEO length status
#
-@widgettemplate_config(mode=INPUT_MODE, template='templates/seo-textline-input.pt', layer=IFormLayer)
+@widgettemplate_config(mode=INPUT_MODE, template='templates/seo-textline-input.pt',
+ layer=IFormLayer)
@implementer_only(ISEOTextLineWidget)
class SEOTextLineWidget(TextWidget):
"""SEO textline widget"""
@property
def length(self):
+ """Get current length of text"""
return len(self.value or '')
@property
def status(self):
+ """Get widget status based on text length; a "good" length is between 40 and 66
+ characters
+ """
status = 'success'
length = self.length
if length < 20 or length > 80:
@@ -751,5 +832,6 @@
return status
-def SEOTextLineFieldWidget(field, request):
+def SEOTextLineFieldWidget(field, request): # pylint: disable=invalid-name
+ """SEO textline field widget factory"""
return FieldWidget(field, SEOTextLineWidget(request))