Giter Club home page Giter Club logo

django-carton's Introduction

Django Carton

  +------+
 /|     /|
+-+----+ |    django-carton is a simple and lightweight application
| |    | |    for shopping carts and wish lists.
| +----+-+
|/     |/
+------+
  • Simple: You decide how to implement the views, templates and payment processing.
  • Lightweight: The cart lives in the session.
  • Just a container: You define your product model the way you want.

Usage Example

View:

from django.http import HttpResponse

from carton.cart import Cart
from products.models import Product

def add(request):
    cart = Cart(request.session)
    product = Product.objects.get(id=request.GET.get('product_id'))
    cart.add(product, price=product.price)
    return HttpResponse("Added")

def show(request):
    return render(request, 'shopping/show-cart.html')

We are assuming here that your products are defined in an application called products.

Template:

{% load carton_tags %}
{% get_cart as cart %}

{% for item in cart.items %}
    {{ item.product.name }}
    {{ item.quantity }}
    {{ item.subtotal }}
{% endfor %}

You can also use this convinent shortcut:
{% for product in cart.products %}
    {{ product.name }}
{% endfor %}

Within the template you can access the product id with {{product.id}}.

Settings:

CART_PRODUCT_MODEL = 'products.models.Product'

This project is shipped with an application example called shopping implementing basic add, remove, display features. To use it, you will need to install the shopping application and include the URLs in your project urls.py

# settings.py
INSTALLED_APPS = (
    'carton',
    'shopping',
    'products',
)

# urls.py
urlpatterns = patterns('',
    url(r'^shopping-cart/', include('shopping.urls')),
)

Assuming you have some products defined, you should be able to add, show and remove products like this:

/shopping-cart/add/?id=1
/shopping-cart/show/
/shopping-cart/remove/?id=1

Installation

This application requires Django version 1.4; all versions above should be fine.

Just install the package using something like pip and add carton to your INSTALLED_APPS setting.

Add the CART_PRODUCT_MODEL setting, a dotted path to your product model.

This is how you run tests:

./manage.py test carton.tests --settings=carton.tests.settings

Abstract

The cart is an object that's stored in session. Products are associated to cart items.

Cart
|-- CartItem
|----- product
|----- price
|----- quantity

A cart item stores a price, a quantity and an arbitrary instance of a product model.

You can access all your product's attributes, for instance it's name:

{% for item in cart.items %}
    {{ item.price }}
    {{ item.quantity }}
    {{ item.product.name }}
{% endfor %}

Managing Cart Items

These are simple operations to add, remove and access cart items:

>>> apple = Product.objects.all()[0]
>>> cart.add(apple, price=1.5)
>>> apple in cart
True
>>> cart.remove(apple)
>>> apple in cart
False

>>> orange = Product.objects.all()[1]
>>> cart.add(apple, price=1.5)
>>> cart.total
Decimal('1.5')
>>> cart.add(orange, price=2.0)
>>> cart.total
Decimal('3.5')

Note how we check weather the product is in the cart - The following statements are different ways to do the same thing:

>>> apple in cart
>>> apple in cart.products
>>> apple in [item.product for item in cart.items]

The "product" refers to the database object. The "cart item" is where we store a copy of the product, it's quantity and it's price.

>>> cart.items
[CartItem Object (apple), CartItem Object (orange)]

>>> cart.products
[<Product: apple>, <Product: orange>]

Clear all items:

>>> cart.clear()
>>> cart.total
0

Increase the quantity by adding more products:

>>> cart.add(apple, price=1.5)
>>> cart.add(apple)  # no need to repeat the price.
>>> cart.total
Decimal('3.0')

Note that the price is only needed when you add a product for the first time.

>>> cart.add(orange)
*** ValueError: Missing price when adding a cart item.

You can tell how many items are in your cart:

>>> cart.clear()
>>> cart.add(apple, price=1.5)
>>> cart.add(orange, price=2.0, quantity=3)
>>> cart.count
4
>>> cart.unique_count  # Regarless of product's quantity
2

You can add several products at the same time:

>>> cart.clear()
>>> cart.add(orange, price=2.0, quantity=3)
>>> cart.total
Decimal('6')
>>> cart.add(orange, quantity=2)
>>> cart.total
Decimal('10')

