hello i try to build a E commerce app with Django i have a shoping cart class that woek with session this is my cart class :
from django.conf import settings
from shop.models import Product, ProductColor
from django.shortcuts import get_object_or_404
from django.utils import timezone
class Cart:
def __init__(self, request_or_session_data):
if hasattr(request_or_session_data, 'session'): # Check if it's a request
self.session = request_or_session_data.session
else:
# If it's session data, use it directly
self.session = request_or_session_data
cart = self.session.get(settings.CART_SESSION_ID)
if not cart:
cart = self.session[settings.CART_SESSION_ID] = {}
self.cart = cart
self.total_discount = None
self.discount_code = None
self.have_discount_code = self.session.get("have_discount_code", False)
self.discount_code_amount = self.session.get("discount_code_amount", 0)
self.last_updated = self.session.get("last_updated")
if hasattr(request_or_session_data, 'user') and request_or_session_data.user.is_authenticated:
self.user = request_or_session_data.user
def add(self, product, color_id, quantity=1, override_quantity=False):
product_id = str(product.id)
color = get_object_or_404(ProductColor, id=color_id)
cart_key = f"{product_id}_{color_id}"
if cart_key not in self.cart:
self.cart[cart_key] = {
'color_id': str(color.id),
'quantity': 0,
'price': str(product.price),
'discount': str(product.discount),
'discount_price': str(product.get_discounted_price()),
}
if override_quantity:
self.cart[cart_key]['quantity'] = quantity
else:
self.cart[cart_key]['quantity'] += quantity
self.total_discount = self.get_total_discount()
self.save()
def mark_session_modified(self):
# Mark session as modified if `self.session` is a Django session object
if hasattr(self.session, 'modified'):
self.session.modified = True
def save(self):
# mark the session as "modified" to make sure it gets saved
self.last_updated = timezone.now()
self.session[settings.CART_SESSION_ID] = self.cart
self.session['have_discount_code'] = self.have_discount_code
self.session['discount_code_amount'] = self.discount_code_amount
self.session['last_updated'] = self.last_updated.isoformat()
if hasattr(self, 'user') and self.user:
self.session["user_id"] = self.user.id
self.mark_session_modified()
def remove(self, product, color_id=None):
product_id = str(product.id)
if color_id:
cart_key = f"{product_id}_{color_id}"
if cart_key in self.cart:
del self.cart[cart_key]
else:
# If no color_id is provided, remove all items related to the product_id
keys_to_remove = [key for key in self.cart if key.startswith(product_id)]
for key in keys_to_remove:
del self.cart[key]
if len(self.cart) == 0:
self.clear() # Call the clear method to remove discount codes and other data
else:
self.total_discount = self.get_total_discount() # Update total discount
self.save()
def __iter__(self):
cart = self.cart.copy()
product_ids = {key.split('_')[0] for key in cart.keys()} # Extract product IDs
products = Product.objects.filter(id__in=product_ids)
for product in products:
for key in cart.keys():
if key.startswith(str(product.id)):
item = cart[key]
item["product"] = product
# Extract color ID from the key
color_id = key.split('_')[1] if '_' in key else None
# Get the corresponding ProductColor instance
if color_id:
try:
product_color = ProductColor.objects.get(id=color_id, product=product)
item["color_name"] = product_color.color # Use the color from ColorChoices
except ProductColor.DoesNotExist:
item["color_name"] = "Unknown Color"
else:
item["color_name"] = "No Color"
item["color_id"] = str(color_id)
item["product_id"] = str(product.id)
item["price"] = int(item["price"])
item["discount_price"] = int(item["discount_price"])
if item["discount"] == "0":
item["total_price"] = item["price"] * item["quantity"]
else:
item["total_price"] = item["discount_price"] * item["quantity"]
yield item
def __len__(self):
return sum(item['quantity'] for item in self.cart.values())
def get_total_price(self):
total_price = 0
for item in self:
total_price += item["total_price"]
if self.have_discount_code:
discount_factor = self.discount_code_amount / 100
total_price = int(total_price * (1 - discount_factor))
return total_price
def add_coupon(self, amount, code):
self.discount_code_amount = amount
self.have_discount_code = True
self.discount_code = code
self.save()
def get_total_discount(self):
total_discount = 0
for item in self.cart.values():
if item["discount"] != "0":
discount = int(item["price"]) - int(item["discount_price"])
total_discount += discount * item["quantity"]
return total_discount
@property
def total_get_price_of_discount(self):
total_price = 0
for item in self:
total_price += item["total_price"]
price = self.get_total_price()
return total_price - price
def clear(self):
# remove cart from session
del self.session[settings.CART_SESSION_ID]
self.have_discount_code = False
self.discount_code = None
self.discount_code_amount = 0
self.total_discount = None
self.save()
every time users add product to shoping cart i decress the number on quantity of product and i have a task to check if their shoping cart is idele for more than 30 minutes first send sms to user and remind them for the shoping cart and after than 1 hour clear shoping cart and add the quanity back to products problem is when in the task i try to loop into all sessions and modify that session with session_data["sms_sent"] = True after that it will send sms again also when is more than 1 hour it add quantity back to databse but its not clear the shoping cart from the session
this is the task i am using :
@shared_task
def clear_abandoned_carts():
expired_time = timezone.now() - CART_EXPIRATION_TIME
warning_time = timezone.now() - SEND_WARNING_TIME
for session in Session.objects.filter(expire_date__gte=timezone.now()):
data = session.get_decoded()
last_updated_str = data.get("last_updated")
cart = data.get(settings.CART_SESSION_ID)
print("sms_sent flag before:", data.get("sms_sent")) # Safe access
if cart and last_updated_str:
last_updated = timezone.datetime.fromisoformat(last_updated_str)
if last_updated < expired_time:
# Clear cart if expired
for key, item in cart.items():
color_id = item["color_id"]
quantity = item["quantity"]
try:
remove_from_cart(color_id, int(quantity))
except ProductColor.DoesNotExist:
pass # Handle as needed
# Clear session data
session_data = session.get_decoded()
session_data.pop(settings.CART_SESSION_ID, None)
session_data.pop("last_updated", None)
session_data.pop("have_discount_code", None)
session_data.pop("discount_code_amount", None)
session_data.pop("sms_sent", None)
session_data["modified"] = True
session.save()
cart_instance = Cart(data)
cart_instance.clear()
elif last_updated < warning_time:
# Check if the SMS warning was already sent
if not data.get("sms_sent"): # Proceed only if SMS hasn't been sent
user_id = data.get("user_id")
if user_id:
try:
user = CustomUser.objects.get(id=user_id)
if user.phone_number:
phone_number = user.phone_number
send_cart_warning(int(phone_number)) # Send SMS
# Mark SMS as sent in session data
session_data = session.get_decoded()
session_data["sms_sent"] = True # Safely update session
session_data["modified"] = True
session.modified = True
session.save()
print("sms_sent flag after:", session_data.get("sms_sent")) # Safe access
except CustomUser.DoesNotExist:
pass
in the console when its send message this print("sms_sent flag after:", session_data.get("sms_sent")) give me True but when it run again this one print("sms_sent flag before:", data.get("sms_sent")) give me None can you guys help me to fix this problem please
When updating session data within your Celery task, ensure that the session is marked as modified and saved properly. for ex:
@shared_task
def clear_abandoned_carts():
expired_time = timezone.now() - CART_EXPIRATION_TIME
warning_time = timezone.now() - SEND_WARNING_TIME
for session in Session.objects.filter(expire_date__gte=timezone.now()):
data = session.get_decoded()
last_updated_str = data.get("last_updated")
cart = data.get(settings.CART_SESSION_ID)
print("sms_sent flag before:", data.get("sms_sent")) # Safe access
if cart and last_updated_str:
last_updated = timezone.datetime.fromisoformat(last_updated_str)
if last_updated < expired_time:
# Clear cart if expired
for key, item in cart.items():
color_id = item["color_id"]
quantity = item["quantity"]
try:
remove_from_cart(color_id, int(quantity))
except ProductColor.DoesNotExist:
pass # Handle as needed
# Clear session data
session_data = session.get_decoded()
session_data.pop(settings.CART_SESSION_ID, None)
session_data.pop("last_updated", None)
session_data.pop("have_discount_code", None)
session_data.pop("discount_code_amount", None)
session_data.pop("sms_sent", None)
# Mark session as modified and save
session_data["modified"] = True
session.session_data = Session.objects.encode(session_data)
session.save()
elif last_updated < warning_time:
# Check if the SMS warning was already sent
if not data.get("sms_sent"):
user_id = data.get("user_id")
if user_id:
try:
user = CustomUser.objects.get(id=user_id)
if user.phone_number:
phone_number = user.phone_number
send_cart_warning(int(phone_number)) # Send SMS
# Mark SMS as sent in session data
session_data = session.get_decoded()
session_data["sms_sent"] = True
session_data["modified"] = True
session.session_data = Session.objects.encode(session_data)
session.save()
except CustomUser.DoesNotExist:
pass
ensure that the cart data is removed from the session and the session is saved afterward.
def clear(self):
# Remove cart-related data from session
keys_to_remove = [
settings.CART_SESSION_ID,
'have_discount_code',
'discount_code_amount',
'last_updated',
'sms_sent'
]
for key in keys_to_remove:
self.session.pop(key, None)
# Mark session as modified and save
self.mark_session_modified()