Commits

Anonymous committed 94581ec

H:o Enter codd product's option and calculation based on it.
- replaced paramater `options` with `option_values` in update_item, remove_item and find_item methods of `Cart` object to pass only list of option values instead of complex dictionary of option and option value pair.
- replace attribute name `type` with `tax_type` of `Cart` Object and changed it's passing value with `excluded` and `included`.
- changed license agreement from GPL to BSD.
- added `AUTHORS` file in build package.
- added `Makefile` in `docs` folder for latex support.
- added more unittest cases.
- added logo in docs.
mmit message. Lines beginning with 'HG:' are removed.

Comments (0)

Files changed (29)

+The PRIMARY AUTHORS is:
+
+- Dharmesh Patel <mr.dlpatel@gmail.com> who is Project Manager / Developer
+
+CONTRIBUTORS:
+
+People who provided bugfixes, code cleaning, etc. are welcome to ask for their names to be mentioned here.
+Version 1.3.0
+-----------------
+
+Released on 26/10/2010.
+
+- fixed problem to add product's option and calculation based on it.
+- replaced paramater `options` with `option_values` in update_item, remove_item and find_item methods of `Cart` object to pass only list of option values instead of complex dictionary of option and option value pair.
+- replace attribute name `type` with `tax_type` of `Cart` Object and changed it's passing value with `excluded` and `included`.
+- changed license agreement from GPL to BSD.
+- added `AUTHORS` file in build package.
+- added `Makefile` in `docs` folder for latex support.
+- added more unittest cases.
+- added logo in docs.
+
 Version 1.2.0
 -----------------
 
 Thanks for downloading shoppingCart.
 
-To install it, make sure you have Python 2.5 or greater and python-setuptools module is installed.
+To install it, make sure you have Python 2.5 or greater and `python-setuptools` module is installed.
 Then run this command from the command prompt:
 
     python setup.py install
+    
+OR you can also install it using `easy_install` command:
+
+    easy_install shoppingCart
 
 AS AN ALTERNATIVE, you can just copy the entire "shoppingCart" directory to Python's
 site-packages directory, which is located wherever your Python installation
     /usr/lib/python2.5/site-packages (Unix, Python 2.5)
     C:\\PYTHON\site-packages         (Windows)
 
-For more detailed instructions, see docs/intro/install.txt.
+For more detailed instructions, see docs/source/intro/installation.txt.
+Copyright (c) 2010, Dharmesh Patel and others, see AUTHORS for 
+more details.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * The names of the contributors may not be used to endorse or
+      promote products derived from this software without specific
+      prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-include CHANGES COPYING README INSTALL
+include CHANGES LICENSE AUTHORS README INSTALL
 recursive-include shoppingCart *
 recursive-include docs *
 prune docs/build
 Metadata-Version: 1.0
 Name: shoppingCart
-Version: 1.2.0
-Summary: shoppingCart is a open source cart developed using Python language.It is to manage cart in Ecommerce applications.
+Version: 1.3.0
+Summary: shoppingCart is a open source cart developed using Python language to manage cart in Ecommerce applications.
 Home-page: https://bitbucket.org/dharmeshpatel/shoppingcart
 Author: Dharmesh Patel
 Author-email: mr[dot]dlpatel[at]gmail[dot]com
-License: GPL-3
+License: BSD
 Description: 
         Majour Feature(s):
         
         1. Product options support.
         2. Multi discount support.
-        3. Multi taxes support(specific to product as well as general taxes).
+        3. Multi tax support(specific to product as well as general taxes).
         4. Multi currency support.
         5. Tax Exclude and Include total.
         6. Shipping method and charge.
 Keywords: cart  shopcart shoppingcart e-shop e-shopcart e-shoppingCart
 Platform: any
 Classifier: Development Status :: 5 - Production/Stable
-Classifier: License :: OSI Approved :: GNU General Public License (GPL)
+Classifier: License :: OSI Approved :: BSD License
 Classifier: Programming Language :: Python
 ================================================================================
 shoppingCart
 ================================================================================
-shoppingCart is a open source cart developed using Python language. It is to manage cart in Ecommerce applications.
+shoppingCart is a open source cart developed using Python language to manage cart in E-Commerce applications.
 
 Majour Feature(s):
-==================
+===========
 
 1. Product options support.
 2. Multi discount support.
-3. Multi taxes support(specific to product as well as general taxes).
+3. Multi tax support(specific to product as well as general taxes).
 4. Multi currency support.
 5. Tax Exclude and Include total.
 6. Shipping method and charge.
     
 Important(s):
-=============
+========
 
     1. Tax dictionary should be like this :
-    ---------------------------------------
-    ==> {'amount': 10.00, 'type': 'percentage'} OR
-    ==> {'amount': 10.00, 'type': 'fixed'}
+    --------------------------------------------------
+        * {'amount': 10.00, 'type': 'percentage'} OR
+        * {'amount': 10.00, 'type': 'fixed'}
     
-    2. Option dictionary should be like this :
-    ------------------------------------------
-    ==> {'color': {'red': {'price': 10.00}}} OR
-    ==> {option_object: {option_value_object:{'price': 10.00}}} OR
-    ==> {option_object: option_value_object}
+    2. Option dictionary or list of Option Value should be like this :
+    -------------------------------------------------------------------------------------
+    
+        2.1 To pass dictionary in attribute `options` of `add_item` method of `Cart` object should be like this :
+        --------------------------------------------------------------------------------------------------------------------------------------------
+            * {1: {2: {'price': 10.00}}} OR        
+            * {'color': {'red': {'price': 10.00}}} OR
+            * {option_object: {option_value_object:{'price': 10.00}}} OR
+            * {option_object: option_value_object}
+            
+        2.2 To pass list in attribute `option_values` of `add_item`, `update_item` and `remove_item` methods of `Cart` object should be like this :
+        -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+            * [2] OR
+            * ['red'] OR
+            * [option_value_object]
     
     3. if cart item is object then it should have `id` field(Unique KeyField) like this:
-    ------------------------------------------------------------------------------------
-    ==> For Example:
+    -----------------------------------------------------------------------------------------------------------
+    For Example:
             class Product(object):
                 def __init__(self):
                     self.id = None
             cart.add_item(product=product, price=10.00, quantity=1)
             
 Reference(s):
-=============
+========
     
     1. Currancy Conversation:
-    -------------------------
+    ------------------------------------
     Used http://www.webservicex.net to know currancy value.
     For Example:
         http://www.webservicex.net/CurrencyConvertor.asmx/ConversionRate?FromCurrency=EUR&ToCurrency=USD
 ################################################################################
 #
 #    Copyright (C) 2010 Dharmesh Patel <mr.dlpatel@gmail.com>.
-#    $Id$
-#
-#    This program is free software: you can redistribute it and/or modify
-#    it under the terms of the GNU General Public License as published by
-#    the Free Software Foundation, either version 3 of the License, or
-#    (at your option) any later version.
-#
-#    This program is distributed in the hope that it will be useful,
-#    but WITHOUT ANY WARRANTY; without even the implied warranty of
-#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#    GNU General Public License for more details.
-#
-#    You should have received a copy of the GNU General Public License
-#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#    License : BSD, see LICENSE for more details.
 #
 ################################################################################
 
         - round fugure check for tax_amount in cart as well as in cart item object.(NEW)
         - unit test with site and customer object and raise exception generate.
         - need to give more name convention to methods.
+        - change option dictionary with option and optionvalue only(no inner dictionary like {'option': {'option_value': 'ov', 'price': 10}})
+        - examples with doc test.
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = sphinx-build
+PAPER         =
+BUILDDIR      = build
+
+# Internal variables.
+PAPEROPT_a4     = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
+
+help:
+	@echo "Please use \`make <target>' where <target> is one of"
+	@echo "  html       to make standalone HTML files"
+	@echo "  dirhtml    to make HTML files named index.html in directories"
+	@echo "  singlehtml to make a single large HTML file"
+	@echo "  pickle     to make pickle files"
+	@echo "  json       to make JSON files"
+	@echo "  htmlhelp   to make HTML files and a HTML help project"
+	@echo "  qthelp     to make HTML files and a qthelp project"
+	@echo "  devhelp    to make HTML files and a Devhelp project"
+	@echo "  epub       to make an epub"
+	@echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+	@echo "  latexpdf   to make LaTeX files and run them through pdflatex"
+	@echo "  text       to make text files"
+	@echo "  man        to make manual pages"
+	@echo "  changes    to make an overview of all changed/added/deprecated items"
+	@echo "  linkcheck  to check all external links for integrity"
+	@echo "  doctest    to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+	-rm -rf $(BUILDDIR)/*
+
+html:
+	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+	$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+	@echo
+	@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+	@echo
+	@echo "Build finished; now you can process the pickle files."
+
+json:
+	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+	@echo
+	@echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+	@echo
+	@echo "Build finished; now you can run HTML Help Workshop with the" \
+	      ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+	@echo
+	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
+	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/shoppingCart.qhcp"
+	@echo "To view the help file:"
+	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/shoppingCart.qhc"
+
+devhelp:
+	$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+	@echo
+	@echo "Build finished."
+	@echo "To view the help file:"
+	@echo "# mkdir -p $$HOME/.local/share/devhelp/shoppingCart"
+	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/shoppingCart"
+	@echo "# devhelp"
+
+epub:
+	$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+	@echo
+	@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo
+	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+	@echo "Run \`make' in that directory to run these through (pdf)latex" \
+	      "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo "Running LaTeX files through pdflatex..."
+	make -C $(BUILDDIR)/latex all-pdf
+	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+text:
+	$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+	@echo
+	@echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+man:
+	$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+	@echo
+	@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+changes:
+	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+	@echo
+	@echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+	@echo
+	@echo "Link check complete; look for any errors in the above output " \
+	      "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+	@echo "Testing of doctests in the sources finished, look at the " \
+	      "results in $(BUILDDIR)/doctest/output.txt."

docs/source/conf.py

 # built documents.
 #
 # The short X.Y version.
-version = '1.2.0'
+version = '1.3.0'
 # The full version, including alpha/beta/rc tags.
 release = version
 
 
 # The name of an image file (relative to this directory) to place at the top
 # of the sidebar.
-#html_logo = None
+html_logo = 'logo.png'
 
 # The name of an image file (within the static path) to use as favicon of the
 # docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
 # (source start file, target name, title, author, documentclass [howto/manual]).
 latex_documents = [
   ('index', 'shoppingCart.tex', u'shoppingCart Documentation',
-   u'others', 'manual'),
+   u'copyright: (c) 2010 Dharmesh Patel', 'manual'),
 ]
 
 # The name of an image file (relative to this directory) to place at the top of
 # the title page.
-#latex_logo = None
+latex_logo = 'logo.png'
 
 # For "manual" documents, if this is true, then toplevel headings are parts,
 # not chapters.
 # (source start file, name, description, authors, manual section).
 man_pages = [
     ('index', 'shoppingcart', u'shoppingCart Documentation',
-     [u'others'], 1)
+     [u'copyright: (c) 2010 Dharmesh Patel'], 1)
 ]

