Reputation: 91
I have this strange TypeError raised : "Object of type Product is not JSON serializable" when I try to set a session value in a view (in basket app). The error occurs with request.session['Hello'] = 'foo'
.
However, this error does not occur elsewhere. For instance, in store app, in views.py, the following request.session['Hello World'] = 'Alloy'
works very well.
Why is that happening ?
from django.shortcuts import render, get_object_or_404
from django.http import JsonResponse
from . basket import Basket
from store.models import Product
from discount.forms import UserDiscountForm
def basket_summary(request):
basket = Basket(request)
context = {'basket':basket}
request.session['Hello'] = 'foo'
return render(request,"store/basket_summary.html",context)
def basket_add(request):
basket = Basket(request)
if request.POST.get('action') == 'post':
product_id = int(request.POST.get('productid'))
product_qty = int(request.POST.get('productqty'))
product = get_object_or_404(Product, id=product_id)
basket.add(product=product, qty=product_qty)
basketqty = basket.__len__()
response = JsonResponse({'qty':basketqty})
return response
def basket_add_new(request):
basket = Basket(request)
if request.POST.get('action') == 'post':
product_id = int(request.POST.get('productid'))
product = get_object_or_404(Product, id=product_id)
basket.add_new(product=product)
basketqty = basket.__len__()
response = JsonResponse({'qty':basketqty})
return response
def basket_delete(request):
basket = Basket(request)
if request.POST.get('action') == 'post':
product_id = int(request.POST.get('productid'))
basket.delete(product=product_id)
basketqty = basket.__len__()
baskettotal = basket.get_total_price()
response = JsonResponse({'qty':basketqty, 'subtotal':baskettotal})
return response
def basket_update(request):
basket = Basket(request)
if request.POST.get('action') == 'update-basket':
product_id = int(request.POST.get('productid'))
product_qty = int(request.POST.get('productqty'))
basket.update(product=product_id, qty=product_qty)
basketqty = basket.__len__()
baskettotal = basket.get_total_price()
itemtotal = basket.get_subtotal_price(product=product_id)
response = JsonResponse({'qty':basketqty, 'baskettotal':baskettotal, 'product_qty':product_qty, 'itemtotal':itemtotal})
return response
from decimal import Decimal
from store.models import Product
class Basket():
def __init__(self, request):
self.session = request.session
basket = self.session.get('cart')
if 'cart' not in request.session:
basket = self.session['cart'] = {}
self.basket = basket
def save(self):
self.session.modified = True
def add(self, product, qty):
"""
Adding and updating basket session data
"""
product_id = str(product.id)
price = float(product.price)
subtotal = qty * price
if product_id not in self.basket:
self.basket[product_id] = {'price': price, 'qty':int(qty), 'subtotal': subtotal}
else:
self.basket[product_id]['qty'] = qty
self.basket[product_id]['subtotal'] = subtotal
self.save()
def add_new(self, product):
"""
Adding new item in basket session data
"""
product_id = str(product.id)
if product_id not in self.basket:
self.basket[product_id] = {'price': float(product.price), 'qty':1, 'subtotal':float(product.price)}
# self.basket[product_id] = {'price': float(product.price), 'qty':1}
else:
pass
self.save()
def __iter__(self):
"""
Collect the product_id in the session data to query the database and return products
"""
product_ids = self.basket.keys()
products = Product.objects.filter(id__in=product_ids)
basket = self.basket.copy()
for product in products:
basket[str(product.id)]['product'] = product
for item in basket.values():
item['price'] = float(item['price'])
item['total_price'] = item['price'] * item['qty']
yield item
def __len__(self):
"""
Get the basket data and count the quantity of all items
"""
return sum(item['qty'] for item in self.basket.values())
def get_total_price(self):
return sum(float(item['price']) * item['qty'] for item in self.basket.values())
def get_subtotal_price(self, product):
product_id = str(product)
return self.basket[product_id]['qty'] * self.basket[product_id]['price']
def delete(self, product):
"""
Delete item from session data
"""
product_id = str(product)
if product_id in self.basket:
del self.basket[product_id]
self.save()
def update(self, product, qty):
"""
Update item in session data
"""
product_id = str(product)
if product_id in self.basket:
self.basket[product_id]['qty'] = qty
self.basket[product_id]['subtotal'] = self.basket[product_id]['qty'] * self.basket[product_id]['price']
self.save()
def clear(self):
try:
del self.session['cart']
except KeyError:
pass
self.save()
from django.db import models
from django.urls import reverse
class Category(models.Model):
name = models.CharField(max_length=254, db_index=True)
slug = models.SlugField(max_length=254, unique = True)
class Meta:
verbose_name_plural = 'categories'
def __str__(self):
return self.name
class Product(models.Model):
category = models.ForeignKey(Category, related_name='product', on_delete=models.CASCADE)
title = models.CharField(max_length=254)
description = models.TextField(blank=True)
image = models.ImageField(upload_to='images/', default='images/default.png')
slug = models.SlugField(max_length=254, unique = True)
price = models.DecimalField(max_digits=5, decimal_places=2)
is_active = models.BooleanField(default=True)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
image2 = models.ImageField(upload_to='images/', null=True, blank=True)
image3 = models.ImageField(upload_to='images/', null=True, blank=True)
stock = models.IntegerField()
weight = models.IntegerField(verbose_name='Poids (g)')
class Meta:
verbose_name_plural = 'products'
ordering = ('-created', ) # ordering in descending order
def get_absolute_url(self):
return reverse('store:product_detail', args=[self.slug])
def __str__(self):
return self.title
from django.shortcuts import get_object_or_404, render
from requests.sessions import session
from .models import Category, Product
def home(request):
print('----------// HOME PAGE //----------')
request.session['Hello World'] = 'Alloy'
context = {}
return render(request, 'store/home.html', context)
def categories(request):
categories = Category.objects.all()
context = {'categories': categories}
return render(request, 'store/categories.html', context)
def all_products(request):
products = Product.objects.all()
context = {'products': products}
return render(request, 'store/all_products.html', context)
def product_detail(request, slug):
product = get_object_or_404(Product, slug=slug, is_active=True)
context = {'product': product}
return render(request, 'store/product_details.html', context)
Environment:
Request Method: GET
Request URL: http://127.0.0.1:8000/basket/
Django Version: 3.2
Python Version: 3.9.4
Installed Applications:
['django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'store',
'account',
'basket',
'orders',
'payment',
'contact',
'address',
'discount',
'shipping']
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\Utilisateur\Documents\Environments\monoi_django_virtualenv\lib\site-packages\django\core\handlers\exception.py", line 47, in inner
response = get_response(request)
File "C:\Users\Utilisateur\Documents\Environments\monoi_django_virtualenv\lib\site-packages\django\utils\deprecation.py", line 119, in __call__
response = self.process_response(request, response)
File "C:\Users\Utilisateur\Documents\Environments\monoi_django_virtualenv\lib\site-packages\django\contrib\sessions\middleware.py", line 61, in process_response
request.session.save()
File "C:\Users\Utilisateur\Documents\Environments\monoi_django_virtualenv\lib\site-packages\django\contrib\sessions\backends\db.py", line 83, in save
obj = self.create_model_instance(data)
File "C:\Users\Utilisateur\Documents\Environments\monoi_django_virtualenv\lib\site-packages\django\contrib\sessions\backends\db.py", line 70, in create_model_instance
session_data=self.encode(data),
File "C:\Users\Utilisateur\Documents\Environments\monoi_django_virtualenv\lib\site-packages\django\contrib\sessions\backends\base.py", line 114, in encode
return signing.dumps(
File "C:\Users\Utilisateur\Documents\Environments\monoi_django_virtualenv\lib\site-packages\django\core\signing.py", line 110, in dumps
return TimestampSigner(key, salt=salt).sign_object(obj, serializer=serializer, compress=compress)
File "C:\Users\Utilisateur\Documents\Environments\monoi_django_virtualenv\lib\site-packages\django\core\signing.py", line 172, in sign_object
data = serializer().dumps(obj)
File "C:\Users\Utilisateur\Documents\Environments\monoi_django_virtualenv\lib\site-packages\django\core\signing.py", line 87, in dumps
return json.dumps(obj, separators=(',', ':')).encode('latin-1')
File "c:\users\utilisateur\appdata\local\programs\python\python39\lib\json\__init__.py", line 234, in dumps
return cls(
File "c:\users\utilisateur\appdata\local\programs\python\python39\lib\json\encoder.py", line 199, in encode
chunks = self.iterencode(o, _one_shot=True)
File "c:\users\utilisateur\appdata\local\programs\python\python39\lib\json\encoder.py", line 257, in iterencode
return _iterencode(o, 0)
File "c:\users\utilisateur\appdata\local\programs\python\python39\lib\json\encoder.py", line 179, in default
raise TypeError(f'Object of type {o.__class__.__name__} '
Exception Type: TypeError at /basket/
Exception Value: Object of type Product is not JSON serializable
Upvotes: 1
Views: 1257
Reputation: 21807
The problem is in the __iter__
method of your Basket
class. I believe you iterate over the basket object in the template so it is used in the request (because there is no loop in the view).
Now what is the problem in the method? Well you have this particular line basket = self.basket.copy()
, what it does is, that it makes a shallow copy of the dictionary, i.e. the internal objects referred to are the same, but you have a nested dictionary, meaning when you change the nested dictionary you actually change the same dictionary in your basket! You can use copy.deepcopy
[Python docs] to make a deep copy of the dictionary:
import copy
def __iter__(self):
...
basket = copy.deepcopy(self.basket)
...
Upvotes: 4