diff --git a/src/core/__init__.py b/src/core/__init__.py
index 0643f2b..38676a8 100644
--- a/src/core/__init__.py
+++ b/src/core/__init__.py
@@ -71,3 +71,27 @@ class ShippingMethodType:
(PRICE_BASED, "Price based shipping"),
(WEIGHT_BASED, "Weight based shipping"),
]
+
+class ShippingService:
+ FIRST_CLASS = "FIRST CLASS"
+ PRIORITY = "PRIORITY"
+ PRIORITY_COMMERCIAL = "PRIORITY COMMERCIAL"
+
+ CHOICES = [
+ (FIRST_CLASS, "First Class"),
+ (PRIORITY, "Priority"),
+ (PRIORITY_COMMERCIAL, "Priority Commercial")
+ ]
+
+class ShippingContainer:
+ LG_FLAT_RATE_BOX = "LG FLAT RATE BOX"
+ REGIONAL_RATE_BOX_A = "REGIONALRATEBOXA"
+ REGIONAL_RATE_BOX_B = "REGIONALRATEBOXB"
+ VARIABLE = "VARIABLE"
+
+ CHOICES = [
+ (LG_FLAT_RATE_BOX, "Flate Rate Box - Large"),
+ (REGIONAL_RATE_BOX_A, "Regional Rate Box A"),
+ (REGIONAL_RATE_BOX_B, "Regional Rate Box B"),
+ (VARIABLE, "Variable")
+ ]
diff --git a/src/core/migrations/0006_alter_order_options_order_shipping_total.py b/src/core/migrations/0006_alter_order_options_order_shipping_total.py
new file mode 100644
index 0000000..1af1363
--- /dev/null
+++ b/src/core/migrations/0006_alter_order_options_order_shipping_total.py
@@ -0,0 +1,22 @@
+# Generated by Django 4.0.2 on 2022-04-24 16:34
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('core', '0005_alter_product_options_product_sorting'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='order',
+ options={'ordering': ('-created_at',)},
+ ),
+ migrations.AddField(
+ model_name='order',
+ name='shipping_total',
+ field=models.DecimalField(decimal_places=2, default=0, max_digits=5),
+ ),
+ ]
diff --git a/src/core/models.py b/src/core/models.py
index 60d809b..dabd4df 100644
--- a/src/core/models.py
+++ b/src/core/models.py
@@ -9,6 +9,8 @@ from django.conf import settings
from django.utils import timezone
from django.urls import reverse
from django.core.validators import MinValueValidator, MaxValueValidator
+from django.core.serializers.json import DjangoJSONEncoder
+from django.forms.models import model_to_dict
from django_measurement.models import MeasurementField
@@ -25,6 +27,10 @@ from .weight import WeightUnits, zero_weight
logger = logging.getLogger(__name__)
+class ProductEncoder(DjangoJSONEncoder):
+ def default(self, obj):
+ logger.info(f"\n{obj}\n")
+ return super().default(obj)
class ProductManager(models.Manager):
def get_queryset(self):
@@ -69,7 +75,7 @@ class Product(models.Model):
try:
return self.productphoto_set.all()[1]
except IndexError:
- pass
+ return 'No image'
class Meta:
ordering = ['sorting', 'name']
@@ -200,6 +206,7 @@ class Order(models.Model):
on_delete=models.SET_NULL,
)
+
coupon = models.ForeignKey(
Coupon,
related_name='orders',
@@ -207,6 +214,12 @@ class Order(models.Model):
null=True
)
+ shipping_total = models.DecimalField(
+ max_digits=5,
+ decimal_places=2,
+ default=0
+ )
+
total_net_amount = models.DecimalField(
max_digits=10,
decimal_places=2,
@@ -237,7 +250,7 @@ class Order(models.Model):
return Decimal('0')
def get_total_price_after_discount(self):
- return round(self.total_net_amount - self.get_discount(), 2)
+ return round((self.total_net_amount - self.get_discount()) + self.shipping_total, 2)
def get_absolute_url(self):
return reverse('dashboard:order-detail', kwargs={'pk': self.pk})
diff --git a/src/core/usps.py b/src/core/usps.py
index 64e6f34..c4e60df 100644
--- a/src/core/usps.py
+++ b/src/core/usps.py
@@ -19,20 +19,20 @@ class USPSApiWithRate(USPSApi):
class Rate:
- def __init__(self, usps, box, **kwargs):
+ def __init__(self, usps, request, **kwargs):
xml = etree.Element('RateV4Request', {'USERID': usps.api_user_id})
etree.SubElement(xml, 'Revision').text = '2'
package = etree.SubElement(xml, 'Package', {'ID': '0'})
- etree.SubElement(package, 'Service').text = box['service']
- etree.SubElement(package, 'ZipOrigination').text = box['zip_origination']
- etree.SubElement(package, 'ZipDestination').text = box['zip_destination']
- etree.SubElement(package, 'Pounds').text = box['pounds']
- etree.SubElement(package, 'Ounces').text = box['ounces']
- etree.SubElement(package, 'Container').text = box['container']
- etree.SubElement(package, 'Width').text = box['width']
- etree.SubElement(package, 'Length').text = box['length']
- etree.SubElement(package, 'Height').text = box['height']
- etree.SubElement(package, 'Girth').text = box['girth']
- etree.SubElement(package, 'Machinable').text = box['machinable']
+ etree.SubElement(package, 'Service').text = request['service']
+ etree.SubElement(package, 'ZipOrigination').text = request['zip_origination']
+ etree.SubElement(package, 'ZipDestination').text = request['zip_destination']
+ etree.SubElement(package, 'Pounds').text = request['pounds']
+ etree.SubElement(package, 'Ounces').text = request['ounces']
+ etree.SubElement(package, 'Container').text = request['container']
+ etree.SubElement(package, 'Width').text = request['width']
+ etree.SubElement(package, 'Length').text = request['length']
+ etree.SubElement(package, 'Height').text = request['height']
+ etree.SubElement(package, 'Girth').text = request['girth']
+ etree.SubElement(package, 'Machinable').text = request['machinable']
self.result = usps.send_request('rate', xml)
diff --git a/src/dashboard/templates/dashboard/order_detail.html b/src/dashboard/templates/dashboard/order_detail.html
index 995eb32..6226116 100644
--- a/src/dashboard/templates/dashboard/order_detail.html
+++ b/src/dashboard/templates/dashboard/order_detail.html
@@ -103,11 +103,12 @@
- Subtotal: {{order.total_net_amount}}
+ Subtotal: ${{order.total_net_amount}}
{% if order.coupon %}
Discount: {{order.coupon.discount_value}} {{order.coupon.get_discount_value_type_display}}
{% endif %}
- Total: {{order.get_total_price_after_discount}}
+ Shipping: ${{order.shipping_total}}
+ Total: ${{order.get_total_price_after_discount}}
diff --git a/src/dashboard/templates/dashboard/order_list.html b/src/dashboard/templates/dashboard/order_list.html
index b620945..8c787bf 100644
--- a/src/dashboard/templates/dashboard/order_list.html
+++ b/src/dashboard/templates/dashboard/order_list.html
@@ -22,7 +22,7 @@
{{order.get_status_display}}
- ${{order.total_net_amount}}
+ ${{order.get_total_price_after_discount}}
{% empty %}
No orders
diff --git a/src/ptcoffee/config.py b/src/ptcoffee/config.py
index 0ca241c..f4c127a 100644
--- a/src/ptcoffee/config.py
+++ b/src/ptcoffee/config.py
@@ -21,6 +21,7 @@ CACHE_CONFIG = {
PAYPAL_CLIENT_ID = os.environ.get('PAYPAL_CLIENT_ID', '')
PAYPAL_SECRET_ID = os.environ.get('PAYPAL_SECRET_ID', '')
USPS_USER_ID = os.environ.get('USPS_USER_ID', '639NATHA3105')
+DEFAULT_ZIP_ORIGINATION = os.environ.get('DEFAULT_ZIP_ORIGINATION', '98368')
ANYMAIL_CONFIG = {
'MAILGUN_API_KEY': os.environ.get('MAILGUN_API_KEY', ''),
diff --git a/src/ptcoffee/settings.py b/src/ptcoffee/settings.py
index dd462df..83cee0d 100644
--- a/src/ptcoffee/settings.py
+++ b/src/ptcoffee/settings.py
@@ -93,6 +93,7 @@ DATABASES = {
CACHES = {'default': CACHE_CONFIG}
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'
+SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'
# Password validation
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators
diff --git a/src/static/images/site_banner.jpg b/src/static/images/site_banner.jpg
new file mode 100644
index 0000000..d78da3c
Binary files /dev/null and b/src/static/images/site_banner.jpg differ
diff --git a/src/static/styles/main.css b/src/static/styles/main.css
index d217e48..3a7da9a 100644
--- a/src/static/styles/main.css
+++ b/src/static/styles/main.css
@@ -7,6 +7,8 @@
--yellow-color: #f8a911;
--yellow-alt-color: #ffce6f;
--yellow-dark-color: #b27606;
+ --red-color: #d43131;
+ --green-color: #3ea165;
--default-border: 2px solid var(--gray-color);
}
@@ -48,6 +50,7 @@ h1, h2, h3, h4, h5 {
h1 {
margin-top: 0;
+ font-family: 'Vollkorn', serif;
font-size: 2.488rem;
}
@@ -443,6 +446,53 @@ section:not(:last-child) {
flex-direction: column;
}
+/* Site Banner
+ ========================================================================== */
+.site__banner {
+ background-color: rgba(0, 0, 0, 0.44);
+ background-blend-mode: multiply;
+ background-image: url("/static/images/site_banner.jpg");
+ background-size: cover;
+ background-position: center;
+ color: white;
+ text-align: center;
+ padding: 8rem 1rem;
+ font-family: 'Vollkorn', serif;
+}
+.site__banner h1 {
+ font-size: 3.5rem;
+}
+
+.site__banner p {
+ text-transform: lowercase;
+ font-variant: small-caps;
+ font-size: 2rem;
+}
+
+/* Messages
+ ========================================================================== */
+.messages {
+ text-align: center;
+ font-weight: bold;
+ margin-bottom: 0 !important;
+}
+
+.messages p {
+ margin-bottom: 0;
+}
+
+.messages .success {
+ background-color: var(--green-color);
+}
+
+.messages .warning {
+ background-color: var(--yellow-color);
+}
+
+.messages .error {
+ background-color: var(--red-color);
+}
+
/* Site Cart
========================================================================== */
.site__cart {
diff --git a/src/storefront/cart.py b/src/storefront/cart.py
index 2a73eb9..25979f5 100644
--- a/src/storefront/cart.py
+++ b/src/storefront/cart.py
@@ -1,18 +1,23 @@
import logging
+
from decimal import Decimal
from django.conf import settings
-from core.models import Product, OrderLine, Coupon
-from .payments import CreateOrder
+from django.contrib import messages
+from core.models import Product, OrderLine, Coupon
from core.usps import USPSApiWithRate
from core import (
DiscountValueType,
VoucherType,
TransactionStatus,
OrderStatus,
- ShippingMethodType
+ ShippingMethodType,
+ ShippingService,
+ ShippingContainer
)
+from .payments import CreateOrder
+
logger = logging.getLogger(__name__)
class Cart:
@@ -24,7 +29,7 @@ class Cart:
cart = self.session[settings.CART_SESSION_ID] = {}
self.cart = cart
- def add(self, product, quantity=1, grind='', update_quantity=False):
+ def add(self, request, product, quantity=1, grind='', update_quantity=False):
product_id = str(product.id)
if product_id not in self.cart:
self.cart[product_id] = {
@@ -37,7 +42,10 @@ class Cart:
self.cart[product_id]['quantity'] = quantity
else:
self.cart[product_id]['quantity'] += quantity
- self.save()
+ if len(self) <= 20:
+ self.save()
+ else:
+ messages.warning(request, "Cart is full: 20 items or less.")
def save(self):
self.session[settings.CART_SESSION_ID] = self.cart
@@ -65,36 +73,23 @@ class Cart:
return sum(item['quantity'] for item in self.cart.values())
def get_total_weight(self):
- return sum([item['product'].weight.value * item['quantity'] for item in self.cart.values()])
+ return sum([item['product'].weight.value * item['quantity'] for item in self])
def get_shipping_box(self):
- logger.debug(len(self))
-
+ logger.info(len(self))
if len(self) > 6 and len(self) <= 10:
- return "LG FLAT RATE BOX"
+ return ShippingContainer.LG_FLAT_RATE_BOX
elif len(self) > 2 and len(self) <= 6:
- return "REGIONALRATEBOXB"
+ return ShippingContainer.REGIONAL_RATE_BOX_B
elif len(self) <= 2:
- return "REGIONALRATEBOXA"
+ return ShippingContainer.REGIONAL_RATE_BOX_A
else:
- return "VARIABLE"
+ return ShippingContainer.VARIABLE
def get_shipping_cost(self):
- box = {
- 'service': 'PRIORITY COMMERCIAL',
- 'zip_origination': '98368',
- 'zip_destination': f'{self.session.get("shipping_address")["postal_code"]}',
- 'pounds': '0',
- 'ounces': f'{self.get_total_weight()}',
- 'container': f'{self.get_shipping_box()}',
- 'width': '',
- 'length': '',
- 'height': '',
- 'girth': '',
- 'machinable': 'TRUE'
- }
+ usps_rate_request = self.build_usps_rate_request()
usps = USPSApiWithRate(settings.USPS_USER_ID, test=True)
- validation = usps.get_rate(box)
+ validation = usps.get_rate(usps_rate_request)
return Decimal(validation.result['RateV4Response']['Package']['Postage']['CommercialRate'])
def get_total_price(self):
@@ -108,6 +103,22 @@ class Cart:
pass
self.session.modified = True
+ def build_usps_rate_request(self):
+ return \
+ {
+ 'service': ShippingService.PRIORITY_COMMERCIAL,
+ 'zip_origination': settings.DEFAULT_ZIP_ORIGINATION,
+ 'zip_destination': f'{self.session.get("shipping_address")["postal_code"]}',
+ 'pounds': '0',
+ 'ounces': f'{self.get_total_weight()}',
+ 'container': f'{self.get_shipping_box()}',
+ 'width': '',
+ 'length': '',
+ 'height': '',
+ 'girth': '',
+ 'machinable': 'TRUE'
+ }
+
def build_order_params(self):
return \
{
diff --git a/src/storefront/forms.py b/src/storefront/forms.py
index 14d4124..0dedc5d 100644
--- a/src/storefront/forms.py
+++ b/src/storefront/forms.py
@@ -96,9 +96,11 @@ class OrderCreateForm(forms.ModelForm):
model = Order
fields = (
'total_net_amount',
+ 'shipping_total',
)
widgets = {
- 'total_net_amount': forms.HiddenInput()
+ 'total_net_amount': forms.HiddenInput(),
+ 'shipping_total': forms.HiddenInput()
}
class CouponApplyForm(forms.Form):
diff --git a/src/storefront/templates/storefront/contact_form.html b/src/storefront/templates/storefront/contact_form.html
index b5ddadf..68c2e89 100644
--- a/src/storefront/templates/storefront/contact_form.html
+++ b/src/storefront/templates/storefront/contact_form.html
@@ -5,13 +5,9 @@
Contact us
Problem with your online order or have a question?
-
- Please contact us, we’re happy to help you over the phone
- (360) 385-5856 between 8:00 am and 10:00 pm Pacific Time.
-
+ Please contact us, we’re happy to help you.
- Or send us a message using the form below and we'll email you back as soon as we can.