Coder Social home page Coder Social logo

formulation's Introduction

formulation

Build Status

Django Form rendering helper tags

NOTE: Formulation stops with Django 1.7

For continuing support please migrate to django-sniplates.

Overview

It's fairly well accepted, now, that having the form rendering decisions in your code is less than ideal.

However, most template-based solutions wind up being slow, because they rely on many templates.

Formulation works by defining all the widgets for your form in a single "widget template", and loading it once for the form.

Installation

You can install formulation using:

$ pip install formulation

You will need to add 'formulation' to your settings.INSTALLED_APPS.

Usage

First, write a template where each block is a way to render a field.

We'll start with a simple one, with one hardy, general purpose field block. Let's call it mytemplate.form:

{% load formulation %}

{% block basic %}
{% if label %}<label id="{{ id_for_label }}" for="{{ id }}">{{ label }}</label>{% endif %}
<input
    type="{{ field_type|default:"text" }}"
    name="{{ html_name }}"
    id="{{ id }}"
    value="{{ value|default:"" }}"
    class="{{ css_classes }}"
    {{ required|yesno:"required," }}
    {{ widget.attrs|flat_attrs }}
>
{{ help_text }}
{% endblock %}

Then, in your own template:

{% load formulation %}

<form method="POST" ... >
{% form "mytemplate.form" %}
{% field form.foo "basic" %}
{% field form.baz "basic" type='email' %}
{% endform %}

You can think of the field tag as being like {% include %} but for blocks. However, it also adds many attributes from the form field into the context.

The following values are take from the BoundField:

  • css_classes
  • errors
  • field
  • form
  • help_text
  • html_name
  • id_for_label
  • label
  • name
  • value

And these from the Field itself:

  • choices
  • widget
  • required

Any extra keyword arguments you pass to the field tag will overwrite Field values of the same name.

{% form %}

The {% form %} tag loads the template, and puts its blocks in a dict in the context, called formulation. You typically won't access this directly, as it's raw BlockNode instances.

{% form "widgets/bootstrap.form" %}
...
{% endform %}

Template Inheritance

Widget templates are just normal templates, so {% extends %} still works as expected. This lets you define a base, common form template, and localised extensions where you need.

{% field %}

Each time you use the {% field %} tag, it renders the block specified.

It's easy to extend this to more complex field types:

{% block TypedChoiceField %}
{% if not nolabel %}
<label for="{{ id }}" {% if required %}class="required"{% endif %}> {{ label }} </label>
{% endif %}
<select name="{{ html_name }}" id="{{ id }}" {% if errors %}class="error"{% endif %}>
{% for option_value, option_label in choices %}
<option value="{{ option_value }}" {% if value == option_value %}selected="selected"{% endif %}>{{ option_label }}</option>
{% endfor %}
</select>
{{ help_text }}
{% endblock %}

Auto-widget

If you omit the widget in the {% field %} tag, formulation will try to auto-detect the block to use. It does so by looking for the first block to match one of the following patterns:

'{field}_{widget}_{name}'
'{field}_{name}'
'{widget}_{name}'
'{field}_{widget}'
'{name}'
'{widget}'
'{field}'

Where 'field' is the form field class (e.g. CharField, ChoiceField, etc), 'widget' is the widget class name (e.g. NumberInput, DateTimeInput, etc) and 'name' is the name of the field.

If no block is found, a TemplateSyntaxError is raised.

{% use %}

You may have some chunks of templating that aren't fields, but are useful within the form. For these, write them as blocks in your xyz.form template, then use the {% use %} to include them:

# demo.html
{% form "demo.form" %}
...
{% use "actions" submit="Update" %}
{% endform %}

# demo.form
{% block actions %}
<div class="actions">
    <input type="submit" value="{{ submit|default:"Save" }}">
    <a href="/">Cancel</a>
</div>
{% endblock %}

It works just like include, but will use a block from the current widget template.

Extras

