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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. from decimal import Decimal as D
  2. from django.db import models
  3. from django.utils.translation import ugettext_lazy as _
  4. from oscar.core.utils import slugify
  5. from oscar.apps.shipping import Scales
  6. class ShippingMethod(models.Model):
  7. """
  8. Fields from shipping.base.ShippingMethod must be added here manually.
  9. """
  10. code = models.SlugField(_("Slug"), max_length=128, unique=True)
  11. name = models.CharField(_("Name"), max_length=128, unique=True)
  12. description = models.TextField(_("Description"), blank=True)
  13. # We allow shipping methods to be linked to a specific set of countries
  14. countries = models.ManyToManyField('address.Country', null=True,
  15. blank=True, verbose_name=_("Countries"))
  16. is_discounted = False
  17. _basket = None
  18. class Meta:
  19. abstract = True
  20. verbose_name = _("Shipping Method")
  21. verbose_name_plural = _("Shipping Methods")
  22. def save(self, *args, **kwargs):
  23. if not self.code:
  24. self.code = slugify(self.name)
  25. super(ShippingMethod, self).save(*args, **kwargs)
  26. def __unicode__(self):
  27. return self.name
  28. def set_basket(self, basket):
  29. self._basket = basket
  30. class OrderAndItemCharges(ShippingMethod):
  31. """
  32. Standard shipping method
  33. This method has two components:
  34. * a charge per order
  35. * a charge per item
  36. Many sites use shipping logic which fits into this system. However, for
  37. more complex shipping logic, a custom shipping method object will need to
  38. be provided that subclasses ShippingMethod.
  39. """
  40. price_per_order = models.DecimalField(
  41. _("Price per order"), decimal_places=2, max_digits=12,
  42. default=D('0.00'))
  43. price_per_item = models.DecimalField(
  44. _("Price per item"), decimal_places=2, max_digits=12,
  45. default=D('0.00'))
  46. # If basket value is above this threshold, then shipping is free
  47. free_shipping_threshold = models.DecimalField(
  48. _("Free Shipping"), decimal_places=2, max_digits=12, blank=True,
  49. null=True)
  50. _basket = None
  51. class Meta:
  52. verbose_name = _("Order and Item Charge")
  53. verbose_name_plural = _("Order and Item Charges")
  54. def set_basket(self, basket):
  55. self._basket = basket
  56. def basket_charge_incl_tax(self):
  57. """
  58. Return basket total including tax
  59. """
  60. if self.free_shipping_threshold != None and \
  61. self._basket.total_incl_tax >= self.free_shipping_threshold:
  62. return D('0.00')
  63. charge = self.price_per_order
  64. for line in self._basket.lines.all():
  65. charge += line.quantity * self.price_per_item
  66. return charge
  67. def basket_charge_excl_tax(self):
  68. """
  69. Return basket total excluding tax.
  70. Default implementation assumes shipping is tax free.
  71. """
  72. return self.basket_charge_incl_tax()
  73. class WeightBased(ShippingMethod):
  74. upper_charge = models.DecimalField(
  75. _("Upper Charge"), decimal_places=2, max_digits=12, null=True,
  76. help_text=_("This is the charge when the weight of the basket "
  77. "is greater than all the weight bands"""))
  78. weight_attribute = 'weight'
  79. default_weight = models.DecimalField(
  80. _("Default Weight"), decimal_places=2, max_digits=12,
  81. default=D('0.00'),
  82. help_text=_("Default product weight in Kg when no weight attribute "
  83. "is defined"))
  84. class Meta:
  85. verbose_name = _("Weight-based Shipping Method")
  86. verbose_name_plural = _("Weight-based Shipping Methods")
  87. def basket_charge_incl_tax(self):
  88. weight = Scales(attribute_code=self.weight_attribute,
  89. default_weight=self.default_weight).weigh_basket(
  90. self._basket)
  91. band = self.get_band_for_weight(weight)
  92. if not band:
  93. if self.bands.all().count() > 0 and self.upper_charge:
  94. return self.upper_charge
  95. else:
  96. return D('0.00')
  97. return band.charge
  98. def basket_charge_excl_tax(self):
  99. return self.basket_charge_incl_tax()
  100. def get_band_for_weight(self, weight):
  101. """
  102. Return the weight band for a given weight
  103. """
  104. bands = self.bands.filter(upper_limit__gte=weight).order_by('upper_limit')
  105. if not bands.count():
  106. # No band for this weight
  107. return None
  108. return bands[0]
  109. class WeightBand(models.Model):
  110. """
  111. Represents a weight band which are used by the WeightBasedShipping method.
  112. """
  113. method = models.ForeignKey(WeightBased, related_name='bands', verbose_name=_("Method"))
  114. upper_limit = models.FloatField(_("Upper Limit"), help_text=_("""Enter upper limit of this
  115. weight band in Kg, the lower
  116. limit will be determine by the
  117. other weight bands"""))
  118. charge = models.DecimalField(_("Charge"), decimal_places=2, max_digits=12)
  119. @property
  120. def weight_from(self):
  121. lower_bands = self.method.bands.filter(
  122. upper_limit__lt=self.upper_limit).order_by('-upper_limit')
  123. if not lower_bands:
  124. return D('0.00')
  125. return lower_bands[0].upper_limit
  126. @property
  127. def weight_to(self):
  128. return self.upper_limit
  129. class Meta:
  130. ordering = ['upper_limit']
  131. verbose_name = _("Weight Band")
  132. verbose_name_plural = _("Weight Bands")
  133. def __unicode__(self):
  134. return _('Charge for weights up to %sKg') % (self.upper_limit,)