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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  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. @property
  57. def charge_incl_tax(self):
  58. """
  59. Return basket total including tax
  60. """
  61. if self.free_shipping_threshold is not None and \
  62. self._basket.total_incl_tax >= self.free_shipping_threshold:
  63. return D('0.00')
  64. charge = self.price_per_order
  65. for line in self._basket.lines.all():
  66. charge += line.quantity * self.price_per_item
  67. return charge
  68. @property
  69. def charge_excl_tax(self):
  70. """
  71. Return basket total excluding tax.
  72. Default implementation assumes shipping is tax free.
  73. """
  74. return self.charge_incl_tax
  75. class WeightBased(ShippingMethod):
  76. upper_charge = models.DecimalField(
  77. _("Upper Charge"), decimal_places=2, max_digits=12, null=True,
  78. help_text=_("This is the charge when the weight of the basket "
  79. "is greater than all the weight bands"""))
  80. weight_attribute = 'weight'
  81. default_weight = models.DecimalField(
  82. _("Default Weight"), decimal_places=2, max_digits=12,
  83. default=D('0.00'),
  84. help_text=_("Default product weight in Kg when no weight attribute "
  85. "is defined"))
  86. class Meta:
  87. verbose_name = _("Weight-based Shipping Method")
  88. verbose_name_plural = _("Weight-based Shipping Methods")
  89. @property
  90. def charge_incl_tax(self):
  91. weight = Scales(attribute_code=self.weight_attribute,
  92. default_weight=self.default_weight).weigh_basket(
  93. self._basket)
  94. band = self.get_band_for_weight(weight)
  95. if not band:
  96. if self.bands.all().count() > 0 and self.upper_charge:
  97. return self.upper_charge
  98. else:
  99. return D('0.00')
  100. return band.charge
  101. @property
  102. def charge_excl_tax(self):
  103. return self.charge_incl_tax
  104. def get_band_for_weight(self, weight):
  105. """
  106. Return the weight band for a given weight
  107. """
  108. bands = self.bands.filter(upper_limit__gte=weight).order_by('upper_limit')
  109. if not bands.count():
  110. # No band for this weight
  111. return None
  112. return bands[0]
  113. class WeightBand(models.Model):
  114. """
  115. Represents a weight band which are used by the WeightBasedShipping method.
  116. """
  117. method = models.ForeignKey(WeightBased, related_name='bands', verbose_name=_("Method"))
  118. upper_limit = models.FloatField(_("Upper Limit"), help_text=_("""Enter upper limit of this
  119. weight band in Kg, the lower
  120. limit will be determine by the
  121. other weight bands"""))
  122. charge = models.DecimalField(_("Charge"), decimal_places=2, max_digits=12)
  123. @property
  124. def weight_from(self):
  125. lower_bands = self.method.bands.filter(
  126. upper_limit__lt=self.upper_limit).order_by('-upper_limit')
  127. if not lower_bands:
  128. return D('0.00')
  129. return lower_bands[0].upper_limit
  130. @property
  131. def weight_to(self):
  132. return self.upper_limit
  133. class Meta:
  134. ordering = ['upper_limit']
  135. verbose_name = _("Weight Band")
  136. verbose_name_plural = _("Weight Bands")
  137. def __unicode__(self):
  138. return _('Charge for weights up to %sKg') % (self.upper_limit,)