Source code for ringo.lib.renderer.form

import logging
import os
import pkg_resources
from mako.lookup import TemplateLookup
from formbar.fields import rules_to_string
from formbar.renderer import (
    FieldRenderer,
    DropdownFieldRenderer as FormbarDropdown,
    SelectionFieldRenderer as FormbarSelectionField,
    CheckboxFieldRenderer as FormbarCheckboxField
)
import ringo.lib.helpers as helpers
from ringo.lib.helpers import get_action_routename, literal, escape, HTML
from ringo.model.base import BaseItem, BaseList, get_item_list
from ringo.lib.table import get_table_config
import ringo.lib.security as security

base_dir = pkg_resources.get_distribution("ringo").location
template_dir = os.path.join(base_dir, 'ringo', 'templates')
template_lookup = TemplateLookup(directories=[template_dir],
                                 default_filters=['h'])

log = logging.getLogger(__name__)

###########################################################################
#                         Renderers for form elements                     #
###########################################################################


def add_renderers(custom_renderers):
    """Helper function to add ringo ringo specific renderers for form
    rendering."""
    for key in custom_renderers:
        if key not in renderers:
            renderers[key] = custom_renderers[key]
    return renderers


def get_link_url(item, request, actionname=None, backurl=False):
    """Return a url to the given item. On default the link will be a
    link to the update or read view of the item depending on the
    permission. If the application is configured to open items in read
    mode on default the update action will not be checked. If the user
    does not have enough permissions than None is returned. Optionally
    you can provide the name of a action. In this case the url will be
    build for the given actionname if the user has enough
    permissions."""
    readmode = request.registry.settings.get("app.readmode") in ["True", "true"]
    if isinstance(item, BaseItem):
        if actionname:
            from ringo.views.helpers import get_item_modul
            modul = get_item_modul(request, item)
            action = modul.get_action(actionname)
            if action is None:
                # This can happen if the action is not part of the modul
                # but a custum userdefined view. No permission checks
                # are done here yet.
                route_name = get_action_routename(item, actionname)
            else:
                permission = action.permission or action.name.lower()
                if security.has_permission(permission, item, request):
                    route_name = get_action_routename(item,
                                                      action.name.lower())
                else:
                    return None
        elif not readmode and security.has_permission("update", item, request):
            route_name = get_action_routename(item, 'update')
        elif security.has_permission("read", item, request):
            route_name = get_action_routename(item, 'read')
        else:
            return None

        query = {}
        if backurl:
            clazz = request.context.__model__
            query['backurl'] = request.session.get('%s.backurl' % clazz) or request.current_route_path()
        return request.route_path(route_name, id=item.id, _query=query)
    return None


def filter_options_on_permissions(request, options):
    """Will iterate over all options and build a tuple for each option
    with the following information:

    1. The option itself
    2. The value of the option
    3. A flag indicating if the requesting user is allowed to `link` the
       option. This information is later used on rendering.

    :request: current request
    :options: list of tuple of options (item, value, linkable)
    :returns: filtered list of option tuples.

    """
    filtered_options = []
    for option in options:
        linkable = False
        if (option[2] and (security.has_permission('link', option[0], request) or
           not hasattr(option[0], 'owner'))):
            linkable = True
        filtered_options.append((option[0], option[1], linkable))
    return filtered_options


