python - custom - select multiple django example
¿Cómo agrupar las opciones en un widget Django Select? (3)
Después de un rápido vistazo al código ModelChoiceField en django.forms.models, diría que intenta extender esa clase y anular su propiedad de elección.
Configure la propiedad para devolver un iterador personalizado, basado en el original ModelChoiceIterator en el mismo módulo (que devuelve la tupla con la que está teniendo problemas): un nuevo GroupedModelChoiceIterator o algo similar.
Voy a tener que dejar de descifrar exactamente cómo escribirle ese iterador, pero supongo que solo necesita que le devuelva una tupla de tuplas de forma personalizada, en lugar de la configuración predeterminada.
Estoy feliz de responder a los comentarios, ya que estoy bastante seguro de que esta respuesta necesita un poco de ajuste fino :)
EDITAR ABAJO
Acabo de tener un pensamiento y comprobado djangosnippets, resulta que alguien acaba de hacer esto: ModelChoiceField con grupos de opciones . Tiene un año, por lo que podría necesitar algunos ajustes para trabajar con el último django, pero es exactamente lo que estaba pensando.
¿Es posible crear grupos de opciones con nombre en un widget de selección (desplegable) de Django, cuando ese widget se encuentra en un formulario que se genera automáticamente a partir de un modelo de datos? ¿Puedo crear el widget en la imagen de la izquierda a continuación?
Mi primer experimento en la creación de un formulario con grupos con nombre, se realizó de forma manual , como este:
class GroupMenuOrderForm(forms.Form):
food_list = [(1, ''burger''), (2, ''pizza''), (3, ''taco''),]
drink_list = [(4, ''coke''), (5, ''pepsi''), (6, ''root beer''),]
item_list = ( (''food'', tuple(food_list)), (''drinks'', tuple(drink_list)),)
itemsField = forms.ChoiceField(choices = tuple(item_list))
def GroupMenuOrder(request):
theForm = GroupMenuOrderForm()
return render_to_response(menu_template, {''form'': theForm,})
# generates the widget in left-side picture
Y funcionó muy bien, creando el widget desplegable a la izquierda, con grupos con nombre.
Luego creé un modelo de datos que tenía básicamente la misma estructura y usé la capacidad de Django para generar automáticamente formularios a partir de modelos. Funcionó, en el sentido de que mostraba todas las opciones. Pero las opciones no estaban en grupos con nombre, y hasta ahora, no he descubierto cómo hacerlo, si es posible.
He encontrado varias preguntas, donde la respuesta fue "crear un constructor de formularios y hacer cualquier procesamiento especial allí". Pero parece que las formas.ChoiceField requiere una tupla para grupos nombrados, y no estoy seguro de cómo convertir una tupla en un QuerySet (lo que probablemente sea imposible de todos modos, si entiendo QuerySets correctamente como puntero a los datos, no el Información actual).
El código que utilicé para el modelo de datos es:
class ItemDesc(models.Model):
'''''' one of "food", "drink", where ID of “food” = 1, “drink” = 2 ''''''
desc = models.CharField(max_length=10, unique=True)
def __unicode__(self):
return self.desc
class MenuItem(models.Model):
'''''' one of ("burger", 1), ("pizza", 1), ("taco", 1),
("coke", 2), ("pepsi", 2), ("root beer", 2) ''''''
name = models.CharField(max_length=50, unique=True)
itemDesc = models.ForeignKey(ItemDesc)
def __unicode__(self):
return self.name
class PatronOrder(models.Model):
itemWanted = models.ForeignKey(MenuItem)
class ListMenuOrderForm(forms.ModelForm):
class Meta:
model = PatronOrder
def ListMenuOrder(request):
theForm = ListMenuOrderForm()
return render_to_response(menu_template, {''form'': theForm,})
# generates the widget in right-side picture
Cambiaré el modelo de datos, si es necesario, pero esto parece una estructura sencilla. Tal vez demasiados ForeignKeys? ¿Contraer los datos y aceptar la desnormalización? :) ¿O hay alguna manera de convertir una tupla a un QuerySet, o algo aceptable para un ModelChoiceField?
Actualización: código final, basado en la answer meshantz :
class FooIterator(forms.models.ModelChoiceIterator):
def __init__(self, *args, **kwargs):
super(forms.models.ModelChoiceIterator, self).__init__(*args, **kwargs)
def __iter__(self):
yield (''food'', [(1L, u''burger''), (2L, u''pizza'')])
yield (''drinks'', [(3L, u''coke''), (4L, u''pepsi'')])
class ListMenuOrderForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ListMenuOrderForm, self).__init__(*args, **kwargs)
self.fields[''itemWanted''].choices = FooIterator()
class Meta:
model = PatronOrder
(Por supuesto, en el código real, haré que algo extraiga los datos del elemento de la base de datos ).
El mayor cambio en el djangosnippet que vinculó, parece ser que Django ha incorporado parte del código, lo que hace posible asignar directamente un iterador a las opciones , en lugar de tener que anular toda la clase. Que es muy bonito
Esto es lo que funcionó para mí, sin extender ninguna de las clases actuales de django:
Tengo una lista de tipos de organismos, dados los diferentes Reinos como el grupo optativo. En una forma OrganismForm, puede seleccionar el organismo de un cuadro de selección desplegable, y están ordenados por el grupo opcional del Reino, y luego todos los organismos de ese reino. Al igual que:
[----------------|V]
|Plantae |
| Angiosperm |
| Conifer |
|Animalia |
| Mammal |
| Amphibian |
| Marsupial |
|Fungi |
| Zygomycota |
| Ascomycota |
| Basidiomycota |
| Deuteromycota |
|... |
|________________|
modelos.py
from django.models import Model
class Kingdom(Model):
name = models.CharField(max_length=16)
class Organism(Model):
kingdom = models.ForeignKeyField(Kingdom)
name = models.CharField(max_length=64)
forms.py:
from models import Kingdom, Organism
class OrganismForm(forms.ModelForm):
organism = forms.ModelChoiceField(
queryset=Organism.objects.all().order_by(''kingdom__name'', ''name'')
)
class Meta:
model = Organism
views.py:
from models import Organism, Kingdom
from forms import OrganismForm
form = OrganismForm()
form.fields[''organism''].choices = list()
# Now loop the kingdoms, to get all organisms in each.
for k in Kingdom.objects.all():
# Append the tuple of OptGroup Name, Organism.
form.fields[''organism''].choices = form.fields[''organism''].choices.append(
(
k.name, # First tuple part is the optgroup name/label
list( # Second tuple part is a list of tuples for each option.
(o.id, o.name) for o in Organism.objects.filter(kingdom=k).order_by(''name'')
# Each option itself is a tuple of id and name for the label.
)
)
)
No necesitas iteradores personalizados . Vas a necesitar apoyar ese código . Simplemente pasa las elecciones right :
from django import forms
from django.db.models import Prefetch
class ProductForm(forms.ModelForm):
class Meta:
model = Product
fields = [...]
def __init__(self, *args, **kwargs):
super(ProductForm, self).__init__(*args, **kwargs)
cats = Category.objects /
.filter(category__isnull=True) /
.order_by(''order'') /
.prefetch_related(Prefetch(''subcategories'',
queryset=Category.objects.order_by(''order'')))
self.fields[''subcategory''].choices = /
[("", self.fields[''subcategory''].empty_label)] /
+ [(c.name, [
(self.fields[''subcategory''].prepare_value(sc),
self.fields[''subcategory''].label_from_instance(sc))
for sc in c.subcategories.all()
]) for c in cats]
Aquí,
class Category(models.Model):
category = models.ForeignKey(''self'', null=True, on_delete=models.CASCADE,
related_name=''subcategories'', related_query_name=''subcategory'')
class Product(models.Model):
subcategory = models.ForeignKey(Category, on_delete=models.CASCADE,
related_name=''products'', related_query_name=''product'')
Esta misma técnica se puede utilizar para personalizar un formulario de administración de Django . Aunque, la clase Meta
no es necesaria en este caso.