mharre
mharre

Reputation: 263

Django - ValueError cannot assign - must be a - instance

I believe I understand where my issue comes from I am just not sure how to solve it. The issue is that I am trying to save a review for a product but in the select field of the product the items which are displayed are ones they have purchased. I accomplished this by overriding the init method in my form. My issue is with this exact query it doesn't give me the product, it gives me the string that is queried (due to the str in my model). Therefore when I try to add the review to the product this error is getting thrown. My issue is I do not know how I can get the product exactly because in order to display what I want to display in the select field I have to give it that specific query. Any help is greatly appreciated

form

class ReviewForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        self.request = kwargs.pop('user')
        super(ReviewForm, self).__init__(*args, **kwargs)
        self.fields['product'].queryset = ShoppingCartOrderItem.objects.filter(user=self.request, ordered=True)
    #     for item in self.fields['product'].queryset:
    #         print(item.item)
    #     print(self.fields['product'].queryset)

    class Meta:
        model = Review
        exclude = ['user', 'date']
        widgets = {
            'product': forms.Select(attrs={
                'class': 'form-control'
            }),
            'rating': forms.NumberInput(attrs={
                'class': 'form-control',
                'placeholder': '5.0..'
            }),
            'comment': forms.Textarea(attrs={
                'class': 'form-control'
            })
        }

view

class MyOrdersView(LoginRequiredMixin, View):
    def get(self, request, *args, **kwargs):
        try:
            order = ShoppingCartOrder.objects.filter(user=self.request.user, ordered=True)
            form = ReviewForm(user=self.request.user)
            context = {
                'object': order,
                'form': form
            }
            return render(request, 'my_site/my_orders.html', context)
        except ObjectDoesNotExist:
            messages.warning(request, 'You do not have any orders')
            return redirect('all_products')

    def post (self, request, *args, **kwargs):
        form = ReviewForm(request.POST, user=self.request.user)
        if form.is_valid():
            test = form.save(commit=False)
            test.user = request.user
            test.save()
            return redirect('starting_page')
        return redirect('all_products')

models in question:

