thelayouts / thelayouts / layouts_base.py

# -*- coding: utf-8 -*-

from weakref import proxy

from webhelpers.html import literal, format_attrs

__all__ = [
    "CannotResolveElementException", "LayoutElement", "CallableElement",
    "Script", "Container", "Column", "Layout", "Row", "Field",
]


class CannotResolveElementException(Exception):
    """Exception is to be thrown if any layout element in layout tree cannot be
    resolved into LayoutElement instance."""
    def __init__(self, element):
        super(CannotResolveElementException, self).__init__(element)


class DefaultFlags(object):
    def __init__(self, **entries):
        entries = ({
            "inline": False,
        }, **entries)
        self.__dict__.update(entries)


class LayoutElement(object):
    """Base element for building layout tree."""
    
    def __init__(self, *elements, **attrs):
        """
        elements -- list of child elements to be resolved into LayoutElement
            instances.
        attrs -- dict of named arguments to be placed as HTML arguments when 
            this layout element is rendered.

        Specific keyword arguments:
        is_present -- a callable or a boolean indicating if this layout element
            is to be rendered within layout tree.
        element_attrs -- keyword arguments intended to be passed to child 
            elements when rendered.
        help -- help string for this layout element.
        """
        self._is_present = attrs.pop("is_present", True)
        self.element_attrs = attrs.pop("element_attrs", {})
        self.help = attrs.pop('help', None)
        self.label_text = attrs.pop('label_text', u"")
        self.attrs = attrs
        # Before elements come LayoutElements they are stored in 
        # self.unresolved_child_elements
        self.unresolved_child_elements = list(elements)
        self.resolved_child_elements = []
        # Containing form with related fields refered to by elements
        self.form = None
        self.title = None

    def is_present(self):
        """Determine if this layout element is to be rendered."""
        if callable(self._is_present):
            return self._is_present()
        return self._is_present

    def get_element_attrs(self, **attrs):
        """Get attributes intended for child layout elements."""
        element_attrs = self.element_attrs.copy()
        element_attrs.update(attrs)
        return element_attrs
    
    def get_help(self):
        """Get help string for this layout element."""
        return self.help

    def get_attrs(self, **attrs):
        """Get attributes of this layout elements updated by attrs if given."""
        result_attrs = self.attrs
        result_attrs.update(attrs)
        return result_attrs

    def get_form(self):
        """Get form bound to this layout element. Form binding is done by form 
        itself calling bind(form) method before rendering the whole layout tree.
        """
        return self.form

    def get_title(self):
        """Get title for this layout element."""
        return self.title

    def get_label_text(self):
        return self.label_text

    def iterate_child_elements(self):
        """Iterate child elements yielding LayoutElement instances."""
        for element in self.resolved_child_elements:
            yield element
        while self.unresolved_child_elements:
            element = self.unresolved_child_elements.pop(0)
            element = self.resolve_element(element)
            self.resolved_child_elements.append(element)
            yield element
    
    def bind(self, form):
        """Bind this element and it's child elements to given form."""
        self.form = form
        for element in self.iterate_child_elements():
            element.bind(form)
            
    def is_bound(self):
        """Check is this layout element bound to a form."""
        return self.form is not None
    
    def render(self, *args, **kwargs):
        """Render this layout element (if self.is_present() == True) and it's 
        child elements."""
        if not self.is_present():
            return u""
        return self.render_elements()
    
    def render_elements(self):
        """Render only child layout elements."""
        rendered_elements = []
        for element in self.iterate_child_elements():
            if not element.is_present():
                continue
            rendered_elements.append(
                self.render_element(element)
            )
        return literal(u"").join(rendered_elements)

    def render_element(self, element, **attrs):
        """Render given layout element applying contained element_attrs."""
        return element.render(**self.get_element_attrs())

    def render_errors(self):
        """Render errors if any for this and child layout elements."""
        html = []
        for child_element in self.iterate_child_elements():
            html.append(child_element.render_errors())
        return literal(u"").join(html)

    def resolve_element(self, element, *args, **kwargs):
        """Resolve element into LayoutElement instance.
        Arguments:
        element -- LayoutElement, basestring or callable.
        """
        if isinstance(element, LayoutElement):
            return element
        if isinstance(element, basestring):
            return self.resolve_string_element(element, *args, **kwargs)
        if callable(element):
            return self.resolve_callable_element(element, *args, **kwargs)
        return self.resolve_other_element(element, *args, **kwargs)
    
    def resolve_string_element(self, element, *args, **kwargs):
        """Resolve a string into Field(LayoutElement) which represents a form 
        field."""
        return Field(element, parent_layout_element=self, *args, **kwargs)
    
    def resolve_callable_element(self, element, *args, **kwargs):
        """Resolve a callable element into CallableElement(LayoutElement)."""
        return CallableElement(element, *args, **kwargs)
    
    def resolve_other_element(self, element, *args, **kwargs):
        """Resolve an element which is not any of LayoutElement, basestring or 
        callable. Throws CannotResolveElementException() by default."""
        raise CannotResolveElementException(element, *args, **kwargs)

    def get_layout_element_by_field_name(self, field_name):
        """Method supports only FormLine layout elements """
        # TODO: Move this method from LayoutElement since is applicable to only 
        # arranged set of elements
        for element in self.iterate_child_elements():
            if (
                hasattr(element, "field_name") and 
                element.field_name == field_name
            ):
                return element
        return None

    def get_flags(self):
        # TODO: Remove from this class
        return DefaultFlags()

    def get_hint_text(self):
        # TODO: Get hint text from self
        return u""


class CallableElement(LayoutElement):
    """Layout element just calling it's element as callable providing given 
    arguments."""

    def __init__(
        self, element, element_args=tuple(), element_kwargs={}, **kwargs
    ):
        """
        Arguments:
        element -- a callable which is to be called when rendering. Signature of
        calling given element is following: element(form, *args, **kwargs),
            where `form` is a bound form.
        element_args -- a list of arguments which are to be passed to given
            element.
        element_kwargs -- a list of keyword arguments which are to be passed to
            given element.
        """
        assert callable(element)
        super(CallableElement, self).__init__(**kwargs)
        self.element = element
        self.element_args = element_args
        self.element_kwargs = element_kwargs

    def render(self):
        """Call the callable element and return results of the call."""
        return self.element(
            self.form, *self.element_args, **self.element_kwargs
        )


class Script(CallableElement):
    """Alias for CallableElement."""
    pass


class Container(LayoutElement):
    """Represents a collection of elements surrounded with additional markup."""
    
    def render(self, **kwargs):
        """Render child elements within a  container."""
        return literal(u"").join((
            self.render_container_start(**self.element_attrs),
            self.render_elements(),
            self.render_container_end(),
        ))
        
    def render_container_start(self):
        """Render start of a container."""
        return literal(u'<div %s>') % (
            format_attrs(**self.attrs),
        )

    def render_container_end(self):
        """Render end of container."""
        return literal(u'</div>')


class Column(Container):
    """Renders child elements in a column."""

    def render_element(self, element, **attrs):
        """Render given child element so it is displayed in column with others.
        """
        return literal(u"<div %s>%s</div>") % (
            format_attrs(**attrs), element.render()
        )


class Layout(Column):
    """Alias for Column."""
    pass


class Row(Container):
    """Renders child elements in a row."""

    def render_element(self, element, **attrs):
        """Render given element so it's displayed in row with others."""
        attrs = attrs.copy()
        attrs['style'] = attrs.get('style', '') + 'float: left;';
        return literal(u"<div %s>%s</div>") % (
            format_attrs(**attrs), element.render()
        )

    def render_container_end(self):
        """Render container end in a different way Container does."""
        return literal(u"").join((
            literal(u"<div style='clear: both;'></div>"),
            super(Row, self).render_container_end(),
        ))


class Field(LayoutElement):
    
    def __init__(
        self, field_name, parent_layout_element=None, hint_text=None, 
        warning_text=None, **attrs
    ):
        assert isinstance(field_name, basestring), type(field_name)
        super(Field, self).__init__(**attrs)
        self.name = field_name
        #self.short_name = field_name
        self.hint_text = hint_text
        self.parent_layout_element = proxy(parent_layout_element) \
            if parent_layout_element is not None else None
        self.warning_text = warning_text

    def get_title(self):
        return self.label.text

    def get_help(self):
        return self.form.helps[self.field_name]

    @property
    def id(self):
        return getattr(self.form, self.name).id

    @property
    def form_field(self):
        return getattr(self.form, self.name)

    def get_flags(self):
        return self.form_field.flags

    def get_parent_layout_element_label_text(self):
        if not self.parent_layout_element:
            return u""
        return self.parent_layout_element.get_label_text()

    @property
    def label(self):
        has_parent_label = self.get_parent_layout_element_label_text() is not None
        return Label(
            getattr(self.form, self.name).label.text if not has_parent_label \
                else self.parent_layout_element.label_text, 
            self.id, 
            required=self.get_flags().required,
        )

    @property
    def errors(self):
        return getattr(self.form, self.name).errors

    def get_hint_text(self):
        return (
            self.hint_text or 
            getattr(self.form, 'hints', {}).get(self.name, u'')
        )

    @property
    def inline_hint_text(self):
        return getattr(self.form, 'inline_hints', {}).get(self.name, u'')

    def get_help(self):
        return self.help or getattr(self.form, 'helps', {}).get(self.name, u'')


    def render(self, **kwargs):
        if not self.is_present():
            return ""
        attrs = getattr(self.form, 'widgets_attrs', {}).get(self.name, {}).copy()
        attrs.update(self.attrs)
        attrs.update(kwargs)
        return self.form_field(**attrs)

    def render_errors(self):
        return ErrorList(self.id, self.errors).render()

    # def is_boolean_field(self):
    #     from wtforms import fields
    #     return isinstance(self.form_field, fields.BooleanField)
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.