1

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.
4
  • can you show the error, it looks like it is giving you the object but whenever you print the item itll print its str output Commented Jul 22, 2021 at 10:45
  • edited with the full traceback, let me know if that wasn't what you requested :)! I'm not sure about printing if its the string or not, technically the print statements where just me trying to figure out how to get the product exactly I just forgot to delete them before pasting it here Commented Jul 22, 2021 at 10:49
  • 1
    ah okay it looks like youre assigning the queryset to product on the form then when you are saving the form it is trying to assign that queryset to the review.product. You need to pass the correct product through to the form so that it saves to an object instead of queryset i think Commented Jul 22, 2021 at 10:53
  • yes exactly and I am not sure how to get the exact product from that queryset Commented Jul 22, 2021 at 10:54

1 Answer 1

2

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)
 
Sign up to request clarification or add additional context in comments.

5 Comments

Would this mean the only solution to the problem is to change my models? My idea was that I only wanted a user to be able to leave a review on the products they have purchased which is why I needed to make that queryset for the select options
Well if a review is against a product, leave the model as it is. You'd then want to find out how to get the products someone has ordered. I'll add some advise.
Thank you for the advice, sorry I am very new to all of this. If you have some time could you explain your solution? I get an error that queryset has no attribute value_list so i'm not entirely sure how to go about solving it
@mharre ah that's a typo on my part - sorry. It's values_list(). That returns just the fields you specify, and if you only want 1 field, you can pass flat=True and it'll bring back a list of the product IDs associated to that user. You can then offer a choice from only those products.
ahh okay thank you, I understand completely now. You're basically grabbing the ID's from the orderitem then querying the product and passing it them. Thank you very much for the solution and explanation which helps me learn, I appreciate it!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.