다 대다 필드를위한 Django ModelForm


80

다음 모델과 형식을 고려하십시오.

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을 어떻게 정의합니까?


따라서 아래 귀하의 의견에서 : 각각 Pizza은 많은를 가질 수 있습니다 Topping. 각각 Topping은 많은를 가질 수 있습니다 Pizza. 나는를 추가한다면 ToppingA와 Pizza것을 수행 Pizza한 후 자동적으로을 Topping반대하고, 그?
Jack M.

답변:


132

나는 새를 추가 하시려면 이곳을 것 같아요 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

멋져 보이지만 코드를 잘 이해하지 못합니다. 특히 'instance', save_m2m 및 old_save_m2m :)
Viet

1
@Viet : django의 양식 문서 ( docs.djangoproject.com/en/dev/topics/forms/modelforms/… )에서 django가 호출 save_m2mModelForm때 자동으로 메소드를 추가하는 것을 볼 수 있습니다 save(commit=False). 이것이 바로 제가 여기서하고있는 일이며, save_m2m관련 객체와 토핑 을 저장 하는 메서드를 추가 하고이 메서드는 원본 save_m2m.
Clément

3
이 솔루션이 Jack M.의 솔루션 (즉, 중간 모델 도입)보다 나은 점은 무엇입니까? 이 솔루션에는 더 많은 코드가 필요한 것 같습니다.
mb21 2013 년

예를 들어 믹스 인, 데코레이터 또는 다른 것을 사용하여이 로직을 리버스 M2M에 재사용 할 수 있습니까?
David D.

16

질문이 100 %인지 확실하지 않으므로 다음 가정으로 실행하겠습니다.

각각 Pizza은 많은를 가질 수 있습니다 Topping. 각각 Topping은 많은를 가질 수 있습니다 Pizza. A는하지만 ToppingA를 추가 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>'

/ requirements / add /의 AttributeError는 중개 모델을 지정하는 ManyToManyField에 값을 설정할 수 없습니다. 대신 requirements.AssetRequirement의 관리자를 사용하세요.
Eloy Roldán Paredes

7

솔직히 말해서 다 대다 관계를 Pizza모델에 넣을 것 입니다. 현실에 가깝다고 생각합니다. 피자를 여러 개 주문하는 사람을 상상해보십시오. 그는 "나는 피자 1과 2에 치즈를, 피자 1과 3에 토마토를 올리고 싶다"라고 말하지 않을 것이지만 아마도 "치즈 피자 하나, 치즈와 토마토 피자 하나 ..."

물론 양식이 당신의 방식으로 작동하도록 할 수는 있지만 다음과 같이 갈 것입니다.

class Pizza(models.Model):
    name = models.CharField(max_length=50)
    toppings = models.ManyToManyField(Topping)

5
Pizza / Topping 모델은 실제 모델의 변장 일뿐입니다. 이 질문의 목적은 내가 피자 모델 폼이 내가 토핑을 선택할 수 있도록하고 토핑 모델 폼이 내가 피자를 선택할 수 있도록하기를 원하기 때문입니다.
theycallmemorty

4

이를 달성하는 또 다른 간단한 방법은 중간 테이블을 만들고 인라인 필드를 사용하여 수행하는 것입니다. 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)

나는 이것이 관리 페이지 / 양식에만 적용된다고 가정합니다. 예를 들어 피자 선호도를 게시 할 수 있도록 익명 / 게스트 사용자 및 / 또는 로그인 한 사용자를 위해 유사하게 만드는 방법은 무엇입니까?
user1271930

2

이것이 당신이 찾고있는 것이 맞는지 확실하지 않지만, 피자에 topping_set속성 이 있다는 것을 알고 있습니까? 이 속성을 사용하면 ModelForm에 새 토핑을 쉽게 추가 할 수 있습니다.

new_pizza.topping_set.add(new_topping)

2

우리는 django admin을 사용하는 앱에서도 비슷한 문제가 발생했습니다. 사용자와 그룹 사이에는 다 대다 관계가 있으며 사용자를 그룹에 쉽게 추가 할 수 없습니다. 나는 django에 대한 패치 를 만들었습니다. 하지만 그것에 대해서는 그다지 관심이 없습니다 ;-) 당신은 그것을 읽고 당신의 피자 / 토핑 문제에 유사한 해결책을 적용 할 수 있습니다. 이렇게하면 토핑 내부에 관련 피자를 쉽게 추가 할 수 있으며 그 반대도 가능합니다.


0

사용자 관리 양식을 사용하여 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)

0

관계에서 테이블의 두 기본 키에 종속 된 항목을 추가하려는 경우 통과 테이블을 사용할 수도 있습니다. 다 대다 관계는 브리지 테이블이라는 것을 사용하여 기본 키의 두 부분에 종속 된 항목을 저장합니다.

예를 들어 models.py에서 Order와 Product 간의 다음 관계를 고려하십시오.

class Order(models.Model):
    date = models.DateField()
    status = models.CharField(max_length=30)

class Product(models.Model):
    name = models.CharField(max_length=50)
    desc = models.CharField(max_length=50)
    price = models.DecimalField(max_dights=7,decimal_places=2)
    qtyOnHand = models.Integer()
    orderLine = models.ManyToManyField(Order, through='OrderLine')

class OrderLine(models.Model):
    product = models.ForeignKey(Product)
    order = models.ForeignKey(Order)
    qtyOrd = models.Integer()

귀하의 경우, 토핑에 ManyToMany를 넣는 것입니다. 사용자가 원하는 피자에 어떤 토핑을 넣을지 선택할 수 있기 때문입니다. 간단하지만 강력한 솔루션입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.