class Review(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    product = models.ForeignKey(Product, on_delete=models.SET_NULL, null=True)
    rating = models.DecimalField(max_digits=2, decimal_places=1, validators=[MinValueValidator(Decimal('0.1'))])
    comment = models.TextField(validators=[MinLengthValidator(10)])
    date = models.DateField(auto_now=True)

    def __str__(self):
        return str(self.user) 

class ShoppingCartOrderItem(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    ordered = models.BooleanField(default=False)
    item = models.ForeignKey(Product, on_delete=models.CASCADE)
    quantity = models.IntegerField(default=1)

    def __str__(self):
        return f'{self.quantity} of {self.item.name}'

    def get_total_order_price(self):
        return self.quantity * self.item.price

    def get_total_discount_item_price(self):
        return self.quantity * self.item.discount_price

    def get_final_price(self):
        if self.item.discount_price:
            return self.get_total_discount_item_price()
        return self.get_total_order_price()

    class Meta:
        verbose_name_plural = 'Order Item(link between)'

In my form the options in the select field are based off of this query self.fields['product'].queryset = ShoppingCartOrderItem.objects.filter(user=self.request, ordered=True). Based on the model it returns the string {self.quantity} of {self.item.name}. I believe this is where my issue is but to be honest I could be completely wrong, that is just my best guess haha.

Added a picture to try to make it more clear what I mean because my explanations are not the best select field options

edit: full traceback

Environment:


Request Method: POST
Request URL: http://localhost:8000/my-orders/

Django Version: 3.2.4
Python Version: 3.9.1
Installed Applications:
['my_site',
 'django_countries',
 'crispy_forms',
 'django.contrib.admin',
 'django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.staticfiles']
Installed Middleware:
['django.middleware.security.SecurityMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.common.CommonMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'django.middleware.clickjacking.XFrameOptionsMiddleware']



Traceback (most recent call last):
  File "C:\Users\Matthew\Desktop\VScode\e_com\.venv\lib\site-packages\django\core\handlers\exception.py", line 47, in inner
    response = get_response(request)
  File "C:\Users\Matthew\Desktop\VScode\e_com\.venv\lib\site-packages\django\core\handlers\base.py", line 181, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "C:\Users\Matthew\Desktop\VScode\e_com\.venv\lib\site-packages\django\views\generic\base.py", line 70, in view
    return self.dispatch(request, *args, **kwargs)
  File "C:\Users\Matthew\Desktop\VScode\e_com\.venv\lib\site-packages\django\contrib\auth\mixins.py", line 71, in dispatch
    return super().dispatch(request, *args, **kwargs)
  File "C:\Users\Matthew\Desktop\VScode\e_com\.venv\lib\site-packages\django\views\generic\base.py", line 98, in dispatch
    return handler(request, *args, **kwargs)
  File "C:\Users\Matthew\Desktop\VScode\e_com\my_site\views.py", line 349, in post
    if form.is_valid():
  File "C:\Users\Matthew\Desktop\VScode\e_com\.venv\lib\site-packages\django\forms\forms.py", line 175, in is_valid
    return self.is_bound and not self.errors
  File "C:\Users\Matthew\Desktop\VScode\e_com\.venv\lib\site-packages\django\forms\forms.py", line 170, in errors
    self.full_clean()
  File "C:\Users\Matthew\Desktop\VScode\e_com\.venv\lib\site-packages\django\forms\forms.py", line 374, in full_clean
    self._post_clean()
  File "C:\Users\Matthew\Desktop\VScode\e_com\.venv\lib\site-packages\django\forms\models.py", line 408, in _post_clean
    self.instance = construct_instance(self, self.instance, opts.fields, opts.exclude)
  File "C:\Users\Matthew\Desktop\VScode\e_com\.venv\lib\site-packages\django\forms\models.py", line 63, in construct_instance
    f.save_form_data(instance, cleaned_data[f.name])
  File "C:\Users\Matthew\Desktop\VScode\e_com\.venv\lib\site-packages\django\db\models\fields\__init__.py", line 910, in save_form_data
    setattr(instance, self.name, data)
  File "C:\Users\Matthew\Desktop\VScode\e_com\.venv\lib\site-packages\django\db\models\fields\related_descriptors.py", line 215, in __set__
    raise ValueError(

Exception Type: ValueError at /my-orders/
Exception Value: Cannot assign "<ShoppingCartOrderItem: 1 of Bayard defends a bridge over the Garigliano, 1505>": "Review.product" must be a "Product" instance.

Upvotes: 1

Views: 5734

Answers (1)

markwalker_
markwalker_

Reputation: 12859

The ReviewForm creates a queryset of ShoppingCartOrderItem objects for the product field. So you're using the wrong model for that field. It needs to be a queryset of the Product model

Exception Value: Cannot assign "<ShoppingCartOrderItem: 1 of Bayard defends a bridge over the Garigliano, 1505>": "Review.product" must be a "Product" instance.

The error here is telling you that you can't assign that ShoppingCartOrderItem that you've selected to Review.product, because it's not a Product.

The item of a ShoppingCartOrderItem is a product, so you can figure out what products a person has ordered by doing something like;

def __init__(self, *args, **kwargs):
    user = kwargs.pop('user')

    # First get a list of the product IDs from the `ShoppingCartOrderItem`
    # objects associated with the user passed to the form.
    ordered_product_ids = ShoppingCartOrderItem.objects.filter(user=user).values_list('item_id', flat=True)

    # Then filter the products by the ordered product IDs so 
    # they can only select from the products which they ordered.
    self.fields['product'].queryset = Product.objects.filter(id__in=ordered_product_ids)
 

Upvotes: 2

Related Questions