There is also the {% reuse %} template tag, which allows you to reuse any template block within the current template [as opposed to the form widget template] like a macro. Again, it follows the same syntax as the {% include %} tag:

{% load reuse %}
{% reuse "otherblock" foo=1 %}

You can also pass a list of block names to search; first found will be used.

Other uses

Formulation is not limited to forms and fields. There's no reason you can't also use it to abstract commonly used fragments of template code.

{% form "widgets.form" %}

{% use "framed-box" title="Some box!" %}

...

{% endform %}

Thanks!

  • kezabelle for the name
  • bradleyayers for ideas on supporting multiple fields. (now removed)
  • SmileyChris for the idea to "explode" fields into the context
  • jwa for major testing and bug hunting
  • schinkel for packaging help

formulation's People

Contributors

bradleyayers avatar chrisdoble avatar funkybob avatar julianwachholz avatar schinckel avatar sergei-maertens avatar sesh avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

formulation's Issues

render_form not documented & not working

Hi,

render_form is not documented and template rendering crashes with: 'NoneType' object has no attribute 'render'

I guess the "raw" block can not be found in default.form.

It would be great to have a way to just {% render_form form "formulation/default.form" %} or better (use "formulation/default.form" as default template: {% render_form form %}.
Whenever you don't want to customize field by field and just render default formulation format.

I assume render_form was the way, but not sure how to use it and if it actually works.

Error mappings

Add some way to allow mapping of error messages to custom blocks to allow finer grained control of error rendering.

Improve debug information

Right now, when using the field tag with a form field that doesn't exist on the form, an AttributeError is raised (get). It would be better if that error is caught and re-raised to specify that the field is probably missing on the form.

Select (and similar) widget in default.form behaves erratically - value normalization

I 'discovered' this a while ago and have since then worked around it, but the solution is dirty. The problematic line in the default.form template is: https://github.com/funkybob/formulation/blob/master/formulation/templates/formulation/default.form#L138

Basically what happens, is that value and val are not always the same type, sometimes incorrectly comparing as not-equal. The best testcase is using a ModelForm with an existing instance, submitting the form with an error so validation errors are triggered, and then the initial value of the Select field will no longer be 'selected'.

The cause is that the first time the form is renderd with an integer (the pk of the object) as value, and this is the value of the field. The second time, the value is form date, so an instance of basestring, which compared to the value set yields no matching selected option.

The temporary work-around I have is to add a template filter 'normalize' which turns everything into strings to render.

If you look at the Django Select widget (django.forms.widgets), method 'render_options', you see that the values are also normalized to strings first.

I'd love submitting a patch if I can find time for this. I believe this is only minor, because the workaround is fairly easy - be it not the cleanest workaround.

Extended blocks are dropped from context

Non-form related blocks defined in base template and overriden in child-child template (foo extends bar which extends base template) disappear.

I'll see if I can get a reproducable test case, bug was just reported by a coworker.

Context needs reset after each field

The context isn't reset to a 'blank' state after every field. This results in unexpected output.
Example usage:

Form template:

{% block input %}
<input type="{{ type|default:'text' }}" name="{{ html_name }}" [...]>
{% endblock %}

Usage:

{% field form.email 'input' type='email' %}
{% field form.first_name 'input' %}

Expected output:

<input type="email" name="email" [...]>
<input type="text" name="first_name" [...]>

Actual output:

<input type="email" name="email" [...]>
<input type="email" name="first_name" [...]>

AttributeError: 'Template' object has no attribute 'nodelist'

on Django master as of f1ff9407c94c4574d100efc3d224c1f79e2fb53d using formulation==2.0.12:


Traceback:
File "/PATH/src/django/django/core/handlers/base.py" in get_response
  163.                 response = response.render()
File "/PATH/src/django/django/template/response.py" in render
  156.             self.content = self.rendered_content
File "/PATH/src/django/django/template/response.py" in rendered_content
  133.         content = template.render(context, self._request)
File "/PATH/src/django/django/template/backends/django.py" in render
  83.         return self.template.render(context)
File "/PATH/src/django/django/template/base.py" in render
  211.             return self._render(context)
File "/PATH/src/django/django/template/base.py" in _render
  199.         return self.nodelist.render(context)
File "/PATH/src/django/django/template/base.py" in render
  905.                 bit = self.render_node(node, context)
File "/PATH/src/django/django/template/debug.py" in render_node
  80.             return node.render(context)
File "/PATH/src/django/django/template/loader_tags.py" in render
  151.                 return template.render(context.new(values))
File "/PATH/src/django/django/template/base.py" in render
  211.             return self._render(context)
File "/PATH/src/django/django/template/base.py" in _render
  199.         return self.nodelist.render(context)
File "/PATH/src/django/django/template/base.py" in render
  905.                 bit = self.render_node(node, context)
File "/PATH/src/django/django/template/debug.py" in render_node
  80.             return node.render(context)
File "/PATH/lib/python3.4/site-packages/formulation/templatetags/formulation.py" in render
  109.             'formulation': resolve_blocks(tmpl_name, safe_context),
File "/PATH/lib/python3.4/site-packages/formulation/templatetags/formulation.py" in resolve_blocks
  34.         for block in template.nodelist.get_nodes_by_type(BlockNode)

Exception Type: AttributeError at /
Exception Value: 'Template' object has no attribute 'nodelist'

Widget inheritance

I just found that the expected inheritance behaviour doesn't work.

Create a form template that inherits from formulation/default.form. Try to inherit/override using {{ block.super }} - it won't work. The extra_context is not passed on to the block.super variable node.

I don't know it this would be a feature request, but it would greatly contribute to the DRY principe - I only wanted to add a wrapper div around the defaults.

auto_widget takes global non-formulation block names

The case

#mywidgets.form

{% block TextInput %}<input type="text" name="{{ field_name }}" value="{{ value }}">{% endblock %}
# mybase.html

{% block title %}MyPage{% endblock %}
{% block content %}{% endblock %}
# mytemplate.html

{% extends "mybase.html" %}

{% block title %}MyTitle - {{ block.super }}{% endblock %}

{% block content %}
    {% form "mywidgets.form" %}
        {% for form_field in form %}
            <span class="justrandomfieldwrapper">{% field form_field %}</span>
        {% endfor %}
    {% endform %}
{% endblock %}
# myform.py

from django import forms

class MyForm(forms.Form):
   title = forms.CharField()
   content = forms.CharField()
   noblocknamecolision = forms.CharField()

The limitations

  • we don't want to rename template block names
  • we can't predict field names to use static call; assume that form/fields are dynamic (e.g django.contrib.formtools.wizard)
  • we don't want to edit mywidgets.form

The expectations

MyTitle - MySite
<span class="justrandomfieldwrapper"><input type="text" name="title" value="">
<span class="justrandomfieldwrapper"><input type="text" name="content" value="">
<span class="justrandomfieldwrapper"><input type="text" name="noblocknamecolision" value=""></span>

The problem

When widget option not provided formulation, while resolving widget block name, will use name field first.

MyTitle - MySite
<span class="justrandomfieldwrapper">MyTitle - MySite</span>
<span class="justrandomfieldwrapper"></span>
<span class="justrandomfieldwrapper"><input type="text" name="noblocknamecolision" value=""></span>

The solution

  1. Reorder auto_widget to:
'{field}_{widget}_{name}'
'{field}_{name}'
'{widget}_{name}'
'{field}_{widget}'
'{widget}'
'{field}'
'{name}'
  1. Limit formulation to search for blocks only in given mywidgets.form context

Adding field_type breaks default.form

In version 1.0.12 field_type was added to the fields variables available in the template. This breaks the *input widgets, as before this value was empty and caught by the 'default' filter (in case of a TextInput).

The html now shows

<input type="CharField">

I suggest renaming 'field_type' in default.form to 'input_type'.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.