用于多对多字段的 Django ModelForm [英] Django ModelForm for Many-to-Many fields
问题描述
考虑以下模型和形式:
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
当您查看 ToppingForm 时,它可以让您选择浇头的比萨饼,一切都很花哨.
When you view the ToppingForm it lets you choose what pizzas the toppings go on and everything is just dandy.
我的问题是:如何为 Pizza 定义 ModelForm,让我利用 Pizza 和 Topping 之间的多对多关系,并让我选择 Pizza 上的 Topping?
My questions is: How do I define a ModelForm for Pizza that lets me take advantage of the Many-to-Many relationship between Pizza and Topping and lets me choose what Toppings go on the Pizza?
推荐答案
我猜你应该在这里添加一个新的 ModelMultipleChoiceField
到你的 PizzaForm
,并手动链接它带有模型字段的表单字段,因为 Django 不会自动为您执行此操作.
I guess you would have here to add a new ModelMultipleChoiceField
to your PizzaForm
, and manually link that form field with the model field, as Django won't do that automatically for you.
以下片段可能会有所帮助:
The following snippet might be helpful :
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
这个 PizzaForm
然后可以在任何地方使用,甚至在管理员中:
This PizzaForm
can then be used everywhere, even in the 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)
注意
save()
方法可能有点冗长,但是如果你不需要支持 commit=False
的情况,你可以简化它,它会然后就是这样:
Note
The save()
method might be a bit too verbose, but you can simplify it if you don't need to support the commit=False
situation, it will then be like that :
def save(self):
instance = forms.ModelForm.save(self)
instance.topping_set.clear()
instance.topping_set.add(*self.cleaned_data['toppings'])
return instance
这篇关于用于多对多字段的 Django ModelForm的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!