from django.conf import settings
from django.contrib.contenttypes import fields
from django.contrib.contenttypes.models import ContentType
from django.core.urlresolvers import reverse
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import pgettext_lazy
from oscar.core.loading import get_model
from oscar.models.fields import ExtendedURLField
# Linking models - these link promotions to content (eg pages, or keywords)
class LinkedPromotion(models.Model):
# We use generic foreign key to link to a promotion model
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = fields.GenericForeignKey('content_type', 'object_id')
position = models.CharField(_("Position"), max_length=100,
help_text="Position on page")
display_order = models.PositiveIntegerField(_("Display Order"), default=0)
clicks = models.PositiveIntegerField(_("Clicks"), default=0)
date_created = models.DateTimeField(_("Date Created"), auto_now_add=True)
class Meta:
abstract = True
app_label = 'promotions'
ordering = ['-clicks']
verbose_name = _("Linked Promotion")
verbose_name_plural = _("Linked Promotions")
def record_click(self):
self.clicks += 1
self.save()
record_click.alters_data = True
@python_2_unicode_compatible
[docs]class PagePromotion(LinkedPromotion):
"""
A promotion embedded on a particular page.
"""
page_url = ExtendedURLField(
_('Page URL'), max_length=128, db_index=True, verify_exists=True)
def __str__(self):
return u"%s on %s" % (self.content_object, self.page_url)
def get_link(self):
return reverse('promotions:page-click',
kwargs={'page_promotion_id': self.id})
class Meta(LinkedPromotion.Meta):
verbose_name = _("Page Promotion")
verbose_name_plural = _("Page Promotions")
# Different model types for each type of promotion
@python_2_unicode_compatible
[docs]class RawHTML(AbstractPromotion):
"""
Simple promotion - just raw HTML
"""
_type = 'Raw HTML'
name = models.CharField(_("Name"), max_length=128)
# Used to determine how to render the promotion (eg
# if a different width container is required). This isn't always
# required.
display_type = models.CharField(
_("Display type"), max_length=128, blank=True,
help_text=_("This can be used to have different types of HTML blocks"
" (eg different widths)"))
body = models.TextField(_("HTML"))
date_created = models.DateTimeField(auto_now_add=True)
class Meta:
app_label = 'promotions'
verbose_name = _('Raw HTML')
verbose_name_plural = _('Raw HTML')
def __str__(self):
return self.name
@python_2_unicode_compatible
[docs]class Image(AbstractPromotion):
"""
An image promotion is simply a named image which has an optional
link to another part of the site (or another site).
This can be used to model both banners and pods.
"""
_type = 'Image'
name = models.CharField(_("Name"), max_length=128)
link_url = ExtendedURLField(
_('Link URL'), blank=True,
help_text=_('This is where this promotion links to'))
image = models.ImageField(
_('Image'), upload_to=settings.OSCAR_PROMOTION_FOLDER,
max_length=255)
date_created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
class Meta:
app_label = 'promotions'
verbose_name = _("Image")
verbose_name_plural = _("Image")
@python_2_unicode_compatible
[docs]class MultiImage(AbstractPromotion):
"""
A multi-image promotion is simply a collection of image promotions
that are rendered in a specific way. This models things like
rotating banners.
"""
_type = 'Multi-image'
name = models.CharField(_("Name"), max_length=128)
images = models.ManyToManyField(
'promotions.Image', blank=True,
help_text=_(
"Choose the Image content blocks that this block will use. "
"(You may need to create some first)."))
date_created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
class Meta:
app_label = 'promotions'
verbose_name = _("Multi Image")
verbose_name_plural = _("Multi Images")
@python_2_unicode_compatible
[docs]class SingleProduct(AbstractPromotion):
_type = 'Single product'
name = models.CharField(_("Name"), max_length=128)
product = models.ForeignKey('catalogue.Product')
description = models.TextField(_("Description"), blank=True)
date_created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
def template_context(self, request):
return {'product': self.product}
class Meta:
app_label = 'promotions'
verbose_name = _("Single product")
verbose_name_plural = _("Single product")
@python_2_unicode_compatible
[docs]class AbstractProductList(AbstractPromotion):
"""
Abstract superclass for promotions which are essentially a list
of products.
"""
name = models.CharField(
pgettext_lazy(u"Promotion product list title", u"Title"),
max_length=255)
description = models.TextField(_("Description"), blank=True)
link_url = ExtendedURLField(_('Link URL'), blank=True)
link_text = models.CharField(_("Link text"), max_length=255, blank=True)
date_created = models.DateTimeField(auto_now_add=True)
class Meta:
abstract = True
app_label = 'promotions'
verbose_name = _("Product list")
verbose_name_plural = _("Product lists")
def __str__(self):
return self.name
def template_context(self, request):
return {'products': self.get_products()}
[docs]class HandPickedProductList(AbstractProductList):
"""
A hand-picked product list is a list of manually selected
products.
"""
_type = 'Product list'
products = models.ManyToManyField('catalogue.Product',
through='OrderedProduct', blank=True,
verbose_name=_("Products"))
def get_queryset(self):
return self.products.base_queryset()\
.order_by('%s.display_order' % OrderedProduct._meta.db_table)
def get_products(self):
return self.get_queryset()
class Meta:
app_label = 'promotions'
verbose_name = _("Hand Picked Product List")
verbose_name_plural = _("Hand Picked Product Lists")
[docs]class OrderedProduct(models.Model):
list = models.ForeignKey('promotions.HandPickedProductList',
verbose_name=_("List"))
product = models.ForeignKey('catalogue.Product', verbose_name=_("Product"))
display_order = models.PositiveIntegerField(_('Display Order'), default=0)
class Meta:
app_label = 'promotions'
ordering = ('display_order',)
unique_together = ('list', 'product')
verbose_name = _("Ordered product")
verbose_name_plural = _("Ordered product")
[docs]class AutomaticProductList(AbstractProductList):
_type = 'Auto-product list'
BESTSELLING, RECENTLY_ADDED = ('Bestselling', 'RecentlyAdded')
METHOD_CHOICES = (
(BESTSELLING, _("Bestselling products")),
(RECENTLY_ADDED, _("Recently added products")),
)
method = models.CharField(_('Method'), max_length=128,
choices=METHOD_CHOICES)
num_products = models.PositiveSmallIntegerField(_('Number of Products'),
default=4)
def get_queryset(self):
Product = get_model('catalogue', 'Product')
qs = Product.browsable.base_queryset().select_related('stats')
if self.method == self.BESTSELLING:
return qs.order_by('-stats__score')
return qs.order_by('-date_created')
def get_products(self):
return self.get_queryset()[:self.num_products]
class Meta:
app_label = 'promotions'
verbose_name = _("Automatic product list")
verbose_name_plural = _("Automatic product lists")
[docs]class OrderedProductList(HandPickedProductList):
tabbed_block = models.ForeignKey('promotions.TabbedBlock',
related_name='tabs',
verbose_name=_("Tabbed Block"))
display_order = models.PositiveIntegerField(_('Display Order'), default=0)
class Meta:
app_label = 'promotions'
ordering = ('display_order',)
verbose_name = _("Ordered Product List")
verbose_name_plural = _("Ordered Product Lists")
[docs]class TabbedBlock(AbstractPromotion):
_type = 'Tabbed block'
name = models.CharField(
pgettext_lazy(u"Tabbed block title", u"Title"), max_length=255)
date_created = models.DateTimeField(_("Date Created"), auto_now_add=True)
class Meta:
app_label = 'promotions'
verbose_name = _("Tabbed Block")
verbose_name_plural = _("Tabbed Blocks")