docs/source/contents.txt

File contents unchanged.

docs/source/index.txt

 ========================================
 pypi page: http://pypi.python.org/pypi/shoppingCart
 
-Licence
+repository page: http://bitbucket.org/dharmeshpatel/shoppingcart
+
+License
 ========================================
 
 This documentation is under the GFDL

docs/source/intro/index.txt

File contents unchanged.

docs/source/intro/installation.txt

 Then run this command from the command prompt:
 
     **python setup.py install**
+    
+OR you can also install it using **easy_install** command:
+
+    **easy_install shoppingCart**
 
 AS AN ALTERNATIVE, you can just copy the entire "shoppingCart" directory to Python's
 site-packages directory, which is located wherever your Python installation

docs/source/intro/overview.txt

 Overview
 ================================================================================
 
-**shoppingCart** is a open source cart developed using `Python`_ language. It is to manage cart in Ecommerce applications.
+**shoppingCart** is a open source cart developed using `Python`_ language to manage cart in Ecommerce applications.
 
 **Main Feature(s):**
    1. Product options support.
    2. Multi discount support.
-   3. Multi taxes support(specific to product as well as general taxes).
+   3. Multi tax support(specific to product as well as general taxes).
    4. Multi currency support.
    5. Tax Exclude and Include total.
    6. Shipping method and charge.

docs/source/logo.png

Added
New image

docs/source/objects/Cart.txt

File contents unchanged.

docs/source/objects/CartItem.txt

File contents unchanged.

docs/source/objects/index.txt

File contents unchanged.
     platforms='any',
     keywords='cart  shopcart shoppingcart e-shop e-shopcart e-shoppingCart',
     include_package_data=True,
-    namespace_packages=['shoppingCart']
+    namespace_packages=['shoppingCart'],
+    zip_safe=False
 )

shoppingCart/__init__.py

 ################################################################################
 #
 #    Copyright (C) 2010 Dharmesh Patel <mr.dlpatel@gmail.com>.
-#    $Id$
-#
-#    This program is free software: you can redistribute it and/or modify
-#    it under the terms of the GNU General Public License as published by
-#    the Free Software Foundation, either version 3 of the License, or
-#    (at your option) any later version.
-#
-#    This program is distributed in the hope that it will be useful,
-#    but WITHOUT ANY WARRANTY; without even the implied warranty of
-#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#    GNU General Public License for more details.
-#
-#    You should have received a copy of the GNU General Public License
-#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#    Licence : BSD, see LICENSE for more details.
 #
 ################################################################################
 

shoppingCart/cart.py

 ################################################################################
 #
 #    Copyright (C) 2010 Dharmesh Patel <mr.dlpatel@gmail.com>.
-#    $Id$
-#
-#    This program is free software: you can redistribute it and/or modify
-#    it under the terms of the GNU General Public License as published by
-#    the Free Software Foundation, either version 3 of the License, or
-#    (at your option) any later version.
-#
-#    This program is distributed in the hope that it will be useful,
-#    but WITHOUT ANY WARRANTY; without even the implied warranty of
-#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#    GNU General Public License for more details.
-#
-#    You should have received a copy of the GNU General Public License
-#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#    Licence : BSD, see LICENSE for more details.
 #
 ################################################################################
 
-from datetime import datetime
 from shoppingCart.tax import calculate
-from shoppingCart.utils import id_from_object, get_dict_of_ids
+from shoppingCart.utils import id_from_object, get_dict_of_ids, get_list_of_ids
 
 __all__ = ['Cart']
 
         self.currency_code = 'EUR'
         self.__shipping_charge = 0.0
         self.shipping_method = 'Flat Rate Per Order'
-        self.type = 'tax_excluded'
+        self.tax_type = 'excluded'
         self.__discounts = list()
 
     def add_item(self, product, price=0.0, quantity=1, taxes=[], options={}):
             raise TypeError('quantity field value must be integer or float type', 'quantity')
         elif not (quantity or quantity >= 1):
             raise ValueError('quantity field value must be greater then 1', 'quantity')
+        
+        option_values = []
+        for option_value in options.values():            
+            if isinstance(option_value, dict):
+                option_value = option_value.keys()[0]
+            option_values.append(option_value)
             
-        cart_item = self.find_item(product, options)
+        cart_item = self.find_item(product, option_values)
         
         if cart_item:
             cart_item.update_quantity(cart_item.quantity + quantity)
             cart_item = CartItem(self, product, price, quantity, taxes, options)
             self.__items.append(cart_item)
 
-    def update_item(self, product, quantity, options={}):
+    def update_item(self, product, quantity, option_values=[]):
         """
         To update :class:`CartItem` object quantity.
         
         :param product: Unique id or name of :class:`Product` object or instance of :class:`Product`.
         :param quantity: Updated quantity.
-        :param options: Options of the product(default {}).
+        :param option_values: Option values of the product(default []).
         """
         if not isinstance(quantity, (int, float)):
             raise TypeError('quantity field value must be integer or float type', 'price')
         elif not (quantity or quantity >= 1):
             raise ValueError('quantity field value must be greater then 1')
             
-        cart_item = self.find_item(product, options)
+        cart_item = self.find_item(product, option_values)
         
         if cart_item:
             cart_item.update_quantity(quantity)
 
-    def remove_item(self, product, options={}):
+    def remove_item(self, product, option_values=[]):
         """
-        To remove existing :class:`CartItem` Object related to product.
+        To remove existing :class:`CartItem` object related to product.
 
         :param product: Unique id or name of :class:`Product` object or instance of :class:`Product`.
-        :param options: Options of the product(default {}).
+        :param option_values: Option values of the product(default []).
         """
-        cart_item = self.find_item(product, options)
+        cart_item = self.find_item(product, option_values)
         if cart_item:
             self.__items.remove(cart_item)
             
     def remove_items(self):
         """
-        To remove all existing :class:`CartItem` Objects.
+        To remove all existing :class:`CartItem` objects.
         """
         self.__items = list()
 
-    def find_item(self, product, options={}):
+    def find_item(self, product, option_values=[]):
         """
-        To find :class:`CartItem` Object related to product.
+        To find :class:`CartItem` object related to product.
         
-        :param product: Unique id of :class:`Product` object.
-        :param options: Options of the product(default {}).
-        :return: :class:`CartItem` object.
+        :param product: Unique id or name of :class:`Product` object or instance of :class:`Product`.
+        :param option_values: Option values of the product(default []).
+        :return: :class:`CartItem` object if cart item is exist else None.
         """
         product = id_from_object(product)
-        options = get_dict_of_ids(options)
-            
+        option_values = get_list_of_ids(option_values)
         for cart_item in self.__items:
             cart_product = id_from_object(cart_item.product)
-
             if not cmp(cart_product, product):
-                if options or cart_item.has_options:
+                if option_values:
+                    if not cart_item.has_options:
+                        continue
+                    cart_item_option_values = []
                     cart_item_options = get_dict_of_ids(cart_item.get_options())
-                    if not cmp(options.keys(), cart_item_options.keys()):
-                        for option, option_value in options.items():
-                            if isinstance(cart_item_options[option], dict) and isinstance(option_value, dict):
-                                if cmp(cart_item_options[option].keys(), option_value.keys()):
-                                    return None
-                            elif isinstance(cart_item_options[option], dict):
-                                if cmp(cart_item_options[option].keys(), [option_value]):
-                                    return None
-                            elif isinstance(option_value, dict):
-                                if cmp([cart_item_options[option]], option_value.keys()):
-                                    return None
-                            else:
-                                if cmp(cart_item_options[option], option_value):
-                                    return None
+                    for option, option_value in cart_item_options.items():
+                        if isinstance(option_value, dict):
+                            option_value = option_value.keys()[0]
+                        cart_item_option_values.append(option_value)
+                    if not cmp(option_values, cart_item_option_values):
                         return cart_item
                 else:
-                    return cart_item
-
+                    if not cart_item.has_options:
+                        return cart_item
         return None
 
     def get_items(self):
         """
-        :return: List of :class:`CartItem` object.
+        :return: List of :class:`CartItem` objects.
         """
         return self.__items
 
         
         :param amount: Discount amount.
         :param type: Discount type like 'percentage' or 'amount'(default amount).
-        :return: True if discount applied.
         """
         self.__discounts.append({'amount': amount, 'type': type})
- 
-        return True
             
     def remove_discounts(self):
         """
         :param tax: Tax amount according to country region.
         :param type: Tax type like 'percentage' or 'fixed'(default percentage).
 
-        :return: True if tax is applied and tax is already applied then False.
+        :return: True if tax is applied and tax is already exist then False.
         """
         if not isinstance(amount, (int, float)):
             raise TypeError('tax field value must be integer or float type', 'price')
         
         :param amount: Tax amount according to country region.
         :param type: Tax type like 'percentage' or 'fixed'(defualt percentage).
-        :return: True if tax is available else False.
+        :return: True if tax is exist else False.
         """
         if {'amount': amount, 'type': type} in self.__taxes:
             return True
         :return: Untaxed amount after deducating discount amount.
         """
         total_untaxed_amount = self.sub_total() - self.total_discount()
-        if self.type == 'tax_included':
+        if self.tax_type == 'included':
             total_untaxed_amount -= self.total_tax()
-#        for cart_item in self.get_items():
-#            total_untaxed_amount += cart_item.untaxed_amount()
         return round(total_untaxed_amount, self.price_accuracy)
 
     def total_tax(self):
         """
         total_tax = 0.0
         
-        if self.type == 'tax_included':
+        if self.tax_type == 'included':
             total = self.sub_total() - self.total_discount()
         else:
             total = self.total_untaxed_amount()
-        total_tax = calculate(total, self.__taxes, self.type, self.currency_rate, self.price_accuracy)
+        total_tax = calculate(total, self.__taxes, self.tax_type, self.currency_rate, self.price_accuracy)
         for cart_item in self.get_items():
             total_tax += cart_item.tax_amount()
         return round(total_tax, self.price_accuracy)
         """
         :return: Total amount(`tax excluded` or `tax included`) by adding total untaxed amount, total tax and shipping charge.
         """
+
         return round(self.total_untaxed_amount() + self.total_tax() + self.shipping_charge, self.price_accuracy)
 
     def count(self):
         
     def get_options(self):
         """
-        :return:  Product's options.
+        :return:  Dict of product's option.
         """
         for option, option_value in self.__options.items():
             if isinstance(option_value, dict):
 
     def sub_total(self):
         """
-        :return: Total amount by multiplying product price and quantity.
+        :return: Total amount by multiplying product price and quantity(without discount deduction).
         """
         price = self.price
         for option, option_value in self.get_options().items():
         
     def discount_amount(self):
         """
-        :return: discount amount.
+        :return: Discount amount.
         """
         discount_amount = 0.0
         sub_total = self.sub_total()
 
     def untaxed_amount(self):
         """
-        :return: Untaxed amount.
+        :return: Untaxed amount(after deducating discount amount).
         """
         total = self.sub_total() - self.discount_amount()
 
