Рассмотрим следующие модели и формы:
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 для пиццы, которая позволяет мне использовать преимущества отношения «многие ко многим» между пиццей и топпингом и позволяет мне выбирать, какие начинки идут в пиццу?
python
django
django-forms
они называют память
источник
источник
Pizza
может быть многоTopping
s. У каждогоTopping
может быть многоPizza
s. Но если я добавлю aTopping
к aPizza
, будет ли этоPizza
автоматически иметь aTopping
, и наоборот?Ответы:
Я предполагаю, что вам придется здесь добавить новое
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
метод к вашему,ModelForm
когда вы его вызываетеsave(commit=False)
. Это именно то, что я здесь делаю, добавляяsave_m2m
метод для сохранения связанных объектов и начинок , и этот метод вызывает оригиналsave_m2m
.Я не уверен, что отвечу на вопрос на 100%, поэтому я буду исходить из следующего предположения:
У каждого
Pizza
может быть многоTopping
s. У каждогоTopping
может быть многоPizza
s. Но еслиTopping
добавить к aPizza
,Topping
тогда автоматически будет aPizza
, и наоборот.В этом случае лучшим выбором будет таблица отношений, которую 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
модель отношение «многие ко многим» . Думаю, это ближе к реальности. Представьте себе человека, который заказывает несколько пицц. Он не сказал бы: «Я бы хотел сыр на пиццу номер один и второй и помидоры на пиццу один и три», но, вероятно, «Одна пицца с сыром, одна пицца с сыром и помидорами, ...».Конечно, можно заставить форму работать по-вашему, но я бы пошел с:
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.источник
У нас была аналогичная проблема в нашем приложении, которое использовало админку django. Между пользователями и группами существует связь "многие ко многим", и добавить пользователей в группу нелегко. Я создал патч для 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)
источник
Вы также можете использовать сквозную таблицу, если хотите добавить материал, который зависит от обоих первичных ключей таблицы в отношении. Отношения «многие ко многим» используют так называемую таблицу мостов для хранения данных, зависящих от обеих частей первичного ключа.
Например, рассмотрите следующую связь между заказом и продуктом в models.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()
В вашем случае вы бы поместили ManyToMany на начинки, потому что это позволяет пользователю выбирать, какие начинки использовать для пиццы, которую он хочет. Простое, но мощное решение.
источник