views:

3665

answers:

5

When I define a Django form class similar to this:

def class MyForm(forms.Form):
    check = forms.BooleanField(required=True, label="Check this")

It expands to HTML that looks like this:

<form action="." id="form" method=POST>
<p><label for="check">Check this:</label> <input type="checkbox" name="check" id="check" /></p>
<p><input type=submit value="Submit"></p>
</form>

I would like the checkbox input element to have a label that follows the checkbox, not the other way around. Is there a way to convince Django to do that?

[Edit]

Thanks for the answer from Jonas - still, while it fixes the issue I asked about (checkbox labels are rendered to the right of the checkbox) it introduces a new problem (all widget labels are rendered to the right of their widgets...)

I'd like to avoid overriding _html_output() since it's obviously not designed for it. The design I would come up with would be to implement a field html output method in the Field classes, override the one for the Boolean field and use that method in _html_output(). Sadly, the Django developers chose to go a different way, and I would like to work within the existing framework as much as possible.

CSS sounds like a decent approach, except that I don't know enough CSS to pull this off or even to decide whether I like this approach or not. Besides, I prefer markup that still resembles the final output, at least in rendering order.

Furthermore, since it can be reasonable to have more than one style sheet for any particular markup, doing this in CSS could mean having to do it multiple times for multiple styles, which pretty much makes CSS the wrong answer.

[Edit]

Seems like I'm answering my own question below. If anyone has a better idea how to do this, don't be shy.

+3  A: 

It's possible to write the form rendering manually in your template:

{% for field in form %}
    <p>
        {{ field.errors }}
        {{ field }} {{ field.label_tag }}
    </p>
{% endfor %}
Jonas
Interesting. How can I specialize the display just for checkboxes, though? This will display the label to the right of the control for all field types.
Ori Pessach
Why is this upvoted so much? It doesn't address the issue of displaying the label on one side or the other depending on whether the widget is a checkbox.
romkyns
StackOverflow seems to have a very short attention span.
Ori Pessach
Voted up?? Strange.
simplyharsh
+1  A: 

Order of inputs and labels is provided via normal_row parameter of the form and there are no different row patterns for checkboxes. So there are two ways to do this (in 0.96 version exactly):
1. override _html_output of the form
2. use CSS to change position of the label and checkbox

zihotki
+1  A: 

Here's what I ended up doing. I wrote a custom template stringfilter to switch the tags around. Now, my template code looks like this:

{% load pretty_forms %}
<form action="." method="POST">
{{ form.as_p|pretty_checkbox }}
<p><input type="submit" value="Submit"></p>
</form>

The only difference from a plain Django template is the addition of the {% load %} template tag and the pretty_checkbox filter.

Here's a functional but ugly implementation of pretty_checkbox - this code doesn't have any error handling, it assumes that the Django generated attributes are formatted in a very specific way, and it would be a bad idea to use anything like this in your code:

from django import template
from django.template.defaultfilters import stringfilter
import logging

register=template.Library()

@register.filter(name='pretty_checkbox')
@stringfilter
def pretty_checkbox(value):
    # Iterate over the HTML fragment, extract <label> and <input> tags, and
    # switch the order of the pairs where the input type is "checkbox".
    scratch = value
    output = ''
    try:
        while True:
            ls = scratch.find('<label')
            if ls > -1:
                le = scratch.find('</label>')
                ins = scratch.find('<input')
                ine = scratch.find('/>', ins)
                # Check whether we're dealing with a checkbox:
                if scratch[ins:ine+2].find(' type="checkbox" ')>-1:
                    # Switch the tags
                    output += scratch[:ls]
                    output += scratch[ins:ine+2]
                    output += scratch[ls:le-1]+scratch[le:le+8]
                else:
                    output += scratch[:ine+2]
                scratch = scratch[ine+2:]
            else:
                output += scratch
                break
    except:
        logging.error("pretty_checkbox caught an exception")
    return output

pretty_checkbox scans its string argument, finds pairs of <label> and <input> tags, and switches them around if the <input> tag's type is "checkbox". It also strips the last character of the label, which happens to be the ':' character.

Advantages:

  1. No futzing with CSS.
  2. The markup ends up looking the way it's supposed to.
  3. I didn't hack Django internals.
  4. The template is nice, compact and idiomatic.

Disadvantages:

  1. The filter code needs to be tested for exciting values of the labels and input field names.
  2. There's probably something somewhere out there that does it better and faster.
  3. More work than I planned on doing on a Saturday.
Ori Pessach
Can you provide the code for pretty_checkbox? My own preferred markup for this is <label for="check"><input type="checkbox" name="check" id="check" /> Check this</label>
EoghanM
I don't have the code handy right now. It wasn't very elaborate, if I recall - it's simply a function that's fed a short HTML snippet, and swaps the label and input around if the input is a checkbox. The markup is generated by Django, so any preference you might have is probably irrelevant.
Ori Pessach
+5  A: 

Here's a solution I've come up with (Django v1.1):

{% load myfilters %}

[...]

{% for field in form %}
    [...]
    {% if field.field.widget|is_checkbox %}
      {{ field }}{{ field.label_tag }}
    {% else %}
      {{ field.label_tag }}{{ field }}
    {% endif %}
    [...]
{% endfor %}

You'll need to create a custom template tag (in this example in a "myfilters.py" file) containing something like this:

from django import template
from django.forms.fields import CheckboxInput

register = template.Library()

@register.filter(name='is_checkbox')
def is_checkbox(value):
    return isinstance(value, CheckboxInput)

More info on custom template tags available here.

Edit: in the spirit of asker's own answer:

Advantages:

  1. No futzing with CSS.
  2. The markup ends up looking the way it's supposed to.
  3. I didn't hack Django internals. (but had to look at quite a bunch)
  4. The template is nice, compact and idiomatic.
  5. The filter code plays nice regardless of the exact values of the labels and input field names.

Disadvantages:

  1. There's probably something somewhere out there that does it better and faster.
  2. Unlikely that the client will be willing to pay for all the time spent on this just to move the label to the right...
romkyns
I like this answer. The implementation is simple, which is always a plus. It's clear what's going on just from reading the template. The only downside (in my biased and admittedly lazy view) is that my solution makes for shorter templates, and is closer to what you could have expected Django to have done in the first place.
Ori Pessach
+1  A: 

I took the answer from romkyns and made it a little more general

def field_type(field, ftype):
try:
    t = field.field.widget.__class__.__name__
    return t.lower() == ftype
except:
    pass
return False

This way you can check the widget type directly with a string

   `{% if field|field_type:'checkboxinput' %}
   {{ field }} {{ field.label }}
   {% else %}
    {{ field.label }}  {{ field }}
   {% endif %}`
   
Marco