用于多对多字段的 Django ModelForm [英] Django ModelForm for Many-to-Many fields

查看:41
本文介绍了用于多对多字段的 Django ModelForm的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

考虑以下模型和形式:

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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