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 :)
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
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']
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
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'])
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.
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()
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)
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
).
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.
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.
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
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 %}