The price is relevant only the first time you add a product:

>>> cart.clear()
>>> cart.add(orange, price=2.0)
>>> cart.total
Decimal('2')
>>> cart.add(orange, price=100)  # this price is ignored
>>> cart.total
Decimal('4')

Note how the price is ignored on the second call.

You can change the quantity of product that are already in the cart:

>>> cart.add(orange, price=2.0)
>>> cart.total
Decimal('2')
>>> cart.set_quantity(orange, quantity=3)
>>> cart.total
Decimal('6')
>>> cart.set_quantity(orange, quantity=1)
>>> cart.total
Decimal('2')
>>> cart.set_quantity(orange, quantity=0)
>>> cart.total
0
>>> cart.set_quantity(orange, quantity=-1)
*** ValueError: Quantity must be positive when updating cart

Removing all occurrence of a product:

>>> cart.add(apple, price=1.5, quantity=4)
>>> cart.total
Decimal('6.0')
>>> cart.remove(apple)
>>> cart.total
0
>>> apple in cart
False

Remove a single occurrence of a product:

>>> cart.add(apple, price=1.5, quantity=4)
>>> cart.remove_single(apple)
>>> apple in cart
True
>>> cart.total
Decimal('4.5')
>>> cart.remove_single(apple)
>>> cart.total
Decimal('3.0')
>>> cart.remove_single(apple)
>>> cart.total
Decimal('1.5')
>>> cart.remove_single(apple)
>>> cart.total
0

Multiple carts

Django Carton has support for using multiple carts in the same project. The carts would need to be stored in Django session using different session keys.

from carton.cart import Cart

cart_1 = Cart(session=request.session, session_key='CART-1')
cart_2 = Cart(session=request.session, session_key='CART-2')

Working With Product Model

Django Carton needs to know how to list your product objects.

The default behaviour is to get the product model using the CART_PRODUCT_MODEL setting and list all products.

The default queryset manager is used and all products are retrieved. You can filter products by defining some lookup parameters in CART_PRODUCT_LOOKUP setting.

# settings.py

CART_PRODUCT_LOOKUP = {
    'published': True,
    'status': 'A',
}

If you need further customization of the way product model and queryset are retrieved, you can always sub-class the default Cart and overwrite the get_queryset method. In that case, you should take into account that:

  • You probably won't need CART_PRODUCT_MODEL and CART_PRODUCT_LOOKUP if you get a direct access to your product model and define the filtering directly on the cart sub-class.
  • You probably have to write your own template tag to retrieve the cart since the default get_cart template tag point on the Cart class defined by django-carton.

Settings

Template Tag Name

You can retrieve the cart in templates using {% get_cart as my_cart %}.

You can change the name of this template tag using the CART_TEMPLATE_TAG_NAME setting.

# In you project settings
CART_TEMPLATE_TAG_NAME = 'get_basket'

# In templates
{% load carton_tags %}
{% get_basket as my_basket %}

Stale Items

Cart items are associated to products in the database. Sometime a product can be found in the cart when its database instance has been removed. These items are called stale items. By default they are removed from the cart.

Session Key

The CART_SESSION_KEY settings controls the name of the session key.

django-carton's People

Contributors

beckastar avatar daniel-avila avatar debetux avatar lazybird avatar sect2k avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

django-carton's Issues

Doesn't work with django 1.6 out of the box

Django 1.6 changed the default SESSION_SERIALIZER from PickleSerializer to JSONSerializer which causes <carton.cart.Cart object> is not JSON serializable error.

I've looked trough the code and it seems you are storing the cart (along with "products") directly in session, which is a bad practice as it can cause problems when serializing complex "product" models and other sorts of unexpected problems (I did something similar on one of my early django projects and it came back to bite me in the a**).

The solution is to save only the state in the session and the recreate the Cart object on initialization. Given that your code is quite neatly organized it should be trivial. I forked the code and will try to put something together.

Really liking the simplicity of your app BTW.

Cheers,
Mitja

Please add example using Ajax to edit quantities or delete

I apologize. I don't know where else to go for support, and I am trying to figure out how to pass an id from my cart to my view so that I can edit quantities or delete an item by clicking the button. It would be so great to see an example of this with Ajax. I've asked a SO question here

