Showing posts with label python e-commerce. Show all posts
Showing posts with label python e-commerce. Show all posts

Saturday, 5 February 2011

Improving the Cart

Updating the Model

In my second update I will make some improvements to the shopping cart model, notably allowing multiple products to be added to the cart and fixing the remove() method so it actually works.

class Cart(object):
    """
    Shopping cart stores products in the session and persists to a database
    """
    # Stores the items
    __items = dict()

    # Constructor
    def __init__(self):
        # Get cartID from the session if it exists
        cartID = session.get('cartID', None)
        # If cartID is set then load an instance of the database model
        # populated with existing items
        if cartID:
            cart = CartModel()
            self.cartModel = cart.get(cartID)
        # If not then create a new instance and save self to the database
        # and set cartID in the session
        else:
            cart = CartModel(object=self, status=0)
            DBSession.add(cart)
            transaction.commit()
            session['cartID'] = cart.id
            self.cartModel = cart

    # Main methods
    def add(self, product, qty):
        # Save dictionary of product attributes
        items = dict({'id': product.id,
          'qty': qty,
          'description': product.name,
          'price': product.price})
        self.set_items(items)
        self.save()

    def remove(self, productID):
        # Remove an item
        items = self.get_items()
        if int(productID) in items:
            del items[int(productID)]

        self.save()

    def clear(self):
        # Clear items from session, but NOT database as we will need
        # a record of transactions
        session.delete()

    def save(self):
        # Persist changes
        session.save()
        self.cartModel.object = self
        transaction.commit()

    # Getters/Setters
    def set_items(self, items):
        self.__items[items['id']] = items
        session['items'] = self.__items

    def get_items(self):
        if session.get('items', None):
            self.__items = session['items']
        return self.__items


All I have done here is change the set_items() method to properly append the items dict to the self.__items dict. The remove method was puzzling to me coming from a background in PHP until I remembered that the keys in a Python dictionary can be any type (except another dictionary) and that the productID passed into it from the controller was a string and the key is an integer. In PHP this would not matter, but Python was throwing a KeyError, but explicitly casting productID to an integer solved this little issue.

Now the cart properly handles adding and removing products from it.

The Controller

I will briefly go over the controller functions in root.py. I think in my finished application I will move all cart related actions into a separate controller but for now when I am mainly just debugging my models I shall leave them in the root controller.

@expose()
    def add_to_cart(self, productID, quantity):
        # Get cart instance
        cart = self.get_cart()
        # create a new product object
        product = Product()
        # add product to cart
        cart.add(product.get(productID), quantity)
        # Feedback and redirect
        flash('Product added')
        redirect('/')

    @expose()
    def remove_from_cart(self, productID):
        # Get cart
        cart = self.get_cart()
        # Remove productID from cart
        cart.remove(productID)
        # Feedback and redirect
        flash('Product removed')
        redirect('/')

These are fairly self-explanatory, we simply call the instance of the cart add/remove and then set a flash message and redirect back to the home page.

In my next update I will bring in an address model and start creating some checkout logic so that the cart can do something useful.

Friday, 4 February 2011

Building a Shopping Cart using TurboGears

Overview

This is my first foray into the world of Python and I am trying to build an e-commerce website. I have chosen TurboGears as my framework after messing about with Django and Web2Py it fits my needs well as a professional PHP developer with a lot of experience coding in Zend. The primary reason is that is uses the excellent SQLAlchemy ORM and unlike Django, follows a more traditional MVC design pattern.

After hours of fruitless searching for a decent tutorial that covers building a shopping cart I decided to take my experience as a Zend developer and try to do it in Python, like I would do it in PHP. I am documenting my progress to aid others, and also for the developer community to help me with my efforts. So here goes…

After setting up my models and tables, which I won't go into too much here, the main effort is building a shopping cart class that performs the following functions:
  • Add products to the cart
  • Remove products from the cart
  • Persist changes to both the web session and also to a database 

Cart Database Model

class CartModel(DeclarativeBase, Defaults):
  # Table name
  __tablename__ = 'carts'
  # Columns
  id = Column('id', Integer, primary_key=True, autoincrement=True)
  user_id = Column('user_id', Integer, ForeignKey(User.user_id))
  object = Column('object', PickleType)
  create_date = Column('create_date', DateTime, default=func.now())
  update_date = Column('update_date', DateTime, onupdate=func.now())
  status = Column('status', String)

  def get(self, cartID):
    query = DBSession.query(CartModel).get(cartID)
    return query

# Set relationships
CartModel.user = relation(User, primaryjoin=CartModel.user_id==User.user_id)

Basically this class saves a serialised copy of the cart object (which I will detail next) in the object field. SQLALchemy is great for this, the PickleType field will automatically serialise/unserialise Python objects. Doing this in PHP would be a pain in the backside. We also relate carts to users and define some metadata about the cart.


Shopping Cart Model

class Cart(object):
  __items = dict()

  def __init__(self):
    cartID = session.get('cartID', None)

    if cartID:
      cart = CartModel()
      self.cartModel = cart.get(cartID)
    else:
      cart = CartModel(object=self, status=0)
      DBSession.add(cart)
      transaction.commit()
      session['cartID'] = cart.id
      self.cartModel = cart

  def add(self, product, qty):
    items = dict(id=product.id, qty=qty, description=product.name, price=product.price)
    self.set_items(items)
    self.save()

  def remove(self, product):
    items = self.get_items()
    del items[product.id]
    self.set_items(items)
    self.save()

  def clear(self):
    session.delete()

  def save(self):
    session.save()
    self.cartModel.object = self
    transaction.commit()

  def set_items(self, items):
    self.__items[items['id']] = items['qty']
    session['items'] = items

  def get_items(self):
    if session.get('items', None):
      self.__items = session['items']
    return self.__items  

Here I have chosen to represent my cart items as a dict, self.__items the add and remove methods basically take attributes from the Product object that's passed to it and saves them to this dict.

The __init__() method is responsible for ensuring that the database and the session are synchronised. The cartID which is the primary key is saved in the session and if it exists then we create a CartModel object from this primary key, if not we create a new row and save the primary key in the session so subsequent actions all act on the same database row.

This is really in it infancy at the moment and I am not really sure if I can refactor certain functions to be more pythonic.

Your comments and improvements are more than welcome.