from django.db import models
from django.urls import reverse
from django.utils.crypto import get_random_string
from django.utils.translation import gettext_lazy as _
from django.utils.translation import pgettext_lazy
from oscar.core.compat import AUTH_USER_MODEL
[docs]
class AbstractWishList(models.Model):
"""
Represents a user's wish lists of products.
A user can have multiple wish lists, move products between them, etc.
"""
# Only authenticated users can have wishlists
owner = models.ForeignKey(
AUTH_USER_MODEL,
related_name="wishlists",
on_delete=models.CASCADE,
verbose_name=_("Owner"),
)
name = models.CharField(
verbose_name=_("Name"), default=_("Default"), max_length=255
)
#: This key acts as primary key and is used instead of an int to make it
#: harder to guess
key = models.CharField(
_("Key"), max_length=6, db_index=True, unique=True, editable=False
)
# Oscar core does not support public or shared wishlists at the moment, but
# all the right hooks should be there
PUBLIC, PRIVATE, SHARED = ("Public", "Private", "Shared")
VISIBILITY_CHOICES = (
(PRIVATE, _("Private - Only the owner can see the wish list")),
(
SHARED,
_(
"Shared - Only the owner and people with access to the"
" obfuscated link can see the wish list"
),
),
(PUBLIC, _("Public - Everybody can see the wish list")),
)
visibility = models.CharField(
_("Visibility"), max_length=20, default=PRIVATE, choices=VISIBILITY_CHOICES
)
# Convention: A user can have multiple wish lists. The last created wish
# list for a user shall be their "default" wish list.
# If an UI element only allows adding to wish list without
# specifying which one , one shall use the default one.
# That is a rare enough case to handle it by convention instead of a
# BooleanField.
date_created = models.DateTimeField(
_("Date created"), auto_now_add=True, editable=False, db_index=True
)
def __str__(self):
return "%s's Wish List '%s'" % (self.owner, self.name)
[docs]
def save(self, *args, **kwargs):
if not self.pk or kwargs.get("force_insert", False):
self.key = self.__class__.random_key()
super().save(*args, **kwargs)
[docs]
@classmethod
def random_key(cls, length=6):
"""
Get a unique random generated key
"""
while True:
key = get_random_string(
length=length, allowed_chars="abcdefghijklmnopqrstuvwxyz0123456789"
)
if not cls._default_manager.filter(key=key).exists():
return key
def is_allowed_to_see(self, user):
if user == self.owner:
return True
elif self.visibility == self.PUBLIC:
return True
elif self.visibility == self.SHARED and user.is_authenticated:
return self.shared_emails.filter(email=user.email).exists()
return False
def is_allowed_to_edit(self, user):
# currently only the owner can edit their wish list
return user == self.owner
class Meta:
abstract = True
app_label = "wishlists"
ordering = (
"owner",
"date_created",
)
verbose_name = _("Wish List")
def get_absolute_url(self):
return reverse("customer:wishlists-detail", kwargs={"key": self.key})
[docs]
def add(self, product):
"""
Add a product to this wishlist
"""
lines = self.lines.filter(product=product)
if len(lines) == 0:
self.lines.create(product=product, title=product.get_title())
else:
line = lines[0]
line.quantity += 1
line.save()
def get_shared_url(self):
return reverse("wishlists:detail", kwargs={"key": self.key})
@property
def is_shareable(self):
return self.visibility in [self.PUBLIC, self.SHARED]
[docs]
class AbstractLine(models.Model):
"""
One entry in a wish list. Similar to order lines or basket lines.
"""
wishlist = models.ForeignKey(
"wishlists.WishList",
on_delete=models.CASCADE,
related_name="lines",
verbose_name=_("Wish List"),
)
product = models.ForeignKey(
"catalogue.Product",
verbose_name=_("Product"),
related_name="wishlists_lines",
on_delete=models.SET_NULL,
blank=True,
null=True,
)
quantity = models.PositiveIntegerField(_("Quantity"), default=1)
#: Store the title in case product gets deleted
title = models.CharField(pgettext_lazy("Product title", "Title"), max_length=255)
def __str__(self):
return "%sx %s on %s" % (self.quantity, self.title, self.wishlist.name)
def get_title(self):
if self.product:
return self.product.get_title()
else:
return self.title
class Meta:
abstract = True
app_label = "wishlists"
# Enforce sorting by order of creation.
ordering = ["pk"]
unique_together = (("wishlist", "product"),)
verbose_name = _("Wish list line")
[docs]
class AbstractWishListSharedEmail(models.Model):
"""
An email which is allowed to view and possibly edit the wishlist, if shared.
The user will have to login/create an account with this email in order to view it.
"""
wishlist = models.ForeignKey(
"wishlists.WishList",
on_delete=models.CASCADE,
related_name="shared_emails",
verbose_name=_("Wish List"),
)
email = models.EmailField(verbose_name=_("Email"))
def __str__(self):
return "%s - %s" % (self.wishlist.name, self.email)
class Meta:
abstract = True
app_label = "wishlists"
verbose_name = _("Wish list shared email")