예제 #1
0
파일: button.py 프로젝트: Py-AMS/pyams-form
class IImageButton(IButton):
    """An image button in a form."""

    image = TextLine(
        title=_('Image Path'),
        description=_('A relative image path to the root of the resources'),
        required=True)
예제 #2
0
class IFieldsForm(IForm):
    """A form that is based upon defined fields."""

    fields = Object(title=_('Fields'),
                    description=_(
                        'A field manager describing the fields to be used for '
                        'the form.'),
                    schema=IFields)
예제 #3
0
class IHandlerForm(Interface):
    """A form that stores the handlers locally."""

    handlers = Object(
        title=_('Handlers'),
        description=_('A list of action handlers defined on the form.'),
        schema=IButtonHandlers,
        required=True)
예제 #4
0
파일: button.py 프로젝트: Py-AMS/pyams-form
class IActionEvent(Interface):
    """An event specific for an action."""

    action = Object(
        title=_('Action'),
        description=_('The action for which the event is created.'),
        schema=IAction,
        required=True)
예제 #5
0
class IButtonForm(IForm):
    """A form that is based upon defined buttons."""

    buttons = Object(
        title=_('Buttons'),
        description=_('A button manager describing the buttons to be used for '
                      'the form.'),
        schema=IButtons)
예제 #6
0
class IFieldsAndContentProvidersForm(IForm):
    """A form that is based upon defined fields and content providers"""

    content_providers = Object(
        title=_('Content providers'),
        description=_(
            'A manager describing the content providers to be used for '
            'the form.'),
        schema=IContentProviders)
예제 #7
0
class EditForm(Form):
    """A simple edit form with an apply button."""

    success_message = _('Data successfully updated.')
    no_changes_message = _('No changes were applied.')

    @button_and_handler(_('Apply'), name='apply')
    def handle_apply(self, action):  # pylint: disable=unused-argument
        """Apply action handler"""
        data, errors = {}, {}
        for form in self.get_forms():
            form_data, form_errors = form.extract_data()
            if form_errors:
                if not IGroup.providedBy(form):
                    form.status = getattr(form, 'form_errors_message',
                                          self.form_errors_message)
                errors[form] = form_errors
            data[form] = form_data
        if errors:
            self.status = self.form_errors_message
            return
        changes = self.apply_changes(data)
        if changes:
            self.status = self.success_message
        else:
            self.status = self.no_changes_message
        self.finished_state.update({'action': action, 'changes': changes})

    def apply_changes(self, data):
        """Apply updates to form context"""
        changes = {}
        contents, changed_contents = {}, {}
        for form in self.get_forms():
            if form.mode == DISPLAY_MODE:
                continue
            content = form.get_content()
            form_changes = apply_changes(form, content, data)
            if form_changes:
                merge_changes(changes, form_changes)
                content_hash = ICacheKeyValue(content)
                contents[content_hash] = content
                merge_changes(changed_contents.setdefault(content_hash, {}),
                              form_changes)
        if changes:
            # Construct change-descriptions for the object-modified event
            for content_hash, content_changes in changed_contents.items():
                descriptions = []
                for interface, names in content_changes.items():
                    descriptions.append(Attributes(interface, *names))
                # Send out a detailed object-modified event
                self.request.registry.notify(
                    ObjectModifiedEvent(contents[content_hash], *descriptions))
        return changes
예제 #8
0
class IBoolTerms(ITerms):
    """A specialization that handles boolean choices."""

    true_label = TextLine(
        title=_('True-value Label'),
        description=_('The label for a true value of the Bool field'),
        required=True)

    false_label = TextLine(
        title=_('False-value Label'),
        description=_('The label for a false value of the Bool field'),
        required=False)
