Last active
April 11, 2016 22:53
-
-
Save kamilion/23e01fb4c6904619c151f29ccccfb0c9 to your computer and use it in GitHub Desktop.
django/mezzanine/cartridge and shippingness
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Dis goes in kamicart/admin.py | |
# To enable this, append to INSTALLED_APPS: | |
# "m3shop.kamicart", | |
# You need the comma at the end too. | |
# Now add to EXTRA_MODEL_FIELDS | |
#EXTRA_MODEL_FIELDS = ( | |
# ( | |
# "cartridge.shop.models.Product.weight_shipping", | |
# "DecimalField", | |
# ("Shipping Weight",), | |
# {"blank": False, "default": 0.0, "max_digits": 5, "decimal_places": 2, | |
# "help_text": 'Specify the shipping weight of the Product including packing material'}, | |
# ), | |
#) | |
# Run manage.py makemigrations && manage.py migrate | |
# And the billship_handler module should work! | |
# This file is responsible for making the needed override to display extra fields in the Product | |
from copy import deepcopy | |
from django.contrib import admin | |
# Need to override the ProductAdmin to add the weight_shipping field. | |
# Pull in the original modules | |
from cartridge.shop.admin import ProductAdmin | |
from cartridge.shop.models import Product | |
# Deepcopy the original to a new object. | |
new_fieldsets = deepcopy(ProductAdmin.fieldsets) | |
# Append the weight_shipping field. | |
new_fieldsets[0][1]["fields"].insert(-3, "weight_shipping") | |
# Define an override class for ProductAdmin | |
class WeightedProductAdmin(ProductAdmin): | |
# Set the fieldsets to the new fieldsets with the additional field | |
fieldsets = new_fieldsets | |
# Unregister the original instance of Product, freeing it's admin module as well. | |
admin.site.unregister(Product) | |
# Reregister the original instance of Product with a new subclassed ProductAdmin. | |
admin.site.register(Product, WeightedProductAdmin) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Blank lines above so we don't get hashbang processed on unix if set executable. | |
# This file is referred to as m3shop.kamicart.billship_handler by django. | |
from __future__ import unicode_literals | |
from future.builtins import int | |
from future.builtins import str | |
from django.core.exceptions import ImproperlyConfigured | |
from django.utils.translation import ugettext as _ | |
from mezzanine.conf import settings | |
from cartridge.shop.checkout import CheckoutError | |
from cartridge.shop.models import Order | |
from cartridge.shop.utils import set_shipping | |
import sys | |
from fedex.config import FedexConfig | |
from fedex.services.rate_service import FedexRateServiceRequest | |
try: | |
fedexcfg = FedexConfig(key=settings.FEDEX_KEY, | |
password=settings.FEDEX_PASSWORD, | |
account_number=settings.FEDEX_ACCOUNT_NUM, | |
meter_number=settings.FEDEX_METER_NUM, | |
freight_account_number=settings.FEDEX_FREIGHT_NUM, | |
use_test_server=settings.FEDEX_USE_TEST_SERVER) | |
except AttributeError: | |
raise ImproperlyConfigured("You need to define the following keys " | |
"in your settings module to use this " | |
"shipping calculation module." | |
"FEDEX_KEY, " | |
"FEDEX_PASSWORD, " | |
"FEDEX_ACCOUNT_NUM, " | |
"FEDEX_METER_NUM, " | |
"FEDEX_FREIGHT_NUM, " | |
"FEDEX_USE_TEST_SERVER.") | |
def m3_billship_handler_fedex(request, order_form): | |
try: | |
settings.use_editable() | |
########## Do some processing here for gathering the weights of items in the cart. | |
print("FEDEX:0:CART:{}".format(request.cart)) | |
for item in request.cart: | |
print("FEDEX:0:CART_ITEM:{}".format(item)) | |
########## Fedex Object Creation | |
# This is the object that will be handling our request. | |
# We're using the FedexConfig object from above, populated | |
# by the mezzanine settings defined in local_settings.py | |
customer_transaction_id = "*** RateService Request v18 using Python ***" # Optional transaction_id | |
rate_request = FedexRateServiceRequest(fedexcfg, customer_transaction_id=customer_transaction_id) | |
########## Fedex Object Configuration | |
# If you wish to have transit data returned with your request you | |
# need to uncomment the following | |
# rate_request.ReturnTransitAndCommit = True | |
# Who pays for the rate_request? | |
# RECIPIENT, SENDER or THIRD_PARTY | |
rate_request.RequestedShipment.ShippingChargesPayment.PaymentType = 'SENDER' | |
# This is very generalized, top-level information. | |
# REGULAR_PICKUP, REQUEST_COURIER, DROP_BOX, BUSINESS_SERVICE_CENTER or STATION | |
rate_request.RequestedShipment.DropoffType = 'REGULAR_PICKUP' | |
# See page 355 in WS_ShipService.pdf for a full list. Here are the common ones: | |
# STANDARD_OVERNIGHT, PRIORITY_OVERNIGHT, FEDEX_GROUND, FEDEX_EXPRESS_SAVER | |
# To receive rates for multiple ServiceTypes set to None. | |
rate_request.RequestedShipment.ServiceType = 'FEDEX_GROUND' | |
########## Define the source and customer addresses | |
# Shipper's address | |
rate_request.RequestedShipment.Shipper.Address.PostalCode = '95008' | |
rate_request.RequestedShipment.Shipper.Address.CountryCode = 'US' | |
rate_request.RequestedShipment.Shipper.Address.Residential = False | |
# Recipient address | |
rate_request.RequestedShipment.Recipient.Address.PostalCode = request.session.get('order')['shipping_detail_postcode'] | |
rate_request.RequestedShipment.Recipient.Address.CountryCode = request.session.get('order')['shipping_detail_country'].lower() | |
# This is needed to ensure an accurate rate quote with the response. | |
# rate_request.RequestedShipment.Recipient.Address.Residential = True | |
# include estimated duties and taxes in rate quote, can be ALL or NONE | |
rate_request.RequestedShipment.EdtRequestType = 'NONE' | |
########## Define the package manifest | |
# What kind of package this will be shipped in. | |
# FEDEX_BOX, FEDEX_PAK, FEDEX_TUBE, YOUR_PACKAGING | |
rate_request.RequestedShipment.PackagingType = 'YOUR_PACKAGING' | |
total_weight = 1.00 | |
#for item in request.cart: | |
# total_weight = total_weight + item.weight_shipping | |
package1_weight = rate_request.create_wsdl_object_of_type('Weight') | |
# Weight, in LB. | |
package1_weight.Value = total_weight | |
package1_weight.Units = "LB" | |
package1 = rate_request.create_wsdl_object_of_type('RequestedPackageLineItem') | |
package1.Weight = package1_weight | |
# can be other values this is probably the most common | |
package1.PhysicalPackaging = 'BOX' | |
# Required, but according to FedEx docs: | |
# "Used only with PACKAGE_GROUPS, as a count of packages within a | |
# group of identical packages". In practice you can use this to get rates | |
# for a shipment with multiple packages of an identical package size/weight | |
# on rate request without creating multiple RequestedPackageLineItem elements. | |
# You can OPTIONALLY specify a package group: | |
# package1.GroupNumber = 0 # default is 0 | |
# The result will be found in RatedPackageDetail, with specified GroupNumber. | |
package1.GroupPackageCount = 1 | |
########## Debugging for the request | |
# Un-comment this to see the other variables you may set on a package. | |
#print("FEDEX:1:{}".format(package1)) | |
# This adds the RequestedPackageLineItem WSDL object to the rate_request. It | |
# increments the package count and total weight of the rate_request for you. | |
rate_request.add_package(package1) | |
# If you'd like to see some documentation on the ship service WSDL, un-comment | |
# this line. (Spammy). | |
#print("FEDEX:2:{}".format(rate_request.client)) | |
# Un-comment this to see your complete, ready-to-send request as it stands | |
# before it is actually sent. This is useful for seeing what values you can | |
# change. | |
#print("FEDEX:3:{}".format(rate_request.RequestedShipment)) | |
print("FEDEX:4:SENDING_REQUEST") | |
########## Send the WSDL Request to fedex's API | |
# Fires off the request, sets the 'response' attribute on the object. | |
rate_request.send_request() | |
print("FEDEX:5:SENT_REQUEST") | |
########## Debugging for the reply | |
# This will show the reply to your rate_request being sent. You can access the | |
# attributes through the response attribute on the request object. This is | |
# good to un-comment to see the variables returned by the FedEx reply. | |
print("FEDEX:6:{}".format(rate_request.response)) | |
# This will convert the response to a python dict object. To | |
# make it easier to work with. | |
# from fedex.tools.conversion import basic_sobject_to_dict | |
print("FEDEX:7:{}".format(basic_sobject_to_dict(rate_request.response))) | |
# This will dump the response data dict to json. | |
# from fedex.tools.conversion import sobject_to_json | |
print("FEDEX:8:{}".format(sobject_to_json(rate_request.response))) | |
# Here is the overall end result of the query. | |
print("FEDEX:10:HighestSeverity: {}".format(rate_request.response.HighestSeverity)) | |
########## Iterate over the reply details | |
# RateReplyDetails can contain rates for multiple ServiceTypes if ServiceType was set to None | |
for service in rate_request.response.RateReplyDetails: | |
for detail in service.RatedShipmentDetails: | |
for surcharge in detail.ShipmentRateDetail.Surcharges: | |
if surcharge.SurchargeType == 'OUT_OF_DELIVERY_AREA': | |
print("FEXEX:15:{}: ODA rate_request charge {}".format(service.ServiceType, surcharge.Amount.Amount)) | |
for rate_detail in service.RatedShipmentDetails: | |
print("FEDEX:20:{}: Net FedEx Charge {} {}".format(service.ServiceType, | |
rate_detail.ShipmentRateDetail.TotalNetFedExCharge.Currency, | |
rate_detail.ShipmentRateDetail.TotalNetFedExCharge.Amount)) | |
########## Warning checks | |
# Check for warnings, this is also logged by the base class. | |
if rate_request.response.HighestSeverity == 'NOTE': | |
for notification in rate_request.response.Notifications: | |
if notification.Severity == 'NOTE': | |
print(sobject_to_dict(notification)) | |
fedex_charge = rate_detail.ShipmentRateDetail.TotalNetFedExCharge.Amount | |
print("FEDEX:99:COMPLETE:RATE_IS {}".format(fedex_charge)) | |
########## Do more processing here for handling charges based on item count and weight. | |
########## Set the final amount for shipping and return. | |
set_shipping(request, _("FedEx Shipping"), rate_detail.ShipmentRateDetail.TotalNetFedExCharge.Amount) | |
except Exception as e: | |
raise CheckoutError(_("A general error occured while calculating fedex shipping rate: ") + str(e)) | |
def m3_billship_handler_testing(request, order_form): | |
try: | |
settings.use_editable() | |
subtotal = request.cart.total_price() | |
item_count = request.cart.total_quantity() | |
if subtotal > 149 or item_count > 4: | |
set_shipping(request, _("Flat rate shipping"), settings.SHOP_DEFAULT_SHIPPING_VALUE) | |
else: | |
set_shipping(request, _("Free shipping"), 0) | |
except Exception as e: | |
raise CheckoutError(_("A general error occured while calculating tax: ") + str(e)) | |
########## Soap object helpers | |
def basic_sobject_to_dict(obj): | |
"""Converts suds object to dict very quickly. | |
Does not serialize date time or normalize key case. | |
:param obj: suds object | |
:return: dict object | |
""" | |
if not hasattr(obj, '__keylist__'): | |
return obj | |
data = {} | |
fields = obj.__keylist__ | |
for field in fields: | |
val = getattr(obj, field) | |
if isinstance(val, list): | |
data[field] = [] | |
for item in val: | |
data[field].append(basic_sobject_to_dict(item)) | |
else: | |
data[field] = basic_sobject_to_dict(val) | |
return data | |
def sobject_to_dict(obj, key_to_lower=False, json_serialize=False): | |
""" | |
Converts a suds object to a dict. Includes advanced features. | |
:param json_serialize: If set, changes date and time types to iso string. | |
:param key_to_lower: If set, changes index key name to lower case. | |
:param obj: suds object | |
:return: dict object | |
""" | |
import datetime | |
if not hasattr(obj, '__keylist__'): | |
if json_serialize and isinstance(obj, (datetime.datetime, datetime.time, datetime.date)): | |
return obj.isoformat() | |
else: | |
return obj | |
data = {} | |
fields = obj.__keylist__ | |
for field in fields: | |
val = getattr(obj, field) | |
if key_to_lower: | |
field = field.lower() | |
if isinstance(val, list): | |
data[field] = [] | |
for item in val: | |
data[field].append(sobject_to_dict(item, json_serialize=json_serialize)) | |
else: | |
data[field] = sobject_to_dict(val, json_serialize=json_serialize) | |
return data | |
def sobject_to_json(obj, key_to_lower=False): | |
""" | |
Converts a suds object to a JSON string. | |
:param obj: suds object | |
:param key_to_lower: If set, changes index key name to lower case. | |
:return: json object | |
""" | |
import json | |
data = sobject_to_dict(obj, key_to_lower=key_to_lower, json_serialize=True) | |
return json.dumps(data) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Blank lines above so we don't get hashbang processed on unix if set executable. | |
# This file is referred to as m3shop.kamicart.models by django. | |
from copy import deepcopy | |
from django.utils.encoding import force_text | |
from cartridge.shop.models import Cart | |
def add_item_mod(self, variation, quantity): | |
""" | |
Increase quantity of existing item if SKU matches, otherwise create | |
new. | |
""" | |
if not self.pk: | |
self.save() | |
kwargs = {"sku": variation.sku, "unit_price": variation.price()} | |
item, created = self.items.get_or_create(**kwargs) | |
if created: | |
item.description = force_text(variation) | |
item.unit_price = variation.price() | |
item.url = variation.product.get_absolute_url() | |
try: | |
item.weight_shipping = self.weight_shipping | |
except AttributeError: | |
pass | |
image = variation.image | |
if image is not None: | |
item.image = force_text(image.file) | |
variation.product.actions.added_to_cart() | |
item.quantity += quantity | |
item.save() | |
Cart.add_item = add_item_mod | |
#cartridge.shop.models.Cart.add_item = add_item_mod |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Blank lines above so we don't get hashbang processed on unix if set executable. | |
# This file is referred to as m3shop.kamicart.tax_handler by django. | |
from __future__ import unicode_literals | |
from future.builtins import int | |
from future.builtins import str | |
from django.core.exceptions import ImproperlyConfigured | |
from django.utils.translation import ugettext as _ | |
from mezzanine.conf import settings | |
from decimal import Decimal | |
from cartridge.shop.checkout import CheckoutError | |
from cartridge.shop.models import Order | |
from cartridge.shop.utils import set_tax, set_shipping | |
try: | |
tax_percentage = Decimal(settings.TAX_PERCENTAGE) | |
taxed_state_id = settings.TAX_STATE_ID.lower() | |
taxed_state_name = settings.TAX_STATE_NAME.lower() | |
except AttributeError: | |
raise ImproperlyConfigured("You need to define TAX_PERCENTAGE, " | |
"TAX_STATE_ID, and TAX_STATE_NAME " | |
"in your settings module to use this " | |
"tax calculation module.") | |
def m3_tax_handler(request, order_form): | |
""" | |
Cartridge tax handler - called immediately after the handler defined | |
by ``SHOP_HANDLER_BILLING_SHIPPING``. | |
Specify the path to import this from via the setting | |
``SHOP_HANDLER_TAX``. | |
This function will typically contain any tax calculation where the | |
tax amount can then be set using the function | |
``cartridge.shop.utils.set_tax``. | |
The Cart object is also accessible via ``request.cart``. | |
""" | |
settings.use_editable() # Drop the settings cache and reload so we don't run into time-to-use vulns. | |
try: | |
# Compare lowercase texts between where we tax and where the order is shipped to. | |
dest_state = request.session.get('order')['shipping_detail_state'].lower() | |
# Decide if we need to add the tax based on the state's name. | |
if dest_state in (taxed_state_id, taxed_state_name): | |
# Calculate the total after discounts | |
total = request.cart.total_price() | |
if request.session.has_key('discount'): | |
#if 'discount' in request.session: # which one?? | |
discount = request.cart.calculate_discount(request.session['discount']) | |
total -= discount # decrement the discount from the total value. | |
# Get the properly capitalized string directly from the mezzanine settings. | |
set_tax(request, _("{} Sales Tax".format(settings.TAX_STATE_NAME)), total * tax_percentage ) | |
except Exception as e: | |
raise CheckoutError(_("A general error occured while calculating tax: ") + str(e)) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment