Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improved wishlist based on @dfalk's work #105

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions cartridge/shop/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,3 +293,15 @@
editable=False,
default=True,
)

register_setting(
name="SHOP_WISHLIST_NOTIFICATIONS",
label=_(
"Sends email notifications if an item in user's wishlist is in stock"
),
description=_(
"Sends email notifications if an item in user's wishlist is in stock"
),
editable=True,
default=True,
)
33 changes: 33 additions & 0 deletions cartridge/shop/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,3 +202,36 @@ def get_valid(self, code, cart):
if products.filter(variations__sku__in=cart.skus()).count() == 0:
raise self.model.DoesNotExist
return discount


class WishlistManager(Manager):

def from_request(self, request):
"""
Gets current user's wishlist. Authenticated users' wishlists are stored
in the database, while unauthenticated users' are stored in a cookie.
Note that this method returns a `list` of SKUs in both cases, not a
`QuerySet` of `ProductVariation` objects.
"""
if request.user.is_authenticated():
wishlist = []
skus = self.filter(user=request.user).values_list("sku", flat=True)
for sku in skus:
wishlist.append(sku)
else:
wishlist = request.COOKIES.get("wishlist", "").split(",")
if not wishlist[0]:
wishlist = []
return wishlist

def delete_for_request(self, sku_to_delete, request):
"""
Delete item from user's wishlist. Authenticated users' wishlists are
stored in the database, while unauthenticated users' are stored in a
cookie. In the latter case, the SKU is removed directly from
`request.wishlist`, which is provided by `ShopMiddleware`.
"""
if request.user.is_authenticated():
self.get(user=request.user, sku=sku_to_delete).delete()
else:
request.wishlist.remove(sku_to_delete)
7 changes: 2 additions & 5 deletions cartridge/shop/middleware.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

from mezzanine.conf import settings

from cartridge.shop.models import Cart
from cartridge.shop.models import Cart, Wishlist


class SSLRedirect(object):
Expand Down Expand Up @@ -29,7 +29,4 @@ class ShopMiddleware(SSLRedirect):
"""
def process_request(self, request):
request.cart = Cart.objects.from_request(request)
wishlist = request.COOKIES.get("wishlist", "").split(",")
if not wishlist[0]:
wishlist = []
request.wishlist = wishlist
request.wishlist = Wishlist.objects.from_request(request)

Large diffs are not rendered by default.

57 changes: 56 additions & 1 deletion cartridge/shop/models.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from decimal import Decimal
from operator import iand, ior

from django.contrib.auth.models import User
from django.contrib.auth.signals import user_logged_in
from django.core.urlresolvers import reverse
from django.db import models
from django.db.models.signals import m2m_changed
from django.db.models.signals import m2m_changed, pre_save
from django.db.models import CharField, F, Q
from django.db.models.base import ModelBase
from django.db.utils import DatabaseError
Expand All @@ -18,6 +20,7 @@
from mezzanine.generic.fields import RatingField
from mezzanine.pages.models import Page
from mezzanine.utils.models import AdminThumbMixin, upload_to
from mezzanine.utils.email import send_mail_template

from cartridge.shop import fields, managers

Expand Down Expand Up @@ -305,6 +308,30 @@ def update_stock(self, quantity):
self.product.save()


@receiver(pre_save, sender=ProductVariation,
dispatch_uid="wishlist_notifications_on_save")
def wishlist_notifications_on_save(sender, instance, **kwargs):
if settings.SHOP_WISHLIST_NOTIFICATIONS is not True:
return
if instance.id:
productvariation = ProductVariation.objects.get(pk=instance.id)
old_stock = productvariation.num_in_stock
if (old_stock == 0) and (instance.num_in_stock > old_stock):
# Send email
email_from = settings.DEFAULT_FROM_EMAIL
wishlist = Wishlist.objects.filter(sku=productvariation.sku)
for item in wishlist:
email_to = item.user.email
subject = _("Notification from wishlist")
context = {
"productvariation": productvariation,
"user": item.user,
}
send_mail_template(subject, "email/wishlist_notification",
email_from, email_to, context,
fail_silently=settings.DEBUG)


class Category(Page, RichText):
"""
A category of products on the website.
Expand Down Expand Up @@ -843,3 +870,31 @@ def calculate(self, amount):
class Meta:
verbose_name = _("Discount code")
verbose_name_plural = _("Discount codes")


class Wishlist(models.Model):

user = models.ForeignKey(User)
sku = fields.SKUField()

objects = managers.WishlistManager()

class Meta:
unique_together = ('user', 'sku')
verbose_name = _("Wishlist item")
verbose_name_plural = _("Wishlist items")


