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

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