Source code for layout

import warnings

from django.conf import settings
from django.template import Context, Template
from django.template.loader import render_to_string
from django.utils.html import conditional_escape

from crispy_forms.compatibility import string_types, text_type
from crispy_forms.utils import render_field, flatatt

TEMPLATE_PACK = getattr(settings, 'CRISPY_TEMPLATE_PACK', 'bootstrap')


class LayoutObject(object):
    def __getitem__(self, slice):
        return self.fields[slice]

    def __setitem__(self, slice, value):
        self.fields[slice] = value

    def __delitem__(self, slice):
        del self.fields[slice]

    def __len__(self):
        return len(self.fields)

    def __getattr__(self, name):
        """
        This allows us to access self.fields list methods like append or insert, without
        having to declaee them one by one
        """
        # Check necessary for unpickling, see #107
        if 'fields' in self.__dict__ and hasattr(self.fields, name):
            return getattr(self.fields, name)
        else:
            return object.__getattribute__(self, name)

    def get_field_names(self, index=None):
        """
        Returns a list of lists, those lists are named pointers. First parameter
        is the location of the field, second one the name of the field. Example::

            [
               [[0,1,2], 'field_name1'],
               [[0,3], 'field_name2']
            ]
        """
        return self.get_layout_objects(string_types, greedy=True)

    def get_layout_objects(self, *LayoutClasses, **kwargs):
        """
        Returns a list of lists pointing to layout objects of any type matching
        `LayoutClasses`::

            [
               [[0,1,2], 'div'],
               [[0,3], 'field_name']
            ]

        :param max_level: An integer that indicates max level depth to reach when
        traversing a layout.
        :param greedy: Boolean that indicates whether to be greedy. If set, max_level
        is skipped.
        """
        index = kwargs.pop('index', None)
        max_level = kwargs.pop('max_level', 0)
        greedy = kwargs.pop('greedy', False)

        pointers = []

        if index is not None and not isinstance(index, list):
            index = [index]
        elif index is None:
            index = []

        for i, layout_object in enumerate(self.fields):
            if isinstance(layout_object, LayoutClasses):
                if len(LayoutClasses) == 1 and LayoutClasses[0] == string_types:
                    pointers.append([index + [i], layout_object])
                else:
                    pointers.append([index + [i], layout_object.__class__.__name__.lower()])

            # If it's a layout object and we haven't reached the max depth limit or greedy
            # we recursive call
            if hasattr(layout_object, 'get_field_names') and (len(index) < max_level or greedy):
                new_kwargs = {'index': index + [i], 'max_level': max_level, 'greedy': greedy}
                pointers = pointers + layout_object.get_layout_objects(*LayoutClasses, **new_kwargs)

        return pointers


