38 votes

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

Lorsque je définis une classe de formulaire Django similaire à ceci :

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

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

Cocher ceci : 

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

[Édition]

Merci pour la réponse de Jonas - néanmoins, bien que cela corrige le problème que j'ai mentionné (les étiquettes des cases à cocher sont affichées à droite de la case à cocher), cela introduit un nouveau problème (toutes les étiquettes des widgets sont affichées à droite de leurs widgets...)

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

Le CSS semble être une approche décente, sauf que je ne connais pas suffisamment le CSS pour y parvenir ou même pour décider si j'aime cette approche ou non. De plus, 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 de devoir le faire plusieurs fois pour plusieurs styles, ce qui fait du CSS la mauvaise réponse.

[Édition]

On dirait que je réponds à ma propre question ci-dessous. Si quelqu'un a une meilleure idée pour le faire, n'hésitez pas.

33voto

romkyns Points 17295

Voici une solution que j'ai trouvée (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 template personnalisée (dans cet exemple, dans un fichier "myfilters.py") contenant quelque chose comme ceci :

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 template personnalisées disponibles ici.

Éditer : dans l'esprit de la propre réponse du demandeur :

Avantages :

  1. Pas besoin de manipuler le CSS.
  2. Le balisage se retrouve tel qu'il est censé être.
  3. Je n'ai pas bricolé les internes de Django. (mais ai dû regarder tout un tas de choses)
  4. Le modèle est agréable, compact et idiomatique.
  5. Le code du filtre se comporte bien, quelles que soient les valeurs exactes des noms des libellés et des champs de saisie.

Désavantages :

  1. Il y a probablement quelque chose quelque part qui le fait mieux et plus rapidement.
  2. Il est peu probable que le client accepte de payer pour tout le temps passé là-dessus juste pour déplacer le libellé à droite...

0 votes

J'aime cette réponse. La mise en œuvre est simple, ce qui est toujours un avantage. Il est clair ce qui se passe juste en lisant le modèle. Le seul inconvénient (dans mon point de vue partial et avoué paresseux) est que ma solution donne des modèles plus courts et se rapproche de ce que vous auriez pu attendre que Django fasse en premier lieu.

15voto

Marco Points 947

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 de caractères

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

12voto

Simon Steinberger Points 1787

Toutes les solutions présentées impliquent des modifications de modèles, qui sont en général plutôt inefficaces en termes de performance. 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(' {2}', 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)

# utilisation dans le formulaire
class MyForm(forms.Form):
    my_boolean = PrettyCheckboxField(label=_('Some label'), widget=PrettyCheckboxWidget())

J'ai PrettyCheckboxWidget et PrettyCheckboxField dans un fichier supplémentaire, donc ils peuvent être importés au besoin. 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 inférieures.

Avantages :

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

Inconvénients :

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

2voto

Ori Pessach Points 4957

Voici ce que j'ai finalement fait. J'ai écrit un filtre de chaîne de modèles personnalisé pour échanger les balises. Maintenant, mon code de modèle ressemble à ceci :

{% load pretty_forms %}

{{ form.as_p|pretty_checkbox }}

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

Voici une implémentation fonctionnelle mais laide de pretty_checkbox - ce code n'a pas de gestion d'erreurs, il suppose que les attributs générés par Django sont formatés de manière très spécifique, et ce serait une mauvaise idée d'utiliser quelque chose de tel 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):
    # Itérer sur le fragment HTML, extraire les balises  et , et
    # échanger l'ordre des paires lorsque le type d'entrée est "checkbox".
    scratch = value
    output = ''
    try:
        while True:
            ls = scratch.find(' -1:
                le = scratch.find('')
                ins = scratch.find('', ins)
                # Vérifiez si nous traitons une case à cocher :
                if scratch[ins:ine+2].find(' type="checkbox" ')>-1:
                    # Échangez les balises
                    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 a capturé une exception")
    return output

pretty_checkbox analyse son argument de type chaîne, trouve des paires de balises et , et les échange si le type de la balise est "checkbox". Il supprime également le dernier caractère du label, qui est le caractère ':'.

Avantages:

  1. Pas de bricolage avec CSS.
  2. Le balisage finit par ressembler à ce qu'il est censé être.
  3. Je n'ai pas bidouillé les entrailles de Django.
  4. Le modèle est agréable, compact et idiomatique.

Inconvénients:

  1. Le code du filtre doit être testé pour des valeurs excitantes des libellés et des noms des champs de saisie.
  2. Il y a probablement quelque chose quelque part qui le fait mieux et plus rapidement.
  3. Plus de travail que prévu pour un samedi.

1 votes

Pouvez-vous fournir le code pour pretty_checkbox? Mon balisage préféré pour cela est  Cocher cette case

0 votes

Je n'ai pas le code sous la main en ce moment. Il n'était pas très élaboré, si je me souviens bien - c'est simplement une fonction qui reçoit un court extrait HTML, et échange les étiquettes et les entrées si l'entrée est une case à cocher. Le balisage est généré par Django, donc toute préférence que vous pourriez avoir est probablement sans importance.

0 votes

Tu dois plaisanter. C'est très simple en HTML. Alors j'utilise Django pour compliquer les choses, parce que je suis un vrai programmeur ?? Les contrôles de formulaire de Django sont cassés. Simples et clairs.

1voto

zihotki Points 4729

L'ordre des saisies et des libellés est fourni via le paramètre normal_row du formulaire et il n'y a pas de motifs de lignes différents pour les cases à cocher. Il y a donc deux façons de faire cela (dans la version 0.96 exactement) :
1. remplacer _html_output du formulaire
2. utiliser CSS pour changer la position du libellé et de la case à cocher

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X