You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

models.py 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. from django.db import models
  2. from django.conf import settings
  3. from django.utils.translation import ugettext as _
  4. from django.core.urlresolvers import reverse
  5. from django.contrib.contenttypes.models import ContentType
  6. from django.contrib.contenttypes import generic
  7. from django.db.models import get_model
  8. from django.utils.translation import ugettext_lazy as _
  9. from oscar.models.fields import ExtendedURLField
  10. Product = get_model('catalogue', 'Product')
  11. Item = get_model('product', 'Item')
  12. # Linking models - these link promotions to content (eg pages, or keywords)
  13. class LinkedPromotion(models.Model):
  14. # We use generic foreign key to link to a promotion model
  15. content_type = models.ForeignKey(ContentType)
  16. object_id = models.PositiveIntegerField()
  17. content_object = generic.GenericForeignKey('content_type', 'object_id')
  18. position = models.CharField(_("Position"), max_length=100, help_text="Position on page")
  19. display_order = models.PositiveIntegerField(_("Display Order"), default=0)
  20. clicks = models.PositiveIntegerField(_("Clicks"), default=0)
  21. date_created = models.DateTimeField(_("Date Created"), auto_now_add=True)
  22. class Meta:
  23. abstract = True
  24. ordering = ['-clicks']
  25. verbose_name = _("Linked Promotion")
  26. verbose_name_plural = _("Linked Promotions")
  27. def record_click(self):
  28. self.clicks += 1
  29. self.save()
  30. class PagePromotion(LinkedPromotion):
  31. """
  32. A promotion embedded on a particular page.
  33. """
  34. page_url = ExtendedURLField(_('Page URL'), max_length=128, db_index=True)
  35. def __unicode__(self):
  36. return u"%s on %s" % (self.content_object, self.page_url)
  37. def get_link(self):
  38. return reverse('promotions:page-click', kwargs={'page_promotion_id': self.id})
  39. class Meta:
  40. verbose_name = _("Page Promotion")
  41. verbose_name_plural = _("Page Promotions")
  42. class KeywordPromotion(LinkedPromotion):
  43. """
  44. A promotion linked to a specific keyword.
  45. This can be used on a search results page to show promotions
  46. linked to a particular keyword.
  47. """
  48. keyword = models.CharField(_("Keyword"), max_length=200)
  49. # We allow an additional filter which will let search query matches
  50. # be restricted to different parts of the site.
  51. filter = models.CharField(_("Filter"), max_length=200, blank=True, null=True)
  52. def get_link(self):
  53. return reverse('promotions:keyword-click', kwargs={'keyword_promotion_id': self.id})
  54. class Meta:
  55. verbose_name = _("Keyword Promotion")
  56. verbose_name_plural = _("Keyword Promotions")
  57. # Different model types for each type of promotion
  58. class AbstractPromotion(models.Model):
  59. """
  60. Abstract base promotion that defines the interface
  61. that subclasses must implement.
  62. """
  63. _type = 'Promotion'
  64. keywords = generic.GenericRelation(KeywordPromotion, verbose_name=_('Keywords'))
  65. pages = generic.GenericRelation(PagePromotion, verbose_name=_('Pages'))
  66. class Meta:
  67. abstract = True
  68. verbose_name = _("Promotion")
  69. verbose_name_plural = _("Promotions")
  70. @property
  71. def type(self):
  72. return _(self._type)
  73. @classmethod
  74. def classname(cls):
  75. return cls.__name__.lower()
  76. @property
  77. def code(self):
  78. return self.__class__.__name__.lower()
  79. def template_name(self):
  80. """
  81. Returns the template to use to render this promotion.
  82. """
  83. return 'promotions/%s.html' % self.code
  84. def template_context(self, request):
  85. return {}
  86. @property
  87. def content_type(self):
  88. return ContentType.objects.get_for_model(self)
  89. @property
  90. def num_times_used(self):
  91. ctype = self.content_type
  92. page_count = PagePromotion.objects.filter(content_type=ctype,
  93. object_id=self.id).count()
  94. keyword_count = KeywordPromotion.objects.filter(content_type=ctype,
  95. object_id=self.id).count()
  96. return page_count + keyword_count
  97. class RawHTML(AbstractPromotion):
  98. """
  99. Simple promotion - just raw HTML
  100. """
  101. _type = 'Raw HTML'
  102. name = models.CharField(_("Name"), max_length=128)
  103. # Used to determine how to render the promotion (eg
  104. # if a different width container is required). This isn't always
  105. # required.
  106. display_type = models.CharField(
  107. _("Display type"), max_length=128,
  108. blank=True, null=True,
  109. help_text=_("This can be used to have different types of HTML blocks (eg different widths)"))
  110. body = models.TextField(_("HTML"))
  111. date_created = models.DateTimeField(auto_now_add=True)
  112. class Meta:
  113. verbose_name = _('Raw HTML')
  114. verbose_name_plural = _('Raw HTML')
  115. def __unicode__(self):
  116. return self.name
  117. class Image(AbstractPromotion):
  118. """
  119. An image promotion is simply a named image which has an optional
  120. link to another part of the site (or another site).
  121. This can be used to model both banners and pods.
  122. """
  123. _type = 'Image'
  124. name = models.CharField(_("Name"), max_length=128)
  125. link_url = ExtendedURLField(_('Link URL'), blank=True, null=True, help_text=_("""This is
  126. where this promotion links to"""))
  127. image = models.ImageField(_('Image'), upload_to=settings.OSCAR_PROMOTION_FOLDER)
  128. date_created = models.DateTimeField(auto_now_add=True)
  129. def __unicode__(self):
  130. return self.name
  131. class Meta:
  132. verbose_name = _("Image")
  133. verbose_name_plural = _("Image")
  134. class MultiImage(AbstractPromotion):
  135. """
  136. A multi-image promotion is simply a collection of image promotions
  137. that are rendered in a specific way. This models things like
  138. rotating banners.
  139. """
  140. _type = 'Multi-image'
  141. name = models.CharField(_("Name"), max_length=128)
  142. images = models.ManyToManyField('promotions.Image', null=True, blank=True)
  143. date_created = models.DateTimeField(auto_now_add=True)
  144. def __unicode__(self):
  145. return self.name
  146. class Meta:
  147. verbose_name = _("Multi Image")
  148. verbose_name_plural = _("Multi Images")
  149. class SingleProduct(AbstractPromotion):
  150. _type = 'Single product'
  151. name = models.CharField(_("Name"), max_length=128)
  152. product = models.ForeignKey('catalogue.Product')
  153. description = models.TextField(_("Description"), null=True, blank=True)
  154. date_created = models.DateTimeField(auto_now_add=True)
  155. def __unicode__(self):
  156. return self.name
  157. def template_context(self, request):
  158. return {'product': self.product}
  159. class Meta:
  160. verbose_name = _("Single Product")
  161. verbose_name_plural = _("Single Product")
  162. class AbstractProductList(AbstractPromotion):
  163. """
  164. Abstract superclass for promotions which are essentially a list
  165. of products.
  166. """
  167. name = models.CharField(_("Title"), max_length=255)
  168. description = models.TextField(_("Description"), null=True, blank=True)
  169. link_url = ExtendedURLField(_('Link URL'), blank=True, null=True)
  170. link_text = models.CharField(_("Link text"), max_length=255, blank=True, null=True)
  171. date_created = models.DateTimeField(auto_now_add=True)
  172. class Meta:
  173. abstract = True
  174. verbose_name = _("Product List")
  175. verbose_name_plural = _("Product Lists")
  176. def __unicode__(self):
  177. return self.name
  178. def template_context(self, request):
  179. return {'products': self.get_products()}
  180. class HandPickedProductList(AbstractProductList):
  181. """
  182. A hand-picked product list is a list of manually selected
  183. products.
  184. """
  185. _type = 'Product list'
  186. products = models.ManyToManyField('catalogue.Product', through='OrderedProduct', blank=True, null=True,
  187. verbose_name=_("Products"))
  188. def get_queryset(self):
  189. return self.products.all().order_by('%s.display_order' % OrderedProduct._meta.db_table)
  190. def get_products(self):
  191. return self.get_queryset()
  192. class Meta:
  193. verbose_name = _("Hand Picked Product List")
  194. verbose_name_plural = _("Hand Picked Product Lists")
  195. class OrderedProduct(models.Model):
  196. list = models.ForeignKey('promotions.HandPickedProductList', verbose_name=_("List"))
  197. product = models.ForeignKey('catalogue.Product', verbose_name=_("Product"))
  198. display_order = models.PositiveIntegerField(_('Display Order'), default=0)
  199. class Meta:
  200. ordering = ('display_order',)
  201. verbose_name = _("Ordered Product")
  202. verbose_name_plural = _("Ordered Product")
  203. class AutomaticProductList(AbstractProductList):
  204. _type = 'Auto-product list'
  205. BESTSELLING, RECENTLY_ADDED = ('Bestselling', 'RecentlyAdded')
  206. METHOD_CHOICES = (
  207. (BESTSELLING, _("Bestselling products")),
  208. (RECENTLY_ADDED, _("Recently added products")),
  209. )
  210. method = models.CharField(_('Method'), max_length=128, choices=METHOD_CHOICES)
  211. num_products = models.PositiveSmallIntegerField(_('Number of Products'), default=4)
  212. def get_queryset(self):
  213. if self.method == self.BESTSELLING:
  214. return (Product.browsable.all()
  215. .select_related('stockrecord__partner')
  216. .prefetch_related('variants', 'images')
  217. .order_by('-score'))
  218. return (Product.browsable.all()
  219. .select_related('stockrecord__partner')
  220. .prefetch_related('variants', 'images')
  221. .order_by('-date_created'))
  222. def get_products(self):
  223. return self.get_queryset()[:self.num_products]
  224. class Meta:
  225. verbose_name = _("Automatic Product List")
  226. verbose_name_plural = _("Automatic Product Lists")
  227. class OrderedProductList(HandPickedProductList):
  228. tabbed_block = models.ForeignKey('promotions.TabbedBlock',
  229. related_name='tabs', verbose_name=_("Tabbed Block"))
  230. display_order = models.PositiveIntegerField(_('Display Order'), default=0)
  231. class Meta:
  232. ordering = ('display_order',)
  233. verbose_name = _("Ordered Product List")
  234. verbose_name_plural = _("Ordered Product Lists")
  235. class TabbedBlock(AbstractPromotion):
  236. _type = 'Tabbed block'
  237. name = models.CharField(_("Title"), max_length=255)
  238. date_created = models.DateTimeField(_("Date Created"), auto_now_add=True)
  239. class Meta:
  240. verbose_name = _("Tabbed Block")
  241. verbose_name_plural = _("Tabbed Blocks")