예제 #9
0
class MultiWidget(HTMLFormElement, MultiWidgetBase, FormMixin):
    # pylint: disable=function-redefined
    """Multi widget implementation."""

    buttons = Buttons()

    prefix = 'widget'
    klass = 'multi-widget'
    css = 'multi'
    items = ()

    actions = None
    show_label = True  # show labels for item subwidgets or not

    # Internal attributes
    _adapter_value_attributes = MultiWidgetBase._adapter_value_attributes + (
        'show_label', )

    def update(self):
        """See pyams_form.interfaces.widget.IWidget."""
        super().update()
        self.update_actions()
        self.actions.execute()
        self.update_actions()  # Update again, as conditions may change

    def update_actions(self):
        """Update widget actions"""
        self.update_allow_add_remove()
        if self.name is not None:
            self.prefix = self.name
        registry = self.request.registry
        self.actions = registry.getMultiAdapter((self, self.request, self),
                                                IActions)
        self.actions.update()

    @button_and_handler(_('Add'),
                        name='add',
                        condition=attrgetter('allow_adding'))
    def handle_add(self, action):  # pylint: disable=unused-argument
        """Add button handler"""
        self.append_adding_widget()

    @button_and_handler(_('Remove selected'),
                        name='remove',
                        condition=attrgetter('allow_removing'))
    def handle_remove(self, action):  # pylint: disable=unused-argument
        """Remove button handler"""
        self.remove_widgets([
            widget.name for widget in self.widgets
            if '{}.remove'.format(widget.name) in self.request.params
        ])
예제 #10
0
class IActionForm(Interface):
    """A form that stores executable actions"""

    actions = Object(title=_('Actions'),
                     description=_('A list of actions defined on the form'),
                     schema=IActions,
                     required=True)

    refresh_actions = Bool(title=_('Refresh actions'),
                           description=_(
                               'A flag, when set, causes form actions to be '
                               'updated again after their execution.'),
                           default=False,
                           required=True)
예제 #11
0
파일: button.py 프로젝트: Py-AMS/pyams-form
class IAction(Interface):
    """Action"""

    __name__ = TextLine(title=_('Name'),
                        description=_('The object name.'),
                        required=False,
                        default=None)

    title = TextLine(title=_('Title'),
                     description=_('The action title.'),
                     required=True)

    def is_executed(self):
        """Determine whether the action has been executed."""
예제 #12
0
파일: button.py 프로젝트: Py-AMS/pyams-form
class IButton(IField):
    """A button in a form."""

    access_key = TextLine(
        title=_('Access Key'),
        description=_('The key when pressed causes the button to be pressed'),
        min_length=1,
        max_length=1,
        required=False)

    action_factory = Field(title=_('Action Factory'),
                           description=_('The action factory'),
                           required=False,
                           default=None,
                           missing_value=None)
예제 #13
0
파일: ajax.py 프로젝트: Py-AMS/pyams-form
class AJAXAddForm(AJAXForm):
    """AJAX add form mix-in class"""

    no_changes_message = _("No data was created.")

    def get_ajax_output(self, changes):
        request = self.request  # pylint: disable=no-member
        # pylint: disable=no-member
        renderer = None
        if 'action' in self.finished_state:
            name = self.finished_state['action'].field.getName()
            renderer = queryMultiAdapter((self.context, request, self),
                                         IAJAXFormRenderer,
                                         name=name)
        if renderer is None:
            renderer = queryMultiAdapter((self.context, request, self),
                                         IAJAXFormRenderer)
        if renderer is not None:
            result = renderer.render(changes)
            if result:
                return result
        if changes is None:
            return {
                'status': 'info',
                'message': request.localizer.translate(self.no_changes_message)
            }
        return {'status': 'reload'}
예제 #14
0
def extract_file_name(form,
                      widget_id,
                      cleanup=True,
                      allow_empty_postfix=False):
    """Extract the filename of the widget with the given id.

    Uploads from win/IE need some cleanup because the filename includes also
    the path. The option ``cleanup=True`` will do this for you. The option
    ``allowEmptyPostfix`` allows to have a filename without extensions. By
    default this option is set to ``False`` and will raise a ``ValueError`` if
    a filename doesn't contain a extension.
    """
    widget = get_widget_by_id(form, widget_id)
    clean_file_name = ''
    dotted_parts = []
    if not allow_empty_postfix or cleanup:
        # We need to strip out the path section even if we do not reomve them
        # later, because we just need to check the filename extension.
        clean_file_name = widget.filename.split('\\')[-1]
        clean_file_name = clean_file_name.split('/')[-1]
        dotted_parts = clean_file_name.split('.')
    if not allow_empty_postfix:
        if len(dotted_parts) <= 1:
            raise ValueError(_('Missing filename extension.'))
    if cleanup:
        return clean_file_name
    return widget.filename
