Skip to main content

Elegant Form Templating for Django

Project description

https://pypip.in/version/django-silhouette/badge.svg https://pypip.in/format/django-silhouette/badge.svg https://travis-ci.org/jthi3rry/django-silhouette.svg?branch=master https://coveralls.io/repos/jthi3rry/django-silhouette/badge.png?branch=master https://pypip.in/py_versions/django-silhouette/badge.svg https://pypip.in/license/django-silhouette/badge.svg

Silhouette is a form templating app for Django just like Django Crispy Forms or Django Floppy Forms. Unlike these, form templating with Silhouette is exclusively done at the template level. Your form classes don’t need to change.

You no longer have to plague your forms with widgets that are only here to add some css or form helpers simulating html. Your django forms are used for server-side input validation, your templates are the only ones responsible for making them look awesome.

If you happen to have a team of frontend developers who don’t want to dig deep into the darkness of your python code to change a class on one of your field’s help text, they’ll probably thank you. And even if you don’t, you’ll probably be happy to keep your form templating code where it belongs: in your templates.

Silhouette also lets you create global themes and form specific themes to style everything from the form tag down to field errors and widget types. Since everything happens in templates, you can use template inheritance and blocks to achieve anything you like. Read on.

Installation

pip install django-silhouette

In your settings.py

INSTALLED_APPS = [
    ...
    "silhouette",
    ...
]

Getting Started

Instead of explaining the internals of Silhouette head-on, let’s get a feel for it by creating a form styled with Twitter’s Bootstrap. We’ll assume that your layout already includes the bootstrap stylesheet.

Let’s pretend we have an imaginary form like this one:

class BasicForm(forms.Form):

    email_address = forms.EmailField()
    password = forms.CharField(widget=forms.PasswordInput)
    file = forms.FileField()
    check_me_out = forms.BooleanField()

With Silhouette, you could display the form like this (in the imaginary template templates/app/index.html):

{% load silhouette_tags %}

<form>
    {% csrf_token %}
    <div class="form-group{% if form.name.errors %} has-error{% endif %}">
        {% field form.name widget_placeholder="Enter email" widget_id="exampleInputEmail1" widget_class="form-control" %}
    </div>
    <div class="form-group{% if form.password.errors %} has-error{% endif %}">
        {% field form.password widget_placeholder="Password" widget_id="exampleInputPassword1" widget_class="form-control" %}
    </div>
    <div class="form-group{% if form.file.errors %} has-error{% endif %}">
        {% field form.file help_text_contents="Example block-level help text here." help_text_class="help-block" %}
    </div>
    <div class="checkbox{% if form.check_me_out.errors %} has-error{% endif %}">
        <label>{% form_widget form.check_me_out %} {{ form.check_me_out.label }}</label>
    </div>
    <button type="submit" class="btn btn-default">Submit</button>
</form>

This will give you the form as per Bootstrap’s example. We didn’t do anything about errors, but Silhouette will also render these for free (you could add errors_class="alert alert-danger" to each field to display errors using bootstrap’s alerts).

This is still not ideal though. All our forms should be displayed consistently and there will be a lot of repetition if we need to do this for every single form.

Let’s create a global theme that will handle all this for us.

Global Themes

In your settings.py, configure the silhouette theme name. By default this is:

SILHOUETTE_THEME = "default"

Let’s change the theme name to bootstrap:

SILHOUETTE_THEME = "bootstrap"

Create a file templates/silhouette/bootstrap/fields/field.html. You could create a template from scratch and render the label, widget, errors and help text individually (refer to the base field template for an example), but let’s see how to take advantage of Django’s template inheritance and Silhouette’s base theme

{% extends "silhouette/base/fields/field.html" %}

{% load silhouette_tags silhouette_filters %}

{% block field %}
    <!-- Output field wrapper based on widget type -->
    {% if field|is_checkbox_input %}
        <div class="checkbox{% if field.errors %} has-error{% endif %}">
            {{ block.super }}
        </div>
    {% else %}
        <div class="form-group{% if field.errors %} has-error{% endif %}">
            {{ block.super }}
        </div>
    {% endif %}
{% endblock %}

