Source code for abilian.web.forms

# coding=utf-8
"""
Extensions to WTForms fields, widgets and validators.
"""
from __future__ import absolute_import, print_function, division

import logging
from functools import partial
from collections import OrderedDict

from wtforms.fields import HiddenField
from wtforms.fields.core import Field
from wtforms_alchemy import model_form_factory
from flask import current_app
from flask_login import current_user
from flask_wtf.form import Form as BaseForm

from abilian.i18n import _, _n
from abilian.services.security import READ, WRITE, Role, Anonymous
from abilian.core.logging import patch_logger

from .fields import *  # noqa
from .filters import *  # noqa
from .validators import *  # noqa
from .widgets import *  # noqa

logger = logging.getLogger(__name__)


#  setup Form class with babel support
class _BabelTranslation(object):
  def gettext(self, string):
    return _(string)

  def ngettext(self, singular, plural, n):
    return _n(singular, plural, n)


BabelTranslation = _BabelTranslation()


[docs]class FormPermissions(object): """ Form role/permission manager """ def __init__(self, default=Anonymous, read=None, write=None, fields_read=None, fields_write=None): """ """ if isinstance(default, Role): default = (default,) self.default = default self.form = dict() self.fields = dict() for permission, roles in ((READ, read), (WRITE, write)): if roles is None: continue if isinstance(roles, Role): roles = (roles,) self.form[permission] = roles for fields, permission in ((fields_read, READ), (fields_write, WRITE)): if fields: for field_name, allowed_roles in fields.items(): if isinstance(allowed_roles, Role): allowed_roles = (allowed_roles,) self.fields.setdefault(field_name, dict())[permission] = allowed_roles
[docs] def has_permission(self, permission, field=None, obj=None, user=current_user): """ """ allowed_roles = self.default definition = None eval_roles = lambda fun: fun(permission=permission, field=field, obj=obj) if field is None: definition = self.form else: if isinstance(field, Field): field = field.name if field in self.fields: definition = self.fields[field] if definition and permission in definition: allowed_roles = definition[permission] if callable(allowed_roles): allowed_roles = eval_roles(allowed_roles) roles = [] for r in allowed_roles: if callable(r): r = eval_roles(r) if isinstance(r, (Role, bytes, unicode)): roles.append(r) else: roles.extend(r) svc = current_app.services['security'] return svc.has_role(user, role=roles, object=obj,)
[docs]class Form(BaseForm): _groups = OrderedDict() #: :class:`FormPermissions` instance _permissions = None def __init__(self, *args, **kwargs): permission = kwargs.pop('permission', None) user= kwargs.pop('user', current_user) super(Form, self).__init__(*args, **kwargs) self._field_groups = {} # map field -> group if not isinstance(self.__class__._groups, OrderedDict): self.__class__._groups = OrderedDict(self.__class__._groups) for label, fields in self._groups.items(): self._groups[label] = list(fields) self._field_groups.update(dict.fromkeys(fields, label)) obj = kwargs.get('obj') if permission and self._permissions is not None: # we are going to alter groups: copy dict on instance to preserve class # definition self._groups = OrderedDict() for label, fields in self.__class__._groups.items(): self._groups[label] = list(fields) has_permission = partial(self._permissions.has_permission, permission, obj=obj, user=user) empty_form = not has_permission() for field_name in list(self._fields): if empty_form or not has_permission(field=field_name): logger.debug('{}(permission={!r}): field {!r}: removed' ''.format(self.__class__.__name__, permission, field_name)) del self[field_name] group = self._field_groups.get(field_name) if group: self._groups[group].remove(field_name) def _get_translations(self): return BabelTranslation def _fields_for_group(self, group): for g, field_names in self._groups: if group == g: fields = field_names break else: raise ValueError("Group %s not found", repr(group)) return fields def _has_required(self, group=None, fields=()): if group is not None: fields = self._fields_for_group(group) return any(self[f].flags.required for f in fields) def _count_errors(self, group=None, fields=()): if group is not None: fields = self._fields_for_group(group) return len([1 for f in fields if self[f].errors])
ModelForm = model_form_factory(Form) ### PATCH wtforms.field.core.Field #################### _PATCHED = False if not _PATCHED: Field.view_template = None _wtforms_Field_init = Field.__init__ def _core_field_init(self, *args, **kwargs): view_widget = None if 'view_widget' in kwargs: view_widget = kwargs.pop('view_widget') _wtforms_Field_init(self, *args, **kwargs) if view_widget is None: view_widget = self.widget self.view_widget = view_widget patch_logger.info(Field.__init__) Field.__init__ = _core_field_init del _core_field_init def _core_field_repr(self): ''' __repr__ that shows the name of the field instance. Useful for tracing field errors (like in sentry) ''' return '<{}.{} at 0x{:x} name={!r}>'.format( self.__class__.__module__, self.__class__.__name__, id(self), self.name, ) patch_logger.info(Field.__module__ + '.Field.__repr__') Field.__repr__ = _core_field_repr del _core_field_repr # support 'widget_options' for some custom widgets _wtforms_Field_render = Field.__call__ def _core_field_render(self, **kwargs): if 'widget_options' in kwargs and not kwargs['widget_options']: kwargs.pop('widget_options') return _wtforms_Field_render(self, **kwargs) patch_logger.info(Field.__call__) Field.__call__ = _core_field_render del _core_field_render def render_view(self, **kwargs): """ Render data """ if 'widget_options' in kwargs and not kwargs['widget_options']: kwargs.pop('widget_options') if hasattr(self.view_widget, 'render_view'): return self.view_widget.render_view(self, **kwargs) return DefaultViewWidget().render_view(self, **kwargs) patch_logger.info('Add method %s.Field.render_view' % Field.__module__) Field.render_view = render_view del render_view def is_hidden(self): """ WTForms is not consistent with hidden fields, since `flags.hidden` is not set on `HiddenField` :-( """ return (self.flags.hidden or isinstance(self, HiddenField)) patch_logger.info('Add method %s.Field.is_hidden' % Field.__module__) Field.is_hidden = property(is_hidden) del is_hidden _PATCHED = True ### END PATCH wtforms.field.core.Field #################