예제 #15
0
파일: error.py 프로젝트: Py-AMS/pyams-form
class IErrorViewSnippet(Interface):
    """A view providing a view for an error"""

    widget = Field(title=_("Widget"),
                   description=_("The widget that the view is on"),
                   required=True)

    error = Field(title=_('Error'),
                  description=_('Error the view is for'),
                  required=True)

    def update(self):
        """Update view"""

    def render(self):
        """Render view"""
예제 #16
0
파일: error.py 프로젝트: Py-AMS/pyams-form
class ValueErrorViewSnippet(ErrorViewSnippet):
    """An error view for ValueError."""

    default_message = _('The system could not process the given value.')

    def create_message(self):
        return self.default_message
예제 #17
0
파일: term.py 프로젝트: Py-AMS/pyams-form
 def _make_missing_term(self, value):
     """Return a term that should be displayed for the missing token"""
     uvalue = to_unicode(value)
     return SimpleTerm(value,
                       self._make_token(value),
                       title=_('Missing: ${value}',
                               mapping=dict(value=uvalue)))
예제 #18
0
class IData(Interface):
    """A proxy object for form data.

    The object will make all keys within its data attribute available as
    attributes. The schema that is represented by the data will be directly
    provided by instances.
    """
    def __init__(self, schema, data, context):  # pylint: disable=super-init-not-called
        """The data proxy is instantiated using the schema it represents, the
        data fulfilling the schema and the context in which the data are
        validated.
        """

    __context__ = Field(
        title=_('Context'),
        description=_('The context in which the data are validated'),
        required=True)
예제 #19
0
class IContextAware(Interface):
    """Offers a context attribute.

    For advanced uses, the widget will make decisions based on the context
    it is rendered in.
    """

    context = Field(
        title=_('Context'),
        description=_('The context in which the widget is displayed.'),
        required=True)

    ignore_context = Bool(
        title=_('Ignore Context'),
        description=_('A flag, when set, forces the widget not to look at '
                      'the context for a value.'),
        default=False,
        required=False)
예제 #20
0
파일: term.py 프로젝트: Py-AMS/pyams-form
class BoolTerms(Terms):
    """Default yes and no terms are used by default for IBool fields."""

    true_label = _('yes')
    false_label = _('no')

    def __init__(self, context, request, form, field, widget):
        # pylint: disable=too-many-arguments
        self.context = context
        self.request = request
        self.form = form
        self.field = field
        self.widget = widget
        terms = [
            SimpleTerm(*args)
            for args in [(True, 'true',
                          self.true_label), (False, 'false', self.false_label)]
        ]
        self.terms = SimpleVocabulary(terms)
예제 #21
0
class EditSubForm(BaseForm):
    """Edit sub-form"""

    form_errors_message = _('There were some errors.')
    success_message = _('Data successfully updated.')
    no_changes_message = _('No changes were applied.')

    def __init__(self, context, request, parent_form):
        super().__init__(context, request)
        self.parent_form = self.__parent__ = parent_form

    @handler(EditForm.buttons['apply'])
    def handle_apply(self, action):  # pylint: disable=unused-argument
        """Handler for apply button"""
        data, errors = self.widgets.extract()
        if errors:
            self.status = self.form_errors_message
            return
        content = self.get_content()
        changed = apply_changes(self, content, data)
        if changed:
            registry = self.request.registry
            registry.notify(ObjectModifiedEvent(content))
            self.status = self.success_message
        else:
            self.status = self.no_changes_message

    def update(self):
        super().update()
        registry = self.request.registry
        for action in self.parent_form.actions.executed_actions:
            adapter = registry.queryMultiAdapter(
                (self, self.request, self.get_content(), action),
                IActionHandler)
            if adapter:
                adapter()
