web/uihelper.py
author Pierre-Yves David <pierre-yves.david@logilab.fr>
Tue, 27 Nov 2012 11:27:49 +0100
branchstable
changeset 8600 d74addac92bb
parent 8238 087bb529035c
child 8665 e65af61bde7d
permissions -rw-r--r--
[downloadable] fix filename in HTTP header (closes #2522325, #2522324) Before this changeset we use the `filename` header with utf8 encoded filename all the time. However RFC6266 says: The parameters "filename" and "filename*" differ only in that "filename*" uses the encoding defined in [RFC5987], allowing the use of characters not present in the ISO-8859-1 character set ([ISO-8859-1]). Therefore, we alter the code to: 1. Use `filename` and `ascii` encoding whenever possible, 2. use `filename*` with `utf8` encoding otherwise (with a filename fallback for old browser) We also switch the `content-disposition` value to attachement if filename is specified, this will result as a mandatory download according to RFC6266. This mandatory download is the expected behavior. We changes the filename encoding to RFC5987 which is simpler, supported by all and modern browser (including IE from version 6) and does not suffer from the continuation issue. (see ticket #2522324 for details)

# copyright 2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
#
# CubicWeb is free software: you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 2.1 of the License, or (at your option)
# any later version.
#
# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
"""This module provide highlevel helpers to avoid uicfg boilerplate
for most common tasks such as fields ordering, widget customization, etc.


Here are a few helpers to customize *action box* rendering:

.. autofunction:: cubicweb.web.uihelper.append_to_addmenu
.. autofunction:: cubicweb.web.uihelper.remove_from_addmenu


and a few other ones for *form configuration*:

.. autofunction:: cubicweb.web.uihelper.set_fields_order
.. autofunction:: cubicweb.web.uihelper.hide_field
.. autofunction:: cubicweb.web.uihelper.hide_fields
.. autofunction:: cubicweb.web.uihelper.set_field_kwargs
.. autofunction:: cubicweb.web.uihelper.set_field
.. autofunction:: cubicweb.web.uihelper.edit_inline
.. autofunction:: cubicweb.web.uihelper.edit_as_attr
.. autofunction:: cubicweb.web.uihelper.set_muledit_editable

The module also provides a :class:`FormConfig` base class that lets you gather
uicfg declaration in the scope of a single class, which can sometimes
be clearer to read than a bunch of sequential function calls.

.. autoclass:: cubicweb.web.uihelper.FormConfig

"""
__docformat__ = "restructuredtext en"

from cubicweb.web import uicfg
from functools import partial

def _tag_rel(rtag, etype, attr, desttype='*', *args, **kwargs):
    if isinstance(attr, basestring):
        attr, role = attr, 'subject'
    else:
        attr, role = attr
    if role == 'subject':
        rtag.tag_subject_of((etype, attr, desttype), *args, **kwargs)
    else:
        rtag.tag_object_of((desttype, attr, etype), *args, **kwargs)


## generic uicfg helpers ######################################################
def append_to_addmenu(etype, attr, createdtype='*'):
    """adds `attr` in the actions box *addrelated* submenu of `etype`.

    :param etype: the entity type as a string
    :param attr: the name of the attribute or relation to hide

    `attr` can be a string or 2-tuple (relname, role_of_etype_in_the_relation)

    """
    _tag_rel(uicfg.actionbox_appearsin_addmenu, etype, attr, createdtype, True)

def remove_from_addmenu(etype, attr, createdtype='*'):
    """removes `attr` from the actions box *addrelated* submenu of `etype`.

    :param etype: the entity type as a string
    :param attr: the name of the attribute or relation to hide

    `attr` can be a string or 2-tuple (relname, role_of_etype_in_the_relation)
    """
    _tag_rel(uicfg.actionbox_appearsin_addmenu, etype, attr, createdtype, False)


## form uicfg helpers ##########################################################
def set_fields_order(etype, attrs):
    """specify the field order in `etype` main edition form.

    :param etype: the entity type as a string
    :param attrs: the ordered list of attribute names (or relations)

    `attrs` can be strings or 2-tuples (relname, role_of_etype_in_the_relation)

    Unspecified fields will be displayed after specified ones, their
    order being consistent with the schema definition.

    Examples:

.. sourcecode:: python

  from cubicweb.web import uihelper
  uihelper.set_fields_order('CWUser', ('firstname', 'surname', 'login'))
  uihelper.set_fields_order('CWUser', ('firstname', ('in_group', 'subject'), 'surname', 'login'))

    """
    afk = uicfg.autoform_field_kwargs
    for index, attr in enumerate(attrs):
        _tag_rel(afk, etype, attr, '*', {'order': index})


def hide_field(etype, attr, desttype='*', formtype='main'):
    """hide `attr` in `etype` forms.

    :param etype: the entity type as a string
    :param attr: the name of the attribute or relation to hide
    :param formtype: which form will be affected ('main', 'inlined', etc.), *main* by default.

    `attr` can be a string or 2-tuple (relname, role_of_etype_in_the_relation)

    Examples:

.. sourcecode:: python

  from cubicweb.web import uihelper
  uihelper.hide_field('CWUser', 'login')
  uihelper.hide_field('*', 'name')
  uihelper.hide_field('CWUser', 'use_email', formtype='inlined')

    """
    _tag_rel(uicfg.autoform_section, etype, attr, desttype,
             formtype=formtype, section='hidden')


def hide_fields(etype, attrs, formtype='main'):
    """simple for-loop wrapper around :func:`hide_field`.

    :param etype: the entity type as a string
    :param attrs: the ordered list of attribute names (or relations)
    :param formtype: which form will be affected ('main', 'inlined', etc.), *main* by default.

    `attrs` can be strings or 2-tuples (relname, role_of_etype_in_the_relation)

    Examples:

.. sourcecode:: python

  from cubicweb.web import uihelper
  uihelper.hide_fields('CWUser', ('login', ('use_email', 'subject')), formtype='inlined')
    """
    for attr in attrs:
        hide_field(etype, attr, formtype=formtype)


def set_field_kwargs(etype, attr, **kwargs):
    """tag `attr` field of `etype` with additional named paremeters.

    :param etype: the entity type as a string
    :param attr: the name of the attribute or relation

    `attr` can be a string or 2-tuple (relname, role_of_etype_in_the_relation)

    Examples:

.. sourcecode:: python

  from cubicweb.web import uihelper, formwidgets as fwdgs

  uihelper.set_field_kwargs('Person', 'works_for', widget=fwdgs.AutoCompletionWidget())
  uihelper.set_field_kwargs('CWUser', 'login', label=_('login or email address'),
                            widget=fwdgs.TextInput(attrs={'size': 30}))
    """
    _tag_rel(uicfg.autoform_field_kwargs, etype, attr, '*', kwargs)


def set_field(etype, attr, field):
    """sets the `attr` field of `etype`.

    :param etype: the entity type as a string
    :param attr: the name of the attribute or relation

    `attr` can be a string or 2-tuple (relname, role_of_etype_in_the_relation)

    """
    _tag_rel(uicfg.autoform_field, etype, attr, '*', field)


def edit_inline(etype, attr, desttype='*', formtype=('main', 'inlined')):
    """edit `attr` with and inlined form.

    :param etype: the entity type as a string
    :param attr: the name of the attribute or relation
    :param desttype: the destination type(s) concerned, default is everything
    :param formtype: which form will be affected ('main', 'inlined', etc.), *main* and *inlined* by default.

    `attr` can be a string or 2-tuple (relname, role_of_etype_in_the_relation)

    Examples:

.. sourcecode:: python

  from cubicweb.web import uihelper

  uihelper.edit_inline('*', 'use_email')
  """
    _tag_rel(uicfg.autoform_section, etype, attr, desttype,
             formtype=formtype, section='inlined')


def edit_as_attr(etype, attr, desttype='*', formtype=('main', 'muledit')):
    """make `attr` appear in the *attributes* section of `etype` form.

    :param etype: the entity type as a string
    :param attr: the name of the attribute or relation
    :param desttype: the destination type(s) concerned, default is everything
    :param formtype: which form will be affected ('main', 'inlined', etc.), *main* and *muledit* by default.

    `attr` can be a string or 2-tuple (relname, role_of_etype_in_the_relation)

    Examples:

.. sourcecode:: python

  from cubicweb.web import uihelper

  uihelper.edit_as_attr('CWUser', 'in_group')
    """
    _tag_rel(uicfg.autoform_section, etype, attr, desttype,
             formtype=formtype, section='attributes')


def set_muledit_editable(etype, attrs):
    """make `attrs` appear in muledit form of `etype`.

    :param etype: the entity type as a string
    :param attrs: the ordered list of attribute names (or relations)

    `attrs` can be strings or 2-tuples (relname, role_of_etype_in_the_relation)

    Examples:

.. sourcecode:: python

  from cubicweb.web import uihelper

  uihelper.set_muledit_editable('CWUser', ('firstname', 'surname', 'in_group'))
    """
    for attr in attrs:
        edit_as_attr(etype, attr, formtype='muledit')


class meta_formconfig(type):
    """metaclass of FormConfig classes, only for easier declaration purpose"""
    def __init__(cls, name, bases, classdict):
        if cls.etype is None:
            return
        for attr_role in cls.hidden:
            hide_field(cls.etype, attr_role, formtype=cls.formtype)
        for attr_role in cls.rels_as_attrs:
            edit_as_attr(cls.etype, attr_role, formtype=cls.formtype)
        for attr_role in cls.inlined:
            edit_inline(cls.etype, attr_role, formtype=cls.formtype)
        for rtype, widget in cls.widgets.items():
            set_field_kwargs(cls.etype, rtype, widget=widget)
        for rtype, field in cls.fields.items():
            set_field(cls.etype, rtype, field)
        set_fields_order(cls.etype, cls.fields_order)
        super(meta_formconfig, cls).__init__(name, bases, classdict)


class FormConfig:
    """helper base class to define uicfg rules on a given entity type.

    In all descriptions below, attributes list can either be a list of
    attribute names of a list of 2-tuples (relation name, role of
    the edited entity in the relation).

    **Attributes**

    :attr:`etype`
      which entity type the form config is for. This attribute is **mandatory**

    :attr:`formtype`
      the formtype the class tries toc customize (i.e. *main*, *inlined*, or *muledit*),
      default is *main*.

    :attr:`hidden`
      the list of attributes or relations to hide.

    :attr:`rels_as_attrs`
      the list of attributes to edit in the *attributes* section.

    :attr:`inlined`
      the list of attributes to edit in the *inlined* section.

    :attr:`fields_order`
      the list of attributes to edit, in the desired order. Unspecified
      fields will be displayed after specified ones, their order
      being consistent with the schema definition.

    :attr:`widgets`
      a dictionary mapping attribute names to widget instances.

    :attr:`fields`
      a dictionary mapping attribute names to field instances.

    Examples:

.. sourcecode:: python

  from cubicweb.web import uihelper, formwidgets as fwdgs

  class LinkFormConfig(uihelper.FormConfig):
      etype = 'Link'
      hidden = ('title', 'description', 'embed')
      widgets = dict(
          url=fwdgs.TextInput(attrs={'size':40}),
          )

  class UserFormConfig(uihelper.FormConfig):
      etype = 'CWUser'
      hidden = ('login',)
      rels_as_attrs = ('in_group',)
      fields_order = ('firstname', 'surname', 'in_group', 'use_email')
      inlined = ('use_email',)

    """
    __metaclass__ = meta_formconfig
    formtype = 'main'
    etype = None # must be defined in concrete subclasses
    hidden = ()
    rels_as_attrs = ()
    inlined = ()
    fields_order = ()
    widgets = {}
    fields = {}