@receiver(user_logged_in, sender=User)
def transfer_wishlist_data(sender, request, user, *args, **kwargs):
skus = getattr(request, "wishlist", [])
if not skus:
return
existed = (Wishlist.objects.filter(user=user, sku__in=skus)
.values_list("sku", flat=True))
for sku in skus:
if sku not in existed:
wishlist = Wishlist()
wishlist.user = user
wishlist.sku = sku
wishlist.save()
11 changes: 11 additions & 0 deletions cartridge/shop/templates/email/wishlist_notification.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{% extends "email/base.txt" %}
{% load shop_tags i18n %}

{% block main %}
{% trans "Dear" %} {{ user }},

{% trans "This product is available for order" %}:

{{ productvariation }}

{% endblock %}
2 changes: 1 addition & 1 deletion cartridge/shop/templates/shop/wishlist.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

{% block main %}
{% if error %}{{ error }}{% endif %}
{% if request.wishlist %}
{% if wishlist_items %}
<table class="table table-striped wishlist">
{% for item in wishlist_items %}
<tr>
Expand Down
23 changes: 23 additions & 0 deletions cartridge/shop/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,20 @@ def add_item(self, *args, **kwargs):
self._request.session["cart"] = cart.id


class CookieBackedWishlist(object):
"""
A dummy wishlist object used for unauthenticated users, backed by cookie
storage.
"""
def __init__(self, request):
super(CookieBackedWishlist, self).__init__()
self.request = request

def save(self, *args, **kwargs):
if self.sku not in self.request.wishlist:
self.request.wishlist.append(self.sku)


def make_choices(choices):
"""
Zips a list with itself for field choices.
Expand Down Expand Up @@ -113,3 +127,12 @@ def set_locale():
"configure the SHOP_CURRENCY_LOCALE setting in your settings "
"module.")
raise ImproperlyConfigured(msg % currency_locale)


def get_wishlist(request):
if request.user.is_authenticated():
from cartridge.shop.models import Wishlist
wishlist = Wishlist()
else:
wishlist = CookieBackedWishlist(request)
return wishlist
30 changes: 18 additions & 12 deletions cartridge/shop/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from django.contrib.auth.decorators import login_required
from django.contrib.messages import info
from django.core.urlresolvers import get_callable, reverse
from django.db import IntegrityError
from django.http import Http404, HttpResponse
from django.shortcuts import get_object_or_404, redirect
from django.template import RequestContext
Expand All @@ -19,8 +20,8 @@
from cartridge.shop import checkout
from cartridge.shop.forms import AddProductForm, DiscountForm, CartItemFormSet
from cartridge.shop.models import Product, ProductVariation, Order, OrderItem
from cartridge.shop.models import DiscountCode
from cartridge.shop.utils import recalculate_discount, sign
from cartridge.shop.models import DiscountCode, Wishlist
from cartridge.shop.utils import recalculate_discount, sign, get_wishlist


# Set up checkout handlers.
Expand Down Expand Up @@ -60,13 +61,16 @@ def product(request, slug, template="shop/product.html"):
info(request, _("Item added to cart"))
return redirect("shop_cart")
else:
skus = request.wishlist
sku = add_product_form.variation.sku
if sku not in skus:
skus.append(sku)
info(request, _("Item added to wishlist"))
response = redirect("shop_wishlist")
set_cookie(response, "wishlist", ",".join(skus))
wishlist = get_wishlist(request)
wishlist.user = request.user
wishlist.sku = add_product_form.variation.sku
try:
wishlist.save()
except IntegrityError:
pass
set_cookie(response, "wishlist", ",".join(request.wishlist))
info(request, _("Item added to wishlist"))
return response
context = {
"product": product,
Expand All @@ -76,7 +80,8 @@ def product(request, slug, template="shop/product.html"):
"variations_json": variations_json,
"has_available_variations": any([v.has_price() for v in variations]),
"related_products": product.related_products.published(
for_user=request.user),
for_user=request.user
),
"add_product_form": add_product_form
}
return render(request, template, context)
Expand Down Expand Up @@ -110,15 +115,16 @@ def wishlist(request, template="shop/wishlist.html"):
message = _("Item removed from wishlist")
url = "shop_wishlist"
sku = request.POST.get("sku")
if sku in skus:
skus.remove(sku)
if sku in request.wishlist:
Wishlist.objects.delete_for_request(sku, request)
if not error:
info(request, message)
response = redirect(url)
set_cookie(response, "wishlist", ",".join(skus))
set_cookie(response, "wishlist", ",".join(request.wishlist))
return response

# Remove skus from the cookie that no longer exist.
skus = request.wishlist
published_products = Product.objects.published(for_user=request.user)
f = {"product__in": published_products, "sku__in": skus}
wishlist = ProductVariation.objects.filter(**f).select_related(depth=1)
Expand Down