कई-कई क्षेत्रों के लिए 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

जब आप टॉपिंगफार्म को देखते हैं तो यह आपको चुनता है कि किस टॉपिंग पर पिज्जा चला जाए और सब कुछ बस बांका हो।

मेरे सवाल हैं: मैं पिज्जा के लिए एक मॉडलफार्म को कैसे परिभाषित करूं जो मुझे पिज्जा और टॉपिंग के बीच कई-से-कई संबंधों का लाभ उठाने देता है और मुझे यह चुनने देता है कि पिज्जा पर टॉपिंग्स क्या हैं?


तो नीचे अपनी टिप्पणियों से: प्रत्येक Pizzaमें कई Toppingएस हो सकते हैं । प्रत्येक Toppingमें कई Pizzaएस हो सकते हैं । लेकिन अगर मैं एक जोड़ने के Toppingएक करने के लिए Pizzaकरता है कि Pizzaउसके बाद पूर्ण रूप से अपने एक है Toppingविपरीत है, और इसके?
जैक एम।

जवाबों:


132

मुझे लगता है कि तुम यहाँ के लिए होता है एक नया जोड़ने के लिए 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

यह अच्छा लग रहा है, लेकिन मैं कोड को समझ नहीं पाया, 'उदाहरण' के लिए, save_m2m और old_save_m2m :)
Viet

1
@Viet: रूपों पर django के दस्तावेज़ में ( docs.djangoproject.com/en/dev/topics/forms/modelforms/… ), आप देख सकते हैं कि आपके द्वारा कॉल करने पर django स्वचालित रूप save_m2mसे आपके लिए एक विधि जोड़ देता है। यह वही है जो मैं यहां कर रहा हूं, संबंधित वस्तुओं और टॉपिंग को बचाने के लिए एक विधि जोड़ रहा हूं , और यह विधि मूल कॉल करती है । ModelFormsave(commit=False)save_m2msave_m2m
क्लेमेंट

3
यह समाधान जैक एम। से बेहतर कैसे है, यानी एक मध्यवर्ती मॉडल की शुरुआत? इस समाधान के लिए बहुत अधिक कोड की आवश्यकता होती है।
mb21

क्या यह तर्क किसी भी रिवर्स एम 2 एम के लिए पुन: प्रयोज्य हो सकता है, उदाहरण के लिए एक मिक्सिन, डेकोरेटर या कुछ और?
डेविड डी।

16

मुझे यकीन नहीं है कि मुझे प्रश्न 100% मिलेगा, इसलिए मैं इस धारणा के साथ चलने वाला हूं:

प्रत्येक Pizzaमें कई Toppingएस हो सकते हैं । प्रत्येक Toppingमें कई Pizzaएस हो सकते हैं । लेकिन अगर इसमें a Toppingजोड़ा जाता है Pizza, Toppingतो फिर ऑटोमैटिक रूप से a Pizza, और इसके विपरीत होगा।

इस मामले में, आपकी सबसे अच्छी शर्त एक रिलेशनशिप टेबल है, जिसे Django काफी अच्छी तरह से सपोर्ट करता है। यह इस तरह दिख सकता है:

model.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

रूपों

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>'

एट्रीब्यूट / एट / रिक्वायरमेंट्स / ऐड / कनेक्ट नहीं कर सकते हैं मानों को एकTTMManyField पर सेट करता है जो एक मध्यस्थ मॉडल को निर्दिष्ट करता है। आवश्यकताओं का उपयोग करें। इसके बजाय ऑफसेटस्क्रिप्समेंट प्रबंधक।
एलो रल्डन परेड्स

7

ईमानदार होने के लिए, मैं कई-से-कई संबंध Pizzaमॉडल में डालूंगा । मुझे लगता है कि यह वास्तविकता के करीब है। एक ऐसे व्यक्ति की कल्पना करें जो कई पिज्जा ऑर्डर करता है। वह यह नहीं कहेगा कि "मुझे पिज्जा एक और दो पर पनीर चाहिए और पिज्जा एक और तीन पर टमाटर" लेकिन शायद "पनीर के साथ एक पिज्जा, पनीर और टमाटर के साथ एक पिज्जा ..."।

बेशक आपके रास्ते में काम करना संभव है, लेकिन मैं इसके साथ जाऊंगा:

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

5
पिज़्ज़ा / टॉपिंग मॉडल मेरे असली मॉडल के लिए एक भेस हैं। इस सवाल का उद्देश्य यह है क्योंकि मैं चाहता हूं कि पिज़्ज़ा मॉडलफ़ॉर्म मुझे टॉपिंग चुनने दें और मैं चाहता हूं कि टॉपिंग मॉडलफ़ॉर्म मुझे पिज़ा चुनने दें।
themcallmemorty

4

इसे प्राप्त करने का एक और सरल तरीका एक मध्यस्थ तालिका बनाना और इसे प्राप्त करने के लिए इनलाइन क्षेत्रों का उपयोग करना है। कृपया इस https://docs.djangoproject.com/en/1.2/ref/contrib/admin/#working-with-many-to-many-intermediary-models को देखें

नीचे कुछ नमूना कोड

model.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)

व्यवस्थापक

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विशेषता है? उस विशेषता का उपयोग करके आप आसानी से अपने मॉडलफार्म में एक नया टॉपिंग जोड़ सकते हैं।

new_pizza.topping_set.add(new_topping)

2

हमें अपने ऐप में ऐसी ही समस्या थी, जो django admin का उपयोग करता था। उपयोगकर्ताओं और समूहों के बीच कई संबंध हैं और कोई आसानी से उपयोगकर्ताओं को एक समूह में नहीं जोड़ सकता है। मैंने django के लिए एक पैच बनाया है , जो ऐसा करता है, लेकिन इस पर बहुत ध्यान नहीं दिया जाता; ;-) आप इसे पढ़ सकते हैं और अपने पिज़्ज़ा / टॉपिंग समस्या के समान समाधान लागू करने का प्रयास कर सकते हैं। इस तरह एक टॉपिंग के अंदर होने के कारण, आप आसानी से संबंधित पिज्जा या इसके विपरीत जोड़ सकते हैं।


0

मैंने उपयोगकर्ता व्यवस्थापक फ़ॉर्म के साथ क्लेमेंट के कोड के आधार पर कुछ ऐसा ही किया:

# 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

यदि आप संबंध में तालिका की दोनों प्राथमिक कुंजियों पर निर्भर है, तो सामान जोड़ना चाहते हैं, तो आप तालिका के माध्यम से भी उपयोग कर सकते हैं। कई रिश्तों के लिए कई सामानों को संग्रहीत करने के लिए एक पुल तालिका का उपयोग करते हैं जो प्राथमिक कुंजी के दोनों हिस्सों पर निर्भर है।

उदाहरण के लिए, model.py में ऑर्डर और प्रोडक्ट के बीच निम्न संबंध पर विचार करें

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()

आपके मामले में, आप जो कुछ भी करेंगे उसे कई टॉपमनी टॉपपिंग्स पर डाल दिया जाएगा, क्योंकि यह उपयोगकर्ता को यह चुनने देता है कि टॉपिंग पिज्जा पर क्या चाहते हैं। सरल, लेकिन शक्तिशाली समाधान।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.