I'm trying to teach myself Django by using it to make an e-commerce site. I'm working on the shopping cart right now; it's implemented using Django sessions and it's currently working fine, but I'm having trouble implementing the "save for later" functionality that you can find on lots of online stores (i.e. Amazon or whatever) that allows users to remove items from their shopping cart and instead put them on a list that allows them to easily see it from their shopping cart page. Before I go on, here's the views.py and cart.py for my current shopping cart:
cart.py:
from decimal import Decimal
from django.conf import settings
from bookDetails.models import Book
# This is the cart class.
class Cart(object):
# Constructor method for the class - includes a request parameter
def __init__(self, request):
# Start by creating a session for the new cart
self.session = request.session
userCart = self.session.get(settings.CART_SESSION_ID)
if not userCart:
userCart = self.session[settings.CART_SESSION_ID] = {}
self.userCart = userCart
def save(self):
self.session.modified = True
def add(self, book, amount=1, change_amount=False):
book_id = str(book.id)
if book_id not in self.userCart:
self.userCart[book_id] = {'amount': 0,
'author': book.book_author,
'author_bio': book.author_bio,
'description': book.book_description,
'genre': book.book_genre,
'publishing_info': book.publishing_info,
'avg_rating': str(book.avg_rating),
'price': str(book.price)}
if change_amount:
self.userCart[book_id]['amount'] = amount
else:
self.userCart[book_id]['amount'] += amount
self.save()
def remove(self, book):
book_id = str(book.id)
if book_id in self.userCart:
del self.userCart[book_id]
self.save()
def __iter__(self):
book_ids = self.userCart.keys()
books = Book.objects.filter(id__in=book_ids)
cart = self.userCart.copy()
for book in books:
cart[str(book.id)]['book'] = book
for book in cart.values():
book['price'] = Decimal(book['price'])
book['total_price'] = book['price'] * book['amount']
yield book
def __len__(self):
return sum(book['amount'] for book in self.userCart.values())
def get_total_price(self):
return sum((book['price'] * book['amount']) for book in self.userCart.values())
def clear(self):
del self.session[settings.CART_SESSION_ID]
self.save()
Views.py:
from django.shortcuts import render, redirect, get_object_or_404
from django.views.decorators.http import require_POST
# This is the Book model from the bookDetails package I made.
from bookDetails.models import Book
# These are the cart and cart forms.
from .cart import Cart
from .forms import AddToCartForm
@require_POST
def addToCart(request, book_id):
userCart = Cart(request)
book = get_object_or_404(Book, id=book_id)
form = AddToCartForm(request.POST)
if form.is_valid():
data = form.cleaned_data
userCart.add(book=book,
amount=data['amount'],
change_amount=data['change_amount'])
return redirect('cart:cart_info')
def removeFromCart(request, book_id):
userCart = Cart(request)
book = get_object_or_404(Book, id=book_id)
userCart.remove(book)
return redirect('cart:cart_info')
def cart_info(request):
userCart = Cart(request)
for current in userCart:
current['update_amount_form'] = AddToCartForm(
initial={'amount': current['amount'],
'change_amount': True}
)
return render(request, 'cart/info.html', {'userCart': userCart})
# This view displays the checkout page
def checkout(request):
userCart = Cart(request)
userCart.clear()
return render(request, 'cart/checkout.html', {'userCart': userCart})
So given the way I have the cart set up, what would be the simplest/most efficient way to set up a "save for later" functionality? What I originally tried to do is create another class just like the Cart class, except it was called SFLList (Saved For Later List), then just copy-pasted most of the code from the cart class right onto it and adjusted them for a simple list, something like this
class SFLList(object):
def __init__(self, request):
self.session = request.session
SFL = self.session.get(settings.SFL_SESSION_ID)
if not SFL:
SFL = self.session[settings.SFL_SESSION_ID] = {}
self.SFL = SFL
# Below this would go functions like addSFL, removeSFL,
# and the __iter__ function, all redefined to work with SFLList
...But this ended up giving me a TypeError because "the Decimal type is not JSON serializable" or something to that effect, which may look like it has something to do with the way I converted the price attribute to str so it could be serializeable (in the add function of the cart class), but the code and site work perfectly fine as they are. It only broke down and gave me that error when I added the SFLList code and tried to integrate it.
I spent all day yesterday trying to get my new SFLList class to work, but to no avail. I ended up just discarding the changes and reverting to my latest commit (before I made SFLList and the changes related to it). As expected, no TypeError because of Decimal when it's just the Cart class, defined exactly as I've got it here.
I feel like there has to be an easier way to do this. All "saved for later" has to do is just the exact same thing my cart already does, but without having the book inside the cart. Ordinarily what I'd do in a language like Java or C++ or whatever is just create an array and move the "Book" instances into it, then just iterate through the array and print all of each book's attributes in order. This is my first time working with something like Django where things are seemingly mostly done through database queries. It doesn't seem I like I can really use an array or list to store things inside models - the closest thing I found was something called an ArrayField, but apparently it requires that your database be "Postgres", which I don't think my project is using (settings.py has the DB set up as sqlite3).
What's the best way to do this?
I think you can simplify the problem just setting a boolean variable in your cart item dictionary like this
self.userCart[book_id] = {
'amount': 0,
'author': book.book_author,
'author_bio': book.author_bio,
'description': book.book_description,
'genre': book.book_genre,
'publishing_info': book.publishing_info,
'avg_rating': str(book.avg_rating),
'price': str(book.price),
'for_later': False # saved for later if True
}
If you want only the books in the cart you can:
def get_books_ids(self):
books_ids = []
for key, val in self.userCart.items():
if not val['for_later']:
books_ids.append(key)
return books_ids
def __iter__(self):
book_ids = self.get_books_ids()
books = Book.objects.filter(id__in=book_ids)
cart = self.userCart.copy()
for book in books:
cart[str(book.id)]['book'] = book
for book in cart.values():
book['price'] = Decimal(book['price'])
book['total_price'] = book['price'] * book['amount']
yield book