Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  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 oscar.models.fields import ExtendedURLField
  9. Item = get_model('product', 'Item')
  10. # Linking models - these link promotions to content (eg pages, or keywords)
  11. class LinkedPromotion(models.Model):
  12. # We use generic foreign key to link to a promotion model
  13. content_type = models.ForeignKey(ContentType)
  14. object_id = models.PositiveIntegerField()
  15. content_object = generic.GenericForeignKey('content_type', 'object_id')
  16. position = models.CharField(_("Position"), max_length=100, help_text="Position on page")
  17. display_order = models.PositiveIntegerField(_("Display Order"), default=0)
  18. clicks = models.PositiveIntegerField(_("Clicks"), default=0)
  19. date_created = models.DateTimeField(_("Date Created"), auto_now_add=True)
  20. class Meta:
  21. abstract = True
  22. ordering = ['-clicks']
  23. verbose_name = _("Linked Promotion")
  24. verbose_name_plural = _("Linked Promotions")
  25. def record_click(self):
  26. self.clicks += 1
  27. self.save()
  28. record_click.alters_data = True
  29. class PagePromotion(LinkedPromotion):
  30. """
  31. A promotion embedded on a particular page.
  32. """
  33. page_url = ExtendedURLField(
  34. _('Page URL'), max_length=128, db_index=True, verify_exists=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)
  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, blank=True,
  108. help_text=_("This can be used to have different types of HTML blocks (eg different widths)"))
  109. body = models.TextField(_("HTML"))
  110. date_created = models.DateTimeField(auto_now_add=True)
  111. class Meta:
  112. verbose_name = _('Raw HTML')
  113. verbose_name_plural = _('Raw HTML')
  114. def __unicode__(self):
  115. return self.name
  116. class Image(AbstractPromotion):
  117. """
  118. An image promotion is simply a named image which has an optional
  119. link to another part of the site (or another site).
  120. This can be used to model both banners and pods.
  121. """
  122. _type = 'Image'
  123. name = models.CharField(_("Name"), max_length=128)
  124. link_url = ExtendedURLField(
  125. _('Link URL'), blank=True,
  126. help_text=_('This is where this promotion links to'))
  127. image = models.ImageField(_('Image'),
  128. upload_to=settings.OSCAR_PROMOTION_FOLDER,
  129. max_length=255)
  130. date_created = models.DateTimeField(auto_now_add=True)
  131. def __unicode__(self):
  132. return self.name
  133. class Meta:
  134. verbose_name = _("Image")
  135. verbose_name_plural = _("Image")
  136. class MultiImage(AbstractPromotion):
  137. """
  138. A multi-image promotion is simply a collection of image promotions
  139. that are rendered in a specific way. This models things like
  140. rotating banners.
  141. """
  142. _type = 'Multi-image'
  143. name = models.CharField(_("Name"), max_length=128)
  144. images = models.ManyToManyField('promotions.Image', null=True, blank=True)
  145. date_created = models.DateTimeField(auto_now_add=True)
  146. def __unicode__(self):
  147. return self.name
  148. class Meta:
  149. verbose_name = _("Multi Image")
  150. verbose_name_plural = _("Multi Images")
  151. class SingleProduct(AbstractPromotion):
  152. _type = 'Single product'
  153. name = models.CharField(_("Name"), max_length=128)
  154. product = models.ForeignKey('catalogue.Product')
  155. description = models.TextField(_("Description"), blank=True)
  156. date_created = models.DateTimeField(auto_now_add=True)
  157. def __unicode__(self):
  158. return self.name
  159. def template_context(self, request):
  160. return {'product': self.product}
  161. class Meta:
  162. verbose_name = _("Single Product")
  163. verbose_name_plural = _("Single Product")
  164. class AbstractProductList(AbstractPromotion):
  165. """
  166. Abstract superclass for promotions which are essentially a list
  167. of products.
  168. """
  169. name = models.CharField(_("Title"), max_length=255)
  170. description = models.TextField(_("Description"), blank=True)
  171. link_url = ExtendedURLField(_('Link URL'), blank=True, null=True)
  172. link_text = models.CharField(_("Link text"), max_length=255, blank=True)
  173. date_created = models.DateTimeField(auto_now_add=True)
  174. class Meta:
  175. abstract = True
  176. verbose_name = _("Product List")
  177. verbose_name_plural = _("Product Lists")
  178. def __unicode__(self):
  179. return self.name
  180. def template_context(self, request):
  181. return {'products': self.get_products()}
  182. class HandPickedProductList(AbstractProductList):
  183. """
  184. A hand-picked product list is a list of manually selected
  185. products.
  186. """
  187. _type = 'Product list'
  188. products = models.ManyToManyField('catalogue.Product', through='OrderedProduct', blank=True, null=True,
  189. verbose_name=_("Products"))
  190. def get_queryset(self):
  191. return self.products.all().order_by('%s.display_order' % OrderedProduct._meta.db_table)
  192. def get_products(self):
  193. return self.get_queryset()
  194. class Meta:
  195. verbose_name = _("Hand Picked Product List")
  196. verbose_name_plural = _("Hand Picked Product Lists")
  197. class OrderedProduct(models.Model):
  198. list = models.ForeignKey('promotions.HandPickedProductList', verbose_name=_("List"))
  199. product = models.ForeignKey('catalogue.Product', verbose_name=_("Product"))
  200. display_order = models.PositiveIntegerField(_('Display Order'), default=0)
  201. class Meta:
  202. ordering = ('display_order',)
  203. verbose_name = _("Ordered Product")
  204. verbose_name_plural = _("Ordered Product")
  205. class AutomaticProductList(AbstractProductList):
  206. _type = 'Auto-product list'
  207. BESTSELLING, RECENTLY_ADDED = ('Bestselling', 'RecentlyAdded')
  208. METHOD_CHOICES = (
  209. (BESTSELLING, _("Bestselling products")),
  210. (RECENTLY_ADDED, _("Recently added products")),
  211. )
  212. method = models.CharField(_('Method'), max_length=128, choices=METHOD_CHOICES)
  213. num_products = models.PositiveSmallIntegerField(_('Number of Products'), default=4)
  214. def get_queryset(self):
  215. Product = get_model('catalogue', 'Product')
  216. if self.method == self.BESTSELLING:
  217. return (Product.browsable.all()
  218. .select_related('stockrecord__partner')
  219. .prefetch_related('variants', 'images')
  220. .order_by('-score'))
  221. return (Product.browsable.all()
  222. .select_related('stockrecord__partner')
  223. .prefetch_related('variants', 'images')
  224. .order_by('-date_created'))
  225. def get_products(self):
  226. return self.get_queryset()[:self.num_products]
  227. class Meta:
  228. verbose_name = _("Automatic Product List")
  229. verbose_name_plural = _("Automatic Product Lists")
  230. class OrderedProductList(HandPickedProductList):
  231. tabbed_block = models.ForeignKey('promotions.TabbedBlock',
  232. related_name='tabs', verbose_name=_("Tabbed Block"))
  233. display_order = models.PositiveIntegerField(_('Display Order'), default=0)
  234. class Meta:
  235. ordering = ('display_order',)
  236. verbose_name = _("Ordered Product List")
  237. verbose_name_plural = _("Ordered Product Lists")
  238. class TabbedBlock(AbstractPromotion):
  239. _type = 'Tabbed block'
  240. name = models.CharField(_("Title"), max_length=255)
  241. date_created = models.DateTimeField(_("Date Created"), auto_now_add=True)
  242. class Meta:
  243. verbose_name = _("Tabbed Block")
  244. verbose_name_plural = _("Tabbed Blocks")