次のモデルとフォームを検討してください。
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を表示すると、トッピングのピザを選択できます。
私の質問は次のとおりです。ピザとトッピングの多対多の関係を活用し、ピザにトッピングするものを選択できるようにするピザのModelFormを定義するにはどうすればよいですか。
新しいModelMultipleChoiceField
をPizzaForm
に追加し、そのフォームフィールドをモデルフィールドに手動でリンクする必要があると思います。Djangoはそれを自動的に行います。
次のスニペットが役立つ場合があります。
_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
_
100%の質問を受け取るかどうかは定かではないので、この仮定で実行します。
各Pizza
には、多くのTopping
を含めることができます。各Topping
には、多くのPizza
を含めることができます。ただし、Topping
にPizza
が追加された場合、そのTopping
は自動的にPizza
になり、その逆も同様です。
この場合、最善の策はリレーションシップテーブルです。これはDjangoが非常によくサポートされています。次のようになります。
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
例:
>>> 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>'
正直に言うと、多対多のリレーションをPizza
モデルに入れます。これは現実に近いと思います。いくつかのピザを注文する人を想像してください。 「ピザ1と2にチーズ、ピザ1と3にトマトが欲しい」とは言わないでしょうが、おそらく「チーズ1枚のピザ、チーズ1枚とトマトの1枚のピザ...」と言うでしょう。
もちろん、あなたのやり方でフォームを機能させることは可能ですが、私は次のようにします:
class Pizza(models.Model):
name = models.CharField(max_length=50)
toppings = models.ManyToManyField(Topping)
これを実現する別の簡単な方法は、中間テーブルを作成し、インラインフィールドを使用してそれを実行することです。これを参照してください https://docs.djangoproject.com/en/1.2/ref/contrib/admin/#working-with-many-to-many-intermediary-models
以下のサンプルコード
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)
これがあなたの探しているものかどうかはわかりませんが、ピザにはtopping_set
属性?その属性を使用すると、ModelFormに新しいトッピングを簡単に追加できます。
new_pizza.topping_set.add(new_topping)
Django adminを使用するアプリでも同様の問題が発生しました。ユーザーとグループの間には多くの関係があり、ユーザーをグループに簡単に追加することはできません。 patch Djangoの場合、これを行いますが、あまり注意を払っていません;-)これを読んで、ピザ/トッピングの問題に同様の解決策を適用してみてください。この方法でトッピングの中に入れると、関連するピザを簡単に追加できます。
ユーザー管理フォームを使用してClémentのコードに基づいて同様のことを行いました。
# 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)