widgets personalizados formularios form fields example ejemplos crear avanzados python django django-forms

python - fields - formularios personalizados django



Django ModelForm para campos de muchos a muchos (7)

Hice algo similar basado en el código de Clément con un formulario de administrador de usuario:

# models.py class Clinica(models.Model): ... users = models.ManyToManyField(User, null=True, blank=True, related_name=''clinicas'') # admin.py class CustomUserChangeForm(UserChangeForm): clinicas = forms.ModelMultipleChoiceField(queryset=Clinica.objects.all()) def __init__(self,*args,**kwargs): if ''instance'' in kwargs: initial = kwargs.setdefault(''initial'',{}) initial[''clinicas''] = kwargs[''instance''].clinicas.values_list(''pk'',flat=True) super(CustomUserChangeForm,self).__init__(*args,**kwargs) def save(self,*args,**kwargs): instance = super(CustomUserChangeForm,self).save(*args,**kwargs) instance.clinicas = self.cleaned_data[''clinicas''] return instance class Meta: model = User admin.site.unregister(User) UserAdmin.fieldsets += ( (u''Clinicas'', {''fields'': (''clinicas'',)}), ) UserAdmin.form = CustomUserChangeForm admin.site.register(User,UserAdmin)

Considere los siguientes modelos y formas:

class Pizza(models.Model): name = models.CharField(max_length=50) class Topping(models.Model): name = models.CharField(max_length=50) ison = models.ManyToManyField(Pizza, blank=True) class ToppingForm(forms.ModelForm): class Meta: model = Topping

Cuando ves el ToppingForm, te permite elegir qué pizzas van a seguir los ingredientes y todo es excelente.

Mi pregunta es: ¿cómo defino un ModelForm para Pizza que me permite aprovechar la relación de Muchos a Muchos entre Pizza y Topping y me permite elegir qué ingredientes van para Pizza?


No estoy seguro de obtener la pregunta al 100%, así que voy a correr con esta suposición:

Cada Pizza puede tener muchos Topping s. Cada Topping puede tener muchas Pizza s. Pero si se agrega un Topping a una Pizza , ese Topping entonces automágicamente tendrá una Pizza , y viceversa.

En este caso, tu mejor apuesta es una tabla de relaciones, que Django admite bastante bien. Podría verse así:

models.py

class PizzaTopping(models.Model): topping = models.ForeignKey(''Topping'') pizza = models.ForeignKey(''Pizza'') class Pizza(models.Model): name = models.CharField(max_length=50) topped_by = models.ManyToManyField(''Topping'', through=PizzaTopping) def __str__(self): return self.name def __unicode__(self): return self.name class Topping(models.Model): name=models.CharField(max_length=50) is_on = models.ManyToManyField(''Pizza'', through=PizzaTopping) def __str__(self): return self.name def __unicode__(self): return self.name

forms.py

class PizzaForm(forms.ModelForm): class Meta: model = Pizza class ToppingForm(forms.ModelForm): class Meta: model = Topping

Ejemplo:

>>> p1 = Pizza(name="Monday") >>> p1.save() >>> p2 = Pizza(name="Tuesday") >>> p2.save() >>> t1 = Topping(name="Pepperoni") >>> t1.save() >>> t2 = Topping(name="Bacon") >>> t2.save() >>> PizzaTopping(pizza=p1, topping=t1).save() # Monday + Pepperoni >>> PizzaTopping(pizza=p2, topping=t1).save() # Tuesday + Pepperoni >>> PizzaTopping(pizza=p2, topping=t2).save() # Tuesday + Bacon >>> tform = ToppingForm(instance=t2) # Bacon >>> tform.as_table() # Should be on only Tuesday. u''<tr><th><label for="id_name">Name:</label></th><td><input id="id_name" type="text" name="name" value="Bacon" maxlength="50" /></td></tr>/n<tr><th><label for="id_is_on">Is on:</label></th><td><select multiple="multiple" name="is_on" id="id_is_on">/n<option value="1">Monday</option>/n<option value="2" selected="selected">Tuesday</option>/n</select><br /> Hold down "Control", or "Command" on a Mac, to select more than one.</td></tr>'' >>> pform = PizzaForm(instance=p1) # Monday >>> pform.as_table() # Should have only Pepperoni u''<tr><th><label for="id_name">Name:</label></th><td><input id="id_name" type="text" name="name" value="Monday" maxlength="50" /></td></tr>/n<tr><th><label for="id_topped_by">Topped by:</label></th><td><select multiple="multiple" name="topped_by" id="id_topped_by">/n<option value="1" selected="selected">Pepperoni</option>/n<option value="2">Bacon</option>/n</select><br /> Hold down "Control", or "Command" on a Mac, to select more than one.</td></tr>'' >>> pform2 = PizzaForm(instance=p2) # Tuesday >>> pform2.as_table() # Both Pepperoni and Bacon u''<tr><th><label for="id_name">Name:</label></th><td><input id="id_name" type="text" name="name" value="Tuesday" maxlength="50" /></td></tr>/n<tr><th><label for="id_topped_by">Topped by:</label></th><td><select multiple="multiple" name="topped_by" id="id_topped_by">/n<option value="1" selected="selected">Pepperoni</option>/n<option value="2" selected="selected">Bacon</option>/n</select><br /> Hold down "Control", or "Command" on a Mac, to select more than one.</td></tr>''