[docs]class Layout(LayoutObject): """ Form Layout. It is conformed by Layout objects: `Fieldset`, `Row`, `Column`, `MultiField`, `HTML`, `ButtonHolder`, `Button`, `Hidden`, `Reset`, `Submit` and fields. Form fields have to be strings. Layout objects `Fieldset`, `Row`, `Column`, `MultiField` and `ButtonHolder` can hold other Layout objects within. Though `ButtonHolder` should only hold `HTML` and BaseInput inherited classes: `Button`, `Hidden`, `Reset` and `Submit`. You need to add your `Layout` to the `FormHelper` using its method `add_layout`. Example:: layout = Layout( Fieldset('Company data', 'is_company' ), Fieldset(_('Contact details'), 'email', Row('password1', 'password2'), 'first_name', 'last_name', HTML('<img src="/media/somepicture.jpg"/>'), 'company' ), ButtonHolder( Submit('Save', 'Save', css_class='button white'), ), ) helper.add_layout(layout) """ def __init__(self, *fields): self.fields = list(fields) def render(self, form, form_style, context, template_pack=TEMPLATE_PACK): html = "" for field in self.fields: html += render_field( field, form, form_style, context, template_pack=template_pack ) return html
[docs]class ButtonHolder(LayoutObject): """ Layout object. It wraps fields in a <div class="buttonHolder"> This is where you should put Layout objects that render to form buttons like Submit. It should only hold `HTML` and `BaseInput` inherited objects. Example:: ButtonHolder( HTML(<span style="display: hidden;">Information Saved</span>), Submit('Save', 'Save') ) """ template = "uni_form/layout/buttonholder.html" def __init__(self, *fields, **kwargs): self.fields = list(fields) self.css_class = kwargs.get('css_class', None) self.css_id = kwargs.get('css_id', None) self.template = kwargs.get('template', self.template) def render(self, form, form_style, context, template_pack=TEMPLATE_PACK): html = u'' for field in self.fields: html += render_field(field, form, form_style, context, template_pack=template_pack) return render_to_string(self.template, Context({'buttonholder': self, 'fields_output': html}))
[docs]class BaseInput(object): """ A base class to reduce the amount of code in the Input classes. """ template = "%s/layout/baseinput.html" % TEMPLATE_PACK def __init__(self, name, value, **kwargs): self.name = name self.value = value self.id = kwargs.pop('css_id', '') self.attrs = {} if 'css_class' in kwargs: self.field_classes += ' %s' % kwargs.pop('css_class') self.template = kwargs.pop('template', self.template) self.flat_attrs = flatatt(kwargs)
[docs] def render(self, form, form_style, context, template_pack=TEMPLATE_PACK): """ Renders an `<input />` if container is used as a Layout object. Input button value can be a variable in context. """ self.value = Template(text_type(self.value)).render(context) return render_to_string(self.template, Context({'input': self}))
[docs]class Submit(BaseInput): """ Used to create a Submit button descriptor for the {% crispy %} template tag:: submit = Submit('Search the Site', 'search this site') .. note:: The first argument is also slugified and turned into the id for the submit button. """ input_type = 'submit' field_classes = 'submit submitButton' if TEMPLATE_PACK == 'uni_form' else 'btn btn-primary'
[docs]class Button(BaseInput): """ Used to create a Submit input descriptor for the {% crispy %} template tag:: button = Button('Button 1', 'Press Me!') .. note:: The first argument is also slugified and turned into the id for the button. """ input_type = 'button' field_classes = 'button' if TEMPLATE_PACK == 'uni_form' else 'btn'
[docs]class Hidden(BaseInput): """ Used to create a Hidden input descriptor for the {% crispy %} template tag. """ input_type = 'hidden' field_classes = 'hidden'
[docs]class Reset(BaseInput): """ Used to create a Reset button input descriptor for the {% crispy %} template tag:: reset = Reset('Reset This Form', 'Revert Me!') .. note:: The first argument is also slugified and turned into the id for the reset. """ input_type = 'reset' field_classes = 'reset resetButton' if TEMPLATE_PACK == 'uni_form' else 'btn btn-inverse'
[docs]class Fieldset(LayoutObject): """ Layout object. It wraps fields in a <fieldset> Example:: Fieldset("Text for the legend", 'form_field_1', 'form_field_2' ) The first parameter is the text for the fieldset legend. This text is context aware, so you can do things like:: Fieldset("Data for {{ user.username }}", 'form_field_1', 'form_field_2' ) """ template = "uni_form/layout/fieldset.html" def __init__(self, legend, *fields, **kwargs): self.fields = list(fields) self.legend = legend self.css_class = kwargs.pop('css_class', '') self.css_id = kwargs.pop('css_id', None) # Overrides class variable with an instance level variable self.template = kwargs.pop('template', self.template) self.flat_attrs = flatatt(kwargs) def render(self, form, form_style, context, template_pack=TEMPLATE_PACK): fields = '' for field in self.fields: fields += render_field(field, form, form_style, context, template_pack=template_pack) legend = '' if self.legend: legend = u'%s' % Template(text_type(self.legend)).render(context) return render_to_string(self.template, Context({'fieldset': self, 'legend': legend, 'fields': fields, 'form_style': form_style}))
[docs]class MultiField(LayoutObject): """ MultiField container. Renders to a MultiField <div> """ template = "uni_form/layout/multifield.html" field_template = "uni_form/multifield.html" def __init__(self, label, *fields, **kwargs): self.fields = list(fields) self.label_html = label self.label_class = kwargs.pop('label_class', u'blockLabel') self.css_class = kwargs.pop('css_class', u'ctrlHolder') self.css_id = kwargs.pop('css_id', None) self.template = kwargs.pop('template', self.template) self.field_template = kwargs.pop('field_template', self.field_template) self.flat_attrs = flatatt(kwargs) def render(self, form, form_style, context, template_pack=TEMPLATE_PACK): # If a field within MultiField contains errors if context['form_show_errors']: for field in map(lambda pointer: pointer[1], self.get_field_names()): if field in form.errors: self.css_class += " error" fields_output = u'' for field in self.fields: fields_output += render_field( field, form, form_style, context, self.field_template, self.label_class, layout_object=self, template_pack=template_pack ) context.update({'multifield': self, 'fields_output': fields_output}) return render_to_string(self.template, context)
[docs]class Div(LayoutObject): """ Layout object. It wraps fields in a <div> You can set `css_id` for a DOM id and `css_class` for a DOM class. Example:: Div('form_field_1', 'form_field_2', css_id='div-example', css_class='divs') """ template = "uni_form/layout/div.html" def __init__(self, *fields, **kwargs): self.fields = list(fields) if hasattr(self, 'css_class') and 'css_class' in kwargs: self.css_class += ' %s' % kwargs.pop('css_class') if not hasattr(self, 'css_class'): self.css_class = kwargs.pop('css_class', None) self.css_id = kwargs.pop('css_id', '') self.template = kwargs.pop('template', self.template) self.flat_attrs = flatatt(kwargs) def render(self, form, form_style, context, template_pack=TEMPLATE_PACK): fields = '' for field in self.fields: fields += render_field(field, form, form_style, context, template_pack=template_pack) return render_to_string(self.template, Context({'div': self, 'fields': fields}))
[docs]class Row(Div): """ Layout object. It wraps fields in a div whose default class is "formRow". Example:: Row('form_field_1', 'form_field_2', 'form_field_3') """ css_class = 'formRow' if TEMPLATE_PACK == 'uni_form' else 'row'
[docs]class Column(Div): """ Layout object. It wraps fields in a div whose default class is "formColumn". Example:: Column('form_field_1', 'form_field_2') """ css_class = 'formColumn'
[docs]class HTML(object): """ Layout object. It can contain pure HTML and it has access to the whole context of the page where the form is being rendered. Examples:: HTML("{% if saved %}Data saved{% endif %}") HTML('<input type="hidden" name="{{ step_field }}" value="{{ step0 }}" />') """ def __init__(self, html): self.html = html def render(self, form, form_style, context, template_pack=TEMPLATE_PACK): return Template(text_type(self.html)).render(context)
[docs]class Field(LayoutObject): """ Layout object, It contains one field name, and you can add attributes to it easily. For setting class attributes, you need to use `css_class`, as `class` is a Python keyword. Example:: Field('field_name', style="color: #333;", css_class="whatever", id="field_name") """ template = "%s/field.html" % TEMPLATE_PACK def __init__(self, *args, **kwargs): self.fields = list(args) if not hasattr(self, 'attrs'): self.attrs = {} if 'css_class' in kwargs: if 'class' in self.attrs: self.attrs['class'] += " %s" % kwargs.pop('css_class') else: self.attrs['class'] = kwargs.pop('css_class') self.wrapper_class = kwargs.pop('wrapper_class', None) self.template = kwargs.pop('template', self.template) # We use kwargs as HTML attributes, turning data_id='test' into data-id='test' self.attrs.update(dict([(k.replace('_', '-'), conditional_escape(v)) for k, v in kwargs.items()])) def render(self, form, form_style, context, template_pack=TEMPLATE_PACK): if hasattr(self, 'wrapper_class'): context['wrapper_class'] = self.wrapper_class html = '' for field in self.fields: html += render_field(field, form, form_style, context, template=self.template, attrs=self.attrs, template_pack=template_pack) return html
[docs]class MultiWidgetField(Field): """ Layout object. For fields with :class:`~django.forms.MultiWidget` as `widget`, you can pass additional attributes to each widget. Example:: MultiWidgetField( 'multiwidget_field_name', attrs=( {'style': 'width: 30px;'}, {'class': 'second_widget_class'} ), ) .. note:: To override widget's css class use ``class`` not ``css_class``. """ def __init__(self, *args, **kwargs): self.fields = list(args) self.attrs = kwargs.pop('attrs', {}) self.template = kwargs.pop('template', self.template)

crispy-forms

django-crispy-forms is a Django application that lets you easily build, customize and reuse forms using your favorite CSS framework, without writing template code and without having to take care of annoying details. You are currently looking at the documentation of the development release.

Support

If you love django-crispy-forms, consider making a small donation on Flattr:

Related Topics