# -*- coding: utf-8 -*-
from setuptools import setup

packages = \
['dj_shop_cart', 'dj_shop_cart.migrations', 'migrations']

package_data = \
{'': ['*']}

install_requires = \
['Django>=3.2,<5.0', 'attrs>=21.4.0,<22.0.0']

setup_kwargs = {
    'name': 'dj-shop-cart',
    'version': '4.0.1',
    'description': 'Simple django cart manager for your django projects.',
    'long_description': '# dj-shop-cart\n\nA simple and flexible cart manager for your django projects.\n\n[![pypi](https://badge.fury.io/py/dj-shop-cart.svg)](https://pypi.org/project/dj-shop-cart/)\n[![python](https://img.shields.io/pypi/pyversions/dj-shop-cart)](https://github.com/Tobi-De/dj-shop-cart)\n[![django](https://img.shields.io/pypi/djversions/dj-shop-cart)](https://github.com/Tobi-De/dj-shop-cart)\n[![MIT License](https://img.shields.io/apm/l/atomic-design-ui.svg?)](https://github.com/Tobi-De/dj-shop-cart/blob/master/LICENSE)\n[![black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)\n\n## Features\n\n- Add, remove, decrement and clear items from cart\n- Authenticated users cart can be saved to database\n- Write custom methods to easily hook into the items add / remove flow\n- Custom **get_price** method to ensure that the cart always have an up-to-date products price\n- Each item in the cart hold a reference to the associated product\n- Metadata data can be attached to cart items\n- Supports specification of product variation details\n- Available context processor for easy access to the user cart in all your django templates\n- Swappable backend storage, with session and database provided by default\n\n\n## Installation\n\nInstall **dj-shop-cart** with pip or poetry.\n\n```bash\n  pip install dj-shop-cart\n```\n\n## Usage/Examples\n\n```python3\n\n# settings.py\n\nTEMPLATES = [\n    {\n        "OPTIONS": {\n            "context_processors": [\n                ...,\n                "dj_shop_cart.context_processors.cart", # If you want access to the cart instance in all templates\n            ],\n        },\n    }\n]\n\n# models.py\n\nfrom django.db import models\nfrom dj_shop_cart.cart import CartItem\nfrom decimal import Decimal\n\nclass Product(models.Model):\n    ...\n\n    def get_price(self, item:CartItem)->Decimal:\n        """The only requirements of the dj_shop_cart package apart from the fact that the products you add\n        to the cart must be instances of django based models. You can use a different name for this method\n        but be sure to update the corresponding setting (see Configuration). Even if you change the name the\n        function signature should match this one.\n        """\n\n\n# views.py\n\nfrom dj_shop_cart.cart import get_cart_class\nfrom django.http import HttpRequest\nfrom django.views.decorators.http import require_POST\n\n# This function has nothing to do with the package itself\nfrom .helpers import collect_params\n\nCart = get_cart_class()\n\n\n@require_POST\ndef add_product(request: HttpRequest):\n    product, quantity = collect_params(request)\n    cart = Cart.new(request)\n    cart.add(product, quantity=quantity)\n    ...\n\n\n@require_POST\ndef remove_product(request: HttpRequest):\n    product, quantity = collect_params(request)\n    cart = Cart.new(request)\n    cart.remove(product, quantity=quantity)\n    ...\n\n\n@require_POST\ndef empty_cart(request: HttpRequest):\n    Cart.new(request).empty()\n    ...\n\n```\n\n## Configuration\n\nConfigure the cart behaviour in your Django settings. All settings are optional and must be strings if defined.\n\n| Name                          | Description                                                                                                                                                        | Default                              |\n|-------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------|\n| CART_SESSION_KEY              | The key used to store the cart in session                                                                                                                          | `CART-ID`                            |\n| CART_CLASS                    | The path to the `Cart` class to use. If you are using a custom class it must subclass `dj_shop_cart.cart.Cart`                                                     | `dj_shop_cart.cart.Cart`             |\n| CART_PRODUCT_GET_PRICE_METHOD | The method name to use to dynamically get the price on the product instance                                                                                        | `get_price`                          |\n| CART_STORAGE_BACKEND          | The path to the storage backend to use. If you define a custom storage backend, it should follow the `Storage` protocol, see the **Backend Storage section** below | `dj_shop_cart.storages.SessionStorage` |\n\n## API Reference\n\n### Instantiate a new cart\n\n```python\nfrom dj_shop_cart.cart import get_cart_class\nfrom django.http import HttpRequest\n\nCart = get_cart_class()\n\ndef view(request:HttpRequest):\n    cart = Cart.new(request)\n    ...\n```\n\nThe `new` method create a new cart and load existing data via the specified storage backend.\n\n### Add a product to the cart\n\n```python\ncart.add(product, quantity=20)\n```\n\nParameters\n\n - **product**: An instance of a database product.\n - **quantity**: The quantity to add.\n - **variant**:  The product variation details, when specified, are used to uniquely identify items in the cart related to the same product,\n                 can be a python dictionary, a set, an integer or a string.\n - **override_quantity** : Default to `False`, if `True` instead of adding to the existing quantity, will override it\n - **metadata**: Optional metadata that is attached to the item, this dictionary can contain\n                anything that you would want to attach to the created item in cart, the only requirements about\n                it is that it needs to be json serializable.\n\nReturns a `CartItem`.\n\n### Remove / Decrement a product from the cart\n\n```python\n# Remove 10 from the quantity\ncart.remove(product, quantity=10)\n# Remove the whole item\ncart.remove(product)\n```\n\nParameters\n\n- **product** : An instance of a database product.\n- **quantity** :  An optional quantity of the product to remove from the cart.\n  Indicate if you do not want to delete the item completely, if the quantity ends up being zero after the quantity is decreased, the item is completely removed.\n- **variant** : Variant details of the product.\n\nReturns a `CartItem` or `None` if no item to remove was found.\n\n### Empty the cart\n\n```python\ncart.empty()\n```\nThis method take no arguments.\n\n### Additional Properties of the cart object\n\n```python\ndef my_view(request):\n    cart = Cart.new(request)\n    # by looping through the cart, we return all the CartItem objects.\n    for item in cart:\n        print(item.subtotal)\n\n    item = cart.find()[0]\n    # you can use the in operator to check if a CartItem is in the basket, perhaps for a manually built one.\n    assert item in cart\n\n    # calling len on the cart returns the number of unique items in the cart, regardless of the quantity.\n    print(len(cart))\n```\n\n- **total** : The total cost of the cart.\n- **is_empty** : A boolean value indicating whether the cart is empty or not.\n- **count** :  The number of items in the cart, that\'s the sum of quantities.\n- **unique_count** : The number of unique items in the cart, regardless of the quantity.\n- **products** : A list of associated products.\n- **find(\\*\\*criteria)** : Returns a list of cart items matching the given criteria.\n- **find_one(\\*\\*criteria)** : Returns the first cart item that matches the given criteria, if no match is found return None.\n- **variants_group_by_product()** :  Return a dictionary with the products ids as keys and a list of variant as values.\n\n### Custom Cart Class\n\n````python\n# settings.py\nCART_CLASS = "your_project.somewhere_in_your_project.Cart"\n\n# somewhere_in_your_project.py\nfrom dj_shop_cart.cart import CartItem, Cart as DjCart\n\n\nclass Cart(DjCart):\n\n    def before_add(self, item: CartItem, quantity: int) -> None:\n        pass\n\n    def after_add(self, item: CartItem) -> None:\n        pass\n\n    def before_remove(self, item: CartItem | None = None, quantity: int | None = None) -> None:\n        pass\n\n    def after_remove(self, item: CartItem | None = None) -> None:\n        pass\n````\nThe 4 methods defined in the class above are custom hooks that you can override to customize the `Add/Remove` process.\n\n### Properties of `CartItem`\n\n- **price** : The item price calculated via the `get_price` method.\n- **subtotal** : Item price x quantity.\n- **product** : The associated product.\n- **variant** : Variant info specified when the product was added to the cart, default to `None`, is used to compare items in the cart.\n- **metadata** : A dictionary containing the metadata specified when the product was added to the cart, not used when comparing two cart items.\n\n## Storage Backend\n\nThe storage backend are used to store the cart items data. Two backends are provided by default, `SessionStorage` and\n`DBStorage`.\n\n### SessionStorage\n\n```python\n# settings.py\n\nCART_STORAGE_BACKEND = "dj_shop_cart.storages.SessionStorage"\n```\n\nThis is the default backend used when no one is specified. It uses the django sessions app to store carts in the user\nsession. Carts only live for the duration of the user session and each new session generates a new cart.\n\n### DBStorage\n\n```python\n# settings.py\n\nINSTALLED_APPS = [\n    ...,\n    "dj_shop_cart",\n    ...,\n]\n\nCART_STORAGE_BACKEND = "dj_shop_cart.storages.DBStorage"\n```\n\nThis backend persists users carts in the database but only when they are authenticated. There is no point in saving\na cart that is linked to a user with no account in your system, your database will be filled with carts that\ncan\'t be associated with a specific user. This backend works by using `SessionStorage` when users are not authenticated,\nand then saving their cart to the database when the user authenticates. There is always only one Cart object associated with\na user at a time, so be sure to empty the cart after the checkout process to avoid reusing data from a previously processed\ncart. Cart objects in the database are not automatically deleted.\n\n### Custom storage backend\n\nYou can also create your own custom storage backend, a redis storage backend for example. You can also import and use\nthe provided backend storages when building your own (like the DBStorage does). You don\'t need to inherit a specific class,\nall you need to do is write a class that defines some specific methods and attributes, a class that follows a protocol.\nNow that your custom storage backend is ready, all you have to do is specify it via the `CART_STORAGE_BACKEND` settings.\nThe protocol to be implemented is described as follows:\n\n```python\nfrom typing import Protocol\n\nfrom django.http import HttpRequest\n\nclass Storage(Protocol):\n    request: HttpRequest\n\n    def load(self) -> list[dict]:\n        ...\n\n    def save(self, items: list[dict]) -> None:\n        ...\n\n    def clear(self) -> None:\n        ...\n```\n\n\n\n## Used By\n\nThis project is used by the following companies:\n\n- [Fêmy bien être](https://www.femybienetre.com/)\n\n## Development\n\nPoetry is required (not really, you can set up the environment however you want and install the requirements\nmanually) to set up a virtualenv, install it then run the following:\n\n```sh\npoetry install\npre-commit install --install-hooks\n```\n\nTests can then be run quickly in that environment:\n\n```sh\npytest\n```\n\n## Feedback\n\nIf you have any feedback, please reach out to me at degnonfrancis@gmail.com\n\n## Todos\n\n- Add an example for a custom redis backend\n- Complete the example project\n- Write more tests\n',
    'author': 'Tobi DEGNON',
    'author_email': 'tobidegnon@protonmail.com',
    'maintainer': None,
    'maintainer_email': None,
    'url': 'https://github.com/Tobi-De/dj-shop-cart',
    'packages': packages,
    'package_data': package_data,
    'install_requires': install_requires,
    'python_requires': '>=3.8,<4.0',
}


setup(**setup_kwargs)
