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 ve el ToppingForm, le permite elegir en qué pizzas van los ingredientes y todo es excelente.
Mi pregunta es: ¿Cómo defino un ModelForm para pizza que me permita aprovechar la relación de muchos a muchos entre Pizza y Topping y me permita elegir qué Toppings van a la pizza?
python
django
django-forms
ellos llaman la memoria
fuente
fuente

Pizzapuede tener muchosToppings. Cada unoToppingpuede tener muchosPizzas. Pero si agrego aToppingaPizza, ¿esoPizzaautomáticamente tiene aTopping, y viceversa?Respuestas:
Supongo que tendría que agregar uno nuevo
ModelMultipleChoiceFielda suPizzaFormy vincular manualmente ese campo de formulario con el campo del modelo, ya que Django no lo hará automáticamente por usted.El siguiente fragmento puede resultar ú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() instance.topping_set.add(*self.cleaned_data['toppings']) self.save_m2m = save_m2m # Do we need to save all changes now? if commit: instance.save() self.save_m2m() return instanceEsto
PizzaFormse puede usar 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
save()método puede ser demasiado detallado, pero puede simplificarlo si no necesita respaldar lacommit=Falsesituación, entonces será así:def save(self): instance = forms.ModelForm.save(self) instance.topping_set.clear() instance.topping_set.add(*self.cleaned_data['toppings']) return instancefuente
save_m2mmétodo a suModelFormcuando lo llamasave(commit=False). Esto es exactamente lo que estoy haciendo aquí, agregando unsave_m2mmétodo para guardar objetos y ingredientes relacionados , y este método llama al originalsave_m2m.No estoy seguro de obtener la pregunta al 100%, así que voy a ejecutar esta suposición:
Cada uno
Pizzapuede tener muchosToppings. Cada unoToppingpuede tener muchosPizzas. Pero siToppingse agrega a aPizza,Toppingentonces automáticamente tendrá aPizza, y viceversa.En este caso, su mejor opción es una tabla de relaciones, que Django admite bastante bien. Podría verse así:
modelos.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.nameformularios.py
class PizzaForm(forms.ModelForm): class Meta: model = Pizza class ToppingForm(forms.ModelForm): class Meta: model = ToppingEjemplo:
>>> 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>'fuente
Para ser honesto, pondría la relación de muchos a muchos en el
Pizzamodelo. Creo que esto se acerca más a la realidad. Imagínese una persona que pide varias pizzas. No diría "Me gustaría queso en la pizza uno y dos y tomates en la pizza uno y tres", sino probablemente "Una pizza con queso, una pizza con queso y tomates, ...".Por supuesto, es posible hacer que el formulario funcione en su camino, pero yo iría con:
class Pizza(models.Model): name = models.CharField(max_length=50) toppings = models.ManyToManyField(Topping)fuente
Otra forma sencilla 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
Algún código de muestra a continuación
modelos.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)fuente
No estoy seguro de si esto es lo que está buscando, pero ¿sabe que Pizza tiene el
topping_setatributo? Usando ese atributo, podría agregar fácilmente un nuevo ingrediente en su ModelForm.fuente
Tuvimos un problema similar en nuestra aplicación, que usaba django admin. Existe una relación de muchas a muchas entre usuarios y grupos y no se pueden agregar fácilmente usuarios a un grupo. He creado un parche para django, que hace esto, pero no se le presta mucha atención ;-) Puede leerlo e intentar aplicar una solución similar a su problema de pizza / aderezo. De esta manera, al estar dentro de un aderezo, puede agregar fácilmente pizzas relacionadas o viceversa.
fuente
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)fuente
También puede usar una tabla directa si desea agregar elementos que dependan de ambas claves primarias de la tabla en la relación. Muchas a muchas relaciones usan algo llamado tabla puente para almacenar cosas que dependen de ambas partes de la clave principal.
Por ejemplo, considere la siguiente relación entre Pedido y Producto en models.py
class Order(models.Model): date = models.DateField() status = models.CharField(max_length=30) class Product(models.Model): name = models.CharField(max_length=50) desc = models.CharField(max_length=50) price = models.DecimalField(max_dights=7,decimal_places=2) qtyOnHand = models.Integer() orderLine = models.ManyToManyField(Order, through='OrderLine') class OrderLine(models.Model): product = models.ForeignKey(Product) order = models.ForeignKey(Order) qtyOrd = models.Integer()En su caso, lo que haría es poner ManyToMany en los ingredientes, porque le permite al usuario elegir qué ingredientes van a la pizza que desea. Solución simple pero poderosa.
fuente