web-dev-qa-db-fra.com

Comment créer un formulaire Django qui affiche une étiquette de case à cocher à droite de la case?

Lorsque je définis une classe de formulaire Django similaire à celle-ci:

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

Il se développe en HTML qui ressemble à ceci:

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

J'aimerais que l'élément d'entrée case à cocher ait une étiquette qui suit la case à cocher, et non l'inverse. Y a-t-il un moyen de convaincre Django de le faire?

[Modifier]

Merci pour la réponse de Jonas - toujours, même s'il résout le problème que j'ai demandé (les étiquettes des cases à cocher sont affichées à droite de la case à cocher), il introduit un nouveau problème (toutes les étiquettes des widgets sont affichées à la droite de leurs widgets ...)

J'aimerais éviter de surcharger _html_output () car ce n'est évidemment pas conçu pour cela. La conception que je proposerais consisterait à implémenter une méthode de sortie HTML de champ dans les classes Field, à remplacer celle du champ booléen et à utiliser cette méthode dans _html_output (). Malheureusement, les développeurs de Django ont choisi une approche différente et j'aimerais travailler autant que possible dans le cadre existant.

CSS sonne comme une approche décente, sauf que je ne connais pas assez de CSS pour réussir cela ou même pour décider si j'aime cette approche ou non. En outre, je préfère un balisage qui ressemble toujours à la sortie finale, du moins dans l’ordre de rendu.

De plus, comme il peut être raisonnable d'avoir plus d'une feuille de style pour un balisage particulier, le faire en CSS pourrait signifier qu'il faille le faire plusieurs fois pour plusieurs styles, ce qui fait du CSS une mauvaise réponse.

[Modifier]

On dirait que je réponds à ma propre question ci-dessous. Si quelqu'un a une meilleure idée de comment faire cela, ne soyez pas timide.

32
Ori Pessach

Voici ce que j'ai fini par faire. J'ai écrit un templatefilter personnalisé pour changer les tags. Maintenant, mon code de modèle ressemble à ceci:

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

La seule différence par rapport à un modèle Django ordinaire est l’ajout de la balise de modèle {% load%} et du filtre pretty_checkbox .

Voici une implémentation fonctionnelle mais moche de pretty_checkbox - ce code ne gère pas les erreurs, il suppose que les attributs générés par Django sont formatés de manière très spécifique, et il serait déconseillé d’utiliser ce genre de chose. dans votre 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 analyse son argument de chaîne, trouve des paires de balises <label> et <input>, et les bascule si le type de la balise <input> est "checkbox". Il supprime également le dernier caractère de l'étiquette, qui se trouve être le caractère ':'.

Avantages: 

  1. Pas de futzing avec CSS.
  2. Le balisage finit par ressembler à ce qu’il est censé faire.
  3. Je n'ai pas piraté les internes de Django.
  4. Le modèle est agréable, compact et idiomatique.

Désavantages: 

  1. Le code de filtre doit être testé pour les valeurs intéressantes des étiquettes et des noms de champs de saisie. 
  2. Il y a probablement quelque chose quelque part là-bas qui le fait mieux et plus rapidement. 
  3. Plus de travail que ce que j'avais prévu de faire un samedi.
2
Ori Pessach

Voici une solution que j'ai mise au point (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 %}

Vous devrez créer une balise de modèle personnalisé (dans cet exemple dans un fichier "myfilters.py") contenant quelque chose comme:

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)

Plus d'informations sur les balises de modèles personnalisés disponibles ici .

Edit : dans l'esprit de la réponse du demandeur:

Avantages:

  1. Pas de futzing avec CSS.
  2. Le balisage finit par ressembler à ce qu’il est censé faire.
  3. Je n'ai pas piraté les internes de Django. (mais j'ai dû regarder pas mal de choses)
  4. Le modèle est agréable, compact et idiomatique.
  5. Le code de filtre joue Nice quelles que soient les valeurs exactes des étiquettes et les noms des champs de saisie.

Désavantages:

  1. Il y a probablement quelque chose quelque part là-bas qui le fait mieux et plus rapidement.
  2. Il est peu probable que le client soit prêt à payer pour tout le temps consacré à cette tâche, rien que pour déplacer l'étiquette vers la droite ...
31
Roman Starkov

J'ai pris la réponse de Romkyns et l'ai rendue un peu plus générale

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

