django-shop
django-shop copied to clipboard
Update of delivery methods
Hi I have a delivery method, which is only valid if a customer is living in a certain area in which she/he lives in.
class ClimateNeutralShippingModifier(ShippingModifier):
identifier = 'climate-neutral-shipping'
def get_choice(self):
return (self.identifier, _("Climate neutral shipping"))
def is_disabled(self, cart):
# geolocate address of customer
if cart.shipping_address:
postal_code = cart.shipping_address.postal_code
city = cart.shipping_address.city
country = cart.shipping_address.country
distance = checkdistance(postal_code, city, country)
if distance > settings.SHIPPING_DISTANCE:
return True
return False
def add_extra_cart_row(self, cart, request):
if not self.is_active(cart.extra.get('shipping_modifier')) and len(cart_modifiers_pool.get_shipping_modifiers()) > 1:
return
amount = Money(settings.BIKING_PRICE)
instance = {'label': _("Shipping costs"), 'amount': amount}
cart.extra_rows[self.identifier] = ExtraCartRow(instance)
cart.total += amount
def ship_the_goods(self, delivery):
super().ship_the_goods(delivery)
But somehow it will not be disabled, if distance is greater than my configured shipping distance. And now in my production instance I am getting errors like:
But it should not even be a possibilty to select anyways... This error always occurs for the first shipping. After that this error does not occur anymore.
I guess it has to do something with saving shipping address to a customer... but I do not really get any other error than
WARNING: Forbidden: /shop/api/shipping_address/add
Any suggestions how to either adapt the error message or check for distance in another way?
Did you ever figure this out?
Did you ever figure this out?
Sadly I think this still occurs ... Do you have the same or a similar issue?
Yes, kind of. I’m looking to modify the label of the modifier depending on the country selected. It works fine if you select a single country and don’t change it. Even if you do change it, the prices will update on the backend but the label on the site will not.
As for now, I precalculated possible values ... but that only helps temporary ... I will let you know, if I can come up with something.
So I think that I have a workaround for at least changing the page display...you may be able to use it in order to just hide the limited delivery option that you're trying to implement...it's a little messy and it sometimes loses synchronization but bear with me.
In each one of my modifiers, I made and instantiated an handler class with a getService()
function that I can call using AJAX from the clicking the NEXT button or numbered steps at the top of the page that contains the service name.
class AjaxHandler():
def __init__(self):
self.friendlyService=''
def updateService(self,name):
print(self.friendlyService)
self.friendlyService=name
return
def getService(self,request):
print((dir(request.POST)),flush=True)
print((request.POST['tag'][0]),flush=True)
print(self.friendlyService)
data={request.POST['tag']:self.friendlyService}
return JsonResponse(data)
Then I overrode the pre_process_cart()
function that it inherits from BaseModifier
. Unfortunately that function will run on every single step of the checkout process, even before any address info has been entered, instead of just at the point where the modifier is activated (i.e. after the address has been entered and on the payment/shipping page). This requires some checking in order to ensure that you're not trying to do anything with address information before it's even available...which'll crash the checkout process. This is also the reason for the default values at the top of the class (i.e. shippingCost
, postCode
, etc.,)
class CanadaPostExpeditedShippingModifier(ShippingModifier):
"""
This is just a demo on how to implement a shipping modifier, which when selected
by the customer, adds an additional charge to the cart.
"""
identifier = 'canpost-expedited'
service_friendlyName=''
shippingCost='15'
postCode=''
ajaxHandler=AjaxHandler()
service=0
currentCountry=None
currentZip=None
def pre_process_cart(self, cart, request, raise_exception=False):
if (hasattr(cart, "shipping_address")):
if (hasattr(cart.shipping_address, "country")):
print(cart.shipping_address.country)
if (self.currentCountry == None or cart.shipping_address.country != self.currentCountry):
if(cart.shipping_address.country=='CA' or cart.shipping_address.country=='US'):
if(cart.shipping_address.zip_code!=self.currentZip):
print("Zip Changed")
print(self.shippingCost)
self.service_friendlyName, self.shippingCost = cpcOps.getShippingCost(
(cart.shipping_address.country,
cart.shipping_address.zip_code),
service=self.service)
self.ajaxHandler.friendlyService = "{}-{}".format(self.service_friendlyName,
self.shippingCost)
self.currentZip = cart.shipping_address.zip_code
print(self.ajaxHandler.friendlyService)
else:
print("Country Changed")
print(self.shippingCost)
self.service_friendlyName, self.shippingCost = cpcOps.getShippingCost(
(cart.shipping_address.country,
cart.shipping_address.zip_code),
service=self.service)
self.ajaxHandler.updateService("{}-{}".format(self.service_friendlyName, self.shippingCost))
self.currentCountry = cart.shipping_address.country
print(self.ajaxHandler.friendlyService)
else:
print("No location change detected")
return
From there, you can create a checkout
folder in your templates
folder and copy and paste the shipping-method-form.html
, next-step-button.html
, process-bar.html
, etc., and modify them to your liking. For instance, I needed to break out each individual option that I had so I got rid of the {{shipping_modifier.as_div}}
and replaced it with a modified version of the template that it generates.
<script>
function nextAjax() {
var labelUrlDict={'canpost-regular': '{% url "updateRegShip" %}',
'canpost-priority': '{% url "updatePriShip" %}',
'canpost-expedited': '{% url "updateExpShip" %}',
'canpost-xpressPost': '{% url "updateXprShip" %}'
}
var labelTagDict={'canpost-regular': 'label_shipping_method_form-shipping_modifier_2',
'canpost-priority': 'label_shipping_method_form-shipping_modifier_1',
'canpost-expedited': 'label_shipping_method_form-shipping_modifier_0',
'canpost-xpressPost': 'label_shipping_method_form-shipping_modifier_3'
}
for(var serviceId in labelUrlDict){
$.ajax({
url: labelUrlDict[serviceId],
type: "POST",
dataType: 'json',
data:{'csrfmiddlewaretoken':'{{ csrf_token }}','tag':labelTagDict[serviceId]},
beforeSend: function() {
$('#loader').show();
console.log("AJAX Started");
},
complete: function(){
$('#loader').hide();
console.log("AJAX Done");
},
success: function (data) {
if (data) {
console.log(Object.entries(data));
$("span[name="+Object.entries(data)[0][0]+"]").text(Object.entries(data)[0][1]);
}
}
});
}
}
</script>
{% endaddtoblock %}
...
<form shop-method-form djng-endpoint="{% url 'shop:checkout-upload' %}" name="{{ shipping_method_form.form_name }}" class="mt-3" novalidate>
{% if shipping_method_form.has_choices %}
<div class="djng-line-spreader">
<ul ng-show="shipping_method_form.$pristine" class="djng-form-errors" ng-cloak>
<li ng-show="shipping_method_form.$error.rejected && shipping_method_form.$message" class="invalid" ng-bind="shipping_method_form.$message"></li>
</ul>
</div>
<div class="form-group has-feedback djng-field-required">
<div id="shipping_method_form-shipping_modifier">
{% for choice in shipping_method_form.shipping_modifier %}
<div class="radio">
<label>
{{choice.tag}}
<span class="label" name="label_shipping_method_form-shipping_modifier_{{ forloop.counter0 }}">
{{choice.choice_label}}
</span>
</label>
</div>
{% endfor %}
</div>
<ul ng-show="shipping_method_form['shipping_modifier'].$dirty && !shipping_method_form['shipping_modifier'].$untouched" class="djng-form-control-feedback djng-field-errors" ng-cloak>
<li ng-show="shipping_method_form['shipping_modifier'].$error.required" class="invalid">At least one radio button has to be selected.</li>
<li ng-show="shipping_method_form['shipping_modifier'].$valid" class="valid"></li>
</ul>
<ul ng-show="shipping_method_form['shipping_modifier'].$pristine" class="djng-form-control-feedback djng-field-errors" ng-cloak>
<li ng-show="shipping_method_form['shipping_modifier'].$valid" class="valid"></li>
<li ng-show="shipping_method_form['shipping_modifier'].$error.rejected && shipping_method_form['shipping_modifier'].$message" class="invalid" ng-bind="shipping_method_form['shipping_modifier'].$message"></li>
</ul>
<input type="hidden" name="plugin_id" value="1043" ng-model="shipping_method['plugin_id']" class="form-control" id="shipping_method_form-plugin_id">
<input type="hidden" name="plugin_order" value="5" ng-model="shipping_method['plugin_order']" class="form-control" id="shipping_method_form-plugin_order">
</div>
{% if show_additional_charge %}
<ul class="additional-charge">
{% for choice, label in shipping_method_form.shipping_modifier.field.choices %}
<li ng-repeat="extra_row in cart.extra_rows|filter:{modifier:'{{ choice }}'}">{% trans "Additional charge: {{ extra_row.amount }}" context "checkout" %}</li>
{% endfor %}
{% if shipping_modifiers.initial_row %}
<li ng-if="!cart.extra_rows">{% blocktrans with amount=shipping_modifiers.initial_row.amount context "checkout" %}Additional charge: {{ amount }}{% endblocktrans %}</li>
{% endif %}
</ul>
{% endif %}
{% else %}
<input name="__force_invalidation__" style="display: none;" required />
<p class="bg-danger">{% trans "No shipping method available" context "checkout" %}</p>
{% endif %}
</form>
I also modified the next-step-button.html
file to run the nextAjax()
on every click in order to make sure that the information being provided to/by the Python AJAX handler is current.
<button **onclick="nextAjax()"** ng-click="do(prepare()).then(update()).then(showOK()).then(emit('shop.checkout.digest')).then(delay(333)).then(nextStep).catch(scrollToRejected()).finally(restore())
I'm not sure if any of this helps you but you may be able to use the AJAX call to just outright hide the shipping modifier from view instead of changing its name. Let me know what you think.
Thanks for the extensive research. I wil have a look at it, and will come back to you. It looks promising.