Как успешно включать дополнительные поля и обновлять/удалять существующие поля (за один раз) с помощью modelformset_factory/inlineformset_factory
У меня есть готовые модели, добавление новых продуктов, с этим я разобрался, но включение дополнительных полей и обновление/удаление этих новых добавленных полей на одной html форме было такой болью, ниже приведены мои точные коды с небольшими изменениями
#my models.py
class Product(models.Model):
title = models.CharField()
class ProductSpecification(models.Model):
name = models.CharField()
class ProductSpecificationValue(models.Model):
product = models.ForeignKey(Product)
specification = models.ForeignKey(ProductSpecification)
value = models.CharField()
class ProductImages(models.Model):
product = models.ForeignKey(Product)
images = models.ImageField(upload_to="images/uploads/")
Здесь я покажу мой рабочий код для ДОБАВЛЕНИЯ новых объектов продукта (на всякий случай, если я делаю это неправильно, и это имеет отношение к тому, почему часть обновления не работает)
#MY Добавление формы продукта.py
class AddNewProductForm(forms.ModelForm):
title = forms.CharField()
class Meta:
model = Product
fields = ['title',]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['title'].widget.attrs.update{'class': 'form-control'})
class AddNewProductSpecForm(forms.ModelForm):
class Meta:
model = ProductSpecificationValue
fields = ['specification', 'value']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['specification'].widget.attrs.update({'class': 'form-control'})
self.fields['value'].widget.attrs.update({'class': 'form-control'})
class AddNewProductImgForm(forms.ModelForm):
#this is to upload multiple img in bulk at one go
images = forms.FileField(widget=forms.ClearableFileInput(attrs={'multiple': True}))
class Meta:
model = ProductImages
fields = ['images',]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['images'].widget.attrs.update({'class': 'form-control'})
#MY Добавление представления продукта.py
@login_required
def add_new_product(request):
AddNewProductFormSet = modelformset_factory(ProductSpecificationValue, form=AddNewProductSpecForm, extra=3)
if request.method == "POST":
product_form = AddNewProductForm(request.POST, request.FILES)
product_spec_form = AddNewProductFormSet(request.POST, request.FILES, queryset=ProductSpecificationValue.objects.none())
product_img_form = AddNewProductImgForm(request.POST, request.FILES)
if product_form.is_valid() and product_spec_form.is_valid() and product_img_form.is_valid:
product_form = product_form.save(commit=False)
product_form.save()
for form in product_spec_form.cleaned_data:
# this will not allow site crash if the user do not upload all the spec fields
if form:
specification = form['specification']
value = form['value']
spec = ProductSpecificationValue(product=product_form, specification=specification, value=value)
spec.save()
for img in request.FILES.getlist('images'):
ProductImages.objects.create(product=product_form, images=img)
return redirect('.')
else:
product_form=AddNewProductForm()
product_spec_form = AddNewProductFormSet(queryset=ProductSpecificationValue.objects.none())
product_img_form = AddNewProductImgForm()
return render(request,'add_new_product.html', {'product_form':product_form,
'product_spec_form':product_spec_form, 'product_img_form':product_img_form})
#Мой добавленный продукт add_new_product.html
{% block content %}
<form method="post" action="." enctype="multipart/form-data">
{% csrf_token %}
<div class="block">
<div class="label">{{ product_form.title.label }}:</div>
<div class="input">{{ product_form.title}}</div>
</div>
<div class="block">
{{ product_spec_form.management_form }}
{% for form in product_spec_form %}
<div>
<div class="place1">
<div class="label">{{ form.specification.label }}:</div>
<div class="input">{{ form.specification}}</div>
</div>
<div class="place2">
<div class="label">{{ form.value.label }}:</div>
<div class="input">{{ form.value}}</div>
</div>
</div>
{% endfor %}
</div>
<div class="block">
<div class="label">{{ product_img_form.images.label }}</div>
<div class="input">{{ product_img_form.images}}</div>
</div>
<button type="submit">Submit</button>
</form>
{% endblock %}
Это все для добавления нового продукта, теперь я покажу мой нерабочий код для добавления дополнительного объекта и обновления/удаления только что добавленного объекта
#MY Обновление формы продукта.py
class UpdateProductForm(forms.ModelForm):
title = forms.CharField()
class Meta:
model = Product
fields = ['title',]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['title'].widget.attrs.update{'class': 'form-control'})
class UpdateProductSpecForm(forms.ModelForm):
class Meta:
model = ProductSpecificationValue
fields = ['specification', 'value']
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['specification'].widget.attrs.update({'class': 'form-control'})
self.fields['value'].widget.attrs.update({'class': 'form-control'})
class UpdateProductImgForm(forms.ModelForm):
class Meta:
model = ProductImages
fields = ['images',]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['images'].widget.attrs.update({'class': 'form-control'})
#My Update product views.py(modelformset_factory)
@login_required
def edit_product(request, id):
product = get_object_or_404(Product, id=id)
product_spec = ProductSpecificationValue.objects.filter(product__id=product.id)
product_img = ProductImages.objects.filter(product__id=product.id)
UpdateProductImgFormSet = modelformset_factory (ProductImages, form=UpdateProductImgForm, extra=2, can_delete=True)
UpdateProductSpecFormSet = modelformset_factory (ProductSpecificationValue, form=UpdateProductSpecForm, extra=2, can_delete=True)
if request.method == "POST":
product_form = UpdateProductForm(request.POST or None, request.FILES or None, instance=product)
product_spec_form = UpdateProductSpecFormSet(data=request.POST or None, queryset=product_spec)
product_img_form = UpdateProductImgFormSet(data=request.FILES, queryset=product_img)
if product_form.is_valid() and product_spec_form.is_valid() and product_img_form.is_valid:
product_form = product_form.save(commit=False)
product_spec_form = product_spec_form.save(commit=False)
product_img_form = product_img_form.save(commit=False)
product_form.save()
product_spec_form.save()
for img in product_img_form:
updated_image = ProductImages(product=product_form, images=img)
updated_image.save()
return ('some_page')
else:
product_form = UpdateProductForm(instance=product)
product_spec_form = UpdateProductSpecFormSet(query=product_spec)
product_img_form = UpdateProductImgFormSet(query=product_img)
return render(request, 'update_product.html', {'product': product, 'product_form':
product_form, 'product_spec_form': product_spec_form, 'product_img_form': product_img_form})
Теперь, используя вышеуказанный modelformset_factory, он не выдает ошибок, отображает форму, как и предполагалось в update_product.html, правильно обновляет модель Parent Product, затем обновляет только первый объект в ProductSpecificationValue Model (оставляя остальные), но ничего не делает с ProductImages Model
#My Update product views.py(inlineformset_factory)
@login_required
def edit_product(request, id):
product = get_object_or_404(Product, id=id)
#product_spec = ProductSpecificationValue.objects.filter(product__id=product.id)
#product_img = ProductImages.objects.filter(product__id=product.id)
UpdateProductImgFormSet = inlineformset_factory(Product, ProductImages, form=UpdateProductImageForm, extra=2, can_delete=True)
UpdateProductSpecFormSet = inlineformset_factory(Product, ProductSpecificationValue, form=UpdateProductSpecForm, extra=2, can_delete=True)
if request.method == "POST":
product_form = UpdateProductForm(request.POST or None, request.FILES or None, instance=product)
product_spec_form = UpdateProductSpecFormSet(data=request.POST or None, instance=product)
product_img_form = UpdateProductImgFormSet(data=request.FILES, instance=product)
if product_form.is_valid() and product_spec_form.is_valid() and product_img_form.is_valid:
product_form = product_form.save(commit=False)
product_spec_form = product_spec_form.save(commit=False)
product_img_form = product_img_form.save(commit=False)
product_form.save()
product_spec_form.save()
for img in product_img_form:
updated_image = ProductImages(product=product_form, images=img)
updated_image.save()
return ('some_page')
else:
product_form = UpdateProductForm(instance=product)
product_spec_form = UpdateProductSpecFormSet(instance=product)
product_img_form = UpdateProductImgFormSet(instance=product)
return render(request, 'update_product.html', {'product': product, 'product_form':
product_form, 'product_spec_form': product_spec_form, 'product_img_form': product_img_form})
Теперь, используя вышеуказанный inlineformset_factory, который кажется более подходящим для обновлений, он выбрасывает всевозможные ошибки и даже не получает возможности отображения в update_product.html. сначала я не знаю какой экземпляр использовать для набора форм, либо [product = get_object_or_404(Product, id=id)] для всего как выше, либо [product_spec = ProductSpecificationValue.objects.filter(product__id=product.id)] и [product_img = ProductImages.objects.filter(product__id=product.id)] соответственно.
Оставив код в том виде, в котором он приведен выше, вы получите следующую ошибку Объект 'set' не имеет атрибута 'append' Переход к их соответствующему экземпляру дает следующую ошибку Объект 'QuerySet' не имеет атрибута 'pk'
Итак, пожалуйста, мне нужна корректировка, где я делаю ошибку, чтобы я мог обновить & также удалить каждый ProductSpecification & ProductImages успешно