First I contributed to this issue that has already been closed, but probably nobody noticed as the issue is closed.
Could someone give me a hint what is the best and cleanest way to implement shipping price calculation by weight?
I get better in understanding the code, but nevertheless it’s cumbersome without explanation.
You need a place where you set the shipping price and you can get hold of the items ordered.
Maybe when a DeliveryGroup is created in core.utils.random_data.create_delivery_group(order)? Or in checkout.core.Checkout.deliveries(self)?
Any help would be appreciated.
Hello @MacLake
First of all - all code that lives in core.utils.random_data is only used to generate fake data (it's used by ./manage.py populatedb command.
To achieve price by weight, you should take a look on the code that creates order: https://github.com/mirumee/saleor/blob/master/saleor/checkout/core.py#L288 and wire it with shipping methods https://github.com/mirumee/saleor/blob/master/saleor/shipping/models.py#L98
I think it would be the easiest way to do it.
Hello @artursmet ,
Thanks a lot. I’ll try it this way and tell if it worked out.
Hi @MacLake I'm also interest in the shipping by weight. I plan to get started at the end of the month but if you could share any information about your progress I'd be glad to hear in order to not duplicate the functionality or even support your development on the matter.
Hi @curaloucura, I've solved the problem. I’ll give you some detailed instructions tomorrow. Actually it’s quite easy once you’ve understood the code, but that takes some time.
Okay, here are the instructions as promised: First of all, as a physicist I should use the correct term mass instead of weight.
mass = models.FloatField(null=True, verbose_name=pgettext_lazy('Product field', 'mass in kg'))mass_override = models.FloatField(null=True, verbose_name=pgettext_lazy('Product variant field', 'mass in kg'))
@property
def mass(self):
return self.mass_override or self.product.mass or 0
<div class="row">
{{ product_form.mass|materializecss }}
</div>
saleor/templates/dashboard/product/variant_form.html:
<div class="row">
{{ form.mass_override|materializecss }}
</div>
shipping_cost = self.shipping_method.get_total(partition, self.shipping_address)
and Checkout.create_order(self):
shipping_price = self.shipping_method.get_total(partition, self.shipping_address)
self.get_total() calls self.deliveries(), which should do the correct calculation
def get_total(self, partition=None, shipping_address=None):
"""Calculate shipping price by mass and shipping address if partition is available"""
if partition:
# TODO: correct calulation, maybe with data from config file or with foreign key objects
# We ignore self.country_code and use shipping_address.country instead. We also ignore self.price:
eu = ['AT', 'BE', 'BG', 'CY', 'CZ', 'DK', 'DE', 'EE', 'ES', 'FI', 'FR', 'GB', 'GR', 'HR', 'HU',
'IE', 'IT', 'LT', 'LU', 'LV', 'MT', 'NL', 'PL', 'PT', 'RO', 'SE', 'SI', 'SK']
import datetime, pytz
utc = pytz.timezone('UTC')
t_brexit = datetime.datetime(2019, 4, 1, 0, 0, 0, tzinfo=pytz.utc) # or whatever
if utc.localize(datetime.datetime.utcnow()) >= t_brexit:
eu.remove('GB')
cc = shipping_address.country
# Determine the total mass of the partition:
mass = 0
for cart_line in partition:
mass += cart_line.quantity * cart_line.variant.mass
# Just an example: 1 €/kg for Germany, 1.5 €/kg for the rest of the EU and 2 €/kg for other countries:
if cc == 'DE':
shipping_price = Price(mass, currency=settings.DEFAULT_CURRENCY)
elif cc in eu:
shipping_price = Price(1.5 * mass, currency=settings.DEFAULT_CURRENCY)
else:
shipping_price = Price(2 * mass, currency=settings.DEFAULT_CURRENCY)
return shipping_price
# Standard behaviour just in case in order not to break things
else:
return self.price
Note that the stardard shipping price ShippingMethodCountry.price and in this example also ShippingMethodCountry.country_code are completely ignored. This might be confusing, but I wanted to change as few code as possible and not break existing functionality.
<div class="col-5">
<div class="row">
<div class="col-md-12 col-12 text-right">
<p class="cart__delivery-info__price">
{% if default_country_options %}
{# hide fixed standard shipping price {% price_range default_country_options %} #}
{% endif %}
{% trans "+ shipping" %}
</p>
</div>
</div>
</div>
To the maintainers of Saleor: Is there any other way I can contribute in this matter? Should I do a fork? We should somehow put this information into the documentation.
Thanks @MacLake I was woundering how to this do right now heheheh, thanks again!
I ran into a problem that when selecting the Shipping Method, it already displays the old price. I decided to just hide it on:
checkout/forms/forms.py, inside ShippingCountryChoiceField
def label_from_instance(self, obj):
# price_html = format_price(obj.price.gross, obj.price.currency)
price_html = ''
label = mark_safe('%s %s' % (obj.shipping_method, price_html))
return label
Not neat but I just wanted to point out where it lives.
Shipping price by weight was introduced in #2529
For shipping price by size, we will keep #2029 issue as it contains more details
Most helpful comment
Okay, here are the instructions as promised: First of all, as a physicist I should use the correct term mass instead of weight.
In class Product(models.Model, ItemRange, index.Indexed):
mass = models.FloatField(null=True, verbose_name=pgettext_lazy('Product field', 'mass in kg'))In class ProductVariant(models.Model, Item):
saleor/templates/dashboard/product/product_form.html:
saleor/templates/dashboard/product/variant_form.html:
and Checkout.create_order(self):
self.get_total() calls self.deliveries(), which should do the correct calculation
Note that the stardard shipping price ShippingMethodCountry.price and in this example also ShippingMethodCountry.country_code are completely ignored. This might be confusing, but I wanted to change as few code as possible and not break existing functionality.
To the maintainers of Saleor: Is there any other way I can contribute in this matter? Should I do a fork? We should somehow put this information into the documentation.