-        if self.__cart.type == 'tax_included':
+        if self.__cart.tax_type == 'included':
             total -= self.tax_amount()
         
         return round(total, self.__cart.price_accuracy)
         """
         tax_amount = 0.0
         
-        if self.__cart.type == 'tax_included':
+        if self.__cart.tax_type == 'included':
             total = self.sub_total() - self.discount_amount()
         else:
             total = self.untaxed_amount()
-        tax_amount = calculate(total, self.__taxes, self.__cart.type, self.__cart.currency_rate, self.__cart.price_accuracy)    
+        tax_amount = calculate(total, self.__taxes, self.__cart.tax_type, self.__cart.currency_rate, self.__cart.price_accuracy)    
         return tax_amount
 
     def total(self):
         """
-        :return: Total amount by adding untaxed and taxed amount.
+        :return: Total amount(`tax excluded` or `tax included`) by adding untaxed and taxed amount.
         """
         return round(self.untaxed_amount() + self.tax_amount(), self.__cart.price_accuracy)
             
     @property
     def has_options(self):
         """
-        :return: True if product has an options else False.
+        :return: True if product has an option else False.
         """
         if self.__options:
             return True
     @property
     def has_taxes(self):
         """
-        :return: True if product has taxes else False.
+        :return: True if product has tax else False.
         """    
         if self.__taxes:
             return True

shoppingCart/release.py

 ################################################################################
 
 name            = 'shoppingCart'
-version         = '1.2.0'
+version         = '1.3.0'
 major_version   = '1'
-description     = 'shoppingCart is a open source cart developed using Python language.It is to manage cart in Ecommerce applications.'
+description     = 'shoppingCart is a open source cart developed using Python language to manage cart in Ecommerce applications.'
 long_description= """
 Majour Feature(s):
 
 1. Product options support.
 2. Multi discount support.
-3. Multi taxes support(specific to product as well as general taxes).
+3. Multi tax support(specific to product as well as general taxes).
 4. Multi currency support.
 5. Tax Exclude and Include total.
 6. Shipping method and charge.
 """
 classifiers     = [
                         "Development Status :: 5 - Production/Stable",
-                        "License :: OSI Approved :: GNU General Public License (GPL)",
+                        "License :: OSI Approved :: BSD License",
                         "Programming Language :: Python"
                      ]
-url             = 'https://bitbucket.org/dharmeshpatel/shoppingcart'
+url             = 'http://bitbucket.org/dharmeshpatel/shoppingcart'
 author          = 'Dharmesh Patel'
 author_email    = 'mr[dot]dlpatel[at]gmail[dot]com'
-license         = 'GPL-3'
+copyright       = 'Copyright (c) 2010 Dharmesh Patel'
+license         = 'BSD'
 
 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

shoppingCart/tax.py

 ################################################################################
 #
 #    Copyright (C) 2010 Dharmesh Patel <mr.dlpatel@gmail.com>.
-#    $Id$
-#
-#    This program is free software: you can redistribute it and/or modify
-#    it under the terms of the GNU General Public License as published by
-#    the Free Software Foundation, either version 3 of the License, or
-#    (at your option) any later version.
-#
-#    This program is distributed in the hope that it will be useful,
-#    but WITHOUT ANY WARRANTY; without even the implied warranty of
-#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#    GNU General Public License for more details.
-#
-#    You should have received a copy of the GNU General Public License
-#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#    Licence : BSD, see LICENSE for more details.
 #
 ################################################################################
 
     """
     total_tax = 0.0
 
-    if tax_type == 'tax_included':
+    if tax_type == 'included':
         tax_percentage = sum([tax['amount'] for tax in taxes if tax['type'] == 'percentage'])
 
     for tax in taxes:        
         if tax['type'] == 'percentage':
-            if tax_type == 'tax_included':
+            if tax_type == 'included':
                 total_tax += round((float(amount) / (1 + (float(tax_percentage) / 100))) * (float(tax['amount']) / 100), price_accuracy)
             else:
                 total_tax += round(float(amount) * (float(tax['amount']) / 100), price_accuracy)

shoppingCart/tests/__init__.py

 ################################################################################
 #
 #    Copyright (C) 2010 Dharmesh Patel <mr.dlpatel@gmail.com>.
-#    $Id$
-#
-#    This program is free software: you can redistribute it and/or modify
-#    it under the terms of the GNU General Public License as published by
-#    the Free Software Foundation, either version 3 of the License, or
-#    (at your option) any later version.
-#
-#    This program is distributed in the hope that it will be useful,
-#    but WITHOUT ANY WARRANTY; without even the implied warranty of
-#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#    GNU General Public License for more details.
-#
-#    You should have received a copy of the GNU General Public License
-#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#    Licence : BSD, see LICENSE for more details.
 #
 ################################################################################
 

shoppingCart/tests/discount_coupon.py

 ################################################################################
 #
 #    Copyright (C) 2010 Dharmesh Patel <mr.dlpatel@gmail.com>.
-#    $Id$
-#
-#    This program is free software: you can redistribute it and/or modify
-#    it under the terms of the GNU General Public License as published by
-#    the Free Software Foundation, either version 3 of the License, or
-#    (at your option) any later version.
-#
-#    This program is distributed in the hope that it will be useful,
-#    but WITHOUT ANY WARRANTY; without even the implied warranty of
-#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#    GNU General Public License for more details.
-#
-#    You should have received a copy of the GNU General Public License
-#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#    Licence : BSD, see LICENSE for more details.
 #
 ################################################################################
 

shoppingCart/tests/product.py

 ################################################################################
 #
 #    Copyright (C) 2010 Dharmesh Patel <mr.dlpatel@gmail.com>.
-#    $Id$
-#
-#    This program is free software: you can redistribute it and/or modify
-#    it under the terms of the GNU General Public License as published by
-#    the Free Software Foundation, either version 3 of the License, or
-#    (at your option) any later version.
-#
-#    This program is distributed in the hope that it will be useful,
-#    but WITHOUT ANY WARRANTY; without even the implied warranty of
-#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#    GNU General Public License for more details.
-#
-#    You should have received a copy of the GNU General Public License
-#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#    Licence : BSD, see LICENSE for more details.
 #
 ################################################################################
 

shoppingCart/tests/testcases.py

 ################################################################################
 #
 #    Copyright (C) 2010 Dharmesh Patel <mr.dlpatel@gmail.com>.
-#    $Id$
-#
-#    This program is free software: you can redistribute it and/or modify
-#    it under the terms of the GNU General Public License as published by
-#    the Free Software Foundation, either version 3 of the License, or
-#    (at your option) any later version.
-#
-#    This program is distributed in the hope that it will be useful,
-#    but WITHOUT ANY WARRANTY; without even the implied warranty of
-#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#    GNU General Public License for more details.
-#
-#    You should have received a copy of the GNU General Public License
-#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#    Licence : BSD, see LICENSE for more details.
 #
 ################################################################################
 
-import datetime
 import unittest
 
 from shoppingCart.tests.product import Product, Option, OptionValue
 
 class CartTestCase(unittest.TestCase):
 
