web-dev-qa-db-fra.com

Formulaires Django, héritage et ordre des champs de formulaire

J'utilise des formulaires Django sur mon site Web et j'aimerais contrôler l'ordre des champs.

Voici comment je définis mes formulaires:

class edit_form(forms.Form):
    summary = forms.CharField()
    description = forms.CharField(widget=forms.TextArea)


class create_form(edit_form):
    name = forms.CharField()

Le nom est immuable et ne doit être répertorié que lorsque l'entité est créée. J'utilise l'héritage pour ajouter de la cohérence et les principes DRY. Ce qui se produit, ce qui n’est pas erroné, mais en fait tout à fait attendu, c’est que le champ du nom soit répertorié en dernier dans le fichier view/html, mais je souhaite que le champ du nom soit au-dessus du résumé et de la description. Je réalise que je pourrais facilement résoudre ce problème en copiant le résumé et la description dans create_form et perdre l'héritage, mais j'aimerais savoir si cela est possible. 

Pourquoi? Imaginez que vous avez 100 champs dans edit_form et que vous devez ajouter 10 champs en haut dans create_form - la copie et la maintenance des deux formulaires n'auraient pas l'air si sexy alors. (Ceci est pas mon cas, je invente juste un exemple)

Alors, comment puis-je remplacer ce comportement?

Modifier:

Apparemment, il n'y a pas de moyen approprié de faire cela sans passer par de mauvais bidouilles (bidouiller l'attribut .field). L'attribut .field est un SortedDict (une des infrastructures de données internes de Django) qui ne fournit aucun moyen de réorganiser les paires clé: valeur. Il fournit néanmoins un moyen d'insérer des éléments à un index donné, mais cela déplacerait les éléments des membres de la classe vers le constructeur. Cette méthode fonctionnerait, mais rendrait le code moins lisible. La seule autre solution qui me semble appropriée est de modifier le cadre lui-même, qui n'est pas optimal dans la plupart des situations.

En bref, le code deviendrait quelque chose comme ceci:

class edit_form(forms.Form):
    summary = forms.CharField()
    description = forms.CharField(widget=forms.TextArea)


class create_form(edit_form):
    def __init__(self,*args,**kwargs):
        forms.Form.__init__(self,*args,**kwargs)

        self.fields.insert(0,'name',forms.CharField())

Ça m'a fait taire :) 

61
hannson

À partir de Django 1.9+

Django 1.9 ajoute un nouvel attribut Form, field_order, permettant de classer le champ quel que soit son ordre de déclaration dans la classe.

class MyForm(forms.Form):
    summary = forms.CharField()
    description = forms.CharField(widget=forms.TextArea)
    author = forms.CharField()
    notes = form.CharField()

    field_order = ['author', 'summary']

Les champs manquants dans field_order conservent leur ordre dans la classe et sont ajoutés après ceux spécifiés dans la liste. L'exemple ci-dessus produira les champs dans cet ordre: ['author', 'summary', 'description', 'notes']

Voir la documentation: https://docs.djangoproject.com/fr/stable/ref/forms/api/#notes-on-field-ordering

Jusqu'à Django 1.6

J'ai eu le même problème et j'ai trouvé une autre technique pour réorganiser les champs dans Django CookBook :

class EditForm(forms.Form):
    summary = forms.CharField()
    description = forms.CharField(widget=forms.TextArea)


class CreateForm(EditForm):
    name = forms.CharField()

    def __init__(self, *args, **kwargs):
        super(CreateForm, self).__init__(*args, **kwargs)
        self.fields.keyOrder = ['name', 'summary', 'description']
97
Selene

Depuis Django 1.9: https://docs.djangoproject.com/fr/1.10/ref/forms/api/#notes-on-field-ordering


Réponse originale: Django 1.9 supportera cela par défaut sur le formulaire avec field_order:

class MyForm(forms.Form):
    ...
    field_order = ['field_1', 'field_2']
    ...

https://github.com/Django/django/commit/28986da4ca167ae257abcaf7caea230eca2bcd80

16
François Constant

J'ai utilisé la solution publiée par Selene, mais j'ai constaté qu'elle supprimait tous les champs qui n'étaient pas attribués à keyOrder. Le formulaire que je sous-classe comporte de nombreux champs, donc cela ne fonctionnait pas très bien pour moi. J'ai codé cette fonction pour résoudre le problème en utilisant la réponse de akaihola, mais si vous voulez qu'elle fonctionne comme Selene, il vous suffit de définir throw_away à True.

