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 self.visibility in (self.PUBLIC, self.SHARED):
return True
else:
return user == self.owner
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()
[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')