-    def test_add_update_remove_find_item(self):
-        cart_obj = Cart()
-        self.assertEqual(cart_obj.is_empty, True)
-        self.assertEqual(cart_obj.count(), 0)
-#        self.assertRaises(ValueError, cart_obj.add_item(product=1, quantity=1))
-        cart_obj.add_item(product=1, price=10.00, quantity=1)
-        cart_obj.add_item(product=2, price=12.25, quantity=2.5)
-        self.assertEqual(cart_obj.is_empty, False)
-        self.assertEqual(cart_obj.count(), 3.5)
-        self.assertEqual([{'price': item.price, 'options': item.get_options(), 'product': item.product, 'quantity': item.quantity} for item in cart_obj.get_items()], [{'price': 10.0, 'options': {}, 'product': 1, 'quantity': 1}, {'price': 12.25, 'options': {}, 'product': 2, 'quantity': 2.5}])
-        cart_obj.add_item(product=1, quantity=1)
-        self.assertEqual([{'price': item.price, 'options': item.get_options(), 'product': item.product, 'quantity': item.quantity} for item in cart_obj.get_items()], [{'price': 10.0, 'options': {}, 'product': 1, 'quantity': 2}, {'price': 12.25, 'options': {}, 'product': 2, 'quantity': 2.5}])
-        cart_obj.update_item(product=1, quantity=3)
-        self.assertEqual([{'price': item.price, 'options': item.get_options(), 'product': item.product, 'quantity': item.quantity} for item in cart_obj.get_items()], [{'price': 10.0, 'options': {}, 'product': 1, 'quantity': 3}, {'price': 12.25, 'options': {}, 'product': 2, 'quantity': 2.5}])
-        cart_obj.remove_item(product=1)
-        self.assertEqual([{'price': item.price, 'options': item.get_options(), 'product': item.product, 'quantity': item.quantity} for item in cart_obj.get_items()], [{'price': 12.25, 'options': {}, 'product': 2, 'quantity': 2.5}])
-        self.assertEqual(cart_obj.find_item(1), None)
-        self.assertEqual([{'price': item.price, 'options': item.get_options(), 'product': item.product, 'quantity': item.quantity} for item in [cart_obj.find_item(2)]], [{'price': 12.25, 'options': {}, 'product': 2, 'quantity': 2.5}])
-        self.assertEqual(cart_obj.count(), 2.5)
-        cart_obj.clear()
-        self.assertEqual(cart_obj.get_items(), [])
+    def setUp(self):
+        unittest.TestCase.setUp(self)
+        #Created `Cart` object instance.
+        self.cart = Cart()
+        self.cart.currency_rate = 1
+        self.cart.price_accuracy = 2
+        self.cart.currency_symbol = '€'
+        self.cart.currency_code = 'EUR'
+        self.cart.shipping_charge = 10.21
+        #Created `Option` and `OptionValue` object instances.
+        self.option_value1 = OptionValue(id=1, name='option_value-1', code='ov1', price=05.00)
+        self.option_value2 = OptionValue(id=2, name='option_value-2', code='ov2', price=10.00)
+        self.option_value3 = OptionValue(id=3, name='option_value-3', code='ov3', price=15.00)
+        self.option_value4 = OptionValue(id=4, name='option_value-4', code='ov4', price=20.00)
+        self.option1 = Option(id=1, name='option-1', code='o1', values=[self.option_value1, self.option_value2])
+        self.option2 = Option(id=2, name='option-2', code='o2', values=[self.option_value3, self.option_value4])
+        #Created `Product` object instances.
+        self.product1 = Product(id=1, name='product-1', code='p1', price=10.00)
+        self.product2 = Product(id=2, name='product-2', code='p2', price=12.25, options=[self.option1, self.option2])
+        self.product3 = Product(id=3, name='product-3', code='p3', price=20.00)
+        #Created `DiscountCoupon` object instances.
+        self.discount_coupon1 = DiscountCoupon(code='ByQ343X', expiry_date='2010-11-30 12:00:00', type='percentage', discount=30)
+        self.discount_coupon2 = DiscountCoupon(code='Ax9812Y', expiry_date='2010-12-31', type='amount', discount=20)
+        
+    def tearDown(self):
+        unittest.TestCase.tearDown(self)
+        del self.cart
+        del self.product1
+        del self.product2
+        del self.product3
+        del self.option_value1
+        del self.option_value2
+        del self.option_value3
+        del self.option_value4
+        del self.option1
+        del self.option2
+        del self.discount_coupon1
+        del self.discount_coupon2
+
+    def test_add_item(self):
+        self.assertTrue(self.cart.is_empty)
+        self.assertEqual(self.cart.count(), 0)
+        #
+        #TEST : By passing unique id in `product` parameter.
+        #
+        self.cart.add_item(product=1, price=10.00, quantity=1)
+        self.cart.add_item(product=2, price=12.25, quantity=2.5)
+        self.assertFalse(self.cart.is_empty)
+        self.assertEqual(self.cart.count(), 3.5)
+        self.assertEqual([{'product': item.product, 'quantity': item.quantity} for item in self.cart.get_items()], [{'product': 1, 'quantity': 1}, {'product': 2, 'quantity': 2.5}])
+        self.cart.clear()
+        self.assertEqual(self.cart.get_items(), [])
+        #
+        #TEST: By passing unique string(i.e. 'Product Name') in `product` parameter.
+        #
+        self.cart.remove_items()
+        self.cart.add_item(product='product-1', price=10.00, quantity=1)
+        self.cart.add_item(product='product-2', price=12.25, quantity=2.5)
+        self.assertFalse(self.cart.is_empty)
+        self.assertEqual(self.cart.count(), 3.5)
+        self.assertEqual([{'product': item.product, 'quantity': item.quantity} for item in self.cart.get_items()], [{'product': 'product-1', 'quantity': 1}, {'product': 'product-2', 'quantity': 2.5}])
+        self.cart.clear()
+        self.assertEqual(self.cart.get_items(), [])
+        #
+        #TEST: By passing object instance(i.e. Instance of `Product` object) IN `product` parameter.
+        #
+        self.cart.remove_items()
+        self.cart.add_item(product=self.product1, price=10.00, quantity=1)
+        self.cart.add_item(product=self.product2, price=12.25, quantity=2.5)
+        self.assertFalse(self.cart.is_empty)
+        self.assertEqual(self.cart.count(), 3.5)
+        self.assertEqual([{'product': item.product.name, 'quantity': item.quantity} for item in self.cart.get_items()], [{'product': 'product-1', 'quantity': 1}, {'product': 'product-2', 'quantity': 2.5}])
+        self.cart.clear()
+        self.assertEqual(self.cart.get_items(), [])
+        
+    def test_update_item(self):
+        #
+        #TEST : By passing unique id in `product` parameter.
+        #
+        self.cart.add_item(product=1, price=10.00, quantity=1)
+        self.cart.add_item(product=2, price=12.25, quantity=2.5)
+        self.assertEqual(self.cart.count(), 3.5)
+        self.cart.update_item(product=1, quantity=3)
+        self.cart.update_item(product=2, quantity=1.5)
+        self.assertEqual(self.cart.count(), 4.5)
+        self.assertEqual([{'product': item.product, 'quantity': item.quantity} for item in self.cart.get_items()], [{'product': 1, 'quantity': 3}, {'product': 2, 'quantity': 1.5}])
+        self.cart.clear()
+        self.assertEqual(self.cart.get_items(), [])
+        #
+        #TEST: By passing unique string(i.e. 'Product Name') in `product` parameter.
+        #
+        self.cart.remove_items()
+        self.cart.add_item(product='product-1', price=10.00, quantity=1)
+        self.cart.add_item(product='product-2', price=12.25, quantity=2.5)
+        self.assertEqual(self.cart.count(), 3.5)
+        self.cart.update_item(product='product-1', quantity=3)
+        self.cart.update_item(product='product-2', quantity=1.5)
+        self.assertEqual(self.cart.count(), 4.5)
+        self.assertEqual([{'product': item.product, 'quantity': item.quantity} for item in self.cart.get_items()], [{'product': 'product-1', 'quantity': 3}, {'product': 'product-2', 'quantity': 1.5}])
+        self.cart.clear()
+        self.assertEqual(self.cart.get_items(), [])
+        #
+        #TEST: By passing object instance(i.e. Instance of `Product` object) IN `product` parameter.
+        #
+        self.cart.remove_items()
+        self.cart.add_item(product=self.product1, price=10.00, quantity=1)
+        self.cart.add_item(product=self.product2, price=12.25, quantity=2.5)
+        self.assertEqual(self.cart.count(), 3.5)
+        self.cart.update_item(product=self.product1, quantity=3)
+        self.cart.update_item(product=self.product2, quantity=1.5)
+        self.assertEqual(self.cart.count(), 4.5)
+        self.assertEqual([{'product': item.product.name, 'quantity': item.quantity} for item in self.cart.get_items()], [{'product': 'product-1', 'quantity': 3}, {'product': 'product-2', 'quantity': 1.5}])
+        self.cart.clear()
+        self.assertEqual(self.cart.get_items(), [])
+        
+    def test_remove_item(self):
+        #
+        #TEST : By passing unique id in `product` parameter.
+        #
+        self.cart.add_item(product=1, price=10.00, quantity=1)
+        self.cart.add_item(product=2, price=12.25, quantity=2.5)
+        self.assertEqual(self.cart.count(), 3.5)
+        self.cart.remove_item(product=1)
+        self.assertEqual(self.cart.count(), 2.5)
+        self.assertEqual([{'product': item.product, 'quantity': item.quantity} for item in self.cart.get_items()], [{'product': 2, 'quantity': 2.5}])
+        self.cart.clear()
+        self.assertEqual(self.cart.get_items(), [])
+        #
+        #TEST: By passing unique string(i.e. 'Product Name') in `product` parameter.
+        #
+        self.cart.remove_items()
+        self.cart.add_item(product='product-1', price=10.00, quantity=1)
+        self.cart.add_item(product='product-2', price=12.25, quantity=2.5)
+        self.assertEqual(self.cart.count(), 3.5)
+        self.cart.remove_item(product='product-1')
+        self.assertEqual(self.cart.count(), 2.5)
+        self.assertEqual([{'product': item.product, 'quantity': item.quantity} for item in self.cart.get_items()], [{'product': 'product-2', 'quantity': 2.5}])
+        self.cart.clear()
+        self.assertEqual(self.cart.get_items(), [])
+        #
+        #TEST: By passing object instance(i.e. Instance of `Product` object) IN `product` parameter.
+        #
+        self.cart.remove_items()
+        self.cart.add_item(product=self.product1, price=10.00, quantity=1)
+        self.cart.add_item(product=self.product2, price=12.25, quantity=2.5)
+        self.assertEqual(self.cart.count(), 3.5)
+        self.cart.remove_item(product=self.product1)
+        self.assertEqual(self.cart.count(), 2.5)
+        self.assertEqual([{'product': item.product.name, 'quantity': item.quantity} for item in self.cart.get_items()], [{'product': 'product-2', 'quantity': 2.5}])
+        self.cart.clear()
+        self.assertEqual(self.cart.get_items(), [])
+        
+    def test_find_item(self):
+        #
+        #TEST : By passing unique id in `product` parameter.
+        #
+        self.cart.add_item(product=1, price=10.00, quantity=1)
+        self.cart.add_item(product=2, price=12.25, quantity=2.5)
+        self.assertEqual(self.cart.find_item(3), None)
+        self.assertEqual([{'product': item.product, 'quantity': item.quantity} for item in [self.cart.find_item(1)]], [{'product': 1, 'quantity': 1}])
+        self.cart.clear()
+        self.assertEqual(self.cart.get_items(), [])
+        #
+        #TEST: By passing unique string(i.e. 'Product Name') in `product` parameter.
+        #
+        self.cart.remove_items()
+        self.cart.add_item(product='product-1', price=10.00, quantity=1)
+        self.cart.add_item(product='product-2', price=12.25, quantity=2.5)
+        self.assertEqual(self.cart.find_item('product-3'), None)
+        self.assertEqual([{'product': item.product, 'quantity': item.quantity} for item in [self.cart.find_item('product-1')]], [{'product': 'product-1', 'quantity': 1}])
+        self.cart.clear()
+        self.assertEqual(self.cart.get_items(), [])
+        #
+        #TEST: By passing object instance(i.e. Instance of `Product` object) IN `product` parameter.
+        #
+        self.cart.remove_items()
+        self.cart.add_item(product=self.product1, price=10.00, quantity=1)
+        self.cart.add_item(product=self.product2, price=12.25, quantity=2.5)
+        self.assertEqual(self.cart.find_item(self.product3), None)
+        self.assertEqual([{'product': item.product.name, 'quantity': item.quantity} for item in [self.cart.find_item(self.product1)]], [{'product': 'product-1', 'quantity': 1}])
+        self.cart.clear()
+        self.assertEqual(self.cart.get_items(), [])
     