예제 #22
0
    def to_field_value(self, value):
        """See interfaces.IDataConverter"""
        if value is None or value == '':
            # When no new file is uploaded, send a signal that we do not want
            # to do anything special.
            return NOT_CHANGED

        # By default a IBytes field is used for get a file upload widget.
        # But interfaces extending IBytes do not use file upload widgets.
        # Any way if we get a FieldStorage of FileUpload object, we'll
        # convert it.
        # We also store the additional FieldStorage/FileUpload values on the widget
        # before we loose them.
        if isinstance(value, (FieldStorage, FileUpload)):
            self.widget.headers = value.headers
            self.widget.filename = value.filename
            try:
                if isinstance(value, FieldStorage):
                    if value.fp is None:
                        seek = value.file.seek
                        read = value.file.read
                    else:
                        seek = value.fp.seek
                        read = value.fp.read
                else:
                    seek = value.seek
                    read = value.read
            except AttributeError as e:  # pylint: disable=invalid-name
                raise ValueError(_('Bytes data are not a file object')) from e
            else:
                seek(0)
                data = read()
                if data or getattr(value, 'filename', ''):
                    return data
                return self.field.missing_value
        else:
            return to_bytes(value)
예제 #23
0
class Form(BaseForm):
    """The Form."""

    buttons = Buttons()

    method = FieldProperty(IInputForm['method'])
    enctype = FieldProperty(IInputForm['enctype'])
    accept_charset = FieldProperty(IInputForm['accept_charset'])
    accept = FieldProperty(IInputForm['accept'])
    autocomplete = FieldProperty(IInputForm['autocomplete'])

    actions = FieldProperty(IActionForm['actions'])
    refresh_actions = FieldProperty(IActionForm['refresh_actions'])

    # AJAX related form properties
    ajax_form_handler = FieldProperty(IInputForm['ajax_form_handler'])
    ajax_form_options = FieldProperty(IInputForm['ajax_form_options'])
    ajax_form_target = FieldProperty(IInputForm['ajax_form_target'])
    ajax_form_callback = FieldProperty(IInputForm['ajax_form_callback'])

    # common string for use in validation status messages
    form_errors_message = _('There were some errors.')

    def __init__(self, context, request):
        super().__init__(context, request)
        self.finished_state = {}

    @property
    def action(self):
        """See interfaces.IInputForm"""
        return self.request.url

    @property
    def name(self):
        """See interfaces.IInputForm"""
        return self.prefix.strip('.')

    @property
    def id(self):  # pylint: disable=invalid-name
        """Form ID"""
        return self.name.replace('.', '-')

    def update_actions(self):
        """Update form actions"""
        registry = self.request.registry
        self.actions = registry.getMultiAdapter(
            (self, self.request, self.get_content()), IActions)
        self.actions.update()

    def update(self):
        super().update()
        self.update_actions()
        self.actions.execute()
        if self.refresh_actions:
            self.update_actions()

    def get_ajax_handler(self):
        """Get absolute URL of AJAX handler"""
        return absolute_url(self.context, self.request, self.ajax_form_handler)

    def get_form_options(self):
        """Get form options in JSON format"""
        return json.dumps(
            self.ajax_form_options) if self.ajax_form_options else None
예제 #24
0
class IField(Interface):
    """Field wrapping a schema field used in the form."""

    __name__ = TextLine(title=_('Title'),
                        description=_('The name of the field within the form'),
                        required=True)

    field = Field(title=_('Schema Field'),
                  description=_('The schema field that is to be rendered'),
                  required=True)

    prefix = Field(
        title=_('Prefix'),
        description=_('The prefix of the field used to avoid name clashes'),
        required=True)

    mode = Field(
        title=_('Mode'),
        description=_('The mode in which to render the widget for the field'),
        required=True)

    interface = Field(
        title=_('Interface'),
        description=_('The interface from which the field is coming'),
        required=True)

    ignore_context = Bool(
        title=_('Ignore Context'),
        description=_('A flag, when set, forces the widget not to look at '
                      'the context for a value'),
        required=False)

    widget_factory = Field(title=_('Widget Factory'),
                           description=_('The widget factory'),
                           required=False,
                           default=None,
                           missing_value=None)

    show_default = Bool(title=_('Show default value'),
                        description=_(
                            'A flag, when set, makes the widget to display '
                            'field|adapter provided default values'),
                        default=True,
                        required=False)