def order_fields(form, field_list, throw_away=False):
    """
    Accepts a form and a list of dictionary keys which map to the
    form's fields. After running the form's fields list will begin
    with the fields in field_list. If throw_away is set to true only
    the fields in the field_list will remain in the form.

    example use:
    field_list = ['first_name', 'last_name']
    order_fields(self, field_list)
    """
    if throw_away:
        form.fields.keyOrder = field_list
    else:
        for field in field_list[::-1]:
            form.fields.insert(0, field, form.fields.pop(field))

Voici comment je l'utilise dans mon propre code:

class NestableCommentForm(ExtendedCommentSecurityForm):
    # TODO: Have min and max length be determined through settings.
    comment = forms.CharField(widget=forms.Textarea, max_length=100)
    parent_id = forms.IntegerField(widget=forms.HiddenInput, required=False)

    def __init__(self, *args, **kwargs):
        super(NestableCommentForm, self).__init__(*args, **kwargs)
        order_fields(self, ['comment', 'captcha'])
11
Joshua

Il semble qu’à un moment donné, la structure sous-jacente de l’ordre des champs soit passée d’un SordedDict spécifique à Django à un standard python OrderedDict

Ainsi, en 1.7 je devais faire ce qui suit:

from collections import OrderedDict

class MyForm(forms.Form):

    def __init__(self, *args, **kwargs):
        super(MyForm, self).__init__(*args, **kwargs)
        original_fields = self.fields
        new_order = OrderedDict()
        for key in ['first', 'second', ... 'last']:
            new_order[key] = original_fields[key]
        self.fields = new_order

Je suis sûr que quelqu'un pourrait jouer cela en deux ou trois lignes, mais pour S.O. Je pense que le fait de montrer clairement comment cela fonctionne est meilleur que le couperet.

10
Ted

Vous pouvez également créer un décorateur pour commander des champs (inspiré de la solution de Joshua):

def order_fields(*field_list):
    def decorator(form):
        original_init = form.__init__
        def init(self, *args, **kwargs):
            original_init(self, *args, **kwargs)        
            for field in field_list[::-1]:
                self.fields.insert(0, field, self.fields.pop(field))
        form.__init__ = init
        return form            
    return decorator

Cela garantira que tous les champs transmis au décorateur viennent en premier . Vous pouvez l'utiliser comme ceci:

@order_fields('name')
class CreateForm(EditForm):
    name = forms.CharField()
7
Jan Pöschko

L'approche de la réponse acceptée utilise une API de formulaires Django interne qui a été modifiée dans Django 1.7. L’opinion de l’équipe de projet est qu’il n’aurait jamais dû être utilisé. J'utilise maintenant cette fonction pour réorganiser mes formulaires. Ce code utilise une OrderedDict:

def reorder_fields(fields, order):
    """Reorder form fields by order, removing items not in order.

    >>> reorder_fields(
    ...     OrderedDict([('a', 1), ('b', 2), ('c', 3)]),
    ...     ['b', 'c', 'a'])
    OrderedDict([('b', 2), ('c', 3), ('a', 1)])
    """
    for key, v in fields.items():
        if key not in order:
            del fields[key]

    return OrderedDict(sorted(fields.items(), key=lambda k: order.index(k[0])))

Ce que j'utilise dans des cours comme celui-ci:

class ChangeOpenQuestion(ChangeMultipleChoice):

    def __init__(self, *args, **kwargs):
        super(ChangeOpenQuestion, self).__init__(*args, **kwargs)
        key_order = ['title',
                     'question',
                     'answer',
                     'correct_answer',
                     'incorrect_answer']

        self.fields = reorder_fields(self.fields, key_order)
6
Thom Wiggers

