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

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()
Вернуться на верх