python - registro - Formularios Django, herencia y orden de los campos de formulario
formularios web python (10)
Estoy usando formularios de Django en mi sitio web y me gustaría controlar el orden de los campos.
Así es como defino mis formularios:
class edit_form(forms.Form):
summary = forms.CharField()
description = forms.CharField(widget=forms.TextArea)
class create_form(edit_form):
name = forms.CharField()
El nombre es inmutable y solo debe aparecer cuando se crea la entidad. Uso la herencia para agregar consistencia y principios SECOS. Lo que sucede que no es erróneo, de hecho totalmente esperado, es que el campo de nombre aparece en último lugar en la vista / html pero me gustaría que el campo de nombre esté encima del resumen y la descripción. Me doy cuenta de que podría solucionarlo fácilmente copiando el resumen y la descripción en create_form y perdiendo la herencia, pero me gustaría saber si esto es posible.
¿Por qué? Imagine que tiene 100 campos en edit_form y tiene que agregar 10 campos en la parte superior en create_form; copiar y mantener las dos formas no sería tan atractivo en ese momento. (Este no es mi caso, solo estoy inventando un ejemplo)
Entonces, ¿cómo puedo anular este comportamiento?
Editar:
Aparentemente, no hay una forma adecuada de hacerlo sin pasar por hacks desagradables (jugueteando con el atributo .field). El atributo .field es un SortedDict (una de las estructuras de datos internas de Django) que no proporciona ninguna forma de reordenar los pares clave: valor. Sin embargo, proporciona una manera de insertar elementos en un índice determinado, pero eso movería los elementos de los miembros de la clase al constructor. Este método funcionaría, pero haría que el código sea menos legible. La única otra forma que considero adecuada es modificar el marco en sí mismo, que es menos que óptimo en la mayoría de las situaciones.
En resumen, el código sería algo como esto:
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())
Eso me calló :)
También puede crear un decorador para ordenar campos (inspirado en la solución 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
Esto asegurará que todos los campos pasados al decorador sean lo primero. Puedes usarlo así:
@order_fields(''name'')
class CreateForm(EditForm):
name = forms.CharField()
Métodos alternativos para cambiar el orden de los campos:
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)
Copia ordenada:
self.fields = SortedDict( [ (key, self.fields[key])
for key in (''name'', ''summary'' ,''description'') ] )
Pero el enfoque de Selene desde el Django CookBook todavía se siente más claro de todos.
Usé la solución publicada por Selene pero descubrí que eliminaba todos los campos que no estaban asignados a keyOrder. La forma en que estoy subclasificando tiene muchos campos, así que esto no funcionó muy bien para mí. Codifiqué esta función para resolver el problema usando la respuesta de akaihola, pero si quieres que funcione como Selene, todo lo que tienes que hacer es establecer throw_away
en 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))
Así es como lo estoy usando en mi propio código:
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''])
Basado en una respuesta de @akaihola y actualizado para trabajar con la última self.fields.insert
Django 1.5, como self.fields.insert
se está depreciando .
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()
En lo anterior, ampliamos una clase base EasyContactUsForm tal como se define en el paquete django-easycontactus .
Tuve el mismo problema y encontré otra técnica para reordenar campos en el 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'']
Parece que en algún momento la estructura subyacente del orden de campo se cambió de un SordedDict
específico de django a un estándar OrderedDict
python OrderedDict
Por lo tanto, en 1.7 tuve que hacer lo siguiente:
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
Estoy seguro de que alguien podría jugar golf en dos o tres líneas, pero para fines de SO, creo que mostrar claramente cómo funciona es mejor que cortar.
El enfoque de la respuesta aceptada hace uso de una API interna de Django forms que fue modificada en Django 1.7. La opinión del equipo del proyecto es que nunca debería haber sido utilizada en primer lugar. Ahora uso esta función para reordenar mis formularios. Este código hace uso de un 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])))
Que uso en clases como esta:
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)
Creé un formulario ''ExRegistrationForm'' heredado de ''RegistrationForm'' de Django-Registration-Redux. Me enfrenté a dos problemas, uno de los cuales era reordenar los campos en la página de salida html una vez que se había creado el nuevo formulario.
Los resolví de la siguiente manera:
1. NÚMERO 1: eliminar el nombre de usuario del formulario de registro: en 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. NÚMERO 2: Make FirstName y LastName aparecen en la parte superior: En templates / registration / registration_form.html
Puede mostrar individualmente los campos en el orden que desee. Esto ayudaría en caso de que el número de campos sea menor, pero no si tiene una gran cantidad de campos donde resulta prácticamente imposible escribirlos en el formulario.
{% 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 %}
Vea las notas en esta pregunta SO sobre la forma en que las partes internas de Django hacen un seguimiento del orden de campo; las respuestas incluyen sugerencias sobre cómo "reordenar" los campos a su gusto (al final se reduce a jugar con el atributo .fields
).
De Django 1.9: https://docs.djangoproject.com/en/1.10/ref/forms/api/#notes-on-field-ordering
Respuesta original: Django 1.9 lo admitirá de forma predeterminada en el formulario con field_order
:
class MyForm(forms.Form):
...
field_order = [''field_1'', ''field_2'']
...
https://github.com/django/django/commit/28986da4ca167ae257abcaf7caea230eca2bcd80