De cette façon, vous pouvez vérifier le type de widget directement avec une chaîne

{% if field|field_type:'checkboxinput' %}
    <label>{{ field }} {{ field.label }}</label>
{% else %}
    <label> {{ field.label }} </label> {{ field }}
{% endif %}
15
Marco

Toutes les solutions présentées impliquent des modifications de modèles, qui sont généralement plutôt inefficaces en termes de performances. Voici un widget personnalisé qui fait le travail:

from Django import forms
from Django.forms.fields import BooleanField
from Django.forms.util import flatatt
from Django.utils.encoding import force_text
from Django.utils.html import format_html
from Django.utils.translation import ugettext as _


class PrettyCheckboxWidget(forms.widgets.CheckboxInput):
    def render(self, name, value, attrs=None):
        final_attrs = self.build_attrs(attrs, type='checkbox', name=name)
        if self.check_test(value):
            final_attrs['checked'] = 'checked'
        if not (value is True or value is False or value is None or value == ''):
            final_attrs['value'] = force_text(value)
        if 'prettycheckbox-label' in final_attrs:
            label = _(final_attrs.pop('prettycheckbox-label'))
        else:
            label = ''
        return format_html('<label for="{0}"><input{1} /> {2}</label>', attrs['id'], flatatt(final_attrs), label)


class PrettyCheckboxField(BooleanField):
    widget = PrettyCheckboxWidget
    def __init__(self, *args, **kwargs):
        if kwargs['label']:
            kwargs['widget'].attrs['prettycheckbox-label'] = kwargs['label']
            kwargs['label'] = ''
        super(PrettyCheckboxField, self).__init__(*args, **kwargs)


# usage in form
class MyForm(forms.Form):
    my_boolean = PrettyCheckboxField(label=_('Some label'), widget=PrettyCheckboxWidget())

J'ai PrettyCheckboxWidget et PrettyCheckboxField dans un fichier supplémentaire, afin qu'ils puissent être importés si nécessaire. Si vous n'avez pas besoin de traductions, vous pouvez supprimer les parties ugettext. Ce code fonctionne sur Django 1.5 et n’est pas testé pour les versions antérieures.

Avantages:

  • Très performant, ne nécessite aucune modification de modèle
  • Facile à utiliser comme un widget personnalisé

Désavantages:

  • "as_table" rend la case à cocher + étiquette à l'intérieur de la deuxième colonne
  • {{field.label}} à l'intérieur du modèle est vide. L'étiquette est liée à {{field}}
  • Plus de travail que ce que j'avais prévu de faire un samedi ;-)
10

Je sais que l'utilisateur a exclu CSS, mais si l'on considère les réponses les plus longues, cela prend environ une demi-heure de travail, mais sachant que de tels détails sont importants sur un site Web, je me contenterais de la solution CSS.

checkbox.css

input[type="checkbox"] {
    float: left;
    margin-right: 10px;
    margin-top: 4px;
}

forms.py

class MyForm(forms.ModelForm):
    # ...
    class Media:
    css = {
        'all': 'checkbox.css',
    }

template.html

{{ form.media }}
{{ form.as_p }}

Avantages:

  • vite!
  • pas de futzing avec les balises de template (seulement form.as_p
  • pas de nouveaux maudits widgets
  • le fichier CSS est automatiquement inclus dans chaque formulaire

Désavantages:

  • le HTML ne reflète pas la présentation (mais est assez bon!)
  • votre frontend pourrait se plaindre
3
caesarsol

L'ordre des entrées et des étiquettes est fourni via le paramètre normal_row du formulaire et il n'y a pas de modèle de ligne différent pour les cases à cocher. Donc, il y a deux façons de faire cela (dans la version 0.96 exactement):
1. redéfinir _html_output du formulaire
2. utiliser CSS pour changer la position de l'étiquette et de la case à cocher

1
zihotki

Changer la case de la case dans Django admin peut être assez délicat, mais heureusement, il existe une solution simple utilisant un widget personnalisé:

from Django.forms.widgets import Widget, CheckboxInput, boolean_check

class RightCheckbox(Widget):
    render = CheckboxInput().render

    def __init__(self, attrs=None, check_test=None):
        super(RightCheckbox, self).__init__(attrs)
        self.check_test = boolean_check if check_test is None else check_test

Django utilise la position de gauche uniquement lorsque le widget est une sous-classe de CheckboxInput

0
Andrei