Support for using cart in view

in view.py, I am trying to do
for item in Cart.items_serializable:
do something

but this gave me a
TypeError at /calbase/export/custom

'property' object is not iterable

Whereas doing the same thing in template is working fine.

Django 1.8: get_cart fails in templates

I have django-carton installed correctly in my Django 1.8 project, and I can use the Cart object in my views as expected to add, delete and access items. The get_cart template tag does not seem to work, however. I included the carton_tags as documented:

{% load carton_tags %}
{% get_cart as cart %}

It looks like cart is just None. The items list is empty. To access the items in my template, I have to pass them in from the view like this:

def basket_view(request,company_name,webshop_id):
    cart = Cart(request.session)
    context = {"items": cart.items}
    return render(request,"frontpage/company/webshop/basket.html",context=context)

Can anyone reproduce this? Am I missing something here?

ImportError: No module named 'shopping'

hello lazybird;
How can i fix the problem?(Note:python3.5, django 1.11.3 and my settings.py in 'carton','shopping','products',)

Traceback (most recent call last):
  File "manage.py", line 22, in <module>
    execute_from_command_line(sys.argv)
  File "/usr/local/lib/python3.5/dist-packages/django/core/management/__init__.py", line 363, in execute_from_command_line
    utility.execute()
  File "/usr/local/lib/python3.5/dist-packages/django/core/management/__init__.py", line 337, in execute
    django.setup()
  File "/usr/local/lib/python3.5/dist-packages/django/__init__.py", line 27, in setup
    apps.populate(settings.INSTALLED_APPS)
  File "/usr/local/lib/python3.5/dist-packages/django/apps/registry.py", line 85, in populate
    app_config = AppConfig.create(entry)
  File "/usr/local/lib/python3.5/dist-packages/django/apps/config.py", line 94, in create
    module = import_module(entry)
  File "/usr/lib/python3.5/importlib/__init__.py", line 126, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 986, in _gcd_import
  File "<frozen importlib._bootstrap>", line 969, in _find_and_load
  File "<frozen importlib._bootstrap>", line 956, in _find_and_load_unlocked
ImportError: No module named 'shopping'

Ability to serialize cart content

Sometimes it's useful to serialize the cart content, for instance in order to store the cart content as a JSON object. We should have helpers on the cart object for that.

cleared cart item while logout(request)

i have added item in basket after that i have logout using "logout(request)" then cart will clear all item. Because logout(request) will clear all session. How can i overcome this situation.

get_tag tag not displaying in template (Django 1.7)

I can't get the get_tag tag to display my stored cart items. I use django-debug-toolbar and can actually see that the items are actually saved.
Here is my template

{% extends 'base.html' %}
{% load carton_tags %}
{% get_cart as cart %}

{% block main_content %}
    {% for item in cart.items %}
        {{ item.tutor.name }}
        {{ item.quantity }}
        {{ item.subtotal }}
    {% endfor %}   
<ul>

    {% for tutor in tutors %}
        <li><a href="{% url 'add_tutor_to_cart' %}?tutor_id={{ tutor.id }}">{{ tutor.display_name }}/ {{ tutor.price }}</a></li>
   {% endfor %}
</ul>
{% endblock %}

and here is my view function

def add(request):
    cart = Cart(request.session)
    tutor = Tutor.objects.get(id=request.GET.get('tutor_id'))
    cart.add(tutor, price=tutor.price)
    return HttpResponse("Added")

def show(request):
    tutors = Tutor.objects.all()
    return render(request, 'show-cart.html',locals())

django-debug-toolbar shows this after adding items to the cart
Variable Value
u'CART'
{u'1': {u'price': u'400', u'product_pk': 1, u'quantity': 2},
u'5': {u'price': u'800', u'product_pk': 5, u'quantity': 1}}

Any help would be appreciated

Improve the way product model and queryset are handled

There are currently several ways to inform the cart object about the product model and the product queryset.

  1. Product model can be passed in the cart init method

    cart(session, product_model=Product)
    
  2. Product model dotted path can be set in settings

    CART_PRODUCT_MODEL = 'products.models.Product' 
    
  3. Product queryset can be set as an attribute on a cart subclass

    Class MyCart(Cart):
        queryset = Product.objects.published()
    
  4. Product queryset can be customized by redefining a method on a cart subclass

    Class MyCart(Cart):
        def get_queryset(self):
            return Product.objects.published()
    

