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.

abstract_models.py 5.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. from decimal import Decimal
  2. from django.core import exceptions
  3. from django.db import models
  4. from django.utils.encoding import python_2_unicode_compatible
  5. from django.utils.translation import ugettext_lazy as _
  6. from django.utils import timezone
  7. from oscar.core.compat import AUTH_USER_MODEL
  8. @python_2_unicode_compatible
  9. class AbstractVoucher(models.Model):
  10. """
  11. A voucher. This is simply a link to a collection of offers.
  12. Note that there are three possible "usage" models:
  13. (a) Single use
  14. (b) Multi-use
  15. (c) Once per customer
  16. """
  17. name = models.CharField(_("Name"), max_length=128,
  18. help_text=_("This will be shown in the checkout"
  19. " and basket once the voucher is"
  20. " entered"))
  21. code = models.CharField(_("Code"), max_length=128, db_index=True,
  22. unique=True, help_text=_("Case insensitive / No"
  23. " spaces allowed"))
  24. offers = models.ManyToManyField(
  25. 'offer.ConditionalOffer', related_name='vouchers',
  26. verbose_name=_("Offers"), limit_choices_to={'offer_type': "Voucher"})
  27. SINGLE_USE, MULTI_USE, ONCE_PER_CUSTOMER = (
  28. 'Single use', 'Multi-use', 'Once per customer')
  29. USAGE_CHOICES = (
  30. (SINGLE_USE, _("Can be used once by one customer")),
  31. (MULTI_USE, _("Can be used multiple times by multiple customers")),
  32. (ONCE_PER_CUSTOMER, _("Can only be used once per customer")),
  33. )
  34. usage = models.CharField(_("Usage"), max_length=128,
  35. choices=USAGE_CHOICES, default=MULTI_USE)
  36. start_datetime = models.DateTimeField(_('Start datetime'))
  37. end_datetime = models.DateTimeField(_('End datetime'))
  38. # Audit information
  39. num_basket_additions = models.PositiveIntegerField(
  40. _("Times added to basket"), default=0)
  41. num_orders = models.PositiveIntegerField(_("Times on orders"), default=0)
  42. total_discount = models.DecimalField(
  43. _("Total discount"), decimal_places=2, max_digits=12,
  44. default=Decimal('0.00'))
  45. date_created = models.DateField(auto_now_add=True)
  46. class Meta:
  47. abstract = True
  48. app_label = 'voucher'
  49. get_latest_by = 'date_created'
  50. verbose_name = _("Voucher")
  51. verbose_name_plural = _("Vouchers")
  52. def __str__(self):
  53. return self.name
  54. def clean(self):
  55. if all([self.start_datetime, self.end_datetime,
  56. self.start_datetime > self.end_datetime]):
  57. raise exceptions.ValidationError(
  58. _('End date should be later than start date'))
  59. def save(self, *args, **kwargs):
  60. self.code = self.code.upper()
  61. super(AbstractVoucher, self).save(*args, **kwargs)
  62. def is_active(self, test_datetime=None):
  63. """
  64. Test whether this voucher is currently active.
  65. """
  66. test_datetime = test_datetime or timezone.now()
  67. return self.start_datetime <= test_datetime <= self.end_datetime
  68. def is_available_to_user(self, user=None):
  69. """
  70. Test whether this voucher is available to the passed user.
  71. Returns a tuple of a boolean for whether it is successulf, and a
  72. message
  73. """
  74. is_available, message = False, ''
  75. if self.usage == self.SINGLE_USE:
  76. is_available = not self.applications.exists()
  77. if not is_available:
  78. message = _("This voucher has already been used")
  79. elif self.usage == self.MULTI_USE:
  80. is_available = True
  81. elif self.usage == self.ONCE_PER_CUSTOMER:
  82. if not user.is_authenticated():
  83. is_available = False
  84. message = _(
  85. "This voucher is only available to signed in users")
  86. else:
  87. is_available = not self.applications.filter(
  88. voucher=self, user=user).exists()
  89. if not is_available:
  90. message = _("You have already used this voucher in "
  91. "a previous order")
  92. return is_available, message
  93. def record_usage(self, order, user):
  94. """
  95. Records a usage of this voucher in an order.
  96. """
  97. if user.is_authenticated():
  98. self.applications.create(voucher=self, order=order, user=user)
  99. else:
  100. self.applications.create(voucher=self, order=order)
  101. self.num_orders += 1
  102. self.save()
  103. record_usage.alters_data = True
  104. def record_discount(self, discount):
  105. """
  106. Record a discount that this offer has given
  107. """
  108. self.total_discount += discount['discount']
  109. self.save()
  110. record_discount.alters_data = True
  111. @property
  112. def benefit(self):
  113. return self.offers.all()[0].benefit
  114. @python_2_unicode_compatible
  115. class AbstractVoucherApplication(models.Model):
  116. """
  117. For tracking how often a voucher has been used
  118. """
  119. voucher = models.ForeignKey(
  120. 'voucher.Voucher', related_name="applications",
  121. verbose_name=_("Voucher"))
  122. # It is possible for an anonymous user to apply a voucher so we need to
  123. # allow the user to be nullable
  124. user = models.ForeignKey(AUTH_USER_MODEL, blank=True, null=True,
  125. verbose_name=_("User"))
  126. order = models.ForeignKey('order.Order', verbose_name=_("Order"))
  127. date_created = models.DateField(_("Date Created"), auto_now_add=True)
  128. class Meta:
  129. abstract = True
  130. app_label = 'voucher'
  131. verbose_name = _("Voucher Application")
  132. verbose_name_plural = _("Voucher Applications")
  133. def __str__(self):
  134. return _("'%(voucher)s' used by '%(user)s'") % {
  135. 'voucher': self.voucher,
  136. 'user': self.user}