-    def test_add_update_remove_options(self):
-        cart_obj = Cart()
-        self.assertEqual(cart_obj.is_empty, True)
-        self.assertEqual(cart_obj.count(), 0)
-        cart_obj.add_item(product=1, price=10.00, quantity=1, taxes=[{'amount':19.6, 'type': 'percentage'}, {'amount':2.1, 'type': 'fixed'}])
-        cart_obj.add_item(product=2, price=12.25, quantity=2.5, options={'color': {'red': {'price': 10.00}}})
-        self.assertEqual([{'product': item.product, 'quantity': item.quantity, 'price': item.price, 'options': item.get_options()} for item in cart_obj.get_items()], [{'product': 1, 'options': {}, 'price': 10.0, 'quantity': 1}, {'product': 2, 'options': {'color': {'red': {'price': 10.0}}}, 'price': 12.25, 'quantity': 2.5}])
-        self.assertEqual(cart_obj.count(), 3.5)
-        self.assertEqual('%s %s'%(cart_obj.sub_total(), cart_obj.currency_symbol), '65.63 €')
-        self.assertEqual('%s %s'%(cart_obj.total_untaxed_amount(), cart_obj.currency_symbol), '65.63 €')
-        self.assertEqual('%s %s'%(cart_obj.total_tax(), cart_obj.currency_symbol), '4.06 €')
-        self.assertEqual('%s %s'%(cart_obj.total(), cart_obj.currency_symbol), '69.69 €')
-                            
-    def test_apply_remove_discount(self):
-        cart_obj = Cart()
-        self.assertEqual(cart_obj.is_discount_applied, False)
-        discount_coupon = DiscountCoupon(code='ByQ343X', expiry_date='2010-11-30 12:00:00', type='percentage', discount=30)
-        self.assertEqual(cart_obj.add_discount(discount_coupon.discount, discount_coupon.type), True)
-        discount_coupon = DiscountCoupon(code='Ax9812Y', expiry_date='2010-12-31 12:00', type='percentage', discount=30)
-        self.assertEqual(cart_obj.add_discount(discount_coupon.discount, discount_coupon.type), True)
-        self.assertEqual(cart_obj.is_discount_applied, True)
-        self.assertEqual([discount for discount in cart_obj.get_discounts()], [{'amount': 30, 'type': 'percentage'}, {'amount': 30, 'type': 'percentage'}])
+    def test_add_item_with_options(self):
+        self.assertTrue(self.cart.is_empty)
+        self.assertEqual(self.cart.count(), 0)
+        #
+        #TEST : By passing unique id in `product` and 'options' parameters.
+        #        
+        #Added product and specific taxes related to it
+        self.cart.add_item(product=1, price=10.00, quantity=1, taxes=[{'amount': 19.6, 'type': 'percentage'}, {'amount': 2.1, 'type': 'fixed'}])
+        #Added product and related options
+        self.cart.add_item(product=2, price=12.25, quantity=2.5)
+        self.cart.add_item(product=2, price=12.25, quantity=1, options={2: {4: {'price': 20.00}}})
+        self.cart.add_item(product=2, price=12.25, quantity=2, options={1: {1: {'price': 05.00}}, 2: {3: {'price': 15.00}}})
+        self.cart.add_item(product=2, price=12.25, quantity=3.5, options={1: {2: {'price': 10.00}}, 2: {4: {'price': 20.00}}})
+        self.assertEqual([{'product': item.product, 'quantity': item.quantity, 'price': item.price, 'options': item.get_options()} for item in self.cart.get_items()], [{'product': 1, 'options': {}, 'price': 10.0, 'quantity': 1}, {'product': 2, 'options': {}, 'price': 12.25, 'quantity': 2.5}, {'product': 2, 'options': {2: {4: {'price': 20.0}}}, 'price': 12.25, 'quantity': 1}, {'product': 2, 'options': {1: {1: {'price': 5.0}}, 2: {3: {'price': 15.0}}}, 'price': 12.25, 'quantity': 2}, {'product': 2, 'options': {1: {2: {'price': 10.0}}, 2: {4: {'price': 20.0}}}, 'price': 12.25, 'quantity': 3.5}])
+        self.assertEqual(self.cart.count(), 10.0)
+        #Cart tax type
+        self.assertEqual('%s'%(self.cart.tax_type,), 'excluded')
+        #Sub Total
+        self.assertEqual('%s %s'%(self.cart.sub_total(), self.cart.currency_symbol), '285.26 €')
+        #Total Untaxed Amount
+        self.assertEqual('%s %s'%(self.cart.total_untaxed_amount(), self.cart.currency_symbol), '285.26 €')
+        #Total Tax
+        self.assertEqual('%s %s'%(self.cart.total_tax(), self.cart.currency_symbol), '4.06 €')
+        #Shipping Charge
+        self.assertEqual('%s %s'%(self.cart.shipping_charge, self.cart.currency_symbol), '10.21 €')
+        #Total
+        self.assertEqual('%s %s'%(self.cart.total(), self.cart.currency_symbol), '299.53 €')
+        #
+        #TEST: By passing unique string(i.e. 'Product Name', 'Option Name' and 'OptionValue Name') in `product` and `options` parameters.
+        #
+        self.cart.remove_items()
+        #Added product and specific taxes related to it
+        self.cart.add_item(product='product-1', price=10.00, quantity=1, taxes=[{'amount': 19.6, 'type': 'percentage'}, {'amount': 2.1, 'type': 'fixed'}])
+        #Added product and related options
+        self.cart.add_item(product='product-2', price=12.25, quantity=2.5)
+        self.cart.add_item(product='product-2', price=12.25, quantity=1, options={'option-2': {'option_value-4': {'price': 20.00}}})
+        self.cart.add_item(product='product-2', price=12.25, quantity=2, options={'option-1': {'option_value-1': {'price': 05.00}}, 'option-2': {'option_value-3': {'price': 15.00}}})
+        self.cart.add_item(product='product-2', price=12.25, quantity=3.5, options={'option-1': {'option_value-2': {'price': 10.00}}, 'option-2': {'option_value-4': {'price': 20.00}}})
+        self.assertEqual([{'product': item.product, 'quantity': item.quantity, 'price': item.price, 'options': item.get_options()} for item in self.cart.get_items()], [{'product': 'product-1', 'options': {}, 'price': 10.0, 'quantity': 1}, {'product': 'product-2', 'options': {}, 'price': 12.25, 'quantity': 2.5}, {'product': 'product-2', 'options': {'option-2': {'option_value-4': {'price': 20.0}}}, 'price': 12.25, 'quantity': 1}, {'product': 'product-2', 'options': {'option-1': {'option_value-1': {'price': 5.0}}, 'option-2': {'option_value-3': {'price': 15.0}}}, 'price': 12.25, 'quantity': 2}, {'product': 'product-2', 'options': {'option-1': {'option_value-2': {'price': 10.0}}, 'option-2': {'option_value-4': {'price': 20.0}}}, 'price': 12.25, 'quantity': 3.5}])
+        self.assertEqual(self.cart.count(), 10.0)
+        #Cart tax type
+        self.assertEqual('%s'%(self.cart.tax_type,), 'excluded')
+        #Sub Total
+        self.assertEqual('%s %s'%(self.cart.sub_total(), self.cart.currency_symbol), '285.26 €')
+        #Total Untaxed Amount
+        self.assertEqual('%s %s'%(self.cart.total_untaxed_amount(), self.cart.currency_symbol), '285.26 €')
+        #Total Tax
+        self.assertEqual('%s %s'%(self.cart.total_tax(), self.cart.currency_symbol), '4.06 €')
+        #Shipping Charge
+        self.assertEqual('%s %s'%(self.cart.shipping_charge, self.cart.currency_symbol), '10.21 €')
+        #Total
+        self.assertEqual('%s %s'%(self.cart.total(), self.cart.currency_symbol), '299.53 €')
+        #
+        #TEST: By passing object instance(i.e. Instance of `Product`, `Option` and `OptionValue` objects) IN `product`, `options` parameters.
+        #
+        self.cart.remove_items()
+        #Added product and specific taxes related to it
+        self.cart.add_item(product=self.product1, price=10.00, quantity=1, taxes=[{'amount': 19.6, 'type': 'percentage'}, {'amount': 2.1, 'type': 'fixed'}])
+        #Added product and related options
+        self.cart.add_item(product=self.product2, price=12.25, quantity=2.5)
+        self.cart.add_item(product=self.product2, price=12.25, quantity=1, options={self.option1: {self.option_value4: {'price': 20.00}}})
+        self.cart.add_item(product=self.product2, price=12.25, quantity=2, options={self.option1: {self.option_value1: {'price': 05.00}}, self.option2: {self.option_value3: {'price': 15.00}}})
+        self.cart.add_item(product=self.product2, price=12.25, quantity=3.5, options={self.option1: {self.option_value2: {'price': 10.00}}, self.option2: {self.option_value4: {'price': 20.00}}})
+        options={}
+#        self.assertEqual([{'product': item.product.name, 'quantity': item.quantity, 'price': item.price, 'options': item.has_options and [options.update({option.name: [{key.name: value} for key, value in option_value.items()][0]}) for option, option_value in item.get_options().items()] and options or {}} for item in self.cart.get_items()], [{'product': 'product-1', 'options': {}, 'price': 10.0, 'quantity': 1}, {'product': 'product-2', 'options': {}, 'price': 12.25, 'quantity': 2.5}, {'product': 'product-2', 'options': {'option-2': {'option_value-4': {'price': 20.0}}}, 'price': 12.25, 'quantity': 1}, {'product': 'product-2', 'options': {'option-1': {'option_value-1': {'price': 5.0}}, 'option-2': {'option_value-3': {'price': 15.0}}}, 'price': 12.25, 'quantity': 2}, {'product': 'product-2', 'options': {'option-1': {'option_value-2': {'price': 10.0}}, 'option-2': {'option_value-4': {'price': 20.0}}}, 'price': 12.25, 'quantity': 3.5}])
+        self.assertEqual(self.cart.count(), 10.0)
+        #Cart tax type
+        self.assertEqual('%s'%(self.cart.tax_type,), 'excluded')
+        #Sub Total
+        self.assertEqual('%s %s'%(self.cart.sub_total(), self.cart.currency_symbol), '285.26 €')
+        #Total Untaxed Amount
+        self.assertEqual('%s %s'%(self.cart.total_untaxed_amount(), self.cart.currency_symbol), '285.26 €')
+        #Total Tax
+        self.assertEqual('%s %s'%(self.cart.total_tax(), self.cart.currency_symbol), '4.06 €')
+        #Shipping Charge
+        self.assertEqual('%s %s'%(self.cart.shipping_charge, self.cart.currency_symbol), '10.21 €')
+        #Total
+        self.assertEqual('%s %s'%(self.cart.total(), self.cart.currency_symbol), '299.53 €')
 
