Merge branch 'release/1.0.7'
This commit is contained in:
commit
ff1df9827a
1
Pipfile
1
Pipfile
@ -25,6 +25,7 @@ usps-api = "*"
|
||||
[dev-packages]
|
||||
django-debug-toolbar = "*"
|
||||
selenium = "*"
|
||||
pycodestyle = "*"
|
||||
|
||||
[requires]
|
||||
python_version = "3.10"
|
||||
|
||||
14
Pipfile.lock
generated
14
Pipfile.lock
generated
@ -1,7 +1,7 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "0087f9e4fd44233bc6329f7a844a96ae4001ee9d173323e7a715c7295844163c"
|
||||
"sha256": "0847a2b4a481c279572830e8044273e65178f30b74b868549ae41e3aa35b2d03"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
@ -981,6 +981,14 @@
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==1.1.0"
|
||||
},
|
||||
"pycodestyle": {
|
||||
"hashes": [
|
||||
"sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20",
|
||||
"sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.8.0"
|
||||
},
|
||||
"pycparser": {
|
||||
"hashes": [
|
||||
"sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9",
|
||||
@ -1039,7 +1047,7 @@
|
||||
"sha256:670a52d3115d0e879e1ac838a4eb999af32f858163e3a704fe4839de2a676070",
|
||||
"sha256:fb2d48e4eab0dfb786a472cd514aaadc71e3445b203bc300bad93daa75d77c1a"
|
||||
],
|
||||
"markers": "python_full_version >= '3.7.0'",
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==0.20.0"
|
||||
},
|
||||
"trio-websocket": {
|
||||
@ -1063,7 +1071,7 @@
|
||||
"sha256:2218cb57952d90b9fca325c0dcfb08c3bda93e8fd8070b0a17f048e2e47a521b",
|
||||
"sha256:a2e56bfd5c7cd83c1369d83b5feccd6d37798b74872866e62616e0ecf111bda8"
|
||||
],
|
||||
"markers": "python_full_version >= '3.7.0'",
|
||||
"markers": "python_version >= '3.7'",
|
||||
"version": "==1.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -95,3 +95,26 @@ class ShippingContainer:
|
||||
(REGIONAL_RATE_BOX_B, "Regional Rate Box B"),
|
||||
(VARIABLE, "Variable")
|
||||
]
|
||||
|
||||
|
||||
class CoffeeGrind:
|
||||
WHOLE = 'whole-beans'
|
||||
ESPRESSO = 'espresso'
|
||||
CONE_DRIP = 'cone-drip'
|
||||
BASKET_DRIP = 'basket-drip'
|
||||
FRENCH_PRESS = 'french-press'
|
||||
STOVETOP_ESPRESSO = 'stovetop-espresso'
|
||||
AEROPRESS = 'aeropress'
|
||||
PERCOLATOR = 'percolator'
|
||||
CAFE_STYLE = 'cafe-style'
|
||||
GRIND_CHOICES = [
|
||||
(WHOLE, 'Whole Beans'),
|
||||
(ESPRESSO, 'Espresso'),
|
||||
(CONE_DRIP, 'Cone Drip'),
|
||||
(BASKET_DRIP, 'Basket Drip'),
|
||||
(FRENCH_PRESS, 'French Press'),
|
||||
(STOVETOP_ESPRESSO, 'Stovetop Espresso (Moka Pot)'),
|
||||
(AEROPRESS, 'AeroPress'),
|
||||
(PERCOLATOR, 'Percolator'),
|
||||
(CAFE_STYLE, 'BLTC cafe pour over')
|
||||
]
|
||||
|
||||
@ -734,7 +734,7 @@ article + article {
|
||||
justify-self: end;
|
||||
}
|
||||
|
||||
.item__form p,
|
||||
.item__form,
|
||||
.coupon__form p {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import logging
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
from measurement.measures import Weight
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.shortcuts import redirect, reverse
|
||||
@ -36,15 +37,20 @@ class Cart:
|
||||
product_id = str(product.id)
|
||||
if product_id not in self.cart:
|
||||
self.cart[product_id] = {
|
||||
'quantity': 0,
|
||||
'grind': grind,
|
||||
'variations': {},
|
||||
'price': str(product.price)
|
||||
}
|
||||
self.cart[product_id]['variations'][grind] = {'quantity': 0}
|
||||
|
||||
if update_quantity:
|
||||
self.cart[product_id]['quantity'] = quantity
|
||||
self.cart[product_id]['variations'][grind]['quantity'] = quantity
|
||||
else:
|
||||
self.cart[product_id]['quantity'] += quantity
|
||||
if not grind in self.cart[product_id]['variations']:
|
||||
# create it
|
||||
self.cart[product_id]['variations'][grind] = {'quantity': quantity}
|
||||
else:
|
||||
# add to it
|
||||
self.cart[product_id]['variations'][grind]['quantity'] += quantity
|
||||
if len(self) <= 20:
|
||||
self.save()
|
||||
else:
|
||||
@ -69,15 +75,31 @@ class Cart:
|
||||
|
||||
for item in self.cart.values():
|
||||
item['price'] = Decimal(item['price'])
|
||||
item['total_price'] = item['price'] * item['quantity']
|
||||
item['total_price'] = Decimal(sum(self.get_item_prices()))
|
||||
item['quantity'] = self.get_single_item_total_quantity(item)
|
||||
yield item
|
||||
|
||||
def __len__(self):
|
||||
return sum(item['quantity'] for item in self.cart.values())
|
||||
return sum(self.get_all_item_quantities())
|
||||
|
||||
def get_all_item_quantities(self):
|
||||
for item in self.cart.values():
|
||||
yield sum([value['quantity'] for value in item['variations'].values()])
|
||||
|
||||
def get_single_item_total_quantity(self, item):
|
||||
return sum([value['quantity'] for value in item['variations'].values()])
|
||||
|
||||
def get_item_prices(self):
|
||||
for item in self.cart.values():
|
||||
yield Decimal(item['price']) * sum([value['quantity'] for value in item['variations'].values()])
|
||||
|
||||
def get_total_price(self):
|
||||
return sum(self.get_item_prices())
|
||||
|
||||
def get_total_weight(self):
|
||||
if len(self) > 0:
|
||||
return sum([item['product'].weight.value * item['quantity'] for item in self])
|
||||
for item in self:
|
||||
return item['product'].weight.value * sum(self.get_all_item_quantities())
|
||||
else:
|
||||
return 0
|
||||
|
||||
@ -106,9 +128,6 @@ class Cart:
|
||||
else:
|
||||
return Decimal('0.00')
|
||||
|
||||
def get_total_price(self):
|
||||
return sum(Decimal(item['price']) * item['quantity'] for item in self.cart.values())
|
||||
|
||||
def clear(self):
|
||||
del self.session[settings.CART_SESSION_ID]
|
||||
try:
|
||||
@ -156,7 +175,7 @@ class Cart:
|
||||
bulk_list = [OrderLine(
|
||||
order=order,
|
||||
product=item['product'],
|
||||
customer_note=item['grind'],
|
||||
customer_note=f"{item['variations']}",
|
||||
unit_price=item['price'],
|
||||
quantity=item['quantity'],
|
||||
tax_rate=2,
|
||||
|
||||
@ -3,6 +3,7 @@ from django import forms
|
||||
from django.core.mail import EmailMessage
|
||||
|
||||
from core.models import Order
|
||||
from core import CoffeeGrind
|
||||
from accounts import STATE_CHOICES
|
||||
|
||||
from .tasks import contact_form_email
|
||||
@ -10,28 +11,7 @@ from .tasks import contact_form_email
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class AddToCartForm(forms.Form):
|
||||
WHOLE = 'Whole Beans'
|
||||
ESPRESSO = 'Espresso'
|
||||
CONE_DRIP = 'Cone Drip'
|
||||
BASKET_DRIP = 'Basket Drip'
|
||||
FRENCH_PRESS = 'French Press'
|
||||
STOVETOP_ESPRESSO = 'Stovetop Espresso (Moka Pot)'
|
||||
AEROPRESS = 'AeroPress'
|
||||
PERCOLATOR = 'Percolator'
|
||||
CAFE_STYLE = 'BLTC cafe pour over'
|
||||
GRIND_CHOICES = [
|
||||
(WHOLE, 'Whole Beans'),
|
||||
(ESPRESSO, 'Espresso'),
|
||||
(CONE_DRIP, 'Cone Drip'),
|
||||
(BASKET_DRIP, 'Basket Drip'),
|
||||
(FRENCH_PRESS, 'French Press'),
|
||||
(STOVETOP_ESPRESSO, 'Stovetop Espresso (Moka Pot)'),
|
||||
(AEROPRESS, 'AeroPress'),
|
||||
(PERCOLATOR, 'Percolator'),
|
||||
(CAFE_STYLE, 'BLTC cafe pour over')
|
||||
]
|
||||
|
||||
grind = forms.ChoiceField(choices=GRIND_CHOICES)
|
||||
grind = forms.ChoiceField(choices=CoffeeGrind.GRIND_CHOICES)
|
||||
quantity = forms.IntegerField(min_value=1, max_value=20, initial=1)
|
||||
|
||||
class UpdateCartItemForm(forms.Form):
|
||||
@ -40,27 +20,6 @@ class UpdateCartItemForm(forms.Form):
|
||||
|
||||
|
||||
class AddToSubscriptionForm(forms.Form):
|
||||
WHOLE = 'Whole Beans'
|
||||
ESPRESSO = 'Espresso'
|
||||
CONE_DRIP = 'Cone Drip'
|
||||
BASKET_DRIP = 'Basket Drip'
|
||||
FRENCH_PRESS = 'French Press'
|
||||
STOVETOP_ESPRESSO = 'Stovetop Espresso (Moka Pot)'
|
||||
AEROPRESS = 'AeroPress'
|
||||
PERCOLATOR = 'Percolator'
|
||||
CAFE_STYLE = 'BLTC cafe pour over'
|
||||
GRIND_CHOICES = [
|
||||
(WHOLE, 'Whole Beans'),
|
||||
(ESPRESSO, 'Espresso'),
|
||||
(CONE_DRIP, 'Cone Drip'),
|
||||
(BASKET_DRIP, 'Basket Drip'),
|
||||
(FRENCH_PRESS, 'French Press'),
|
||||
(STOVETOP_ESPRESSO, 'Stovetop Espresso (Moka Pot)'),
|
||||
(AEROPRESS, 'AeroPress'),
|
||||
(PERCOLATOR, 'Percolator'),
|
||||
(CAFE_STYLE, 'BLTC cafe pour over')
|
||||
]
|
||||
|
||||
SEVEN_DAYS = 7
|
||||
FOURTEEN_DAYS = 14
|
||||
THIRTY_DAYS = 30
|
||||
@ -71,7 +30,7 @@ class AddToSubscriptionForm(forms.Form):
|
||||
]
|
||||
|
||||
quantity = forms.IntegerField(min_value=1, initial=1)
|
||||
grind = forms.ChoiceField(choices=GRIND_CHOICES)
|
||||
grind = forms.ChoiceField(choices=CoffeeGrind.GRIND_CHOICES)
|
||||
schedule = forms.ChoiceField(choices=SCHEDULE_CHOICES)
|
||||
|
||||
|
||||
|
||||
@ -15,13 +15,15 @@
|
||||
<div class="item__info">
|
||||
<h4>{{product.name}}</h4>
|
||||
<p><strong>Grind</strong>: {{item.grind}}</p>
|
||||
<form class="item__form" action="{% url 'storefront:cart-update' product.pk %}" method="POST">
|
||||
{% csrf_token %}
|
||||
<p>
|
||||
{{ item.update_quantity_form }}
|
||||
<input type="submit" value="Update">
|
||||
{% for key, value in item.variations.items %}
|
||||
<p><strong>{{key}}</strong><br>
|
||||
<form class="item__form" action="{% url 'storefront:cart-update' product.pk key %}" method="POST">
|
||||
{% csrf_token %}
|
||||
{{ value.update_quantity_form }}
|
||||
<input type="submit" value="Update">
|
||||
</form>
|
||||
</p>
|
||||
</form>
|
||||
{% endfor %}
|
||||
<p>
|
||||
<a href="{% url 'storefront:cart-remove' product.pk %}">Remove from cart</a>
|
||||
</p>
|
||||
|
||||
@ -34,8 +34,9 @@
|
||||
</figure>
|
||||
<div>
|
||||
<h4>{{product.name}}</h4>
|
||||
<p><strong>Grind options</strong>: {{item.grind}}</p>
|
||||
<p><strong>Quantity</strong>: {{item.quantity}}</p>
|
||||
{% for key, value in item.variations.items %}
|
||||
<p>Grind: <strong>{{key}}</strong>, Qty: <strong>{{value.quantity}}</strong></p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="item__price">
|
||||
<p><strong>${{item.price}}</strong></p>
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import logging
|
||||
from decimal import Decimal
|
||||
|
||||
from django.test import TestCase, Client, RequestFactory
|
||||
from django.urls import reverse
|
||||
from django.conf import settings
|
||||
|
||||
from measurement.measures import Weight
|
||||
from paypalcheckoutsdk.orders import OrdersCreateRequest, OrdersCaptureRequest
|
||||
from paypalcheckoutsdk.core import PayPalHttpClient, SandboxEnvironment
|
||||
|
||||
@ -14,19 +16,30 @@ from .cart import Cart
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
WHOLE = 'whole-beans'
|
||||
ESPRESSO = 'espresso'
|
||||
CONE_DRIP = 'cone-drip'
|
||||
BASKET_DRIP = 'basket-drip'
|
||||
FRENCH_PRESS = 'french-press'
|
||||
STOVETOP_ESPRESSO = 'stovetop-espresso'
|
||||
AEROPRESS = 'aeropress'
|
||||
PERCOLATOR = 'percolator'
|
||||
CAFE_STYLE = 'cafe-style'
|
||||
|
||||
class CartTest(TestCase):
|
||||
def setUp(self):
|
||||
self.client = Client()
|
||||
self.factory = RequestFactory()
|
||||
|
||||
self.customer = User.objects.create_user(
|
||||
username='Peter Templer', email='peter@testing.com', password='peterspassword321'
|
||||
username='petertempler', email='peter@testing.com', password='peterspassword321'
|
||||
)
|
||||
self.product = Product.objects.create(
|
||||
name='Dante\'s Tornado',
|
||||
description='Coffee',
|
||||
sku='23987',
|
||||
price=13.4,
|
||||
weight=10,
|
||||
weight=Weight(oz=16),
|
||||
visible_in_listings=True
|
||||
)
|
||||
self.order = Order.objects.create(
|
||||
@ -44,13 +57,12 @@ class CartTest(TestCase):
|
||||
request = response.wsgi_request
|
||||
cart = Cart(request)
|
||||
cart.add(
|
||||
request=request,
|
||||
request,
|
||||
product=self.product,
|
||||
quantity=1,
|
||||
grind=WHOLE,
|
||||
update_quantity=False
|
||||
)
|
||||
logger.info(f'Body data:\n{body_data}\n')
|
||||
|
||||
|
||||
params = {
|
||||
'email': 'nathanchapman@hey.com',
|
||||
@ -60,3 +72,129 @@ class CartTest(TestCase):
|
||||
}
|
||||
|
||||
response = self.client.post(url, params)
|
||||
self.assertContains(response, 'Checkout', status_code=200)
|
||||
|
||||
def test_cart_item_variations(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.add(
|
||||
request,
|
||||
product=self.product,
|
||||
quantity=1,
|
||||
grind=WHOLE,
|
||||
update_quantity=False
|
||||
)
|
||||
cart.add(
|
||||
request,
|
||||
product=self.product,
|
||||
quantity=1,
|
||||
grind=ESPRESSO,
|
||||
update_quantity=False
|
||||
)
|
||||
for item in cart.cart.values():
|
||||
self.assertTrue('variations' 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.add(
|
||||
request,
|
||||
product=self.product,
|
||||
quantity=1,
|
||||
grind=WHOLE,
|
||||
update_quantity=False
|
||||
)
|
||||
|
||||
self.assertEqual(cart.cart[f'{self.product.id}']['variations'][WHOLE]['quantity'], 1)
|
||||
self.assertEqual(len(cart), 1)
|
||||
self.assertEqual(sum(cart.get_item_prices()), Decimal('13.4'))
|
||||
self.assertEqual(cart.get_total_price(), Decimal('13.4'))
|
||||
cart.add(
|
||||
request,
|
||||
product=self.product,
|
||||
quantity=1,
|
||||
grind=WHOLE,
|
||||
update_quantity=False
|
||||
)
|
||||
self.assertEqual(cart.cart[f'{self.product.id}']['variations'][WHOLE]['quantity'], 2)
|
||||
self.assertEqual(len(cart), 2)
|
||||
|
||||
cart.add(
|
||||
request,
|
||||
product=self.product,
|
||||
quantity=3,
|
||||
grind=ESPRESSO,
|
||||
update_quantity=False
|
||||
)
|
||||
self.assertEqual(cart.cart[f'{self.product.id}']['variations'][ESPRESSO]['quantity'], 3)
|
||||
self.assertEqual(len(cart), 5)
|
||||
self.assertEqual(cart.get_total_price(), Decimal('67'))
|
||||
|
||||
def test_update_cart_item_quantity(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.add(
|
||||
request,
|
||||
product=self.product,
|
||||
quantity=3,
|
||||
grind=WHOLE,
|
||||
update_quantity=False
|
||||
)
|
||||
self.assertEqual(cart.cart[f'{self.product.id}']['variations'][WHOLE]['quantity'], 3)
|
||||
|
||||
cart.add(
|
||||
request,
|
||||
product=self.product,
|
||||
quantity=1,
|
||||
grind=WHOLE,
|
||||
update_quantity=True
|
||||
)
|
||||
self.assertEqual(cart.cart[f'{self.product.id}']['variations'][WHOLE]['quantity'], 1)
|
||||
|
||||
def test_cart_remove_item(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.add(
|
||||
request,
|
||||
product=self.product,
|
||||
quantity=3,
|
||||
grind=WHOLE,
|
||||
update_quantity=False
|
||||
)
|
||||
self.assertEqual(len(cart), 3)
|
||||
cart.remove(self.product)
|
||||
self.assertEqual(len(cart), 0)
|
||||
|
||||
def test_cart_get_total_weight(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.add(
|
||||
request,
|
||||
product=self.product,
|
||||
quantity=3,
|
||||
grind=WHOLE,
|
||||
update_quantity=False
|
||||
)
|
||||
self.assertEqual(cart.get_total_weight(), Decimal(48))
|
||||
|
||||
# 96oz
|
||||
|
||||
@ -14,7 +14,7 @@ urlpatterns = [
|
||||
|
||||
path('cart/', views.CartView.as_view(), name='cart-detail'),
|
||||
path('cart/<int:pk>/add/', views.CartAddProductView.as_view(), name='cart-add'),
|
||||
path('cart/<int:pk>/update/', views.CartUpdateProductView.as_view(), name='cart-update'),
|
||||
path('cart/<int:pk>/update/<slug:grind>/', views.CartUpdateProductView.as_view(), name='cart-update'),
|
||||
path('cart/<int:pk>/remove/', views.cart_remove_product_view, name='cart-remove'),
|
||||
|
||||
path('coupon/apply/', views.CouponApplyView.as_view(), name='coupon-apply'),
|
||||
|
||||
@ -42,11 +42,12 @@ class CartView(TemplateView):
|
||||
context = super().get_context_data(**kwargs)
|
||||
cart = Cart(self.request)
|
||||
for item in cart:
|
||||
item['update_quantity_form'] = UpdateCartItemForm(
|
||||
initial={
|
||||
'quantity': item['quantity'],
|
||||
}
|
||||
)
|
||||
for variation in item['variations'].values():
|
||||
variation['update_quantity_form'] = UpdateCartItemForm(
|
||||
initial={
|
||||
'quantity': variation['quantity']
|
||||
}
|
||||
)
|
||||
context['cart'] = cart
|
||||
context['coupon_apply_form'] = CouponApplyForm()
|
||||
return context
|
||||
@ -90,6 +91,7 @@ class CartUpdateProductView(SingleObjectMixin, FormView):
|
||||
cart.add(
|
||||
request=request,
|
||||
product=self.get_object(),
|
||||
grind=kwargs['grind'],
|
||||
quantity=form.cleaned_data['quantity'],
|
||||
update_quantity=form.cleaned_data['update']
|
||||
)
|
||||
@ -198,14 +200,7 @@ class OrderCreateView(CreateView):
|
||||
'shipping_total': cart.get_shipping_cost()
|
||||
}
|
||||
|
||||
if self.request.user.is_authenticated:
|
||||
user_info = {
|
||||
'email': self.request.user.email,
|
||||
'first_name': self.request.user.first_name,
|
||||
'last_name': self.request.user.last_name,
|
||||
}
|
||||
initial |= user_info
|
||||
elif self.request.session.get('shipping_address'):
|
||||
if self.request.session.get('shipping_address'):
|
||||
a = self.request.session.get('shipping_address')
|
||||
user_info = {
|
||||
'email': a['email'],
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user