예제 #25
0
class AddForm(Form):
    """A field and button based add form."""

    ignore_context = True
    ignore_readonly = True

    content_factory = None

    @button_and_handler(_('Add'), name='add')
    def handle_add(self, action):  # pylint: disable=unused-argument
        """Handler for *add* button"""
        data, errors = {}, {}
        for form in self.get_forms():
            form_data, form_errors = form.extract_data()
            if form_errors:
                if not IGroup.providedBy(form):
                    form.status = getattr(form, 'form_errors_message',
                                          self.form_errors_message)
                errors[form] = form_errors
            data[form] = form_data
        if errors:
            self.status = self.form_errors_message
            return
        obj = self.create_and_add(data)
        if obj is not None:
            # mark only as finished if we get the new object
            self.finished_state.update({'action': action, 'changes': obj})

    def create_and_add(self, data):
        """Create new content and add it to context"""
        obj = self.create(data.get(self, {}))
        self.request.registry.notify(ObjectCreatedEvent(obj))
        if IPersistent.providedBy(
                obj):  # temporary locate to fix raising of INotYet exceptions
            locate(obj, self.context)
        self.update_content(obj, data)
        self.add(obj)
        return obj

    def create(self, data):  # pylint: disable=unused-argument
        """Create new content from form data"""
        if self.content_factory is not None:
            factory = get_object_factory(self.content_factory) \
                if is_interface(self.content_factory) else self.content_factory
            return factory()  # pylint: disable=not-callable
        raise NotImplementedError

    def add(self, obj):  # pylint: disable=redefined-builtin
        """Add new object to form context"""
        raise NotImplementedError

    def update_content(self, obj, data):
        """Update content with form data after creation"""
        changes = {}
        for form in self.get_forms():
            if form.mode == DISPLAY_MODE:
                continue
            merge_changes(changes, apply_changes(form, obj, data))
        return changes

    def next_url(self):
        """Redirection URL after object creation"""
        return self.action

    def render(self):
        if self.finished_state:
            self.request.response.location = self.next_url()
            self.request.response.status = 302
            return ''
        return super().render()
예제 #26
0
class SelectWidget(HTMLSelectWidget, SequenceWidget):
    """Select widget implementation."""

    klass = 'select-widget'
    css = 'select'
    prompt = False

    no_value_message = _('No value')
    prompt_message = _('Select a value...')

    # Internal attributes
    _adapter_value_attributes = SequenceWidget._adapter_value_attributes + \
        ('no_value_message', 'prompt_message', 'prompt')

    def is_selected(self, term):
        """Check for term selection"""
        return term.token in self.value

    def update(self):
        """See pyams_form.interfaces.widget.IWidget."""
        super().update()
        add_field_class(self)

    @property
    def items(self):
        """Items list getter"""
        if self.terms is None:  # update() has not been called yet
            return ()
        items = []
        if (not self.required or self.prompt) and self.multiple is None:
            if self.prompt:
                message = self.prompt_message
            else:
                message = self.no_value_message
            items.append({
                'id': self.id + '-novalue',
                'value': self.no_value_token,
                'content': message,
                'selected': self.value in ((), [])
            })

        ignored = set(self.value)

        def add_item(idx, term, prefix=''):
            selected = self.is_selected(term)
            if selected and term.token in ignored:
                ignored.remove(term.token)
            item_id = '%s-%s%i' % (self.id, prefix, idx)
            content = term.token
            if ITitledTokenizedTerm.providedBy(term):
                content = self.request.localizer.translate(term.title)
            items.append({
                'id': item_id,
                'value': term.token,
                'content': content,
                'selected': selected
            })

        for idx, term in enumerate(self.terms):
            add_item(idx, term)

        if ignored:
            # some values are not displayed, probably they went away from the vocabulary
            for idx, token in enumerate(sorted(ignored)):
                try:
                    term = self.terms.getTermByToken(token)
                except LookupError:
                    # just in case the term really went away
                    continue

                add_item(idx, term, prefix='missing-')
        return items

    def json_data(self):
        data = super().json_data()
        data['type'] = 'select'
        data['options'] = self.items
        return data
예제 #27
0
class DecimalDataConverter(NumberDataConverter):
    """A data converter for integers."""

    type = decimal.Decimal
    error_message = _('The entered value is not a valid decimal literal.')
예제 #28
0
class IntegerDataConverter(NumberDataConverter):
    """A data converter for integers."""

    type = int
    error_message = _('The entered value is not a valid integer literal.')
