Merge branch 'feature/cart-redesign' into develop
This commit is contained in:
commit
b07806a0d7
@ -25,6 +25,14 @@ class Address(models.Model):
|
|||||||
{self.city}, {self.state}, {self.postal_code}
|
{self.city}, {self.state}, {self.postal_code}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
yield ('address_line_1', self.street_address_1),
|
||||||
|
yield ('address_line_2', self.street_address_2),
|
||||||
|
yield ('admin_area_2', self.city),
|
||||||
|
yield ('admin_area_1', self.state),
|
||||||
|
yield ('postal_code', self.postal_code),
|
||||||
|
yield ('country_code', 'US')
|
||||||
|
|
||||||
|
|
||||||
class User(AbstractUser):
|
class User(AbstractUser):
|
||||||
addresses = models.ManyToManyField(
|
addresses = models.ManyToManyField(
|
||||||
|
|||||||
@ -23,14 +23,14 @@ def get_or_create_customer(request, form, shipping_address):
|
|||||||
user.save()
|
user.save()
|
||||||
else:
|
else:
|
||||||
user, u_created = User.objects.get_or_create(
|
user, u_created = User.objects.get_or_create(
|
||||||
email=form.cleaned_data['email'].lower(),
|
email=shipping_address['email'].lower(),
|
||||||
defaults={
|
defaults={
|
||||||
'username': form.cleaned_data['email'].lower(),
|
'username': shipping_address['email'].lower(),
|
||||||
'is_staff': False,
|
'is_staff': False,
|
||||||
'is_active': True,
|
'is_active': True,
|
||||||
'is_superuser': False,
|
'is_superuser': False,
|
||||||
'first_name': form.cleaned_data['first_name'],
|
'first_name': address.first_name,
|
||||||
'last_name': form.cleaned_data['last_name'],
|
'last_name': address.last_name,
|
||||||
'default_shipping_address': address,
|
'default_shipping_address': address,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@ -15,6 +15,8 @@
|
|||||||
"fields": {
|
"fields": {
|
||||||
"usps_user_id": "012BETTE1249",
|
"usps_user_id": "012BETTE1249",
|
||||||
"default_shipping_rate": 1,
|
"default_shipping_rate": 1,
|
||||||
"free_shipping_min": "100.00"
|
"free_shipping_min": "100.00",
|
||||||
|
"max_cart_quantity": 20,
|
||||||
|
"max_cart_weight": "20:lb"
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
# Generated by Django 4.0.2 on 2022-11-27 04:28
|
||||||
|
|
||||||
|
import core.weight
|
||||||
|
from django.db import migrations
|
||||||
|
import django_measurement.models
|
||||||
|
import measurement.measures.mass
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0012_sitesettings_max_cart_quantity_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='shippingrate',
|
||||||
|
name='max_order_weight',
|
||||||
|
field=django_measurement.models.MeasurementField(blank=True, default=core.weight.zero_weight, measurement=measurement.measures.mass.Mass, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='shippingrate',
|
||||||
|
name='min_order_weight',
|
||||||
|
field=django_measurement.models.MeasurementField(blank=True, default=core.weight.zero_weight, measurement=measurement.measures.mass.Mass, null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
# Generated by Django 4.0.2 on 2022-11-27 16:26
|
||||||
|
|
||||||
|
import core.weight
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django_measurement.models
|
||||||
|
import measurement.measures.mass
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0013_alter_shippingrate_max_order_weight_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='sitesettings',
|
||||||
|
name='max_cart_weight',
|
||||||
|
field=django_measurement.models.MeasurementField(blank=True, default=core.weight.zero_weight, help_text='Maximum weight allowed for cart.', measurement=measurement.measures.mass.Mass, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='sitesettings',
|
||||||
|
name='free_shipping_min',
|
||||||
|
field=models.DecimalField(blank=True, decimal_places=2, help_text='Minimum dollar amount in the cart subtotal to qualify for free shipping.', max_digits=12, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='sitesettings',
|
||||||
|
name='max_cart_quantity',
|
||||||
|
field=models.PositiveIntegerField(blank=True, default=20, help_text='Maximum amount of items allowed in cart.', null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
src/core/migrations/0015_alter_order_coupon_amount.py
Normal file
18
src/core/migrations/0015_alter_order_coupon_amount.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 4.0.2 on 2022-11-27 18:47
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('core', '0014_sitesettings_max_cart_weight_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='order',
|
||||||
|
name='coupon_amount',
|
||||||
|
field=models.DecimalField(decimal_places=2, default=0, max_digits=10),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -270,11 +270,17 @@ class ShippingRate(models.Model):
|
|||||||
choices=ShippingContainer.CHOICES,
|
choices=ShippingContainer.CHOICES,
|
||||||
default=ShippingContainer.VARIABLE
|
default=ShippingContainer.VARIABLE
|
||||||
)
|
)
|
||||||
min_order_weight = models.PositiveIntegerField(
|
min_order_weight = MeasurementField(
|
||||||
|
measurement=Weight,
|
||||||
|
unit_choices=WeightUnits.CHOICES,
|
||||||
|
default=zero_weight,
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
max_order_weight = models.PositiveIntegerField(
|
max_order_weight = MeasurementField(
|
||||||
|
measurement=Weight,
|
||||||
|
unit_choices=WeightUnits.CHOICES,
|
||||||
|
default=zero_weight,
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
@ -350,7 +356,11 @@ class Order(models.Model):
|
|||||||
decimal_places=2,
|
decimal_places=2,
|
||||||
default=0
|
default=0
|
||||||
)
|
)
|
||||||
coupon_amount = models.CharField(max_length=255, blank=True)
|
coupon_amount = models.DecimalField(
|
||||||
|
max_digits=10,
|
||||||
|
decimal_places=2,
|
||||||
|
default=0
|
||||||
|
)
|
||||||
shipping_total = models.DecimalField(
|
shipping_total = models.DecimalField(
|
||||||
max_digits=5,
|
max_digits=5,
|
||||||
decimal_places=2,
|
decimal_places=2,
|
||||||
@ -514,10 +524,21 @@ class SiteSettings(SingletonBase):
|
|||||||
decimal_places=settings.DEFAULT_DECIMAL_PLACES,
|
decimal_places=settings.DEFAULT_DECIMAL_PLACES,
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
help_text='Minimum dollar amount in the cart subtotal to qualify for free shipping'
|
help_text='Minimum dollar amount in the cart subtotal to qualify for free shipping.'
|
||||||
)
|
)
|
||||||
max_cart_quantity = models.PositiveIntegerField(
|
max_cart_quantity = models.PositiveIntegerField(
|
||||||
default=20
|
default=20,
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
help_text='Maximum amount of items allowed in cart.'
|
||||||
|
)
|
||||||
|
max_cart_weight = MeasurementField(
|
||||||
|
measurement=Weight,
|
||||||
|
unit_choices=WeightUnits.CHOICES,
|
||||||
|
default=zero_weight,
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
help_text='Maximum weight allowed for cart.'
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|||||||
@ -17,7 +17,7 @@ class WeightUnits:
|
|||||||
|
|
||||||
def zero_weight():
|
def zero_weight():
|
||||||
"""Represent the zero weight value."""
|
"""Represent the zero weight value."""
|
||||||
return Weight(kg=0)
|
return Weight(lb=0)
|
||||||
|
|
||||||
|
|
||||||
def convert_weight(weight: Weight, unit: str) -> Weight:
|
def convert_weight(weight: Weight, unit: str) -> Weight:
|
||||||
|
|||||||
@ -33,6 +33,8 @@
|
|||||||
<p>USPS User ID: {{ site_settings.usps_user_id }}</p>
|
<p>USPS User ID: {{ site_settings.usps_user_id }}</p>
|
||||||
<p>Default shipping rate: {{ site_settings.default_shipping_rate }}</p>
|
<p>Default shipping rate: {{ site_settings.default_shipping_rate }}</p>
|
||||||
<p>Free shipping min: ${{ site_settings.free_shipping_min }}</p>
|
<p>Free shipping min: ${{ site_settings.free_shipping_min }}</p>
|
||||||
|
<p>Max cart quantity: {{ site_settings.max_cart_quantity }} items</p>
|
||||||
|
<p>Max cart weight: {{ site_settings.max_cart_weight }}</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</article>
|
</article>
|
||||||
|
|||||||
@ -22,7 +22,7 @@
|
|||||||
<span class="order__status--display">
|
<span class="order__status--display">
|
||||||
<div class="status__dot order__status--{{order.status}}"></div>
|
<div class="status__dot order__status--{{order.status}}"></div>
|
||||||
{{order.get_status_display}}</span>
|
{{order.get_status_display}}</span>
|
||||||
<span>${{order.get_total_price_after_discount}}</span>
|
<span>${{order.total_amount}}</span>
|
||||||
</a>
|
</a>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<span class="object__item">No orders</span>
|
<span class="object__item">No orders</span>
|
||||||
|
|||||||
@ -66,7 +66,7 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
class ProductCreateViewTests(TestCase):
|
class ProductCreateViewTests(TestCase):
|
||||||
fixtures = [
|
fixtures = [
|
||||||
'shipping_rates.json',
|
'site_settings_and_shipping_rates',
|
||||||
'accounts.json',
|
'accounts.json',
|
||||||
'coupons.json',
|
'coupons.json',
|
||||||
'products.json',
|
'products.json',
|
||||||
@ -101,7 +101,7 @@ class ProductCreateViewTests(TestCase):
|
|||||||
|
|
||||||
class OrderCancelViewTests(TestCase):
|
class OrderCancelViewTests(TestCase):
|
||||||
fixtures = [
|
fixtures = [
|
||||||
'shipping_rates.json',
|
'site_settings_and_shipping_rates',
|
||||||
'accounts.json',
|
'accounts.json',
|
||||||
'coupons.json',
|
'coupons.json',
|
||||||
'products.json',
|
'products.json',
|
||||||
|
|||||||
@ -12,7 +12,7 @@ from django.contrib.staticfiles.testing import StaticLiveServerTestCase
|
|||||||
|
|
||||||
|
|
||||||
class AddressTests(StaticLiveServerTestCase):
|
class AddressTests(StaticLiveServerTestCase):
|
||||||
fixtures = ['shipping_rates.json', 'products.json']
|
fixtures = ['site_settings_and_shipping_rates', 'products.json']
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
|
|||||||
@ -20,7 +20,7 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
class CouponTests(StaticLiveServerTestCase):
|
class CouponTests(StaticLiveServerTestCase):
|
||||||
fixtures = [
|
fixtures = [
|
||||||
'shipping_rates.json', 'products.json', 'accounts.json', 'coupons.json'
|
'site_settings_and_shipping_rates', 'products.json', 'accounts.json', 'coupons.json'
|
||||||
]
|
]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@ -106,7 +106,7 @@ DATABASES = {
|
|||||||
CACHES = {'default': CACHE_CONFIG}
|
CACHES = {'default': CACHE_CONFIG}
|
||||||
|
|
||||||
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'
|
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'
|
||||||
SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'
|
# SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'
|
||||||
|
|
||||||
# Password validation
|
# Password validation
|
||||||
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators
|
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators
|
||||||
@ -179,6 +179,8 @@ STATICFILES_FINDERS = (
|
|||||||
'compressor.finders.CompressorFinder',
|
'compressor.finders.CompressorFinder',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
FILE_UPLOAD_MAX_MEMORY_SIZE = 62914560
|
||||||
|
|
||||||
# Default primary key field type
|
# Default primary key field type
|
||||||
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
|
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
|
||||||
|
|
||||||
@ -238,7 +240,6 @@ LOGGING = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
CART_SESSION_ID = 'cart'
|
|
||||||
|
|
||||||
DEFAULT_COUNTRY = 'US'
|
DEFAULT_COUNTRY = 'US'
|
||||||
DEFAULT_CURRENCY = 'USD'
|
DEFAULT_CURRENCY = 'USD'
|
||||||
|
|||||||
@ -8,7 +8,7 @@ from django.shortcuts import redirect, reverse
|
|||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.db.models import OuterRef, Q, Subquery
|
from django.db.models import OuterRef, Q, Subquery
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError, ObjectDoesNotExist
|
||||||
|
|
||||||
from core.models import (
|
from core.models import (
|
||||||
ProductCategory, Product, ProductVariant, OrderLine, Coupon, ShippingRate,
|
ProductCategory, Product, ProductVariant, OrderLine, Coupon, ShippingRate,
|
||||||
@ -28,23 +28,70 @@ from core import (
|
|||||||
build_usps_rate_request
|
build_usps_rate_request
|
||||||
)
|
)
|
||||||
|
|
||||||
from .forms import UpdateCartItemForm
|
from .forms import CartItemUpdateForm
|
||||||
from .payments import CreateOrder
|
from .payments import CreateOrder
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class CartItem:
|
class CartItem:
|
||||||
update_form = UpdateCartItemForm
|
update_form = CartItemUpdateForm
|
||||||
|
order_line_class = OrderLine
|
||||||
|
variant = None
|
||||||
|
quantity = None
|
||||||
|
options = None
|
||||||
|
|
||||||
def __init__(self, item):
|
def __init__(self, item):
|
||||||
self.variant = item['variant']
|
try:
|
||||||
self.quantity = item['quantity']
|
self.variant = ProductVariant.objects.get(pk=item['variant_pk'])
|
||||||
self.options = item['options']
|
except ProductVariant.DoesNotExist:
|
||||||
|
self.quantity = None
|
||||||
|
else:
|
||||||
|
self.quantity = item['quantity']
|
||||||
|
self.options = item['options']
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
yield ('name', f'{self.variant} {self.options_as_str()}')
|
||||||
|
yield ('description', self.variant.product.subtitle)
|
||||||
|
yield ('unit_amount', {
|
||||||
|
'currency_code': settings.DEFAULT_CURRENCY,
|
||||||
|
'value': str(self.variant.price),
|
||||||
|
})
|
||||||
|
yield ('quantity', str(self.quantity))
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'{self.variant} [{self.quantity} × ${self.variant.price}]'
|
||||||
|
|
||||||
|
def serialize(self):
|
||||||
|
if self.variant is not None:
|
||||||
|
return \
|
||||||
|
{
|
||||||
|
'variant_pk': self.variant.pk,
|
||||||
|
'quantity': self.quantity,
|
||||||
|
'options': self.options
|
||||||
|
}
|
||||||
|
|
||||||
|
def deserialize(self, data):
|
||||||
|
self.variant = ProductVariant.objects.get(data['variant_pk'])
|
||||||
|
self.quantity = data['quantity']
|
||||||
|
self.options = data['options']
|
||||||
|
|
||||||
|
def options_as_str(self):
|
||||||
|
options = [f'{key}: {value}' for key, value in self.options.items()]
|
||||||
|
return '; '.join(options)
|
||||||
|
|
||||||
|
def as_order_line(self, order):
|
||||||
|
return self.order_line_class(
|
||||||
|
order=order,
|
||||||
|
variant=self.variant,
|
||||||
|
customer_note=self.options_as_str(),
|
||||||
|
unit_price=self.variant.price,
|
||||||
|
quantity=self.quantity
|
||||||
|
)
|
||||||
|
|
||||||
def get_update_form(self, index):
|
def get_update_form(self, index):
|
||||||
return self.update_form(initial={
|
return self.update_form(initial={
|
||||||
'item_pk': index,
|
'item_index': index,
|
||||||
'quantity': self.quantity
|
'quantity': self.quantity
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -56,144 +103,133 @@ class CartItem:
|
|||||||
def total_weight(self):
|
def total_weight(self):
|
||||||
return Weight(lb=self.variant.weight.lb * self.quantity)
|
return Weight(lb=self.variant.weight.lb * self.quantity)
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
yield ('name', str(self.variant))
|
|
||||||
yield ('description', self.variant.product.subtitle)
|
|
||||||
yield ('unit_amount', {
|
|
||||||
'currency_code': settings.DEFAULT_CURRENCY,
|
|
||||||
'value': f'{self.variant.price}',
|
|
||||||
})
|
|
||||||
yield ('quantity', f'{item["quantity"]}')
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f'{self.variant} [{self.quantity} × ${self.price}]'
|
|
||||||
|
|
||||||
|
|
||||||
class Cart:
|
class Cart:
|
||||||
item_class = CartItem
|
item_class = CartItem
|
||||||
|
items = []
|
||||||
|
coupon = None
|
||||||
|
request = None
|
||||||
|
site_settings = None
|
||||||
|
|
||||||
def __init__(self, request):
|
def __init__(self, request):
|
||||||
self.request = request
|
self.request = request
|
||||||
self.session = request.session
|
self.session = request.session
|
||||||
self.site_settings = SiteSettings.load()
|
self.site_settings = SiteSettings.load()
|
||||||
self.coupon_code = self.session.get('coupon_code')
|
if self.session.get('cart'):
|
||||||
cart = self.session.get(settings.CART_SESSION_ID)
|
self.deserialize(self.session.get('cart'))
|
||||||
if not cart:
|
|
||||||
cart = self.session[settings.CART_SESSION_ID] = []
|
|
||||||
self.cart = cart
|
|
||||||
|
|
||||||
def add(self, request, item, update_quantity=False):
|
|
||||||
if update_quantity:
|
|
||||||
self.cart[item['variant']]['quantity'] = item['quantity']
|
|
||||||
else:
|
|
||||||
self.add_or_update_item(item)
|
|
||||||
|
|
||||||
# TODO: abstract this to a function that will check the max amount of item in the cart
|
|
||||||
if self.check_max_cart_quantity(request) and self.check_max_shipping_weight(request):
|
|
||||||
self.check_item_stock_quantities(request)
|
|
||||||
self.save()
|
|
||||||
|
|
||||||
def add_or_update_item(self, new_item):
|
|
||||||
new_item_pk = int(new_item['variant'])
|
|
||||||
for item in self:
|
|
||||||
if new_item_pk == item['variant'].pk:
|
|
||||||
if new_item['options'] == item['options']:
|
|
||||||
item['quantity'] += new_item['quantity']
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
self.cart.append(new_item)
|
|
||||||
|
|
||||||
def save(self):
|
|
||||||
self.session[settings.CART_SESSION_ID] = self.cart
|
|
||||||
self.session.modified = True
|
|
||||||
logger.info(f'\nCart:\n{self.cart}\n')
|
|
||||||
|
|
||||||
def check_item_stock_quantities(self, request):
|
|
||||||
for item in self:
|
|
||||||
if item['variant'].track_inventory:
|
|
||||||
if item['quantity'] > item['variant'].stock:
|
|
||||||
if item['quantity'] > item['variant'].product.checkout_limit:
|
|
||||||
messages.warning(request, 'Quantity exceeds checkout limit.')
|
|
||||||
item['quantity'] = item['variant'].product.checkout_limit
|
|
||||||
continue
|
|
||||||
messages.warning(request, 'Quantity exceeds available stock.')
|
|
||||||
item['quantity'] = item['variant'].stock
|
|
||||||
elif item['quantity'] > item['variant'].product.checkout_limit:
|
|
||||||
messages.warning(request, 'Quantity exceeds checkout limit.')
|
|
||||||
item['quantity'] = item['variant'].product.checkout_limit
|
|
||||||
self.save()
|
|
||||||
|
|
||||||
def check_max_cart_quantity(self, request):
|
|
||||||
if len(self) > self.site_settings.max_cart_quantity:
|
|
||||||
messages.warning(request, 'Cart is full: 20 items or less.')
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def check_max_shipping_weight(self, request):
|
|
||||||
if self.get_total_weight() > ShippingProvider.USPS_MAX_SHIPPING_WEIGHT.lb:
|
|
||||||
messages.warning(request, 'Weight exceeds shipping limit')
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def remove(self, pk):
|
|
||||||
self.cart.pop(pk)
|
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
for item in self.cart:
|
for item in self.items:
|
||||||
pk = item['variant'].pk if isinstance(item['variant'], ProductVariant) else item['variant']
|
|
||||||
item['variant'] = ProductVariant.objects.get(pk=pk)
|
|
||||||
item['price_total'] = item['variant'].price * item['quantity']
|
|
||||||
yield item
|
yield item
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
return sum([item['quantity'] for item in self.cart])
|
return sum([item.quantity for item in self])
|
||||||
|
|
||||||
def get_item_prices_for_category(self, category):
|
def serialize(self):
|
||||||
|
return \
|
||||||
|
{
|
||||||
|
'coupon_code': self.coupon.code if self.coupon is not None else None,
|
||||||
|
'items': [item.serialize() for item in self]
|
||||||
|
}
|
||||||
|
|
||||||
|
def deserialize(self, data):
|
||||||
|
# Transform old cart
|
||||||
|
if type(data) is list:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.coupon = Coupon.objects.get(code=data.get('coupon_code'))
|
||||||
|
except Coupon.DoesNotExist:
|
||||||
|
self.coupon = None
|
||||||
|
self.items = [self.item_class(item) for item in data['items'] if item is not None]
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
if self.validate():
|
||||||
|
self.session['cart'] = self.serialize()
|
||||||
|
self.session.modified = True
|
||||||
|
|
||||||
|
def validate(self):
|
||||||
|
self.check_item_checkout_limit_and_stock()
|
||||||
|
if self.check_max_cart_quantity() and self.check_max_shipping_weight():
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def check_item_checkout_limit_and_stock(self):
|
||||||
for item in self:
|
for item in self:
|
||||||
if item['variant'].product.category == category:
|
if item.variant.track_inventory:
|
||||||
yield item['price_total']
|
if item.quantity > item.variant.stock:
|
||||||
else:
|
if item.quantity > item.variant.product.checkout_limit:
|
||||||
continue
|
messages.warning(self.request, 'Quantity exceeds checkout limit.')
|
||||||
|
item.quantity = item.variant.product.checkout_limit
|
||||||
|
continue
|
||||||
|
messages.warning(self.request, 'Quantity exceeds available stock.')
|
||||||
|
item.quantity = item.variant.stock
|
||||||
|
elif item.quantity > item.variant.product.checkout_limit:
|
||||||
|
messages.warning(self.request, 'Quantity exceeds checkout limit.')
|
||||||
|
item.quantity = item.variant.product.checkout_limit
|
||||||
|
|
||||||
def get_total_price_for_category(self, category):
|
def check_max_cart_quantity(self):
|
||||||
return sum(self.get_item_prices_for_category(category))
|
if len(self) > self.site_settings.max_cart_quantity:
|
||||||
|
messages.warning(self.request, 'Cart is full: 20 items or less.')
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
def get_all_item_quantities(self):
|
def check_max_shipping_weight(self):
|
||||||
for item in self.cart:
|
if self.total_weight > self.site_settings.max_cart_weight:
|
||||||
yield item['quantity']
|
messages.warning(self.request, 'Weight exceeds shipping limit')
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
def get_single_item_total_quantity(self, item):
|
def clear(self):
|
||||||
return sum([value['quantity'] for value in item['variations'].values()])
|
del self.session['cart']
|
||||||
|
self.session.modified = True
|
||||||
|
|
||||||
def get_item_prices(self):
|
def add_item(self, new_item):
|
||||||
for item in self:
|
for item in self:
|
||||||
yield item['price_total']
|
if new_item.variant == item.variant:
|
||||||
|
if new_item.options == item.options:
|
||||||
|
item.quantity += new_item.quantity
|
||||||
|
self.save()
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
self.items.append(new_item)
|
||||||
|
self.save()
|
||||||
|
|
||||||
def get_total_price(self):
|
def update_item_quantity(self, item_index, quantity):
|
||||||
return sum(self.get_item_prices())
|
self.items[item_index].quantity = quantity
|
||||||
|
self.save()
|
||||||
|
|
||||||
def get_weight_for_all_items(self):
|
def remove_item(self, index):
|
||||||
|
self.items.pop(index)
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
def add_coupon(self, coupon):
|
||||||
|
# TODO: Apply coupon validity checks
|
||||||
|
self.coupon = coupon
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
def remove_coupon(self):
|
||||||
|
if self.coupon is not None:
|
||||||
|
del self.coupon
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
def get_total_price_for_coupon_items(self):
|
||||||
for item in self:
|
for item in self:
|
||||||
yield round(item['variant'].weight.value * item['quantity'], 3)
|
if item.variant.product in self.coupon.products.all():
|
||||||
|
yield item.total_price
|
||||||
def get_total_weight(self):
|
|
||||||
if len(self) > 0:
|
|
||||||
return sum(self.get_weight_for_all_items())
|
|
||||||
else:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def get_shipping_container_choices(self):
|
def get_shipping_container_choices(self):
|
||||||
is_selectable = Q(
|
is_selectable = Q(
|
||||||
is_selectable=True
|
is_selectable=True
|
||||||
)
|
)
|
||||||
min_weight_matched = Q(
|
min_weight_matched = Q(
|
||||||
min_order_weight__lte=self.get_total_weight()) | Q(
|
min_order_weight__lte=self.total_weight) | Q(
|
||||||
min_order_weight__isnull=True
|
min_order_weight__isnull=True
|
||||||
)
|
)
|
||||||
max_weight_matched = Q(
|
max_weight_matched = Q(
|
||||||
max_order_weight__gte=self.get_total_weight()) | Q(
|
max_order_weight__gte=self.total_weight) | Q(
|
||||||
max_order_weight__isnull=True
|
max_order_weight__isnull=True
|
||||||
)
|
)
|
||||||
containers = ShippingRate.objects.filter(
|
containers = ShippingRate.objects.filter(
|
||||||
@ -201,24 +237,27 @@ class Cart:
|
|||||||
)
|
)
|
||||||
return containers
|
return containers
|
||||||
|
|
||||||
def get_shipping_cost(self, container=None):
|
def get_shipping_container(self):
|
||||||
# free_shipping_min = self.site_settings.free_shipping_min
|
possible_containers = self.get_shipping_container_choices()
|
||||||
# if free_shipping_min is not None:
|
if len(possible_containers) == 0:
|
||||||
# category = ProductCategory.objects.get(name='Coffee')
|
return self.site_settings.default_shipping_rate.container
|
||||||
# if self.get_total_price_for_category(category) >= free_shipping_min:
|
return possible_containers[0].container
|
||||||
# return Decimal('0.00')
|
|
||||||
|
|
||||||
|
def get_shipping_price(self, container=None):
|
||||||
if container is None:
|
if container is None:
|
||||||
container = self.session.get('shipping_container').container
|
container = self.get_shipping_container()
|
||||||
|
|
||||||
|
if not self.total_weight > Weight(lb=0):
|
||||||
|
return Decimal('0.00')
|
||||||
|
|
||||||
if len(self) > 0 and self.session.get('shipping_address'):
|
if len(self) > 0 and self.session.get('shipping_address'):
|
||||||
usps_rate_request = build_usps_rate_request(
|
usps_rate_request = build_usps_rate_request(
|
||||||
str(self.get_total_weight()),
|
str(self.total_weight.lb),
|
||||||
container,
|
container,
|
||||||
str(self.session.get('shipping_address')['postal_code'])
|
str(self.session.get('shipping_address')['postal_code'])
|
||||||
)
|
)
|
||||||
|
|
||||||
usps = USPSApi(settings.USPS_USER_ID, test=True)
|
usps = USPSApi(settings.USPS_USER_ID, test=settings.DEBUG)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
validation = usps.get_rate(usps_rate_request)
|
validation = usps.get_rate(usps_rate_request)
|
||||||
@ -248,98 +287,72 @@ class Cart:
|
|||||||
'Could not retrieve shipping address.'
|
'Could not retrieve shipping address.'
|
||||||
)
|
)
|
||||||
|
|
||||||
def clear(self):
|
def create_order(self):
|
||||||
del self.session[settings.CART_SESSION_ID]
|
params = self.build_order_params()
|
||||||
try:
|
|
||||||
del self.session['coupon_code']
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
self.session.modified = True
|
|
||||||
|
|
||||||
def build_order_params(self, container=None):
|
|
||||||
return \
|
|
||||||
{
|
|
||||||
'items': self,
|
|
||||||
'total_price': f'{self.get_total_price_after_discount()}',
|
|
||||||
'item_total': f'{self.get_total_price()}',
|
|
||||||
'discount': f'{self.get_discount()}',
|
|
||||||
'shipping_price': f'{self.get_shipping_cost()}',
|
|
||||||
'tax_total': '0',
|
|
||||||
'shipping_method': 'US POSTAL SERVICE ' + (
|
|
||||||
container if container else ''
|
|
||||||
),
|
|
||||||
'shipping_address': self.build_shipping_address(
|
|
||||||
self.session.get('shipping_address')
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
def create_order(self, container=None):
|
|
||||||
params = self.build_order_params(container)
|
|
||||||
logger.info(f'\nParams: {params}\n')
|
logger.info(f'\nParams: {params}\n')
|
||||||
if settings.DEBUG:
|
response = CreateOrder().create_order(params, debug=settings.DEBUG)
|
||||||
response = CreateOrder().create_order(params, debug=True)
|
|
||||||
else:
|
|
||||||
response = CreateOrder().create_order(params)
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def get_line_options(self, options_dict):
|
def get_address_as_dict(self, address=None):
|
||||||
options = ''
|
if address is None:
|
||||||
for key, value in options_dict.items():
|
address = self.session.get('shipping_address')
|
||||||
options += f'{key}: {value}; '
|
|
||||||
return options
|
|
||||||
|
|
||||||
def build_bulk_list(self, order):
|
|
||||||
bulk_list = []
|
|
||||||
for item in self:
|
|
||||||
bulk_list.append(OrderLine(
|
|
||||||
order=order,
|
|
||||||
variant=item['variant'],
|
|
||||||
customer_note=self.get_line_options(item['options']),
|
|
||||||
unit_price=item['variant'].price,
|
|
||||||
quantity=item['quantity']
|
|
||||||
))
|
|
||||||
return bulk_list
|
|
||||||
|
|
||||||
def build_shipping_address(self, address):
|
|
||||||
return \
|
return \
|
||||||
{
|
{
|
||||||
'address_line_1': f'{address["street_address_1"]}',
|
'address_line_1': address.get('street_address_1', ''),
|
||||||
'address_line_2': f'{address["street_address_2"]}',
|
'address_line_2': address.get('street_address_2', ''),
|
||||||
'admin_area_2': f'{address["city"]}',
|
'admin_area_2': address.get('city', ''),
|
||||||
'admin_area_1': f'{address["state"]}',
|
'admin_area_1': address.get('state', ''),
|
||||||
'postal_code': f'{address["postal_code"]}',
|
'postal_code': address.get('postal_code', ''),
|
||||||
'country_code': 'US'
|
'country_code': 'US'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def build_order_params(self):
|
||||||
|
return \
|
||||||
|
{
|
||||||
|
'items': self.items,
|
||||||
|
'total_price': self.total_price,
|
||||||
|
'item_total': self.subtotal_price,
|
||||||
|
'discount': self.discount_amount,
|
||||||
|
'shipping_price': self.get_shipping_price(),
|
||||||
|
'tax_total': '0.00',
|
||||||
|
'shipping_method': 'US POSTAL SERVICE',
|
||||||
|
'shipping_address': self.get_address_as_dict(),
|
||||||
|
}
|
||||||
|
|
||||||
|
def build_bulk_list(self, order):
|
||||||
|
return [item.as_order_line(order) for item in self]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def coupon(self):
|
def item_variant_pks(self):
|
||||||
if self.coupon_code:
|
return [item.variant.pk for item in self]
|
||||||
return Coupon.objects.get(code=self.coupon_code)
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_coupon_total_for_specific_products(self):
|
@property
|
||||||
for item in self.cart:
|
def subtotal_price(self):
|
||||||
if item['variant'].product in self.coupon.products.all():
|
return sum([item.total_price for item in self])
|
||||||
yield item['price_total']
|
|
||||||
|
|
||||||
def get_discount(self):
|
@property
|
||||||
# SHIPPING
|
def discount_amount(self):
|
||||||
# ENTIRE_ORDER
|
if self.coupon is not None:
|
||||||
# SPECIFIC_PRODUCT
|
|
||||||
if self.coupon:
|
|
||||||
if self.coupon.discount_value_type == DiscountValueType.FIXED:
|
if self.coupon.discount_value_type == DiscountValueType.FIXED:
|
||||||
return round(self.coupon.discount_value, 2)
|
return self.coupon.discount_value
|
||||||
elif self.coupon.discount_value_type == DiscountValueType.PERCENTAGE:
|
elif self.coupon.discount_value_type == DiscountValueType.PERCENTAGE:
|
||||||
if self.coupon.type == VoucherType.ENTIRE_ORDER:
|
if self.coupon.type == VoucherType.ENTIRE_ORDER:
|
||||||
return round((self.coupon.discount_value / Decimal('100')) * self.get_total_price(), 2)
|
return round((self.coupon.discount_value / Decimal('100.00')) * self.subtotal_price, 2)
|
||||||
elif self.coupon.type == VoucherType.SPECIFIC_PRODUCT:
|
elif self.coupon.type == VoucherType.SPECIFIC_PRODUCT:
|
||||||
# Get the product in cart quantity
|
# Get the product in cart quantity
|
||||||
total = sum(self.get_coupon_total_for_specific_products())
|
total_price = sum(self.get_total_price_for_coupon_items())
|
||||||
return round((self.coupon.discount_value / Decimal('100')) * total, 2)
|
return round((self.coupon.discount_value / Decimal('100.00')) * total_price, 2)
|
||||||
return Decimal('0')
|
return Decimal('0.00')
|
||||||
|
|
||||||
def get_subtotal_price_after_discount(self):
|
@property
|
||||||
return round(self.get_total_price() - self.get_discount(), 2)
|
def subtotal_price_after_discount(self):
|
||||||
|
return self.subtotal_price - self.discount_amount
|
||||||
|
|
||||||
def get_total_price_after_discount(self):
|
@property
|
||||||
return round(self.get_total_price() - self.get_discount() + self.get_shipping_cost(), 2)
|
def total_price(self):
|
||||||
|
return self.subtotal_price - self.discount_amount + self.get_shipping_price()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def total_weight(self):
|
||||||
|
return Weight(lb=sum([item.total_weight.lb for item in self]))
|
||||||
|
|||||||
@ -34,14 +34,9 @@ class AddToCartForm(forms.Form):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class UpdateCartItemForm(forms.Form):
|
class CartItemUpdateForm(forms.Form):
|
||||||
item_pk = forms.IntegerField(widget=forms.HiddenInput())
|
item_index = forms.IntegerField(widget=forms.HiddenInput())
|
||||||
quantity = forms.IntegerField(min_value=1, max_value=20, initial=1)
|
quantity = forms.IntegerField(min_value=1, max_value=20, initial=1)
|
||||||
update = forms.BooleanField(
|
|
||||||
required=False,
|
|
||||||
initial=True,
|
|
||||||
widget=forms.HiddenInput()
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class AddToSubscriptionForm(forms.Form):
|
class AddToSubscriptionForm(forms.Form):
|
||||||
@ -130,20 +125,9 @@ class CheckoutShippingForm(forms.Form):
|
|||||||
|
|
||||||
|
|
||||||
class OrderCreateForm(forms.ModelForm):
|
class OrderCreateForm(forms.ModelForm):
|
||||||
email = forms.CharField(widget=forms.HiddenInput())
|
|
||||||
first_name = forms.CharField(widget=forms.HiddenInput())
|
|
||||||
last_name = forms.CharField(widget=forms.HiddenInput())
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Order
|
model = Order
|
||||||
fields = (
|
fields = []
|
||||||
'total_amount',
|
|
||||||
'shipping_total',
|
|
||||||
)
|
|
||||||
widgets = {
|
|
||||||
'total_amount': forms.HiddenInput(),
|
|
||||||
'shipping_total': forms.HiddenInput()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class CouponApplyForm(forms.Form):
|
class CouponApplyForm(forms.Form):
|
||||||
|
|||||||
@ -90,24 +90,6 @@ class CreateOrder(PayPalClient):
|
|||||||
|
|
||||||
def build_request_body(self, params):
|
def build_request_body(self, params):
|
||||||
"""Method to create body with CAPTURE intent"""
|
"""Method to create body with CAPTURE intent"""
|
||||||
processed_items = [
|
|
||||||
{
|
|
||||||
# Shows within upper-right dropdown during payment approval
|
|
||||||
"name": f"{item['variant']} " + "; ".join(
|
|
||||||
f"{key}: {value}" for key, value in item["options"].items()
|
|
||||||
),
|
|
||||||
# Item details will also be in the completed paypal.com
|
|
||||||
# transaction view
|
|
||||||
"description": item["variant"].product.subtitle,
|
|
||||||
"unit_amount": {
|
|
||||||
"currency_code": settings.DEFAULT_CURRENCY,
|
|
||||||
"value": f"{item['variant'].price}",
|
|
||||||
},
|
|
||||||
"quantity": f"{item['quantity']}",
|
|
||||||
}
|
|
||||||
for item in params["items"]
|
|
||||||
]
|
|
||||||
|
|
||||||
request_body = {
|
request_body = {
|
||||||
"intent": "CAPTURE",
|
"intent": "CAPTURE",
|
||||||
"application_context": {
|
"application_context": {
|
||||||
@ -126,15 +108,15 @@ class CreateOrder(PayPalClient):
|
|||||||
# "soft_descriptor": "HighFashions",
|
# "soft_descriptor": "HighFashions",
|
||||||
"amount": {
|
"amount": {
|
||||||
"currency_code": "USD",
|
"currency_code": "USD",
|
||||||
"value": params["total_price"],
|
"value": str(params["total_price"]),
|
||||||
"breakdown": {
|
"breakdown": {
|
||||||
"item_total": {
|
"item_total": {
|
||||||
"currency_code": "USD",
|
"currency_code": "USD",
|
||||||
"value": params["item_total"],
|
"value": str(params["item_total"]),
|
||||||
},
|
},
|
||||||
"shipping": {
|
"shipping": {
|
||||||
"currency_code": "USD",
|
"currency_code": "USD",
|
||||||
"value": params["shipping_price"],
|
"value": str(params["shipping_price"]),
|
||||||
},
|
},
|
||||||
"tax_total": {
|
"tax_total": {
|
||||||
"currency_code": "USD",
|
"currency_code": "USD",
|
||||||
@ -142,11 +124,11 @@ class CreateOrder(PayPalClient):
|
|||||||
},
|
},
|
||||||
"discount": {
|
"discount": {
|
||||||
"currency_code": "USD",
|
"currency_code": "USD",
|
||||||
"value": params["discount"],
|
"value": str(params["discount"]),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"items": processed_items,
|
"items": [dict(item) for item in params['items']],
|
||||||
"shipping": {
|
"shipping": {
|
||||||
"method": params["shipping_method"],
|
"method": params["shipping_method"],
|
||||||
"address": params["shipping_address"],
|
"address": params["shipping_address"],
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load grind_filter %}
|
{% load initialize_update_form %}
|
||||||
|
|
||||||
{% block head_title %}Cart | {% endblock %}
|
{% block head_title %}Cart | {% endblock %}
|
||||||
|
|
||||||
@ -25,9 +25,9 @@
|
|||||||
{% for key, value in item.options.items %}
|
{% for key, value in item.options.items %}
|
||||||
<p><strong>{{ key }}</strong>: {{ value }}</p>
|
<p><strong>{{ key }}</strong>: {{ value }}</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<form class="item__form" action="{% url 'storefront:cart-update' product.pk %}" method="POST">
|
<form class="item__form" action="{% url 'storefront:cart-detail' %}" method="POST">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ item.update_quantity_form }}
|
{{ item|initialize_update_form:forloop.counter0 }}
|
||||||
<input type="submit" value="Update">
|
<input type="submit" value="Update">
|
||||||
</form>
|
</form>
|
||||||
<p><a href="{% url 'storefront:cart-remove' forloop.counter0 %}">Remove item</a></p>
|
<p><a href="{% url 'storefront:cart-remove' forloop.counter0 %}">Remove item</a></p>
|
||||||
@ -62,7 +62,7 @@
|
|||||||
<table class="cart__totals">
|
<table class="cart__totals">
|
||||||
<tr>
|
<tr>
|
||||||
<td>Subtotal</td>
|
<td>Subtotal</td>
|
||||||
<td>${{ cart.get_total_price|floatformat:"2" }}</td>
|
<td>${{ cart.subtotal_price }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% if cart.coupon and cart.coupon.type == 'entire_order' %}
|
{% if cart.coupon and cart.coupon.type == 'entire_order' %}
|
||||||
<tr>
|
<tr>
|
||||||
@ -72,7 +72,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<tr>
|
<tr>
|
||||||
<th>Total</th>
|
<th>Total</th>
|
||||||
<td><strong>${{cart.get_subtotal_price_after_discount|floatformat:"2"}}</strong></td>
|
<td><strong>${{cart.subtotal_price_after_discount}}</strong></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% load grind_filter %}
|
|
||||||
|
|
||||||
{% block head_title %}Checkout | {% endblock %}
|
{% block head_title %}Checkout | {% endblock %}
|
||||||
|
|
||||||
@ -69,7 +68,7 @@
|
|||||||
<table class="cart__totals">
|
<table class="cart__totals">
|
||||||
<tr>
|
<tr>
|
||||||
<td>Subtotal</td>
|
<td>Subtotal</td>
|
||||||
<td>${{cart.get_total_price|floatformat:"2"}}</td>
|
<td>${{ cart.subtotal_price }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% if cart.coupon and cart.coupon.type == 'entire_order' %}
|
{% if cart.coupon and cart.coupon.type == 'entire_order' %}
|
||||||
<tr>
|
<tr>
|
||||||
@ -79,11 +78,11 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>Shipping</td>
|
<td>Shipping</td>
|
||||||
<td>${{ cart.get_shipping_cost }}</td>
|
<td>${{ cart.get_shipping_price }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Total</th>
|
<th>Total</th>
|
||||||
<td><strong>${{cart.get_total_price_after_discount|floatformat:"2"}}</strong></td>
|
<td><strong>${{ cart.total_price }}</strong></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
9
src/storefront/templatetags/initialize_update_form.py
Normal file
9
src/storefront/templatetags/initialize_update_form.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
from django import template
|
||||||
|
from storefront.cart import CartItem
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def initialize_update_form(item, index):
|
||||||
|
return item.get_update_form(index)
|
||||||
@ -76,9 +76,9 @@ class CartItemTest(TestCase):
|
|||||||
|
|
||||||
def test_calculates_total_weight(self):
|
def test_calculates_total_weight(self):
|
||||||
cart_item = CartItem({
|
cart_item = CartItem({
|
||||||
'options': {'Grind': 'Whole Beans'},
|
'variant_pk': self.variant_1.pk,
|
||||||
'quantity': 14,
|
'quantity': 14,
|
||||||
'variant': self.variant_1
|
'options': {'Grind': 'Whole Beans'}
|
||||||
})
|
})
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
cart_item.total_price,
|
cart_item.total_price,
|
||||||
@ -87,9 +87,9 @@ class CartItemTest(TestCase):
|
|||||||
|
|
||||||
def test_calculates_total_price(self):
|
def test_calculates_total_price(self):
|
||||||
cart_item = CartItem({
|
cart_item = CartItem({
|
||||||
'options': {'Grind': 'Whole Beans'},
|
'variant_pk': self.variant_1.pk,
|
||||||
'quantity': 14,
|
'quantity': 14,
|
||||||
'variant': self.variant_1
|
'options': {'Grind': 'Whole Beans'}
|
||||||
})
|
})
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
cart_item.total_weight,
|
cart_item.total_weight,
|
||||||
@ -98,6 +98,8 @@ class CartItemTest(TestCase):
|
|||||||
|
|
||||||
|
|
||||||
class CartTest(TestCase):
|
class CartTest(TestCase):
|
||||||
|
fixtures = ['site_settings_and_shipping_rates.json']
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
cls.customer = User.objects.create_user(
|
cls.customer = User.objects.create_user(
|
||||||
@ -116,20 +118,48 @@ class CartTest(TestCase):
|
|||||||
product=cls.product,
|
product=cls.product,
|
||||||
name='16 oz',
|
name='16 oz',
|
||||||
sku='234987',
|
sku='234987',
|
||||||
price=13.4,
|
price=Decimal('13.40'),
|
||||||
weight=Weight(oz=16),
|
weight=Weight(oz=16),
|
||||||
)
|
)
|
||||||
cls.order = Order.objects.create(
|
cls.order = Order.objects.create(
|
||||||
customer=cls.customer,
|
customer=cls.customer,
|
||||||
total_amount=13.4
|
total_amount=Decimal('13.40')
|
||||||
)
|
)
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.client = Client()
|
self.client = Client()
|
||||||
self.factory = RequestFactory()
|
self.factory = RequestFactory()
|
||||||
|
|
||||||
self.client.force_login(self.customer)
|
self.client.force_login(self.customer)
|
||||||
self.client.session['shipping_address'] = {
|
|
||||||
|
def test_cart_item_variations(self):
|
||||||
|
cart_detail_url = reverse('storefront:cart-detail')
|
||||||
|
response = self.client.get(cart_detail_url, follow=True)
|
||||||
|
logger.debug(response.context['messages'])
|
||||||
|
request = response.wsgi_request
|
||||||
|
cart = Cart(request)
|
||||||
|
|
||||||
|
cart.add_item(
|
||||||
|
CartItem({
|
||||||
|
'variant_pk': self.variant.pk,
|
||||||
|
'quantity': 1,
|
||||||
|
'options': {'Grind': 'Whole Beans'}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
cart.add_item(
|
||||||
|
CartItem({
|
||||||
|
'variant_pk': self.variant.pk,
|
||||||
|
'quantity': 1,
|
||||||
|
'options': {'Grind': 'Espresso'}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
for item in cart:
|
||||||
|
self.assertTrue(hasattr(item, 'variant'))
|
||||||
|
|
||||||
|
def test_add_item_to_cart(self):
|
||||||
|
cart_detail_url = reverse('storefront:cart-detail')
|
||||||
|
response = self.client.get(cart_detail_url)
|
||||||
|
request = response.wsgi_request
|
||||||
|
request.session['shipping_address'] = {
|
||||||
'first_name': 'Nathan',
|
'first_name': 'Nathan',
|
||||||
'last_name': 'Chapman',
|
'last_name': 'Chapman',
|
||||||
'email': 'contact@nathanjchapman.com',
|
'email': 'contact@nathanjchapman.com',
|
||||||
@ -139,89 +169,53 @@ class CartTest(TestCase):
|
|||||||
'state': 'UT',
|
'state': 'UT',
|
||||||
'postal_code': '84341'
|
'postal_code': '84341'
|
||||||
}
|
}
|
||||||
|
|
||||||
def test_cart_item_variations(self):
|
|
||||||
cart_detail_url = reverse('storefront:cart-detail')
|
|
||||||
response = self.client.get(cart_detail_url, follow=True)
|
|
||||||
logger.debug(response.context['messages'])
|
|
||||||
request = response.wsgi_request
|
|
||||||
cart = Cart(request)
|
|
||||||
|
|
||||||
cart.add(
|
|
||||||
request,
|
|
||||||
item={
|
|
||||||
'options': {'Grind': 'Whole Beans'},
|
|
||||||
'price_total': 13.4,
|
|
||||||
'quantity': 1,
|
|
||||||
'variant': self.variant.pk
|
|
||||||
}
|
|
||||||
)
|
|
||||||
cart.add(
|
|
||||||
request,
|
|
||||||
item={
|
|
||||||
'options': {'Grind': 'Espresso'},
|
|
||||||
'price_total': 13.4,
|
|
||||||
'quantity': 1,
|
|
||||||
'variant': self.variant.pk
|
|
||||||
}
|
|
||||||
)
|
|
||||||
for item in cart.cart:
|
|
||||||
self.assertTrue('variant' in item, item)
|
|
||||||
|
|
||||||
def test_add_item_to_cart(self):
|
|
||||||
cart_detail_url = reverse('storefront:cart-detail')
|
|
||||||
response = self.client.get(cart_detail_url)
|
|
||||||
request = response.wsgi_request
|
|
||||||
cart = Cart(request)
|
cart = Cart(request)
|
||||||
|
|
||||||
cart = Cart(request)
|
cart = Cart(request)
|
||||||
cart.add(
|
cart.add_item(
|
||||||
request,
|
CartItem({
|
||||||
item={
|
'variant_pk': self.variant.pk,
|
||||||
'options': {'Grind': 'Whole Beans'},
|
|
||||||
'price_total': 13.4,
|
|
||||||
'quantity': 1,
|
'quantity': 1,
|
||||||
'variant': self.variant.pk
|
'options': {'Grind': 'Whole Beans'}
|
||||||
}
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
cart.cart[0]['quantity'],
|
cart.items[0].quantity,
|
||||||
1
|
1
|
||||||
)
|
)
|
||||||
self.assertEqual(len(cart), 1)
|
self.assertEqual(len(cart), 1)
|
||||||
self.assertEqual(sum(cart.get_item_prices()), Decimal('13.4'))
|
self.assertEqual(cart.total_weight, Weight(lb=1))
|
||||||
self.assertEqual(cart.get_total_price(), Decimal('13.4'))
|
self.assertEqual(cart.subtotal_price, Decimal('13.40'))
|
||||||
cart.add(
|
self.assertEqual(cart.total_price, cart.get_shipping_price() + Decimal('13.40'))
|
||||||
request,
|
cart.add_item(
|
||||||
item={
|
CartItem({
|
||||||
'options': {'Grind': 'Whole Beans'},
|
'variant_pk': self.variant.pk,
|
||||||
'price_total': 13.4,
|
|
||||||
'quantity': 1,
|
'quantity': 1,
|
||||||
'variant': self.variant.pk
|
'options': {'Grind': 'Whole Beans'}
|
||||||
}
|
})
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
cart.cart[0]['quantity'],
|
cart.items[0].quantity,
|
||||||
2
|
2
|
||||||
)
|
)
|
||||||
self.assertEqual(len(cart), 2)
|
self.assertEqual(len(cart), 2)
|
||||||
|
|
||||||
cart.add(
|
cart.add_item(
|
||||||
request,
|
CartItem({
|
||||||
item={
|
'variant_pk': self.variant.pk,
|
||||||
'options': {'Grind': 'Espresso'},
|
|
||||||
'price_total': 40.2,
|
|
||||||
'quantity': 3,
|
'quantity': 3,
|
||||||
'variant': self.variant.pk
|
'options': {'Grind': 'Espresso'}
|
||||||
}
|
})
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
cart.cart[1]['quantity'],
|
cart.items[1].quantity,
|
||||||
3
|
3
|
||||||
)
|
)
|
||||||
self.assertEqual(len(cart), 5)
|
self.assertEqual(len(cart), 5)
|
||||||
self.assertEqual(cart.get_total_price(), Decimal('67'))
|
self.assertEqual(cart.total_weight, Weight(lb=5))
|
||||||
|
self.assertEqual(cart.subtotal_price, Decimal('67.00'))
|
||||||
|
self.assertEqual(cart.total_price, cart.get_shipping_price() + Decimal('67.00'))
|
||||||
|
|
||||||
def test_update_cart_item_quantity(self):
|
def test_update_cart_item_quantity(self):
|
||||||
cart_detail_url = reverse('storefront:cart-detail')
|
cart_detail_url = reverse('storefront:cart-detail')
|
||||||
@ -230,32 +224,21 @@ class CartTest(TestCase):
|
|||||||
cart = Cart(request)
|
cart = Cart(request)
|
||||||
|
|
||||||
cart = Cart(request)
|
cart = Cart(request)
|
||||||
cart.add(
|
cart.add_item(
|
||||||
request,
|
CartItem({
|
||||||
item={
|
'variant_pk': self.variant.pk,
|
||||||
'options': {'Grind': 'Whole Beans'},
|
|
||||||
'price_total': 40.2,
|
|
||||||
'quantity': 3,
|
'quantity': 3,
|
||||||
'variant': self.variant.pk
|
'options': {'Grind': 'Whole Beans'}
|
||||||
}
|
})
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
cart.cart[0]['quantity'],
|
cart.items[0].quantity,
|
||||||
3
|
3
|
||||||
)
|
)
|
||||||
|
|
||||||
cart.add(
|
cart.update_item_quantity(0, 1)
|
||||||
request,
|
|
||||||
item={
|
|
||||||
'options': {'Grind': 'Whole Beans'},
|
|
||||||
'price_total': 13.4,
|
|
||||||
'quantity': 1,
|
|
||||||
'variant': 0
|
|
||||||
},
|
|
||||||
update_quantity=True
|
|
||||||
)
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
cart.cart[0]['quantity'],
|
cart.items[0].quantity,
|
||||||
1
|
1
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -266,33 +249,29 @@ class CartTest(TestCase):
|
|||||||
cart = Cart(request)
|
cart = Cart(request)
|
||||||
|
|
||||||
cart = Cart(request)
|
cart = Cart(request)
|
||||||
cart.add(
|
cart.add_item(
|
||||||
request,
|
CartItem({
|
||||||
item={
|
'variant_pk': self.variant.pk,
|
||||||
'options': {'Grind': 'Whole Beans'},
|
|
||||||
'price_total': 40.2,
|
|
||||||
'quantity': 3,
|
'quantity': 3,
|
||||||
'variant': self.variant.pk
|
'options': {'Grind': 'Whole Beans'}
|
||||||
}
|
})
|
||||||
)
|
)
|
||||||
self.assertEqual(len(cart), 3)
|
self.assertEqual(len(cart), 3)
|
||||||
cart.remove(0)
|
cart.remove_item(0)
|
||||||
self.assertEqual(len(cart), 0)
|
self.assertEqual(len(cart), 0)
|
||||||
|
|
||||||
def test_cart_get_total_weight(self):
|
def test_cart_total_weight(self):
|
||||||
cart_detail_url = reverse('storefront:cart-detail')
|
cart_detail_url = reverse('storefront:cart-detail')
|
||||||
response = self.client.get(cart_detail_url)
|
response = self.client.get(cart_detail_url)
|
||||||
request = response.wsgi_request
|
request = response.wsgi_request
|
||||||
cart = Cart(request)
|
cart = Cart(request)
|
||||||
|
|
||||||
cart = Cart(request)
|
cart = Cart(request)
|
||||||
cart.add(
|
cart.add_item(
|
||||||
request,
|
CartItem({
|
||||||
item={
|
'variant_pk': self.variant.pk,
|
||||||
'options': {'Grind': 'Whole Beans'},
|
|
||||||
'price_total': 40.2,
|
|
||||||
'quantity': 3,
|
'quantity': 3,
|
||||||
'variant': self.variant.pk
|
'options': {'Grind': 'Whole Beans'}
|
||||||
}
|
})
|
||||||
)
|
)
|
||||||
self.assertEqual(cart.get_total_weight(), 3)
|
self.assertEqual(cart.total_weight, Weight(lb=3))
|
||||||
|
|||||||
@ -13,7 +13,7 @@ from accounts.models import User, Address
|
|||||||
from core.models import Product, ProductVariant, Order
|
from core.models import Product, ProductVariant, Order
|
||||||
from core import CoffeeGrind
|
from core import CoffeeGrind
|
||||||
from storefront.views import OrderCreateView
|
from storefront.views import OrderCreateView
|
||||||
from storefront.cart import Cart
|
from storefront.cart import CartItem, Cart
|
||||||
from storefront.payments import CreateOrder
|
from storefront.payments import CreateOrder
|
||||||
|
|
||||||
from . import RequestFaker
|
from . import RequestFaker
|
||||||
@ -48,23 +48,19 @@ class CreateOrderTest(TestCase):
|
|||||||
request = response.wsgi_request
|
request = response.wsgi_request
|
||||||
|
|
||||||
cart = Cart(request)
|
cart = Cart(request)
|
||||||
cart.add(
|
cart.add_item(
|
||||||
request,
|
CartItem({
|
||||||
item={
|
'variant_pk': self.variant.pk,
|
||||||
'options': {'Grind': 'Whole Beans'},
|
'options': {'Grind': 'Whole Beans'},
|
||||||
'price_total': '13.40',
|
'quantity': 1
|
||||||
'quantity': 1,
|
})
|
||||||
'variant': self.variant.pk
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
cart.add(
|
cart.add_item(
|
||||||
request,
|
CartItem({
|
||||||
item={
|
'variant_pk': self.variant.pk,
|
||||||
'options': {'Grind': 'Percolator'},
|
'options': {'Grind': 'Percolator'},
|
||||||
'price_total': '26.80',
|
'quantity': 2
|
||||||
'quantity': 2,
|
})
|
||||||
'variant': self.variant.pk
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
params = {
|
params = {
|
||||||
'items': cart,
|
'items': cart,
|
||||||
|
|||||||
@ -13,7 +13,7 @@ from core.models import Product, ProductVariant, Order, Coupon
|
|||||||
from core import CoffeeGrind
|
from core import CoffeeGrind
|
||||||
from storefront.forms import AddressForm, OrderCreateForm
|
from storefront.forms import AddressForm, OrderCreateForm
|
||||||
from storefront.views import (
|
from storefront.views import (
|
||||||
CartView, CartAddProductView, CartUpdateProductView, CouponApplyView,
|
CartView, CartAddProductView, CartItemUpdateView, CouponApplyView,
|
||||||
ProductListView, ProductDetailView,
|
ProductListView, ProductDetailView,
|
||||||
CheckoutAddressView, OrderCreateView,
|
CheckoutAddressView, OrderCreateView,
|
||||||
paypal_order_transaction_capture,
|
paypal_order_transaction_capture,
|
||||||
|
|||||||
@ -29,7 +29,7 @@ urlpatterns = [
|
|||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
'cart/<int:pk>/update/',
|
'cart/<int:pk>/update/',
|
||||||
views.CartUpdateProductView.as_view(),
|
views.CartItemUpdateView.as_view(),
|
||||||
name='cart-update',
|
name='cart-update',
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
|
|||||||
@ -44,30 +44,40 @@ from core.forms import ShippingRateForm
|
|||||||
from core import OrderStatus, ShippingContainer
|
from core import OrderStatus, ShippingContainer
|
||||||
|
|
||||||
from .forms import (
|
from .forms import (
|
||||||
AddToCartForm, UpdateCartItemForm, OrderCreateForm,
|
AddToCartForm, CartItemUpdateForm, OrderCreateForm,
|
||||||
AddressForm, CouponApplyForm, ContactForm, CheckoutShippingForm,
|
AddressForm, CouponApplyForm, ContactForm, CheckoutShippingForm,
|
||||||
SubscriptionCreateForm
|
SubscriptionCreateForm
|
||||||
)
|
)
|
||||||
from .cart import Cart
|
from .cart import CartItem, Cart
|
||||||
from .payments import CaptureOrder
|
from .payments import CaptureOrder
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class CartView(TemplateView):
|
class CartView(FormView):
|
||||||
template_name = 'storefront/cart_detail.html'
|
template_name = 'storefront/cart_detail.html'
|
||||||
|
form_class = CartItemUpdateForm
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return reverse('storefront:cart-detail')
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
cart = Cart(request)
|
||||||
|
form = self.get_form()
|
||||||
|
if form.is_valid():
|
||||||
|
cart.update_item_quantity(
|
||||||
|
form.cleaned_data['item_index'],
|
||||||
|
form.cleaned_data['quantity']
|
||||||
|
)
|
||||||
|
return self.form_valid(form)
|
||||||
|
else:
|
||||||
|
return self.form_invalid(form)
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
return super().form_valid(form)
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
cart = Cart(self.request)
|
|
||||||
for i, item in enumerate(cart):
|
|
||||||
item['update_quantity_form'] = UpdateCartItemForm(
|
|
||||||
initial={
|
|
||||||
'item_pk': i,
|
|
||||||
'quantity': item['quantity']
|
|
||||||
}
|
|
||||||
)
|
|
||||||
context['cart'] = cart
|
|
||||||
context['coupon_apply_form'] = CouponApplyForm()
|
context['coupon_apply_form'] = CouponApplyForm()
|
||||||
return context
|
return context
|
||||||
|
|
||||||
@ -97,22 +107,20 @@ class CartAddProductView(SingleObjectMixin, FormView):
|
|||||||
form = self.get_form()
|
form = self.get_form()
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
cleaned_data = form.cleaned_data
|
cleaned_data = form.cleaned_data
|
||||||
cart.add(
|
cart.add_item(
|
||||||
request=request,
|
CartItem({
|
||||||
item={
|
'variant_pk': cleaned_data.pop('variant'),
|
||||||
'variant': cleaned_data.pop('variant'),
|
|
||||||
'quantity': cleaned_data.pop('quantity'),
|
'quantity': cleaned_data.pop('quantity'),
|
||||||
'options': cleaned_data
|
'options': cleaned_data
|
||||||
}
|
})
|
||||||
)
|
)
|
||||||
return self.form_valid(form)
|
return self.form_valid(form)
|
||||||
else:
|
else:
|
||||||
return self.form_invalid(form)
|
return self.form_invalid(form)
|
||||||
|
|
||||||
|
|
||||||
class CartUpdateProductView(SingleObjectMixin, FormView):
|
class CartItemUpdateView(FormView):
|
||||||
model = Product
|
form_class = CartItemUpdateForm
|
||||||
form_class = UpdateCartItemForm
|
|
||||||
http_method_names = ['post']
|
http_method_names = ['post']
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
@ -122,13 +130,9 @@ class CartUpdateProductView(SingleObjectMixin, FormView):
|
|||||||
cart = Cart(request)
|
cart = Cart(request)
|
||||||
form = self.get_form()
|
form = self.get_form()
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
cart.add(
|
cart.update_item_quantity(
|
||||||
request=request,
|
form.cleaned_data['item_index'],
|
||||||
item={
|
form.cleaned_data['quantity']
|
||||||
'variant': form.cleaned_data['item_pk'],
|
|
||||||
'quantity': form.cleaned_data['quantity']
|
|
||||||
},
|
|
||||||
update_quantity=form.cleaned_data['update']
|
|
||||||
)
|
)
|
||||||
return self.form_valid(form)
|
return self.form_valid(form)
|
||||||
else:
|
else:
|
||||||
@ -140,7 +144,7 @@ class CartUpdateProductView(SingleObjectMixin, FormView):
|
|||||||
|
|
||||||
def cart_remove_product_view(request, pk):
|
def cart_remove_product_view(request, pk):
|
||||||
cart = Cart(request)
|
cart = Cart(request)
|
||||||
cart.remove(pk)
|
cart.remove_item(pk)
|
||||||
return redirect('storefront:cart-detail')
|
return redirect('storefront:cart-detail')
|
||||||
|
|
||||||
|
|
||||||
@ -151,17 +155,16 @@ class CouponApplyView(FormView):
|
|||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
today = timezone.localtime(timezone.now()).date()
|
today = timezone.localtime(timezone.now()).date()
|
||||||
code = form.cleaned_data['code'].upper()
|
|
||||||
try:
|
try:
|
||||||
coupon = Coupon.objects.get(
|
coupon = Coupon.objects.get(code__iexact=form.cleaned_data['code'])
|
||||||
code__iexact=code,
|
except Coupon.DoesNotExist:
|
||||||
valid_from__date__lte=today,
|
messages(self.request, 'Coupon does not exist.')
|
||||||
valid_to__date__gte=today
|
else:
|
||||||
)
|
|
||||||
if coupon.is_valid:
|
if coupon.is_valid:
|
||||||
self.request.session['coupon_code'] = coupon.code
|
cart = Cart(self.request)
|
||||||
except ObjectDoesNotExist:
|
cart.add_coupon(coupon)
|
||||||
self.request.session['coupon_code'] = None
|
else:
|
||||||
|
messages.warning(self.request, 'Coupon is invalid.')
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
@ -226,7 +229,7 @@ class ProductDetailView(FormMixin, DetailView):
|
|||||||
class CheckoutAddressView(FormView):
|
class CheckoutAddressView(FormView):
|
||||||
template_name = 'storefront/checkout_address.html'
|
template_name = 'storefront/checkout_address.html'
|
||||||
form_class = AddressForm
|
form_class = AddressForm
|
||||||
success_url = reverse_lazy('storefront:checkout-shipping')
|
success_url = reverse_lazy('storefront:order-create')
|
||||||
|
|
||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
user = self.request.user
|
user = self.request.user
|
||||||
@ -284,7 +287,7 @@ class CheckoutShippingView(FormView):
|
|||||||
def get_containers(self, request):
|
def get_containers(self, request):
|
||||||
if self.containers is None:
|
if self.containers is None:
|
||||||
cart = Cart(request)
|
cart = Cart(request)
|
||||||
self.containers = cart.get_shipping_container_choices()
|
self.containers = cart.get_shipping_container()
|
||||||
return self.containers
|
return self.containers
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
@ -297,20 +300,16 @@ class CheckoutShippingView(FormView):
|
|||||||
cart = Cart(self.request)
|
cart = Cart(self.request)
|
||||||
if len(self.get_containers(request)) == 0:
|
if len(self.get_containers(request)) == 0:
|
||||||
self.request.session['shipping_container'] = site_settings.default_shipping_rate
|
self.request.session['shipping_container'] = site_settings.default_shipping_rate
|
||||||
return HttpResponseRedirect(
|
return HttpResponseRedirect(self.success_url)
|
||||||
reverse('storefront:order-create')
|
|
||||||
)
|
|
||||||
elif len(self.get_containers(request)) == 1:
|
elif len(self.get_containers(request)) == 1:
|
||||||
self.request.session['shipping_container'] = self.get_containers(request)[0]
|
self.request.session['shipping_container'] = self.get_containers(request)[0]
|
||||||
return HttpResponseRedirect(
|
return HttpResponseRedirect(self.success_url)
|
||||||
reverse('storefront:order-create')
|
|
||||||
)
|
|
||||||
return super().get(request, *args, **kwargs)
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_form(self, form_class=None):
|
def get_form(self, form_class=None):
|
||||||
cart = Cart(self.request)
|
cart = Cart(self.request)
|
||||||
for container in self.get_containers(self.request):
|
for container in self.get_containers(self.request):
|
||||||
container.s_cost = cart.get_shipping_cost(container.container)
|
container.s_cost = cart.get_shipping_price(container.container)
|
||||||
if form_class is None:
|
if form_class is None:
|
||||||
form_class = self.get_form_class()
|
form_class = self.get_form_class()
|
||||||
return form_class(self.get_containers(self.request), **self.get_form_kwargs())
|
return form_class(self.get_containers(self.request), **self.get_form_kwargs())
|
||||||
@ -335,42 +334,21 @@ class OrderCreateView(CreateView):
|
|||||||
return HttpResponseRedirect(
|
return HttpResponseRedirect(
|
||||||
reverse('storefront:checkout-address')
|
reverse('storefront:checkout-address')
|
||||||
)
|
)
|
||||||
elif self.request.session.get('coupon_code'):
|
|
||||||
address = self.request.session.get('shipping_address')
|
cart = Cart(request)
|
||||||
coupon = Coupon.objects.get(
|
|
||||||
code=self.request.session.get('coupon_code')
|
if cart.coupon is not None:
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
user = User.objects.get(email=address['email'])
|
user = User.objects.get(
|
||||||
except ObjectDoesNotExist:
|
email=request.session.get('shipping_address').get('email')
|
||||||
|
)
|
||||||
|
except User.DoesNotExist:
|
||||||
user = None
|
user = None
|
||||||
if user in coupon.users.all():
|
if user in cart.coupon.users.all():
|
||||||
del self.request.session['coupon_code']
|
cart.remove_coupon()
|
||||||
messages.warning(request, 'Coupon already used.')
|
messages.warning(request, 'Coupon already used.')
|
||||||
return super().get(request, *args, **kwargs)
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_initial(self):
|
|
||||||
cart = Cart(self.request)
|
|
||||||
shipping_container = self.request.session.get(
|
|
||||||
'shipping_container'
|
|
||||||
).container
|
|
||||||
shipping_cost = cart.get_shipping_cost(shipping_container)
|
|
||||||
|
|
||||||
initial = {
|
|
||||||
'total_amount': cart.get_total_price(),
|
|
||||||
'shipping_total': shipping_cost
|
|
||||||
}
|
|
||||||
if self.request.session.get('shipping_address'):
|
|
||||||
a = self.request.session.get('shipping_address')
|
|
||||||
user_info = {
|
|
||||||
'email': a['email'],
|
|
||||||
'first_name': a['first_name'],
|
|
||||||
'last_name': a['last_name']
|
|
||||||
}
|
|
||||||
initial |= user_info
|
|
||||||
|
|
||||||
return initial
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context['shipping_address'] = self.request.session.get('shipping_address')
|
context['shipping_address'] = self.request.session.get('shipping_address')
|
||||||
@ -379,19 +357,21 @@ class OrderCreateView(CreateView):
|
|||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
cart = Cart(self.request)
|
cart = Cart(self.request)
|
||||||
form.instance.subtotal_amount = cart.get_subtotal_price_after_discount()
|
form.instance.subtotal_amount = cart.subtotal_price_after_discount
|
||||||
form.instance.coupon_amount = cart.get_discount()
|
form.instance.coupon = cart.coupon
|
||||||
form.instance.total_amount = cart.get_total_price_after_discount()
|
form.instance.coupon_amount = cart.discount_amount
|
||||||
form.instance.weight = cart.get_total_weight()
|
form.instance.total_amount = cart.total_price
|
||||||
|
form.instance.weight = cart.total_weight
|
||||||
|
shipping_container = cart.get_shipping_container()
|
||||||
|
form.instance.shipping_total = cart.get_shipping_price(shipping_container)
|
||||||
shipping_address = self.request.session.get('shipping_address')
|
shipping_address = self.request.session.get('shipping_address')
|
||||||
shipping_container = self.request.session.get('shipping_container').container
|
|
||||||
form.instance.customer, form.instance.shipping_address = get_or_create_customer(self.request, form, shipping_address)
|
form.instance.customer, form.instance.shipping_address = get_or_create_customer(self.request, form, shipping_address)
|
||||||
form.instance.status = OrderStatus.DRAFT
|
form.instance.status = OrderStatus.DRAFT
|
||||||
self.object = form.save()
|
self.object = form.save()
|
||||||
bulk_list = cart.build_bulk_list(self.object)
|
bulk_list = cart.build_bulk_list(self.object)
|
||||||
objs = OrderLine.objects.bulk_create(bulk_list)
|
objs = OrderLine.objects.bulk_create(bulk_list)
|
||||||
|
|
||||||
response = cart.create_order(shipping_container)
|
response = cart.create_order()
|
||||||
data = response.result.__dict__['_dict']
|
data = response.result.__dict__['_dict']
|
||||||
|
|
||||||
self.request.session['order_id'] = self.object.pk
|
self.request.session['order_id'] = self.object.pk
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user