-    def test_apply_remove_tax(self):
-        cart_obj = Cart()
-        self.assertEqual(cart_obj.add_tax(19.6, type='percentage'), True)
-        self.assertEqual(cart_obj.add_tax(20.1, type='fixed'), True)
-        self.assertEqual(cart_obj.add_tax(19.6, type='percentage'), False)
-        self.assertEqual(cart_obj.get_taxes(), [{'amount': 19.600000000000001, 'type': 'percentage'}, {'amount': 20.100000000000001, 'type': 'fixed'}])
-        self.assertEqual(cart_obj.remove_tax(10), False)
-        self.assertEqual(cart_obj.remove_tax(20.1, type='fixed'), True)
-        self.assertEqual(cart_obj.get_taxes(), [{'amount': 19.600000000000001, 'type': 'percentage'}])
+    def test_update_item_with_options(self):
+        #
+        #TEST : By passing unique id in `product` and 'options' parameters.
+        #        
+        #Added product and specific taxes related to it
+        self.cart.add_item(product=1, price=10.00, quantity=1, taxes=[{'amount': 19.6, 'type': 'percentage'}, {'amount': 2.1, 'type': 'fixed'}])
+        #Added product and related options
+        self.cart.add_item(product=2, price=12.25, quantity=2.5)
+        self.cart.add_item(product=2, price=12.25, quantity=1, options={2: {4: {'price': 20.00}}})
+        self.cart.add_item(product=2, price=12.25, quantity=2, options={1: {1: {'price': 05.00}}, 2: {3: {'price': 15.00}}})
+        self.cart.add_item(product=2, price=12.25, quantity=3.5, options={1: {2: {'price': 10.00}}, 2: {4: {'price': 20.00}}})
+        self.assertEqual(self.cart.count(), 10)
+        #Product updated
+        self.cart.update_item(product=1, quantity=3)
+        #To update item which has option need to pass option values only in `option_values` parameter
+        self.cart.update_item(product=2, quantity=1.5)        
+        self.cart.update_item(product=2, quantity=2, option_values=[4])
+        self.cart.update_item(product=2, quantity=3, option_values=[1, 3])
+        self.cart.update_item(product=2, quantity=4, option_values=[2, 4])
+        self.assertEqual(self.cart.count(), 13.5)
+        self.assertEqual([{'product': item.product, 'quantity': item.quantity} for item in self.cart.get_items()], [{'product': 1, 'quantity': 3}, {'product': 2, 'quantity': 1.5}, {'product': 2, 'quantity': 2}, {'product': 2, 'quantity': 3}, {'product': 2, 'quantity': 4}])
+        self.cart.clear()
+        self.assertEqual(self.cart.get_items(), [])
+        #
+        #TEST: By passing unique string(i.e. 'Product Name', 'Option Name' and 'OptionValue Name') in `product` and `options` parameters.
+        #
+        self.cart.remove_items()
+        self.cart.add_item(product='product-1', price=10.00, quantity=1, taxes=[{'amount': 19.6, 'type': 'percentage'}, {'amount': 2.1, 'type': 'fixed'}])
+        #Added product and related options
+        self.cart.add_item(product='product-2', price=12.25, quantity=2.5)
+        self.cart.add_item(product='product-2', price=12.25, quantity=1, options={'option-2': {'option_value-4': {'price': 20.00}}})
+        self.cart.add_item(product='product-2', price=12.25, quantity=2, options={'option-1': {'option_value-1': {'price': 05.00}}, 'option-2': {'option_value-3': {'price': 15.00}}})
+        self.cart.add_item(product='product-2', price=12.25, quantity=3.5, options={'option-1': {'option_value-2': {'price': 10.00}}, 'option-2': {'option_value-4': {'price': 20.00}}})
+        self.assertEqual(self.cart.count(), 10)
+        #Product updated        
+        self.cart.update_item(product='product-1', quantity=3)
+        #To update item which has option need to pass option values only in `option_values` parameter
+        self.cart.update_item(product='product-2', quantity=1.5)
+        self.cart.update_item(product='product-2', quantity=2, option_values=['option_value-4'])
+        self.cart.update_item(product='product-2', quantity=3, option_values=['option_value-1', 'option_value-3'])
+        self.cart.update_item(product='product-2', quantity=4, option_values=['option_value-2', 'option_value-4'])
+        self.assertEqual(self.cart.count(), 13.5)
+        self.assertEqual([{'product': item.product, 'quantity': item.quantity} for item in self.cart.get_items()], [{'product': 'product-1', 'quantity': 3}, {'product': 'product-2', 'quantity': 1.5}, {'product': 'product-2', 'quantity': 2}, {'product': 'product-2', 'quantity': 3}, {'product': 'product-2', 'quantity': 4}])
+        self.cart.clear()
+        self.assertEqual(self.cart.get_items(), [])
+        #
+        #TEST: By passing object instance(i.e. Instance of `Product`, `Option` and `OptionValue` objects) IN `product`, `options` parameters.
+        #
+        self.cart.remove_items()
+        self.cart.add_item(product=self.product1, price=10.00, quantity=1)
+        #Added product and related options
+        self.cart.add_item(product=self.product2, price=12.25, quantity=2.5)
+        self.cart.add_item(product=self.product2, price=12.25, quantity=1, options={self.option1: {self.option_value4: {'price': 20.00}}})
+        self.cart.add_item(product=self.product2, price=12.25, quantity=2, options={self.option1: {self.option_value1: {'price': 05.00}}, self.option2: {self.option_value3: {'price': 15.00}}})
+        self.cart.add_item(product=self.product2, price=12.25, quantity=3.5, options={self.option1: {self.option_value2: {'price': 10.00}}, self.option2: {self.option_value4: {'price': 20.00}}})
+        self.assertEqual(self.cart.count(), 10.0)
+        #Product updated
+        self.cart.update_item(product=self.product1, quantity=3)
+        #To update item which has option need to pass option values only in `option_values` parameter
+        self.cart.update_item(product=self.product2, quantity=1.5)
+        self.cart.update_item(product=self.product2, quantity=2, option_values=[self.option_value4])
+        self.cart.update_item(product=self.product2, quantity=3, option_values=[self.option_value1, self.option_value3])
+        self.cart.update_item(product=self.product2, quantity=4, option_values=[self.option_value2, self.option_value4])
+        self.assertEqual(self.cart.count(), 13.5)
+        self.assertEqual([{'product': item.product.name, 'quantity': item.quantity} for item in self.cart.get_items()], [{'product': 'product-1', 'quantity': 3}, {'product': 'product-2', 'quantity': 1.5}, {'product': 'product-2', 'quantity': 2}, {'product': 'product-2', 'quantity': 3}, {'product': 'product-2', 'quantity': 4}])
+        self.cart.clear()
+        self.assertEqual(self.cart.get_items(), [])
         
