Search code examples
pythondjangocheckbox

How do I create a Django form that displays a checkbox label to the right of the checkbox?


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.


Solution

  • 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.