Search code examples
djangoajaxm2m

ValueError: Cannot assign value: must be an instance


I have four fields in a model, one of which is a foreign key field and the other three are m2m fields. The form is opened in the modal, but the data is not being saved, Error given below. I don't understand what I did wrong. I would be very grateful for a little help.

Model:

class ProductAttributes(models.Model):
    product     = models.ForeignKey('Product', blank=True, null=True, on_delete=models.SET_NULL)
    size        = models.ManyToManyField('ProductSize', blank=True)
    colour      = models.ManyToManyField('ProductColour', blank=True)
    cupsize     = models.ManyToManyField('ProductCupSize', blank=True)

    def __str__(self):
        return self.product

Form:

class ProductAttributesForm(forms.ModelForm):
    product     = forms.IntegerField(label=('ID'),required=True, disabled=True)
    size        = forms.ModelMultipleChoiceField(queryset=ProductSize.objects.all(),widget=Select2MultipleWidget, required=False)
    colour      = forms.ModelMultipleChoiceField(queryset=ProductColour.objects.all(),widget=Select2MultipleWidget, required=False)
    cupsize     = forms.ModelMultipleChoiceField(queryset=ProductCupSize.objects.all(),widget=Select2MultipleWidget, required=False)

    class Meta:
        model = ProductAttributes
        fields = ['product','size','colour','cupsize']

Template:

{% load crispy_forms_tags %}

<form id="Form" method="post" action="{% url 'accpack:products_attributes_create' product %}" class="js-product-create-form col s12" >
    {% csrf_token %}
    {% crispy form form.helper %}
</form>

View:

def save_attribute_form(request, form, template_name, pk):
    data = dict()
    if request.method == 'POST':
        if form.is_valid():
            form.save()
            data['form_is_valid'] = True
        else:
            data['form_is_valid'] = False
    context = {'form': form, 'product':pk}
    data['html_form'] = render_to_string(template_name, context, request=request)
    return JsonResponse(data)

def attribute_create(request, pk):
    if request.method == 'POST':
        form = ProductAttributesForm(request.POST, initial={'product': pk})
    else:
        form = ProductAttributesForm(initial={'product': pk})
    return save_attribute_form(request, form, 'main/products/partial_product_attribute_form.html', pk)

ajax:

 var saveForm = function () {
    var form = $(this);
    $.ajax({
      url: form.attr("action"),
      data: form.serialize(),
      type: form.attr("method"),
      dataType: 'json',
      success: function (data) {
        if (data.form_is_valid) {
          $("#modal-product_attribute").modal("hide");
          console.log(data.form_data);
        }
        else {
          $("#modal-product_attribute .modal-content").html(data.html_form);
        }
      }
    });
    return false;
  $("#modal-product_attribute").on("submit", ".js-product-create-form", saveForm);

error:

File "C:\ProgramData\Anaconda3\envs\djangoproject\lib\site-packages\django\db\models\fields\related_descriptors.py", line 220, in __set__
    self.field.remote_field.model._meta.object_name,
ValueError: Cannot assign "111": "ProductAttributes.product" must be a "Product" instance.

Solution

    1. You have an error in your forms.py.
        class Meta:
            model = ProductSize
            fields = ['product','size','colour','cupsize']
    

    Here, the model should be

          model = ProductAttributes
    

    Since the model you specified here ProductSize does exist, submitting form will just create another instance to ProductSize model without those specific fields and error messages. You can check your admin page.

    EDIT ****** 2. Based on what you added in the question and the error message, it looks like in attribute_create you are passing product's pk. Now in the attribute_create, you are passing initial dict {'product': pk}. This is wrong, because, in your ProductAttributesForm, the product field should be an instance rather than a pk.

    You need to refer product as

    product = Product.objects.filter(pk=pk)
    initial = {'product':product}
    

    The current error message will be gone, but you will have other errors.