-    def test_discount_amount(self):
-        discount_coupon = DiscountCoupon(code='Ax9812Y', expiry_date='2010-12-31 12:00', type='percentage', discount=30)
-        cart_obj = Cart()
-        cart_obj.currency_rate = 1
-        cart_obj.price_accuracy = 2
-        cart_obj.currency_symbol = '€'
-        cart_obj.currency_code = 'EUR' 
-        cart_obj.add_item(product=1, price=10.00, quantity=1)
-        cart_obj.add_item(product=2, price=12.25, quantity=2.5)
-        self.assertEqual('%s %s'%(cart_obj.sub_total(), cart_obj.currency_symbol), '40.63 €')
-        cart_obj.add_discount(discount_coupon.discount, discount_coupon.type)
-        self.assertEqual('%s %s'%(cart_obj.total_discount(), cart_obj.currency_symbol), '12.19 €')
+    def test_remove_item_with_options(self):
+        #
+        #TEST : By passing unique id in `product` and 'options' parameters.
+        #        
+        #Added product and specific taxes related to it
+        self.cart.add_item(product=1, price=10.00, quantity=1, taxes=[{'amount': 19.6, 'type': 'percentage'}, {'amount': 2.1, 'type': 'fixed'}])
+        #Added product and related options
+        self.cart.add_item(product=2, price=12.25, quantity=2.5)
+        self.cart.add_item(product=2, price=12.25, quantity=1, options={2: {4: {'price': 20.00}}})
+        self.cart.add_item(product=2, price=12.25, quantity=2, options={1: {1: {'price': 05.00}}, 2: {3: {'price': 15.00}}})
+        self.cart.add_item(product=2, price=12.25, quantity=3.5, options={1: {2: {'price': 10.00}}, 2: {4: {'price': 20.00}}})
+        self.assertEqual(self.cart.count(), 10)
+        #Product removed
+        self.cart.remove_item(product=1)
+        #To remove item which has option need to pass option values only in `option_values` parameter
+        self.cart.remove_item(product=2, option_values=[2, 4])
+        self.assertEqual(self.cart.count(), 5.5)
+        self.assertEqual([{'product': item.product, 'quantity': item.quantity} for item in self.cart.get_items()], [{'product': 2, 'quantity': 2.5}, {'product': 2, 'quantity': 1}, {'product': 2, 'quantity': 2}])
+        self.cart.clear()
+        self.assertEqual(self.cart.get_items(), [])
+        #
+        #TEST: By passing unique string(i.e. 'Product Name', 'Option Name' and 'OptionValue Name') in `product` and `options` parameters.
+        #
+        self.cart.remove_items()
+        self.cart.add_item(product='product-1', price=10.00, quantity=1, taxes=[{'amount': 19.6, 'type': 'percentage'}, {'amount': 2.1, 'type': 'fixed'}])
+        #Added product and related options
+        self.cart.add_item(product='product-2', price=12.25, quantity=2.5)
+        self.cart.add_item(product='product-2', price=12.25, quantity=1, options={'option-2': {'option_value-4': {'price': 20.00}}})
+        self.cart.add_item(product='product-2', price=12.25, quantity=2, options={'option-1': {'option_value-1': {'price': 05.00}}, 'option-2': {'option_value-3': {'price': 15.00}}})
+        self.cart.add_item(product='product-2', price=12.25, quantity=3.5, options={'option-1': {'option_value-2': {'price': 10.00}}, 'option-2': {'option_value-4': {'price': 20.00}}})
+        self.assertEqual(self.cart.count(), 10)
+        #Product removed
+        self.cart.remove_item(product='product-1')
+        #To remove item which has option need to pass option values only in `option_values` parameter
+        self.cart.remove_item(product='product-2', option_values=['option_value-2', 'option_value-4'])
+        self.assertEqual(self.cart.count(), 5.5)
+        self.assertEqual([{'product': item.product, 'quantity': item.quantity} for item in self.cart.get_items()], [{'product': 'product-2', 'quantity': 2.5}, {'product': 'product-2', 'quantity': 1}, {'product': 'product-2', 'quantity': 2}])
+        self.cart.clear()
+        self.assertEqual(self.cart.get_items(), [])
+        #
+        #TEST: By passing object instance(i.e. Instance of `Product`, `Option` and `OptionValue` objects) IN `product`, `options` parameters.
+        #
+        self.cart.remove_items()
+        self.cart.add_item(product=self.product1, price=10.00, quantity=1)
+        #Added product and related options
+        self.cart.add_item(product=self.product2, price=12.25, quantity=2.5)
+        self.cart.add_item(product=self.product2, price=12.25, quantity=1, options={self.option1: {self.option_value4: {'price': 20.00}}})
+        self.cart.add_item(product=self.product2, price=12.25, quantity=2, options={self.option1: {self.option_value1: {'price': 05.00}}, self.option2: {self.option_value3: {'price': 15.00}}})
+        self.cart.add_item(product=self.product2, price=12.25, quantity=3.5, options={self.option1: {self.option_value2: {'price': 10.00}}, self.option2: {self.option_value4: {'price': 20.00}}})
+        self.assertEqual(self.cart.count(), 10.0)
+        #Product removed
+        self.cart.remove_item(product=self.product1)
+        #To remove item which has option need to pass option values only in `option_values` parameter
+        self.cart.remove_item(product=self.product2, option_values=[self.option_value2, self.option_value4])
+        self.assertEqual(self.cart.count(), 5.5)
+        self.assertEqual([{'product': item.product.name, 'quantity': item.quantity} for item in self.cart.get_items()], [{'product': 'product-2', 'quantity': 2.5}, {'product': 'product-2', 'quantity': 1}, {'product': 'product-2', 'quantity': 2}])
+        self.cart.clear()
+        self.assertEqual(self.cart.get_items(), [])
+
+    def test_find_item_with_options(self):
+        #
+        #TEST : By passing unique id in `product` and 'options' parameters.
+        #        
+        #Added product and specific taxes related to it
+        self.cart.add_item(product=1, price=10.00, quantity=1, taxes=[{'amount': 19.6, 'type': 'percentage'}, {'amount': 2.1, 'type': 'fixed'}])
+        #Added product and related options
+        self.cart.add_item(product=2, price=12.25, quantity=2.5)
+        self.cart.add_item(product=2, price=12.25, quantity=1, options={2: {4: {'price': 20.00}}})
+        self.cart.add_item(product=2, price=12.25, quantity=2, options={1: {1: {'price': 05.00}}, 2: {3: {'price': 15.00}}})
+        self.cart.add_item(product=2, price=12.25, quantity=3.5, options={1: {2: {'price': 10.00}}, 2: {4: {'price': 20.00}}})
+        #To find item which has option need to pass option values only in `option_values` parameter
+        self.assertEqual(self.cart.find_item(3), None)
+        self.assertEqual([{'product': item.product, 'quantity': item.quantity} for item in [self.cart.find_item(1)]], [{'product': 1, 'quantity': 1}])
+        self.assertEqual([{'product': item.product, 'quantity': item.quantity} for item in [self.cart.find_item(2)]], [{'product': 2, 'quantity': 2.5}])
+        self.assertEqual([{'product': item.product, 'quantity': item.quantity} for item in [self.cart.find_item(2, option_values=[1, 3])]], [{'product': 2, 'quantity': 2}])
+        self.cart.clear()
+        self.assertEqual(self.cart.get_items(), [])
+        #
+        #TEST: By passing unique string(i.e. 'Product Name', 'Option Name' and 'OptionValue Name') in `product` and `options` parameters.
+        #
+        self.cart.remove_items()
+        self.cart.add_item(product='product-1', price=10.00, quantity=1, taxes=[{'amount': 19.6, 'type': 'percentage'}, {'amount': 2.1, 'type': 'fixed'}])
+        #Added product and related options
+        self.cart.add_item(product='product-2', price=12.25, quantity=2.5)
+        self.cart.add_item(product='product-2', price=12.25, quantity=1, options={'option-2': {'option_value-4': {'price': 20.00}}})
+        self.cart.add_item(product='product-2', price=12.25, quantity=2, options={'option-1': {'option_value-1': {'price': 05.00}}, 'option-2': {'option_value-3': {'price': 15.00}}})
+        self.cart.add_item(product='product-2', price=12.25, quantity=3.5, options={'option-1': {'option_value-2': {'price': 10.00}}, 'option-2': {'option_value-4': {'price': 20.00}}})
+        #To find item which has option need to pass option values only in `option_values` parameter
+        self.assertEqual(self.cart.find_item('product-3'), None)
+        self.assertEqual([{'product': item.product, 'quantity': item.quantity} for item in [self.cart.find_item('product-1')]], [{'product': 'product-1', 'quantity': 1}])
+        self.assertEqual([{'product': item.product, 'quantity': item.quantity} for item in [self.cart.find_item('product-2')]], [{'product': 'product-2', 'quantity': 2.5}])
+        self.assertEqual([{'product': item.product, 'quantity': item.quantity} for item in [self.cart.find_item('product-2', option_values=['option_value-1', 'option_value-3'])]], [{'product': 'product-2', 'quantity': 2}])
+        self.cart.clear()
+        self.assertEqual(self.cart.get_items(), [])
+        #
+        #TEST: By passing object instance(i.e. Instance of `Product`, `Option` and `OptionValue` objects) IN `product`, `options` parameters.
+        #
+        self.cart.remove_items()
+        self.cart.add_item(product=self.product1, price=10.00, quantity=1)
+        #Added product and related options
+        self.cart.add_item(product=self.product2, price=12.25, quantity=2.5)
+        self.cart.add_item(product=self.product2, price=12.25, quantity=1, options={self.option1: {self.option_value4: {'price': 20.00}}})
+        self.cart.add_item(product=self.product2, price=12.25, quantity=2, options={self.option1: {self.option_value1: {'price': 05.00}}, self.option2: {self.option_value3: {'price': 15.00}}})
+        self.cart.add_item(product=self.product2, price=12.25, quantity=3.5, options={self.option1: {self.option_value2: {'price': 10.00}}, self.option2: {self.option_value4: {'price': 20.00}}})
+        #To find item which has option need to pass option values only in `option_values` parameter
+        self.assertEqual(self.cart.find_item(self.product3), None)
+        self.assertEqual([{'product': item.product.name, 'quantity': item.quantity} for item in [self.cart.find_item(self.product1)]], [{'product': 'product-1', 'quantity': 1}])
+        self.assertEqual([{'product': item.product.name, 'quantity': item.quantity} for item in [self.cart.find_item(self.product2)]], [{'product': 'product-2', 'quantity': 2.5}])
+        self.assertEqual([{'product': item.product.name, 'quantity': item.quantity} for item in [self.cart.find_item(self.product2, option_values=[self.option_value1, self.option_value3])]], [{'product': 'product-2', 'quantity': 2}])
+        self.cart.clear()
+        self.assertEqual(self.cart.get_items(), [])
+
+    def test_add_multi_discount(self):
+        self.assertFalse(self.cart.is_discount_applied)
+        #
+        #TEST : By passing amount in `amount` parameter.
+        #
+        self.cart.add_discount(amount=30, type='percentage')
+        self.cart.add_discount(amount=20, type='amount')
+        self.assertTrue(self.cart.is_discount_applied)
+        self.assertEqual([discount for discount in self.cart.get_discounts()], [{'amount': 30, 'type': 'percentage'}, {'amount': 20, 'type': 'amount'}])
+        #
+        #TEST: Using object instance(i.e. Instance of `DiscountCoupon` object).
+        #
+        self.cart.remove_discounts()
+        self.cart.add_discount(amount=self.discount_coupon1.discount, type=self.discount_coupon1.type)
+        self.cart.add_discount(amount=self.discount_coupon2.discount, type=self.discount_coupon2.type)
+        self.assertTrue(self.cart.is_discount_applied)
+        self.assertEqual([discount for discount in self.cart.get_discounts()], [{'amount': 30, 'type': 'percentage'}, {'amount': 20, 'type': 'amount'}])
+        
+    def test_add_multi_tax(self):
+        #Added tax specific to product only
+        self.cart.add_item(product=1, price=10.00, quantity=1, taxes=[{'amount':10.00, 'type': 'percentage'}, {'amount':5.0, 'type': 'fixed'}])
+        #Added general taxes[which will apply on all cart items]
+        self.assertTrue(self.cart.add_tax(19.6, type='percentage'))
+        self.assertTrue(self.cart.add_tax(20.1, type='fixed'))
+        #
+        #TEST: By trying to add tax which is already added
+        #
+        self.assertFalse(self.cart.add_tax(19.6, type='percentage'))
+        self.assertEqual(self.cart.get_taxes(), [{'amount': 10.0, 'type': 'percentage'}, {'amount': 5.0, 'type': 'fixed'}, {'amount': 19.600000000000001, 'type': 'percentage'}, {'amount': 20.100000000000001, 'type': 'fixed'}])
+    
+    def test_remove_tax(self):
+        #Added general taxes
+        self.assertTrue(self.cart.add_tax(19.6, type='percentage'))
+        self.assertTrue(self.cart.add_tax(20.1, type='fixed'))
+        self.assertFalse(self.cart.remove_tax(10))
+        self.assertTrue(self.cart.remove_tax(20.1, type='fixed'))
+        self.assertEqual(self.cart.get_taxes(), [{'amount': 19.600000000000001, 'type': 'percentage'}])
 
     def test_sub_total(self):
-        cart_obj = Cart()
-        cart_obj.currency_rate = 1
-        cart_obj.price_accuracy = 2
-        cart_obj.currency_symbol = '€'
-        cart_obj.currency_code = 'EUR'
-        cart_obj.add_item(product=1, price=10.00, quantity=1)
-        cart_obj.add_item(product=2, price=12.25, quantity=2.5)
-        self.assertEqual('%s %s'%(cart_obj.sub_total(), cart_obj.currency_symbol), '40.63 €')
-        cart_obj.currency_rate = 1.2714
-        cart_obj.price_accuracy = 2
-        cart_obj.currency_symbol = '$'
-        cart_obj.currency_code = 'USD'
-        self.assertEqual('%s%s'%(cart_obj.currency_symbol, cart_obj.sub_total()), '$51.63')
+        self.cart.add_item(product=1, price=10.00, quantity=1)
+        self.cart.add_item(product=2, price=12.25, quantity=2.5)
+        #Sub Total
+        self.assertEqual('%s %s'%(self.cart.sub_total(), self.cart.currency_symbol), '40.63 €')
+        #Set `currency_rate`, `price_accuracy`, `currency_symbol` and `currency_code`
+        self.cart.currency_rate = 1.2714
+        self.cart.price_accuracy = 2
+        self.cart.currency_symbol = '$'
+        self.cart.currency_code = 'USD'
+        #Sub Total
+        self.assertEqual('%s%s'%(self.cart.currency_symbol, self.cart.sub_total()), '$51.63')
+        
+    def test_total_discount(self):
+        self.cart.add_item(product=1, price=10.00, quantity=1)
+        self.cart.add_item(product=2, price=12.25, quantity=2.5)
+        #Sub Total
+        self.assertEqual('%s %s'%(self.cart.sub_total(), self.cart.currency_symbol), '40.63 €')
+        #Added discount
+        self.cart.add_discount(self.discount_coupon1.discount, self.discount_coupon1.type)
+        #Total Discount
+        self.assertEqual('%s %s'%(self.cart.total_discount(), self.cart.currency_symbol), '12.19 €')
+        
+    def test_total_untaxed_amount(self):
+        self.cart.add_item(product=1, price=10.00, quantity=1)
+        self.cart.add_item(product=2, price=12.25, quantity=2.5)
+        #Sub Total
+        self.assertEqual('%s %s'%(self.cart.sub_total(), self.cart.currency_symbol), '40.63 €')
+        #Added discount
+        self.cart.add_discount(self.discount_coupon1.discount, self.discount_coupon1.type)
+        #Total Discount
+        self.assertEqual('%s %s'%(self.cart.total_discount(), self.cart.currency_symbol), '12.19 €')
+        #Added Tax
+        self.assertTrue(self.cart.add_tax(19.6, type='percentage'))
+        #Set `tax_type` with 'excluded'
+        self.cart.tax_type = 'excluded'
+        #Total Tax
+        self.assertEqual('%s %s'%(self.cart.total_tax(), self.cart.currency_symbol), '5.57 €')
+        #Total Untaxed Amount
+        self.assertEqual('%s %s'%(self.cart.total_untaxed_amount(), self.cart.currency_symbol), '28.44 €')
+        #Set `tax_type` with 'included'
+        self.cart.tax_type = 'included'
+        #Total Tax
+        self.assertEqual('%s %s'%(self.cart.total_tax(), self.cart.currency_symbol), '4.66 €')
+        #Total Untaxed Amount
+        self.assertEqual('%s %s'%(self.cart.total_untaxed_amount(), self.cart.currency_symbol), '23.78 €')
 