I'm wondering if this can be improved, simplified, be more consistent.

Issues:

  1. We have the ability to pass the product model via the cart __init__. That's not the case for the product queryset which seems inconsistent. We could have both passed in __init__ or have none of them there.
  2. The product model can be defined without the need to subclass the cart. That's done by defining the dotted path in a setting. We don't have a similar approach for the queryset. Currently, you have to subclass the cart to get a chance to customize the queryset.

Support for multiple models

I would like to have two carts on a single web site, which is easy enough - but I would like each one to work on a different model. I think the best way would be to add an optional model kwarg to the Cart __init__ method, then have get_product_model use the specified model if it exists, otherwise it falls back to the current behaviour.

Is this something done before? if it's useful, could I do a pull request for it?

Support for callables on cart product lookup setting

The default queryset manager is used and all products are retrieved. You can filter products by defining some lookup parameters in CART_PRODUCT_LOOKUP setting.

The CART_PRODUCT_LOOKUP should support passing in callables, making this possible:

CART_PRODUCT_LOOKUP = {
     'status: 'A',
     'date_valid_from__gte': datetime.datetime.now
}

Undocumented feature: Support for multiple carts

Django Carton supports creating multiple carts; this should be documented.

cart_1 = Cart(session=request.session, session_key='CART-1')
cart_2 = Cart(session=request.session, session_key='CART-2')

Can't able to add multiple items

I was tried to add multiple items in cart but its throwing below errors.
Attribute error:
'list' object has no attribute 'filter'.
line number: 50 in cart.py

How can i resolve this issues?

[Suggestion] - Allow different product model

Hello,
With the actual implementation of one CART_PRODUCT_MODEL, it is impossible to add to the shopping cart an item like a "shipment cost", because this is something you calculate based on some inputs and is not a product (should not be stored in the same product Model).
As a workaround, I am using two carts, one for the products and one only for the shipment, where the shipment uses a different product model. I had to modify Cart class and also the template tags.
The suggestion would be to use the variable "session_key" not only to address different carts of the same model (as today), but to point directly to the model you want to use. Do you think it is possible ? Do you have any other suggestion ? Thanks

Support for products that use a custom primary key

Product models sometime overwrite the default primary key behavior - the primary key is called something else that 'id'.
In that case, django-carton will raise an error because it expects the "id" field.

KeyError when adding item to cart

After an item is added to cart, KeyError is raised on subsequent pages. This is because keys in cart_representation dictionary are strings and if product.pk is a number, the key is not found in the dict.

The fix is simple, change

 item = cart_representation[product.pk]

to

 item = cart_representation[str(product.pk)]

Pushed a fix.

Library object has no attribute 'assignment_tag'

Hi,
I have tested the package with Django 3.1.2 and Django 2.1.1.
Each time I tried, I get to see the error that library object has no attribute 'assignment_tag'.

It was mentioned that this error raises if the package is used with Django below 1.9 but it seems like it is not the case as the same error occurs with Django 2+ and 3+.

Handling this error involved modifying carton_tags.py as below but this is not permanent solution for the package. (comment out assignment_tag and register simple_tag as below)

Would there be any other way to use this package without having to see this assignment_tag error?

`
def get_cart(context, session_key=None, cart_class=Cart):
"""
Make the cart object available in template.

Sample usage::

    {% load carton_tags %}
    {% get_cart as cart %}
    {% for product in cart.products %}
        {{ product }}
    {% endfor %}

request = context['request']
return cart_class(request.session, session_key=session_key)

register.assignment_tag(takes_context=True, name=CART_TEMPLATE_TAG_NAME)(get_cart)

register.simple_tag(takes_context=True, name=CART_TEMPLATE_TAG_NAME)(get_cart)
def get_cart(context, session_key=None, cart_class=Cart):
request = context['request']
return cart_class(request.session, session_key=session_key)`

Key Error on Django 1.7

When running your example shopping cart app in Django 1.7, a KeyError is thrown in {% get_cart as cart %}.

'request' at line 22 in get_cart

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.