Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

models.py 5.5KB

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