Source code for shoop.front.basket.storage

# -*- coding: utf-8 -*-
# This file is part of Shoop.
#
# Copyright (c) 2012-2016, Shoop Ltd. All rights reserved.
#
# This source code is licensed under the AGPLv3 license found in the
# LICENSE file in the root directory of this source tree.
from __future__ import unicode_literals

import abc

import six
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured

from shoop.core.utils.users import real_user_or_none
from shoop.front.models import StoredBasket
from shoop.utils.importing import cached_load


[docs]class BasketCompatibilityError(Exception): pass
[docs]class ShopMismatchBasketCompatibilityError(BasketCompatibilityError): pass
[docs]class PriceUnitMismatchBasketCompatibilityError(BasketCompatibilityError): pass
[docs]class BasketStorage(six.with_metaclass(abc.ABCMeta)):
[docs] def load(self, basket): """ Load the given basket's data dictionary from the storage. :type basket: shoop.front.basket.objects.BaseBasket :rtype: dict :raises: `BasketCompatibilityError` if basket loaded from the storage is not compatible with the requested basket. """ stored_basket = self._load_stored_basket(basket) if not stored_basket: return {} if stored_basket.shop_id != basket.shop.id: msg = ( "Cannot load basket of a different Shop (" "%s id=%r with Shop=%s, Dest. Basket Shop=%s)" % ( type(stored_basket).__name__, stored_basket.id, stored_basket.shop_id, basket.shop.id)) raise ShopMismatchBasketCompatibilityError(msg) price_unit_diff = _price_units_diff(stored_basket, basket.shop) if price_unit_diff: msg = "%s %r: Price unit mismatch with Shop (%s)" % ( type(stored_basket).__name__, stored_basket.id, price_unit_diff) raise PriceUnitMismatchBasketCompatibilityError(msg) return stored_basket.data or {}
@abc.abstractmethod def _load_stored_basket(self, basket): """ Load stored basket for the given basket from the storage. The returned object should have ``id``, ``shop_id``, ``currency``, ``prices_include_tax`` and ``data`` attributes. :type basket: shoop.front.basket.objects.BaseBasket :return: Stored basket or None """ pass @abc.abstractmethod
[docs] def save(self, basket, data): # pragma: no cover """ Save the given data dictionary into the storage for the given basket. :type basket: shoop.front.basket.objects.BaseBasket :type data: dict """ pass
@abc.abstractmethod
[docs] def delete(self, basket): # pragma: no cover """ Delete the basket from storage. :type basket: shoop.front.basket.objects.BaseBasket """ pass
[docs] def finalize(self, basket): """ Mark the basket as "finalized" (i.e. completed) in the storage. The actual semantics of what finalization does are up to each backend. :type basket: shoop.front.basket.objects.BaseBasket """ self.delete(basket=basket)
[docs]class DirectSessionBasketStorage(BasketStorage): def __init__(self): if settings.SESSION_SERIALIZER == "django.contrib.sessions.serializers.JSONSerializer": # pragma: no cover raise ImproperlyConfigured( "DirectSessionBasketStorage will not work with the JSONSerializer session serializer." )
[docs] def save(self, basket, data): stored_basket = DictStoredBasket.from_basket_and_data(basket, data) basket.request.session[basket.basket_name] = stored_basket.as_dict()
def _load_stored_basket(self, basket): stored_basket_dict = basket.request.session.get(basket.basket_name) if not stored_basket_dict: return None return DictStoredBasket.from_dict(stored_basket_dict)
[docs] def delete(self, basket): basket.request.session.pop(basket.basket_name, None)
[docs]class DictStoredBasket(object): def __init__(self, id, shop_id, currency, prices_include_tax, data): self.id = id self.shop_id = shop_id self.currency = currency self.prices_include_tax = prices_include_tax self.data = (data or {}) @classmethod
[docs] def from_basket_and_data(cls, basket, data): return cls( id=(getattr(basket, "id", None) or basket.basket_name), shop_id=basket.shop.id, currency=basket.currency, prices_include_tax=basket.prices_include_tax, data=data, )
@classmethod
[docs] def from_dict(cls, mapping): return cls(**mapping)
[docs] def as_dict(self): return { "id": self.id, "shop_id": self.shop_id, "currency": self.currency, "prices_include_tax": self.prices_include_tax, "data": self.data, }
[docs]class DatabaseBasketStorage(BasketStorage): def _get_session_key(self, basket): return "basket_%s_key" % basket.basket_name
[docs] def save(self, basket, data): """ :type basket: shoop.front.basket.objects.BaseBasket """ request = basket.request stored_basket = self._get_stored_basket(basket) stored_basket.data = data stored_basket.taxless_total_price = basket.taxless_total_price_or_none stored_basket.taxful_total_price = basket.taxful_total_price_or_none stored_basket.product_count = basket.product_count stored_basket.customer = (basket.customer or None) stored_basket.orderer = (basket.orderer or None) stored_basket.creator = real_user_or_none(basket.creator) stored_basket.save() stored_basket.products = set(basket.product_ids) basket_get_kwargs = {"pk": stored_basket.pk, "key": stored_basket.key} request.session[self._get_session_key(basket)] = basket_get_kwargs
def _load_stored_basket(self, basket): return self._get_stored_basket(basket)
[docs] def delete(self, basket): stored_basket = self._get_stored_basket(basket) if stored_basket and stored_basket.pk: stored_basket.deleted = True stored_basket.save() basket.request.session.pop(self._get_session_key(basket), None)
[docs] def finalize(self, basket): stored_basket = self._get_stored_basket(basket) if stored_basket and stored_basket.pk: stored_basket.deleted = True stored_basket.finished = True stored_basket.save() basket.request.session.pop(self._get_session_key(basket), None)
def _get_stored_basket(self, basket): request = basket.request basket_get_kwargs = request.session.get(self._get_session_key(basket)) stored_basket = None if basket_get_kwargs: stored_basket = StoredBasket.objects.filter(deleted=False, **basket_get_kwargs).first() if not stored_basket: if basket_get_kwargs: request.session.pop(self._get_session_key(basket), None) stored_basket = StoredBasket( shop=basket.shop, currency=basket.currency, prices_include_tax=basket.prices_include_tax, ) return stored_basket
def _price_units_diff(x, y): diff = [] if x.currency != y.currency: diff.append('currency: %r vs %r' % (x.currency, y.currency)) if x.prices_include_tax != y.prices_include_tax: diff.append('includes_tax: %r vs %r' % ( x.prices_include_tax, y.prices_include_tax)) return ', '.join(diff)
[docs]def get_storage(): """ Retrieve a basket storage object. :return: A basket storage object :rtype: BasketStorage """ storage_class = cached_load("SHOOP_BASKET_STORAGE_CLASS_SPEC") return storage_class()