Betrachten Sie die folgenden Modelle und Formulare:
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
Wenn Sie sich die ToppingForm ansehen, können Sie auswählen, auf welchen Pizzen die Toppings liegen, und alles ist nur Dandy.
Meine Fragen lauten: Wie definiere ich ein ModelForm für Pizza, mit dem ich die Viele-zu-Viele-Beziehung zwischen Pizza und Topping nutzen und auswählen kann, welche Toppings auf die Pizza passen?
python
django
django-forms
sie rufen mich an
quelle
quelle
Pizza
kann vieleTopping
s haben. JederTopping
kann vielePizza
s haben. Aber wenn ich aTopping
zu a hinzufügePizza
, hat dasPizza
dann automatisch ein aTopping
und umgekehrt?Antworten:
Ich denke , man würde hier eine neue hinzufügen ,
ModelMultipleChoiceField
um IhrePizzaForm
und verknüpfen manuell dieses Formularfeld mit dem Modellfeld, wie Django wird das nicht automatisch für Sie.Das folgende Snippet könnte hilfreich sein:
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 instance
Dies
PizzaForm
kann dann überall verwendet werden, auch im Admin:# 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)
Hinweis
Die
save()
Methode ist vielleicht etwas zu ausführlich, aber Sie können sie vereinfachen, wenn Sie diecommit=False
Situation nicht unterstützen müssen. Dann sieht es so aus :def save(self): instance = forms.ModelForm.save(self) instance.topping_set.clear() instance.topping_set.add(*self.cleaned_data['toppings']) return instance
quelle
save_m2m
Methode hinzufügt ,ModelForm
wenn Sie es aufrufensave(commit=False)
. Genau das mache ich hier, indem ich einesave_m2m
Methode zum Speichern verwandter Objekte und Beläge hinzufüge. Diese Methode ruft das Original aufsave_m2m
.Ich bin mir nicht sicher, ob ich die Frage zu 100% bekomme, also gehe ich von dieser Annahme aus:
Jeder
Pizza
kann vieleTopping
s haben. JederTopping
kann vielePizza
s haben. Aber wenn aTopping
zu a hinzugefügt wird,Pizza
hat dasTopping
automatisch aPizza
und umgekehrt.In diesem Fall ist eine Beziehungstabelle die beste Wahl, die Django recht gut unterstützt. Es könnte so aussehen:
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
Beispiel:
>>> 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>'
quelle
Um ehrlich zu sein, würde ich die Viele-zu-Viele-Beziehung in das
Pizza
Modell einbauen. Ich denke das näher an der Realität. Stellen Sie sich eine Person vor, die mehrere Pizzen bestellt. Er würde nicht sagen "Ich möchte Käse auf Pizza eins und zwei und Tomaten auf Pizza eins und drei", sondern wahrscheinlich "Eine Pizza mit Käse, eine Pizza mit Käse und Tomaten, ...".Natürlich ist es möglich, dass das Formular auf Ihre Weise funktioniert, aber ich würde folgendermaßen vorgehen:
class Pizza(models.Model): name = models.CharField(max_length=50) toppings = models.ManyToManyField(Topping)
quelle
Eine andere einfache Möglichkeit, dies zu erreichen, besteht darin, eine Zwischentabelle zu erstellen und Inline-Felder zu verwenden, um dies zu erreichen. Weitere Informationen finden Sie unter https://docs.djangoproject.com/de/1.2/ref/contrib/admin/#working-with-many-to-many-intermediary-models
Einige Beispielcodes unten
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)
quelle
Ich bin mir nicht sicher, ob Sie danach suchen, aber wissen Sie, dass Pizza das
topping_set
Attribut hat? Mit diesem Attribut können Sie Ihrem ModelForm problemlos einen neuen Belag hinzufügen.quelle
Wir hatten ein ähnliches Problem in unserer App, die django admin verwendete. Es gibt viele zu viele Beziehungen zwischen Benutzern und Gruppen, und man kann Benutzer nicht einfach zu einer Gruppe hinzufügen. Ich habe einen Patch für Django erstellt, der dies tut, aber es wird nicht viel darauf geachtet ;-) Sie können ihn lesen und versuchen, eine ähnliche Lösung für Ihr Pizza- / Topping-Problem anzuwenden. Auf diese Weise können Sie in einem Belag ganz einfach verwandte Pizzen hinzufügen oder umgekehrt.
quelle
Ich habe etwas Ähnliches basierend auf dem Code von Clément mit einem Benutzerverwaltungsformular gemacht:
# 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)
quelle
Sie können auch eine Durch-Tabelle verwenden, wenn Sie Inhalte hinzufügen möchten, die von beiden Primärschlüsseln der Tabelle in der Beziehung abhängig sind. Viele bis viele Beziehungen verwenden eine sogenannte Brückentabelle, um Dinge zu speichern, die von beiden Teilen des Primärschlüssels abhängig sind.
Betrachten Sie beispielsweise die folgende Beziehung zwischen Bestellung und Produkt in 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()
In Ihrem Fall würden Sie das ManyToMany auf die Beläge legen, da der Benutzer damit auswählen kann, welche Beläge auf die gewünschte Pizza passen. Einfache, aber leistungsstarke Lösung.
quelle