pythondjangocelery

clear the shoping cart in django app that saving in session with clery task


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


Solution

  • 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()