1. ホーム
  2. python

[解決済み] 多対多フィールドのための Django モデルフォーム

2023-06-05 13:02:34

質問

次のような機種と形状を考えてみましょう。

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を表示すると、トッピングを載せるピザを選択することができ、すべてがうまくいっています。

私の質問です。Pizza と Topping の間の多対多の関係を利用し、Pizza に載せるトッピングを選択できる Pizza のモデルフォームを定義するにはどうしたらよいでしょうか。

どのように解決するのですか?

ここで、新しい ModelMultipleChoiceFieldPizzaForm に追加し、そのフォームフィールドとモデルフィールドを手動でリンクしてください。

次のスニペットが役に立つかもしれません。

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 は、管理画面でも、どこでも使えるようになります。

# 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 のような状況になれば、その時はそのようになります :

def save(self):
    instance = forms.ModelForm.save(self)
    instance.topping_set.clear()
    instance.topping_set.add(*self.cleaned_data['toppings'])
    return instance