{% block widget %}
    <!-- Output field widget based on its type -->
    {% if field|is_file_input or field|is_checkbox_input %}
        {% field_widget field %}
    {% else %}
        {% field_widget field class="form-control" %}
    {% endif %}
{% endblock %}

{% block help_text %}
    <!-- Output help text consistently -->
    {% field_help_text field class="help-block" %}
{% endblock %}

{% block errors %}
    <!-- Output errors consistently -->
    {% field_errors field class="alert alert-danger" %}
{% endblock %}

Notice that the template’s context has a field variable that refers to the form’s bound field being rendered (your default context is also available).

Now we just need an extra template for checkboxes as we want to wrap the label around the field.

In templates/silhouette/bootstrap/fields/checkbox_input_field.html, extend your own field template with:

{% extends "silhouette/bootstrap/fields/field.html" %}

{%load silhouette_tags %}

{% block label %}
    <!-- Do not render the label here -->
{% endblock %}

{% block widget %}
    <label>{% field_widget field %} {{ field.label }}</label>
{% endblock %}

Notice that the template name for a checkbox field is the widget’s class name in underscore notation checkbox_input followed by the _field suffix.

Your templates/app/index.html template now can become:

{% load silhouette_tags %}

<form>
    {% csrf_token %}
    {% field form.name widget_placeholder="Enter email" widget_id="exampleInputEmail1" %}
    {% field form.password widget_placeholder="Password" widget_id="exampleInputPassword1" %}
    {% field form.file help_text_contents="Example block-level help text here." %}
    {% field form.check_me_out %}
    <button type="submit" class="btn btn-default">Submit</button>
</form>

And all your future forms will use the bootstrap theme.

If you need a specific class added to any of the fields, Silhouette will merge these for you with the ones defined in a theme:

{% field form.password ... widget_class="extra-class" %}

Will output:

<div class="form-group">
    ...
    <input type="password" ... class="form-control extra-class" />
    ...
</div>

Now you can extend your theme by adding new widgets like radio buttons, select boxes and so on.

Form Themes

Field templates and global themes remove a lot of the complexity usually involved with displaying forms with Django. But Silhouette doesn’t stop here and also allows you to create form specific theme.

For example, let’s change our templates/app/index.html template, and use the second bootstrap example using the form-inline class.

We’ll also introduce the silhouette tag that allows you to display forms in a single line of code:

{% load silouhette_tags %}

{% silhouette form method="post" action="/action" class="form-inline" errors_class="alert alert-warning" %}

This will render:

<form enctype="multipart/form-data" class="form-inline" method="post" action="/action" >
    <input type="hidden" name="csrf_token" value="...." />

    <!-- Errors will show up here if any -->
    <ul class="alert alert-warning">
        <li>....</li>
    </ul>

    <!-- Fields will show up here -->
    <div class="form-group">
        ...
    </div>
    ...

    <!-- Controls will show up here -->
    <button type="submit">Submit</button>

    <!-- Media will show up here if any -->
    <script ...></script>

</form>

However, by doing so, we just lost the specific attributes that were passed to each field like placehoders, ids, etc. as well as our styled submit button.

Our fields and button are specific to our form, so let’s create a “form theme” for these.

In templates/silhouette/basic_form/fields.html:

{% extends "silhouette/base/forms/fields.html" %}

{% load silhouette_tags %}

{% block visible_fields %}
    {% field form.name widget_placeholder="Enter email" widget_id="exampleInputEmail1" %}
    {% field form.password widget_placeholder="Password" widget_id="exampleInputPassword1" %}
    {% field form.file help_text_contents="Example block-level help text here." %}
    {% field form.check_me_out %}
{% endblock %}

Note that the template is not created under the bootstrap theme, but under the basic_form “theme”. This is the form’s class name BasicForm in underscore notation.

Now, in templates/silhouette/basic_form/controls.html:

<button type="submit" class="btn btn-default">Submit</button>

Note that you could override this in the global theme by modifying templates/silhouette/bootstrap/forms/controls.html instead.

Just like with the global theme, you can override any field, label, widget, field errors, help text in your form by creating a template in templates/silhouette/basic_form/fields/{{overridden_part}}.html.

Anything usually possible with Django templates is possible with Silhouette. Silhouette provides a base theme with what we assumed could be useful and generic, but you can ignore it or replace it altogether.

Template Loader

When rendering a template for a field, form or formset, Silhouette tries and find the first template that exists using a list of path patterns.

The general idea is that Silhouette will look for a template from the most specific to the most generic place.

For example, when doing {% field form.field %}, Silhouette will check:

  • if a template exists for the field in the form’s theme

  • if one exists for the field’s widget in the form’s theme

  • if one exists for the field’s widget in the global theme

  • if one exists for all fields in the form’s theme

  • if one exists for all fields in the global theme

  • otherwise, it will fallback to using the base field template shipped with Silhouette

These rules are defined like this in the SILHOUETTE_PATTERNS setting:

  • {path}/{form}/fields/{field}.html

  • {path}/{form}/fields/{widget}_field.html

  • {path}/{theme}/fields/{widget}_field.html

  • {path}/{form}/fields/field.html

  • {path}/{theme}/fields/field.html

  • silhouette/base/fields/field.html

Where {path} is the value of the SILHOUETTE_PATH setting, {theme} is the value of the SILHOUETTE_THEME setting, {form} is the form class name in underscore notation, {field} is the field name in your form, and {widget} is the widget class name in underscore notation.

Each tag has its own lookup list of patterns. See the default settings for a full list. For advanced usage or if you simply don’t like the convention and want to use another one, new patterns can be added or the lookup order modified by changing the SILHOUETTE_PATTERNS setting.

Bypassing the Template Lookup

Tags also accept a template argument to render a specific template. For example:

{% field form.field1 template="path/to/field1.html" %}

When using the template argument, field patterns will be ignored.

Overriding Path and Theme

Path and theme can also be overridden for a given tag. For example:

{% field form.field1 path="form-themes" theme="my-theme" %}

When using these arguments, the value of {path} and {theme} are overridden for the given tag, and all tags used within its context. So in the above example, the widget, label, help_text and errors rendered by field would use the path form-themes and the theme my-theme.

Running Tests

Get a copy of the repository:

git clone git@github.com:OohlaLabs/django-silhouette.git .

Install tox:

pip install tox

Run the tests:

tox

Contributions

All contributions and comments are welcome.

Change Log

v0.0.2

  • Distribution description & homepage

v0.0.1

  • Initial release

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django-silhouette-0.0.2.tar.gz (13.2 kB view details)

Uploaded Source

Built Distribution

django_silhouette-0.0.2-py2.py3-none-any.whl (20.2 kB view details)

Uploaded Python 2 Python 3

File details

Details for the file django-silhouette-0.0.2.tar.gz.

File metadata

File hashes

Hashes for django-silhouette-0.0.2.tar.gz
Algorithm Hash digest
SHA256 f92fcde1eae13b14150f01cdd8c5d60f413fb0846f523db001f5c1aa82d183de
MD5 7e0e8da279457c930f7e8b90d76580f2
BLAKE2b-256 4418fb75dc5fedbf4a89dc93509a94b67b7d23403da4232a6d477372c23b3707

See more details on using hashes here.

File details

Details for the file django_silhouette-0.0.2-py2.py3-none-any.whl.

File metadata

File hashes

Hashes for django_silhouette-0.0.2-py2.py3-none-any.whl
Algorithm Hash digest
SHA256 d2a8d5105b91afa0182a7c3e70679037def5483ca1c3983bad2d9212236ad34b
MD5 155f86164cb6b389e69e8048eb291af8
BLAKE2b-256 1aa482b6972f9edc425e5f88205f861270f05bbac53167186d8f662735c4e4a4

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page