예제 #29
0
class IForm(Interface):
    """Form interface"""

    mode = Choice(title=_('Mode'),
                  description=_('The mode in which to render the widgets.'),
                  values=(INPUT_MODE, DISPLAY_MODE),
                  required=True)

    ignore_context = Bool(
        title=_('Ignore Context'),
        description=_('If set the context is ignored to retrieve a value.'),
        default=False,
        required=True)

    ignore_request = Bool(
        title=_('Ignore Request'),
        description=_('If set the request is ignored to retrieve a value.'),
        default=False,
        required=True)

    ignore_readonly = Bool(
        title=_('Ignore Readonly'),
        description=_('If set then readonly fields will also be shown.'),
        default=False,
        required=True)

    ignore_required_on_extract = Bool(
        title=_('Ignore Required validation on extract'),
        description=_(
            "If set then required fields will pass validation "
            "on extract regardless whether they're filled in or not"),
        default=False,
        required=True)

    widgets = Object(
        title=_('Widgets'),
        description=_('A widget manager containing the widgets to be used in '
                      'the form.'),
        schema=IWidgets)

    title = TextLine(title=_('Title'),
                     description=_('Main form title'),
                     required=False)

    legend = TextLine(
        title=_('Legend'),
        description=_('A human readable text describing the form that can be '
                      'used in the UI.'),
        required=False)

    required_label = TextLine(
        title=_('Required label'),
        description=_('A human readable text describing the form that can '
                      'be used in the UI for rendering a required info '
                      'legend.'),
        required=False)

    prefix = ASCIILine(
        title=_('Prefix'),
        description=_('The prefix of the form used to uniquely identify it.'),
        default='form.')

    status = Text(title=_('Status'),
                  description=_('The status message of the form.'),
                  default=None,
                  required=False)

    def get_content(self):
        """Return the content to be displayed and/or edited."""

    def update_widgets(self, prefix=None):
        """Update the widgets for the form.

        This method is commonly called from the ``update()`` method and is
        mainly meant to be a hook for subclasses.

        Note that you can pass an argument for ``prefix`` to override
        the default value of ``"widgets."``.
        """

    def extract_data(self, set_errors=True):
        """Extract the data of the form.

        set_errors: needs to be passed to extract() and to sub-widgets"""

    def update(self):
        """Update the form."""

    def render(self):
        """Render the form."""

    def json(self):
        """Returns the form in json format"""
예제 #30
0
class IInputForm(Interface):
    """A form that is meant to process the input of the form controls."""

    action = URI(title=_('Action'),
                 description=_(
                     'The action defines the URI to which the form data are '
                     'sent.'),
                 required=True)

    name = TextLine(title=_('Name'),
                    description=_('The name of the form used to identify it.'),
                    required=False)

    # pylint: disable=invalid-name
    id = TextLine(title=_('Id'),
                  description=_('The id of the form used to identify it.'),
                  required=False)

    method = Choice(title=_('Method'),
                    description=_('The HTTP method used to submit the form.'),
                    values=('get', 'post'),
                    default='post',
                    required=False)

    enctype = ASCIILine(
        title=_('Encoding Type'),
        description=_('The data encoding used to submit the data safely.'),
        default='multipart/form-data',
        required=False)

    accept_charset = ASCIILine(
        title=_('Accepted Character Sets'),
        description=_('This is a list of character sets the server '
                      'accepts. By default this is unknown.'),
        required=False)

    accept = ASCIILine(title=_('Accepted Content Types'),
                       description=_(
                           'This is a list of content types the server can '
                           'safely handle.'),
                       required=False)

    autocomplete = Choice(
        title=_("Form autocomplete"),
        description=_("Enable or disable global form autocomplete"),
        values=('on', 'off', 'new-password'),
        required=False)

    # AJAX related form settings

    ajax_form_handler = TextLine(title="Name of AJAX form handler",
                                 required=False)

    ajax_form_options = Dict(title="AJAX form submit's data options",
                             required=False)

    ajax_form_target = TextLine(
        title="Form submit target",
        description="Form content target, used for HTML and text content "
        "types",
        required=False)

    ajax_form_callback = TextLine(
        title="AJAX submit callback",
        description="Name of a custom form submit callback",
        required=False)

    def get_ajax_handler(self):
        """Get absolute URL of AJAX handler"""

    def get_form_options(self):
        """Get form options in JSON format"""