-    def test_total_taxed_amount(self):
-        cart_obj = Cart()
-        cart_obj.currency_rate = 1
-        cart_obj.price_accuracy = 2
-        cart_obj.currency_symbol = '€'
-        cart_obj.currency_code = 'EUR'
-        cart_obj.add_item(product=1, price=10.00, quantity=1)
-        cart_obj.add_item(product=2, price=12.25, quantity=2.5)
-        self.assertEqual(cart_obj.add_tax(19.6, type='percentage'), True)
-        cart_obj.type = 'tax_excluded'
-        self.assertEqual('%s %s'%(cart_obj.total_tax(), cart_obj.currency_symbol), '7.96 €')
-        cart_obj.type = 'tax_included'
-        self.assertEqual('%s %s'%(cart_obj.total_tax(), cart_obj.currency_symbol), '6.66 €')
+    def test_total_tax(self):
+        self.cart.add_item(product=1, price=10.00, quantity=1)
+        self.cart.add_item(product=2, price=12.25, quantity=2.5)
+        #Added general tax
+        self.assertTrue(self.cart.add_tax(19.6, type='percentage'))
+        #Set `tax_type` with 'excluded'
+        self.cart.tax_type = 'excluded'
+        #Total Tax
+        self.assertEqual('%s %s'%(self.cart.total_tax(), self.cart.currency_symbol), '7.96 €')
+        #Set `tax_type` with 'included'
+        self.cart.tax_type = 'included'
+        #Total Tax
+        self.assertEqual('%s %s'%(self.cart.total_tax(), self.cart.currency_symbol), '6.66 €')
         
-    def test_untaxed_amount(self):
-        discount_coupon = DiscountCoupon(code='Ax9812Y', expiry_date='2010-12-31 12:00', type='percentage', discount=30)
-        cart_obj = Cart()
-        cart_obj.currency_rate = 1
-        cart_obj.price_accuracy = 2
-        cart_obj.currency_symbol = '€'
-        cart_obj.currency_code = 'EUR'        
-        cart_obj.add_item(product=1, price=10.00, quantity=1)
-        cart_obj.add_item(product=2, price=12.25, quantity=2.5)
-        self.assertEqual('%s %s'%(cart_obj.sub_total(), cart_obj.currency_symbol), '40.63 €')
-        self.assertEqual(cart_obj.add_discount(discount_coupon.discount, discount_coupon.type), True)
-        self.assertEqual('%s %s'%(cart_obj.total_discount(), cart_obj.currency_symbol), '12.19 €')        
-        self.assertEqual(cart_obj.add_tax(19.6, type='percentage'), True)
-        cart_obj.type = 'tax_excluded'
-        self.assertEqual('%s %s'%(cart_obj.total_tax(), cart_obj.currency_symbol), '5.57 €')        
-        self.assertEqual('%s %s'%(cart_obj.total_untaxed_amount(), cart_obj.currency_symbol), '28.44 €')
-        cart_obj.type = 'tax_included'
-        self.assertEqual('%s %s'%(cart_obj.total_tax(), cart_obj.currency_symbol), '4.66 €')        
-        self.assertEqual('%s %s'%(cart_obj.total_untaxed_amount(), cart_obj.currency_symbol), '23.78 €')
+    def test_total_with_multi_currency(self):
+        self.cart.add_item(product=1, price=10.00, quantity=1)
+        self.cart.add_item(product=2, price=12.25, quantity=2.5)
+        #Sub Total
+        self.assertEqual('%s %s'%(self.cart.sub_total(), self.cart.currency_symbol), '40.63 €')
+        #Added discount
+        self.cart.add_discount(self.discount_coupon1.discount, self.discount_coupon1.type)
+        #Total discount
+        self.assertEqual('%s %s'%(self.cart.total_discount(), self.cart.currency_symbol), '12.19 €')
+        #Added tax
+        self.assertTrue(self.cart.add_tax(19.6, type='percentage'))
+        #Set `tax_type` with 'excluded'
+        self.cart.tax_type = 'excluded'
+        #Total Untaxed Amount
+        self.assertEqual('%s %s'%(self.cart.total_untaxed_amount(), self.cart.currency_symbol), '28.44 €')
+        #Total Tax
+        self.assertEqual('%s %s'%(self.cart.total_tax(), self.cart.currency_symbol), '5.57 €')
+        #Shipping Charge
+        self.assertEqual('%s %s'%(self.cart.shipping_charge, self.cart.currency_symbol), '10.21 €')
+        #Tax Excluded Total
+        self.assertEqual('%s %s'%(self.cart.total(), self.cart.currency_symbol), '44.22 €')
+        #Set `tax_type` with 'included'
+        self.cart.tax_type = 'included'
+        #Total Untaxed Amount
+        self.assertEqual('%s %s'%(self.cart.total_untaxed_amount(), self.cart.currency_symbol), '23.78 €')
+        #Total Tax
+        self.assertEqual('%s %s'%(self.cart.total_tax(), self.cart.currency_symbol), '4.66 €')
+        #Shipping Charge
+        self.assertEqual('%s %s'%(self.cart.shipping_charge, self.cart.currency_symbol), '10.21 €')
+        #Tax Included Total
+        self.assertEqual('%s %s'%(self.cart.total(), self.cart.currency_symbol), '38.65 €')
+        #Set `currency_rate`, `price_accuracy`, `currency_symbol` and `currency_code`
+        self.cart.currency_rate = 1.2714
+        self.cart.price_accuracy = 2 
+        self.cart.currency_symbol = '$'
+        self.cart.currency_code = 'USD'
+        #Set `tax_type` with 'excluded'
+        self.cart.tax_type = 'excluded'
+        self.assertEqual('%s%s'%(self.cart.currency_symbol, self.cart.total_untaxed_amount()), '$36.14')
+        self.assertEqual('%s%s'%(self.cart.currency_symbol, self.cart.total_tax()), '$7.08')
+        self.assertEqual('%s%s'%(self.cart.currency_symbol, self.cart.shipping_charge), '$12.98')
+        #Tax Excluded Total
+        self.assertEqual('%s%s'%(self.cart.currency_symbol, self.cart.total()), '$56.2')
+        #Set `tax_type` with 'included'
+        self.cart.tax_type = 'included'
+        self.assertEqual('%s%s'%(self.cart.currency_symbol, self.cart.total_untaxed_amount()), '$30.22')
+        self.assertEqual('%s%s'%(self.cart.currency_symbol, self.cart.total_tax()), '$5.92')
+        self.assertEqual('%s%s'%(self.cart.currency_symbol, self.cart.shipping_charge), '$12.98')
+        #Tax Included Total
+        self.assertEqual('%s%s'%(self.cart.currency_symbol, self.cart.total()), '$49.12')
         
-    def test_total(self):
-        discount_coupon = DiscountCoupon(code='Ax9812Y', expiry_date='2010-12-31', type='percentage', discount=30)
-        cart_obj = Cart()
-        cart_obj.currency_rate = 1
-        cart_obj.price_accuracy = 2
-        cart_obj.currency_symbol = '€'
-        cart_obj.currency_code = 'EUR'
-        cart_obj.shipping_charge = 10.21
-        cart_obj.add_item(product=1, price=10.00, quantity=1)
-        cart_obj.add_item(product=2, price=12.25, quantity=2.5)
-        self.assertEqual('%s %s'%(cart_obj.sub_total(), cart_obj.currency_symbol), '40.63 €')
-        self.assertEqual(cart_obj.add_discount(discount_coupon.discount, discount_coupon.type), True)
-        self.assertEqual('%s %s'%(cart_obj.total_discount(), cart_obj.currency_symbol), '12.19 €')        
-        self.assertEqual(cart_obj.add_tax(19.6, type='percentage'), True)
-        cart_obj.type = 'tax_excluded' # set type with `tax_excluded`
-        self.assertEqual('%s %s'%(cart_obj.total_untaxed_amount(), cart_obj.currency_symbol), '28.44 €')
-        self.assertEqual('%s %s'%(cart_obj.total_tax(), cart_obj.currency_symbol), '5.57 €')
-        self.assertEqual('%s %s'%(cart_obj.shipping_charge, cart_obj.currency_symbol), '10.21 €')
-        self.assertEqual('%s %s'%(cart_obj.total(), cart_obj.currency_symbol), '44.22 €') # TAX EXLCUDED TOTAL
-        cart_obj.type = 'tax_included' # set type with `tax_included`
-        self.assertEqual('%s %s'%(cart_obj.total_untaxed_amount(), cart_obj.currency_symbol), '23.78 €')        
-        self.assertEqual('%s %s'%(cart_obj.total_tax(), cart_obj.currency_symbol), '4.66 €')        
-        self.assertEqual('%s %s'%(cart_obj.shipping_charge, cart_obj.currency_symbol), '10.21 €')        
-        self.assertEqual('%s %s'%(cart_obj.total(), cart_obj.currency_symbol), '38.65 €') # TAX INCLUDED TOTAL
-        cart_obj.currency_rate = 1.2714 # change CURRENCY_RATE
-        cart_obj.price_accuracy = 2 # change PRICE_ACCURACY
-        cart_obj.currency_symbol = '$'
-        cart_obj.currency_code = 'USD'
-        cart_obj.type = 'tax_excluded' # set type with `tax_excluded`
-        self.assertEqual('%s%s'%(cart_obj.currency_symbol, cart_obj.total_untaxed_amount()), '$36.14')
-        self.assertEqual('%s%s'%(cart_obj.currency_symbol, cart_obj.total_tax()), '$7.08')
-        self.assertEqual('%s%s'%(cart_obj.currency_symbol, cart_obj.shipping_charge), '$12.98')
-        self.assertEqual('%s%s'%(cart_obj.currency_symbol, cart_obj.total()), '$56.2') # TAX EXLCUDED TOTAL
-        cart_obj.type = 'tax_included' # set type with `tax_included`
-        self.assertEqual('%s%s'%(cart_obj.currency_symbol, cart_obj.total_untaxed_amount()), '$30.22')
-        self.assertEqual('%s%s'%(cart_obj.currency_symbol, cart_obj.total_tax()), '$5.92')
-        self.assertEqual('%s%s'%(cart_obj.currency_symbol, cart_obj.shipping_charge), '$12.98')
-        self.assertEqual('%s%s'%(cart_obj.currency_symbol, cart_obj.total()), '$49.12') # TAX INCLUDED TOTAL
-        
+def suite():
+   suite = unittest.TestSuite()
+   suite.addTest(unittest.makeSuite(CartTestCase))
+   return suite
+
 if __name__ == '__main__':
 #    unittest.main()
-    suite = unittest.TestLoader().loadTestsFromTestCase(CartTestCase)
-    unittest.TextTestRunner().run(suite)
+#    suite = unittest.TestLoader().loadTestsFromTestCase(CartTestCase)
+    unittest.TextTestRunner().run(suite())
 
 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

shoppingCart/utils.py

 ################################################################################
 #
 #    Copyright (C) 2010 Dharmesh Patel <mr.dlpatel@gmail.com>.
-#    $Id$
-#
-#    This program is free software: you can redistribute it and/or modify
-#    it under the terms of the GNU General Public License as published by
-#    the Free Software Foundation, either version 3 of the License, or
-#    (at your option) any later version.
-#
-#    This program is distributed in the hope that it will be useful,
-#    but WITHOUT ANY WARRANTY; without even the implied warranty of
-#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#    GNU General Public License for more details.
-#
-#    You should have received a copy of the GNU General Public License
-#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#    Licence : BSD, see LICENSE for more details.
 #
 ################################################################################