[python] 다 대다 필드를위한 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을 볼 때 토핑에 어떤 피자를 넣을지 선택할 수 있으며 모든 것이 멋지게 보입니다.
내 질문은 다음과 같습니다. Pizza와 Topping 사이의 다 대다 관계를 활용하고 피자에서 토핑을 선택할 수 있도록하는 Pizza에 대한 ModelForm을 어떻게 정의합니까?
답변
나는 새를 추가 하시려면 이곳을 것 같아요 ModelMultipleChoiceField
당신에게 PizzaForm
, 그리고 장고 당신을 위해 자동으로 수행되지 않으므로 수동 모델 필드가 양식 필드를 연결합니다.
다음 스 니펫이 도움이 될 수 있습니다.
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
. A는하지만 Topping
A를 추가 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에 토마토를 올리고 싶다”라고 말하지 않을 것이지만 아마도 “치즈 피자 하나, 치즈와 토마토 피자 하나 …”
물론 양식이 당신의 방식으로 작동하도록 할 수는 있지만 다음과 같이 갈 것입니다.
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을 사용하는 앱에서도 비슷한 문제가 발생했습니다. 사용자와 그룹 사이에는 다 대다 관계가 있으며 사용자를 그룹에 쉽게 추가 할 수 없습니다. 나는 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)