From 4f43cc6a80f4622bdc75557b133a6940c97a0b28 Mon Sep 17 00:00:00 2001 From: Nathan Chapman Date: Wed, 11 May 2022 20:09:45 -0600 Subject: [PATCH] Add coupon check --- src/core/fixtures/coupons.json | 29 +++++++ src/core/migrations/0009_coupon_users.py | 20 +++++ src/functional_tests/test_address.py | 4 - src/functional_tests/test_coupon.py | 100 +++++++++++++++++++++++ src/storefront/cart.py | 5 +- src/storefront/tests/test_cart.py | 1 - src/storefront/tests/test_models.py | 18 ++++ src/storefront/tests/test_views.py | 50 +++++++++++- src/storefront/views.py | 20 ++++- 9 files changed, 237 insertions(+), 10 deletions(-) create mode 100644 src/core/fixtures/coupons.json create mode 100644 src/core/migrations/0009_coupon_users.py create mode 100644 src/functional_tests/test_coupon.py create mode 100644 src/storefront/tests/test_models.py diff --git a/src/core/fixtures/coupons.json b/src/core/fixtures/coupons.json new file mode 100644 index 0000000..966274d --- /dev/null +++ b/src/core/fixtures/coupons.json @@ -0,0 +1,29 @@ +[{ + "model": "core.coupon", + "pk": 1, + "fields": { + "type": "entire_order", + "name": "Save 10%: Valid", + "code": "MAY2022", + "valid_from": "2022-05-01T06:00:00Z", + "valid_to": "2022-05-31T06:00:00Z", + "discount_value_type": "percentage", + "discount_value": "10.00", + "products": [], + "users": [1] + } +}, { + "model": "core.coupon", + "pk": 2, + "fields": { + "type": "entire_order", + "name": "Save 10%: Invalid", + "code": "APR2022", + "valid_from": "2022-04-01T06:00:00Z", + "valid_to": "2022-04-30T06:00:00Z", + "discount_value_type": "percentage", + "discount_value": "10.00", + "products": [], + "users": [] + } +}] diff --git a/src/core/migrations/0009_coupon_users.py b/src/core/migrations/0009_coupon_users.py new file mode 100644 index 0000000..cb3293d --- /dev/null +++ b/src/core/migrations/0009_coupon_users.py @@ -0,0 +1,20 @@ +# Generated by Django 4.0.2 on 2022-05-11 00:55 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('core', '0008_alter_order_coupon_alter_order_weight'), + ] + + operations = [ + migrations.AddField( + model_name='coupon', + name='users', + field=models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/src/functional_tests/test_address.py b/src/functional_tests/test_address.py index 374d3ea..2a85ca9 100644 --- a/src/functional_tests/test_address.py +++ b/src/functional_tests/test_address.py @@ -54,7 +54,3 @@ class AddressTests(StaticLiveServerTestCase): ).text, 'USPS: Address Not Found.' ) - - - - diff --git a/src/functional_tests/test_coupon.py b/src/functional_tests/test_coupon.py new file mode 100644 index 0000000..01ee41a --- /dev/null +++ b/src/functional_tests/test_coupon.py @@ -0,0 +1,100 @@ +import os +import time +import logging + +from django.test import TestCase, Client +from django.conf import settings +from selenium.webdriver.firefox.webdriver import WebDriver +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.support.ui import Select +from selenium.common.exceptions import WebDriverException +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from django.contrib.staticfiles.testing import StaticLiveServerTestCase + +logger = logging.getLogger(__name__) + + +class CouponTests(StaticLiveServerTestCase): + fixtures = ['products.json', 'accounts.json', 'coupons.json'] + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.browser = WebDriver() + + @classmethod + def tearDownClass(cls): + cls.browser.quit() + super().tearDownClass() + + def login(self): + self.browser.get('%s%s' % (self.live_server_url, '/accounts/login/')) + username_input = self.browser.find_element_by_name("login") + username_input.send_keys('john@example.com') + password_input = self.browser.find_element_by_name("password") + password_input.send_keys('Bf25XBdP4vdt2X9L') + self.browser.find_element_by_xpath('//input[@value="Login"]').click() + + def test_driver_has_session(self): + self.browser.get(self.live_server_url) + session_id = self.browser.get_cookie('sessionid') + self.assertTrue(session_id) + + def test_apply_coupon_to_order(self): + # Add item to cart + self.browser.get(self.live_server_url + '/products/1/') + self.browser.find_element_by_xpath( + '//input[@value="Add to cart"]' + ).click() + self.assertEqual( + self.browser.find_element_by_class_name('cart__count').text, + '1' + ) + + # Add coupon code + coupon_input = self.browser.find_element_by_id('id_code') + coupon_input.send_keys('MAY2022') + self.browser.find_element_by_xpath('//input[@value="Apply"]').click() + self.browser.find_element_by_xpath( + '//a[contains(text(), "Proceed to Checkout")]' + ).click() + + # Add address + self.assertEqual( + self.browser.title, + 'Checkout | Port Townsend Roasting Co.' + ) + full_name_input = self.browser.find_element_by_name("full_name") + full_name_input.send_keys('John Doe') + email_input = self.browser.find_element_by_id('id_email') + email_input.send_keys('contact@nathanjchapman.com') + street_address_1_input = self.browser.find_element_by_name( + 'street_address_1' + ) + street_address_1_input.send_keys('1579 Talon Dr') + city_input = self.browser.find_element_by_name('city') + city_input.send_keys('Logan') + state_select = select = Select( + self.browser.find_element_by_name('state') + ) + state_select.select_by_value('UT') + postal_code_input = self.browser.find_element_by_name('postal_code') + postal_code_input.send_keys('84321') + self.browser.find_element_by_xpath( + '//input[@value="Continue to Payment"]' + ).click() + + self.assertEqual( + self.browser.title, + 'Checkout | Port Townsend Roasting Co.' + ) + + message_text = self.browser.find_element_by_css_selector( + '.messages p' + ).text + self.assertEqual( + 'Coupon already used.', + message_text + ) diff --git a/src/storefront/cart.py b/src/storefront/cart.py index 4572634..c14db29 100644 --- a/src/storefront/cart.py +++ b/src/storefront/cart.py @@ -24,6 +24,7 @@ from .payments import CreateOrder logger = logging.getLogger(__name__) + class Cart: def __init__(self, request): self.request = request @@ -34,7 +35,9 @@ class Cart: cart = self.session[settings.CART_SESSION_ID] = {} self.cart = cart - def add(self, request, 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] = { diff --git a/src/storefront/tests/test_cart.py b/src/storefront/tests/test_cart.py index 27970b1..5ea93de 100644 --- a/src/storefront/tests/test_cart.py +++ b/src/storefront/tests/test_cart.py @@ -61,7 +61,6 @@ class CartTest(TestCase): request = response.wsgi_request cart = Cart(request) - cart = Cart(request) cart.add( request, product=self.product, diff --git a/src/storefront/tests/test_models.py b/src/storefront/tests/test_models.py new file mode 100644 index 0000000..a90f008 --- /dev/null +++ b/src/storefront/tests/test_models.py @@ -0,0 +1,18 @@ +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 + +from accounts.models import User, Address +from core.models import Product, Order +from core import CoffeeGrind +from storefront.forms import AddressForm, OrderCreateForm +from storefront.views import OrderCreateView, CheckoutAddressView +from storefront.cart import Cart + +logger = logging.getLogger(__name__) diff --git a/src/storefront/tests/test_views.py b/src/storefront/tests/test_views.py index 6ab2011..14e5755 100644 --- a/src/storefront/tests/test_views.py +++ b/src/storefront/tests/test_views.py @@ -9,7 +9,7 @@ from paypalcheckoutsdk.orders import OrdersCreateRequest, OrdersCaptureRequest from paypalcheckoutsdk.core import PayPalHttpClient, SandboxEnvironment from accounts.models import User, Address -from core.models import Product, Order +from core.models import Product, Order, Coupon from core import CoffeeGrind from storefront.forms import AddressForm, OrderCreateForm from storefront.views import OrderCreateView, CheckoutAddressView @@ -17,7 +17,8 @@ from storefront.cart import Cart logger = logging.getLogger(__name__) -class CheckoutAddressViewTest(TestCase): + +class CheckoutAddressViewTests(TestCase): def setUp(self): self.client = Client() @@ -30,3 +31,48 @@ class CheckoutAddressViewTest(TestCase): response = self.client.get(reverse('storefront:checkout-address')) self.assertTrue(response.context['form']) self.assertTrue(isinstance(response.context['form'], AddressForm)) + + +class OrderCreateViewTests(TestCase): + fixtures = ['accounts.json', 'coupons.json'] + + @classmethod + def setUpTestData(cls): + cls.customer = User.objects.get(pk=1) + cls.product = Product.objects.create( + name="Dante's Tornado", + description='Coffee', + sku='23987', + price=13.4, + weight=Weight(oz=16), + visible_in_listings=True + ) + cls.order = Order.objects.create( + customer=cls.customer, + total_net_amount=13.4 + ) + + def setUp(self): + self.client = Client() + + def test_used_coupon_creates_error_on_checkout(self): + session = self.client.session + session['shipping_address'] = { + 'first_name': 'Nathan', + 'last_name': 'Chapman', + 'email': 'contact@nathanjchapman.com', + 'street_address_1': '1504 N 230 E', + 'street_address_2': '', + 'city': 'North Logan', + 'state': 'UT', + 'postal_code': '84341' + } + session['coupon_code'] = 'MAY2022' + session.save() + + response = self.client.get( + reverse('storefront:order-create'), follow=True + ) + self.assertTrue(self.client.session.get('shipping_address')) + self.assertTemplateUsed(response, 'storefront/order_form.html') + self.assertContains(response, 'Coupon already used', status_code=200) diff --git a/src/storefront/views.py b/src/storefront/views.py index ca9a0f2..63da3e6 100644 --- a/src/storefront/views.py +++ b/src/storefront/views.py @@ -222,8 +222,17 @@ class OrderCreateView(CreateView): return HttpResponseRedirect( reverse('storefront:checkout-address') ) - else: - return super().get(request, *args, **kwargs) + elif self.request.session.get('coupon_code'): + address = self.request.session.get("shipping_address") + coupon = Coupon.objects.get( + code=self.request.session.get('coupon_code') + ) + user = get_object_or_404(User, email=address['email']) + if user in coupon.users.all(): + del self.request.session['coupon_code'] + messages.warning(request, 'Coupon already used.') + + return super().get(request, *args, **kwargs) def get_initial(self): cart = Cart(self.request) @@ -259,6 +268,13 @@ class OrderCreateView(CreateView): shipping_address = self.request.session.get('shipping_address') form.instance.customer, form.instance.shipping_address = get_or_create_customer(self.request, form, shipping_address) form.instance.status = OrderStatus.DRAFT + coupon = get_object_or_404( + Coupon, + code=self.request.session.get('coupon_code') + ) + if coupon: + form.instance.coupon = coupon + coupon.users.add(form.instance.customer) self.object = form.save() bulk_list = cart.build_bulk_list(self.object) objs = OrderLine.objects.bulk_create(bulk_list)