[docs]class LinkFieldRenderer(FieldRenderer): """ Will rendere a Link to the item. The item which will be linked can be set by * the name attribute of the entity * the value attribute of the entity If the item can not be determined by one of the options above the item will be the item linked to the form. The following option in addition to the base Linkfield Renderer are supported: * openmodal: If true the item will be opened in a modal form. Defaults to false. If true a backlink will be generated too. * action: Name of the action for which the link will be rendered. Defaults to None with means the link will be either to the read or update view. * css: CSS classes for the link. * backlink: If set to true a backlink will be generated. * target: Set target attribute of the link. """ def __init__(self, field, translate): """@todo: to be defined""" FieldRenderer.__init__(self, field, translate) self.template = template_lookup.get_template("internal/linkfield.mako") def _get_template_values(self): values = FieldRenderer._get_template_values(self) values['link_text'] = self._field.label try: item = getattr(self._field._form._item, self._field.name) except AttributeError: # If the attribute is not part of the item there may be a # value in the field based on a expression. So set the item # to the value of the field. If this is not an instance of # BaseItem the link can not be generated. item = self._field.value if not item: name = self._field.name item = self._field._form._item # Only print warning if the fieldname is not a helper # field beginning with "_" if not name.startswith("_"): log.warning("Missing value for %s in %s" % (name, item)) backurl = self.openmodal == "true" or self.backlink == "true" values['url'] = get_link_url(item, self._field._form._request, self._field.renderer.action, backurl) or "#" return values def _render_label(self): return ""
[docs]class CheckboxFieldRenderer(FormbarCheckboxField): """Ringo specific DropdownFieldRenderer. Will additionally check permission to read on the items in the option list""" def _get_template_values(self): values = FormbarCheckboxField._get_template_values(self) if self._field.readonly: values['options'] = [] else: values['options'] = filter_options_on_permissions( self._field._form._request, values['options']) return values
[docs]class StateFieldRenderer(FormbarDropdown): """Ringo specific DropdownFieldRenderer to change the current state of an item. This renderer will render a combined widget containing a textfield showing the current state of the item and a dropdown with available actions which can be done from this state. * layout: Option to change the layout of the statefield. The renderer offers currently three options to render setting the `layout` attribute of the renderer: 1. default: In default the renderer shows the current state with description and the resulting state and description when choosing a transtion. 2. simple: Simple will render a simple dropdown with the current state as part of the fields label and the available transitions as options of the dropdown 3. button: Botton will render the current state the available transitions as a button. The button will work like a submit button of the form so if the user clicks on it the form data will be submitted and the state is changed. """ def __init__(self, field, translate): """@todo: to be defined""" FormbarDropdown.__init__(self, field, translate) self.template = template_lookup.get_template("internal/" "statefield.mako") def _render_label(self): return literal("") def _get_template_values(self): values = FieldRenderer._get_template_values(self) item = self._field._form._item sm = item.get_statemachine(self._field.name, request=self._field._form._request) state = sm.get_state() values["state"] = state values["request"] = self._field._form._request return values
[docs]class ListingFieldRenderer(FormbarSelectionField): """Renderer to render a listing of linked items. Used attributes: * form: Name of the form which is used to add new items * table: Name of the table configuration which is used to list items * hideadd: Flag "true" or "false" to configure hiding the add button. * nolinks: Flag "true" or "false" to configure completly disable linking. * showsearch: Flag "true" or "false" to configure rendering a search field. * showall: "true" or "false". If true all items (linked and unlinked) regardless if the current user is allowed to read or update the item will be listed. However clicking on items the user has no permission to read will have no effect as no links are rendered for those items. Defaults to false. * onlylinked: "true" or "false". If true only linked items will be rendered. Checkboxes will be hidden. * multiple: "true" or "false". If false only one option can be selected. Defaults to true. Note that this restriction is only implemented on client side. * openmodal: "true" or "false". If true the item will be opened in a modal popup. * backlink: "true" or "false". If true the user will be redirected back to the listing after creating a new item. Defaults to true. * action: Define the action which will be called when clicking an an entry. On default the action will be determined by checking the users permission and choosing between "read" or "update". Setting action you can enforce using a certain action if the user has sufficient permissions. Example:: <entity id="foo" name="name_of_the_orm_relation" ...> <renderer type="listing" showall="true" table="details"/> </entity> Please note that the name of the entity must be the name of the relation in the ORM model which links the items you want to list in the listing. """ def __init__(self, field, translate): FormbarSelectionField.__init__(self, field, translate) self.template = template_lookup.get_template("internal/listfield.mako") def get_class(self): try: clazz = self._field._get_sa_mapped_class() return clazz except AttributeError: # Special logic if the fields item is not an SQLAlchemy # attribute but a python property (used to add futher # informations to the fields item model). In this case getting # the clazz from the SA attributes mapper -> clazz will # fail. clazz = getattr(self._field._form._item, self._field.name) if isinstance(clazz, BaseList): return clazz.clazz return clazz @property def itemlist(self): clazz = self.get_class() if self.showall == 'true': itemlist = get_item_list(self._field._form._request, clazz) else: itemlist = get_item_list(self._field._form._request, clazz, user=self._field._form._request.user) config = get_table_config(itemlist.clazz, self._field._config.renderer.table) sort_field = config.get_default_sort_column() sort_order = config.get_default_sort_order() itemlist.sort(sort_field, sort_order) # Warning filtering items here can cause loosing relations to # the filtered items. This is esspecially true if the item which # was related to the item before now gets filtered because of a # changes value in an attribute e.g. In this case the filtered # item is not in the list at all and will not be sent on a POST # request. This will result in removing the relation! search = config.get_default_search() itemlist.filter(search) return itemlist def _get_selected_items(self, items): try: selected = getattr(self._field._form._item, self._field.name) or [] # Usually the getattr function should return a single item # or a list of items. But in some cases the attribute can be # a BaseList. This can happen if the attribute is a python # property of the item to dynamically add some attributes. # This property might return a BaseList. if isinstance(selected, BaseList): selected = selected.items elif not isinstance(selected, list): selected = [selected] selected_ids = [s.id for s in selected] return [i for i in items if i.id in selected_ids] except AttributeError: # Can happen when designing forms an the model of the item # is not yet configured. log.warning("Missing %s attribute in %s" % (self._field.name, repr(self._field._form._item))) return [] def render(self): """Initialize renderer""" html = [] active = 'active' if self._active else 'inactive' config = self._field._config.renderer has_errors = len(self._field.get_errors()) has_warnings = len(self._field.get_warnings()) class_options = "form-group %s %s %s" % ((has_errors and 'has-error'), (has_warnings and 'has-warning'), (active)) html.append(HTML.tag("div", _closed=False, rules=u"{}".format(";".join(rules_to_string(self._field))), formgroup="{}".format(self._field.name), desired="{}".format(self._field.desired), required="{}".format(self._field.required), class_=class_options)) html.append(self._render_label()) # All items which can potentially be linked. However this list # of items may be already filtered be defining a default filter # in the overview configuration which is used for this renderer. items = self.itemlist.items selected_items = self._get_selected_items(items) if self._field.readonly or self.onlylinked == "true": items = selected_items # Filter options based on the configured filter expression of # the renderer. Please note the this method return a list of # tuples. item_tuples = self._field.filter_options(items) # Get filtered options and only use the items which are # in the origin items list and has passed filtering. item_tuples = self._field.filter_options(items) # Filter the items again based on the permissions. This means # resetting the third value in the tuple. if self.showall != "true" and not self._field.readonly: item_tuples = filter_options_on_permissions(self._field._form._request, item_tuples) values = {'items': item_tuples, 'selected_item_ids': [i.id for i in selected_items], 'field': self._field, 'clazz': self.get_class(), 'pclazz': self._field._form._item.__class__, 'request': self._field._form._request, '_': self._field._form._translate, 's': security, 'h': helpers, 'url_getter': get_link_url, 'tableconfig': get_table_config(self.itemlist.clazz, config.table)} html.append(literal(self.template.render(**values))) html.append(self._render_errors()) html.append(self._render_help()) html.append(HTML.tag("/div", _closed=False)) return literal("").join(html)
renderers = { "dropdown": DropdownFieldRenderer, "checkbox": CheckboxFieldRenderer, "listing": ListingFieldRenderer, "state": StateFieldRenderer, "link": LinkFieldRenderer }