Voir les notes dans cette SO question sur la façon dont les internes de Django suivent l’ordre des champs; les réponses incluent des suggestions sur la façon de "réorganiser" les champs à votre goût (au final, cela revient à jouer avec l'attribut .fields).

3
Alex Martelli

Autres méthodes pour changer l'ordre des champs:

Pop-and-Insert:

self.fields.insert(0, 'name', self.fields.pop('name'))

Pop-and-append:

self.fields['summary'] = self.fields.pop('summary')
self.fields['description'] = self.fields.pop('description')

Pop-and-append-all:

for key in ('name', 'summary', 'description'):
    self.fields[key] = self.fields.pop(key)

Copie commandée:

self.fields = SortedDict( [ (key, self.fields[key])
                            for key in ('name', 'summary' ,'description') ] )

Mais l'approche de Selene du Django CookBook semble toujours la plus claire.

2
akaihola

Basé sur une réponse de @akaihola et mis à jour pour fonctionner avec le dernier Django 1.5 car self.fields.insert est en cours de amorti

from easycontactus.forms import *
from Django import forms
class  CustomEasyContactUsForm(EasyContactUsForm):
    ### form settings and configuration
    CAPTHCA_PHRASE = 'igolf'

    ### native methods
    def __init__(self, *args, **kwargs):
        super(CustomEasyContactUsForm, self).__init__(*args, **kwargs)
        # re-order placement of added attachment field 
        self.fields.keyOrder.insert(self.fields.keyOrder.index('captcha'),
                                    self.fields.keyOrder.pop(self.fields.keyOrder.index('attachment'))
                                    )

    ### field defintitions
    attachment = forms.FileField()

Dans ce qui précède, nous étendons une classe de base EasyContactUsForm telle qu'elle est définie dans Django-easycontactus package. 

1
Daniel Sokolowski

Les réponses ci-dessus sont exactes mais incomplètes. Ils ne fonctionnent que si tous les champs sont définis en tant que variables de classe. Qu'en est-il des champs de formulaire dynamiques qui doivent être définis dans l'initialialiser (__init__)?

from Django import forms

class MyForm(forms.Form):
    field1 = ...
    field2 = ...

    field_order = ['val', 'field1', 'field2']

    def __init__(self, val_list, *args, **kwargs):
        super(MyForm, self).__init__(*args, **kwargs)
        vals = Zip(val_list, val_list)
        self.fields['val'] = forms.CharField(choices=vals)

Ce qui précède ne fonctionnera jamais pour val mais fonctionnera pour field1 et field2 (si nous les réorganisons). Vous voudrez peut-être essayer de définir field_order dans l'initialiseur:

class MyForm(forms.Form):
    # other fields

    def __init__(self, val_list, *args, **kwargs):
        super(MyForm, self).__init__(*args, **kwargs)
        vals = Zip(val_list, val_list)
        self.fields['val'] = forms.CharField(choices=vals)
        self.field_order = ['val', 'field1', 'field2']

mais cela échouera aussi parce que l'ordre des champs est fixe avant l'appel à super().

Par conséquent, la seule solution est le constructeur (__new__) et définissez field_order sur une variable de classe.

class MyForm(forms.Form):
    # other fields

    field_order = ['val', 'field1', 'field2']

    def __new__(cls, val_list, *args, **kwargs):
        form = super(MyForm, cls).__new__(cls)
        vals = Zip(val_list, val_list)
        form.base_fields['val'] = forms.CharField(choices=vals)
        return form
0
polarise

J'ai construit un formulaire 'ExRegistrationForm' hérité de 'RegistrationForm' de Django-Registration-Redux. J'ai rencontré deux problèmes, l'un d'entre eux étant la réorganisation des champs sur la page de sortie html une fois le nouveau formulaire créé.

Je les ai résolus comme suit:

1. NUMÉRO 1: Suppression du nom d'utilisateur du formulaire d'inscription: Dans my_app.forms.py

    class ExRegistrationForm(RegistrationForm):
          #Below 2 lines extend the RegistrationForm with 2 new fields firstname & lastname
          first_name = forms.CharField(label=(u'First Name'))
          last_name = forms.CharField(label=(u'Last Name'))

          #Below 3 lines removes the username from the fields shown in the output of the this form
          def __init__(self, *args, **kwargs):
          super(ExRegistrationForm, self).__init__(*args, **kwargs)
          self.fields.pop('username')

2. NUMÉRO 2: Faites apparaître Prénom et Nom par dessus: Dans templates/registration/registration_form.html

Vous pouvez afficher individuellement les champs dans l'ordre de votre choix. Cela aiderait si le nombre de champs est inférieur, mais pas si vous avez un grand nombre de champs où il devient pratiquement impossible de les écrire réellement dans le formulaire.

     {% extends "base.html" %}
     {% load i18n %}

     {% block content %}
     <form method="post" action=".">
          {% csrf_token %}

          #The Default html is: {{ form.as_p }} , which can be broken down into individual elements as below for re-ordering.
          <p>First Name: {{ form.first_name }}</p>
          <p>Last Name:  {{ form.last_name }}</p>
          <p>Email: {{ form.email }}</p>
          <p>Password: {{ form.password1 }}</p>
          <p>Confirm Password: {{ form.password2 }}</p>

          <input type="submit" value="{% trans 'Submit' %}" />
      </form>
      {% endblock %}
0
Utkarsh Sinha