No estoy seguro de si esto es lo que estás buscando, pero ¿eres consciente de que Pizza tiene el atributo topping_set ? Usando ese atributo, puede agregar fácilmente un nuevo encabezado en su ModelForm.

new_pizza.topping_set.add(new_topping)


Otra forma simple de lograr esto es crear una tabla intermedia y usar campos en línea para hacerlo. Consulte este https://docs.djangoproject.com/en/1.2/ref/contrib/admin/#working-with-many-to-many-intermediary-models

Algunos ejemplos de código a continuación

models.py

class Pizza(models.Model): name = models.CharField(max_length=50) class Topping(models.Model): name = models.CharField(max_length=50) ison = models.ManyToManyField(Pizza, through=''PizzaTopping'') class PizzaTopping(models.Model): pizza = models.ForeignKey(Pizza) topping = models.ForeignKey(Topping)

admin.py

class PizzaToppingInline(admin.TabularInline): model = PizzaTopping class PizzaAdmin(admin.ModelAdmin): inlines = [PizzaToppingInline,] class ToppingAdmin(admin.ModelAdmin): inlines = [PizzaToppingInline,] admin.site.register(Pizza, PizzaAdmin) admin.site.register(Topping, ToppingAdmin)


Para ser sincero, pondría la relación de muchos a muchos en el modelo de Pizza . Creo que esto más cerca de la realidad. Imagina a una persona que ordena varias pizzas. No dijo "Me gustaría queso en pizza uno y dos y tomates en pizza uno y tres" pero probablemente "Una pizza con queso, una pizza con queso y tomates, ...".

Por supuesto, es posible hacer que la forma funcione a su manera, pero yo iría con:

class Pizza(models.Model): name = models.CharField(max_length=50) toppings = models.ManyToManyField(Topping)


Supongo que tendrías que añadir aquí un nuevo ModelMultipleChoiceField a tu PizzaForm y vincular manualmente ese campo de formulario con el campo de modelo, ya que Django no lo hará automáticamente por ti.

El siguiente fragmento podría ser útil:

class PizzaForm(forms.ModelForm): class Meta: model = Pizza # Representing the many to many related field in Pizza toppings = forms.ModelMultipleChoiceField(queryset=Topping.objects.all()) # Overriding __init__ here allows us to provide initial # data for ''toppings'' field def __init__(self, *args, **kwargs): # Only in case we build the form from an instance # (otherwise, ''toppings'' list should be empty) if kwargs.get(''instance''): # We get the ''initial'' keyword argument or initialize it # as a dict if it didn''t exist. initial = kwargs.setdefault(''initial'', {}) # The widget for a ModelMultipleChoiceField expects # a list of primary key for the selected data. initial[''toppings''] = [t.pk for t in kwargs[''instance''].topping_set.all()] forms.ModelForm.__init__(self, *args, **kwargs) # Overriding save allows us to process the value of ''toppings'' field def save(self, commit=True): # Get the unsave Pizza instance instance = forms.ModelForm.save(self, False) # Prepare a ''save_m2m'' method for the form, old_save_m2m = self.save_m2m def save_m2m(): old_save_m2m() # This is where we actually link the pizza with toppings instance.topping_set.clear() for topping in self.cleaned_data[''toppings'']: instance.topping_set.add(topping) self.save_m2m = save_m2m # Do we need to save all changes now? if commit: instance.save() self.save_m2m() return instance

Este PizzaForm puede usarse en todas partes, incluso en el administrador:

# yourapp/admin.py from django.contrib.admin import site, ModelAdmin from yourapp.models import Pizza from yourapp.forms import PizzaForm class PizzaAdmin(ModelAdmin): form = PizzaForm site.register(Pizza, PizzaAdmin)

Nota

El método save() puede ser un poco demasiado detallado, pero puedes simplificarlo si no necesitas soportar la situación commit=False , entonces será así:

def save(self): instance = forms.ModelForm.save(self) instance.topping_set.clear() for topping in self.cleaned_data[''toppings'']: instance.topping_set.add(topping)


Tuvimos un problema similar en nuestra aplicación, que usó admin de django. Hay muchas relaciones entre usuarios y grupos y uno no puede agregar usuarios fácilmente a un grupo. He creado un patch para django, que hace esto, pero no le presto mucha atención ;-) Puedes leerlo y tratar de aplicar una solución similar a tu problema de pizza / cobertura. De esta manera, al estar dentro de un topping, puede agregar fácilmente pizzas relacionadas o viceversa.