Merge branch 'feature/addresses' into develop

This commit is contained in:
Nathan Chapman 2022-05-08 10:24:26 -06:00
commit 2ce2b59118
10 changed files with 343 additions and 58 deletions

View File

@ -0,0 +1,60 @@
import os, time
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
class AddressTests(StaticLiveServerTestCase):
fixtures = ['products.json']
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.browser = WebDriver()
@classmethod
def tearDownClass(cls):
cls.browser.quit()
super().tearDownClass()
def test_invalid_address_returns_errorlist(self):
self.browser.get(self.live_server_url + '/checkout/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('john@example.com')
street_address_1_input = self.browser.find_element_by_name('street_address_1')
street_address_1_input.send_keys('1579')
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('37461')
self.browser.find_element_by_xpath('//input[@value="Continue to Payment"]').click()
# try:
# WebDriverWait(self.browser, 4).until(
# EC.presence_of_element_located((By.CLASS_NAME, 'errorlist'))
# )
# finally:
# self.browser.quit()
self.assertEqual(
self.browser.find_element_by_css_selector(
'.errorlist li'
).text,
'USPS: Address Not Found.'
)

View File

@ -190,57 +190,40 @@ TEMPLATED_EMAIL_BACKEND = 'templated_email.backends.vanilla_django.TemplateBacke
SITE_ID = 1
# Logging
if DEBUG:
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '{name} {levelname} {asctime} {module} {process:d} {thread:d} {message}',
'style': '{',
}
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'filters': {
'require_debug_false': {
'()': 'django.utils.log.RequireDebugFalse',
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'formatter': 'verbose',
},
'require_debug_true': {
'()': 'django.utils.log.RequireDebugTrue',
},
'root': {
'handlers': ['console'],
},
'formatters': {
'verbose': {
'format': '{name} {levelname} {asctime} {module} {process:d} {thread:d} {message}',
'style': '{',
},
},
'handlers': {
'file': {
'level': 'DEBUG',
'class': 'logging.FileHandler',
'filters': ['require_debug_false'],
'filename': '/var/log/django-ptcoffee/debug.log',
'formatter': 'verbose',
},
}
else:
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '{name} {levelname} {asctime} {module} {process:d} {thread:d} {message}',
'style': '{',
},
'simple': {
'format': '{levelname} {message}',
'style': '{',
}
},
'loggers': {
'django.file': {
'handlers': ['file'],
'level': 'INFO',
'propagate': True,
},
'handlers': {
'file': {
'level': 'DEBUG',
'class': 'logging.FileHandler',
'filename': '/var/log/django-ptcoffee/debug.log',
'formatter': 'verbose',
},
},
'loggers': {
'django': {
'handlers': ['file'],
'level': 'DEBUG',
'propagate': True,
},
},
}
},
}
CART_SESSION_ID = 'cart'

View File

@ -193,7 +193,7 @@ input[type=submit],
color: var(--fg-color);
background-color: var(--yellow-color);
padding: 0.25rem 1rem;
padding: 0.4rem 1rem;
border-radius: 0.2rem;
border: none;
@ -206,6 +206,15 @@ input[type=submit]:hover,
background-color: var(--yellow-alt-color);
}
.errorlist {
background-color: var(--red-color);
color: white;
list-style: none;
padding: 0 1rem;
box-sizing: border-box;
font-weight: bold;
}
/* Contact form
========================================================================== */
@ -788,11 +797,33 @@ article + article {
/* Checkout / Shipping Address
========================================================================== */
.checkout__address-form {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 0 2rem;
}
.checkout__address-form .errorlist {
grid-column: span 2;
}
@media screen and (max-width: 600px) {
.checkout__address-form {
grid-template-columns: 1fr;
}
.checkout__address-form .errorlist {
grid-column: 1;
}
}
.checkout__address-form p:last-child {
align-self: end;
}
.checkout__address-form input,
.checkout__address-form select {
display: block;
width: 100%;
max-width: 24rem;
}
.checkout__address {

View File

@ -121,7 +121,14 @@ class Cart:
except TypeError as e:
return Decimal('0.00')
usps = USPSApiWithRate(settings.USPS_USER_ID, test=True)
validation = usps.get_rate(usps_rate_request)
try:
validation = usps.get_rate(usps_rate_request)
except ConnectionError:
raise ValidationError(
'Could not connect to USPS, try again.'
)
logger.info(validation.result)
if not 'Error' in validation.result['RateV4Response']['Package']:
rate = validation.result['RateV4Response']['Package']['Postage']['CommercialRate']

View File

@ -1,6 +1,10 @@
import logging
import logging, json
from requests import ConnectionError
from django import forms
from django.conf import settings
from django.core.mail import EmailMessage
from django.core.exceptions import ValidationError
from usps import USPSApi, Address
from core.models import Order
from core import CoffeeGrind
@ -35,8 +39,7 @@ class AddToSubscriptionForm(forms.Form):
class AddressForm(forms.Form):
first_name = forms.CharField()
last_name = forms.CharField()
full_name = forms.CharField()
email = forms.EmailField()
street_address_1 = forms.CharField()
street_address_2 = forms.CharField(required=False)
@ -46,6 +49,54 @@ class AddressForm(forms.Form):
)
postal_code = forms.CharField()
def process_full_name(self, full_name):
name = full_name.split()
if len(name) > 2:
last_name = ''.join(name.pop(-1))
first_name = ' '.join(name)
elif len(name) > 1:
first_name = name[0]
last_name = name[1]
else:
first_name = name[0]
last_name = ''
return first_name, last_name
def clean(self):
cleaned_data = super().clean()
address = Address(
name=cleaned_data.get('full_name'),
address_1=cleaned_data.get('street_address_1'),
address_2=cleaned_data.get('street_address_2'),
city=cleaned_data.get('city'),
state=cleaned_data.get('state'),
zipcode=cleaned_data.get('postal_code')
)
usps = USPSApi(settings.USPS_USER_ID, test=True)
try:
validation = usps.validate_address(address)
except ConnectionError:
raise ValidationError(
'Could not connect to USPS, try again.'
)
if 'Error' in validation.result['AddressValidateResponse']['Address']:
error = validation.result['AddressValidateResponse']['Address']['Error']['Description']
raise ValidationError(
"USPS: " + error
)
try:
cleaned_data['postal_code'] = validation.result['AddressValidateResponse']['Address']['Zip5']
except KeyError:
raise ValidationError(
'Could not find Zip5'
)
class OrderCreateForm(forms.ModelForm):
email = forms.CharField(widget=forms.HiddenInput())
first_name = forms.CharField(widget=forms.HiddenInput())

View File

@ -17,6 +17,7 @@
<input type="submit" value="Continue to Payment">
</p>
</form>
<p>We validate addresses with USPS, if you are having issues please contact us at <a href="mailto:support@ptcoffee.com">support@ptcoffee.com</a> or use the contact information found on our <a href="{% url 'storefront:contact' %}">contact</a> page.</p>
</section>
</article>
{% endblock %}

View File

@ -0,0 +1,106 @@
import logging
from decimal import Decimal
from measurement.measures import Weight
from django.test import TestCase, Client, RequestFactory
from django.urls import reverse
from django.conf import settings
from django.contrib.sessions.middleware import SessionMiddleware
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.views import OrderCreateView
from storefront.forms import AddressForm
from storefront.cart import Cart
from storefront.payments import CreateOrder
from . import RequestFaker
logger = logging.getLogger(__name__)
class AddressFormTest(TestCase):
def test_invalid_address_returns_form_error(self):
form = AddressForm(data={
'full_name': 'John Doe',
'email': 'john@example.com',
# Wrong street address
'street_address_1': '1579 ',
'street_address_2': '',
'city': 'Logan',
'state': 'UT',
# Wrong Zip code
'postal_code': '23481'
})
self.assertFalse(form.is_valid())
def test_usps_finds_zip_from_address(self):
form = AddressForm(data={
'full_name': 'John Doe',
'email': 'john@example.com',
'street_address_1': '1579 Talon Dr.',
'street_address_2': '',
'city': 'Logan',
'state': 'UT',
# Wrong Zip code
'postal_code': '23481'
})
self.assertTrue(form.is_valid())
if form.is_valid():
cleaned_data = form.cleaned_data
postal_code = cleaned_data.get('postal_code')
self.assertEqual(postal_code, '84321')
def test_invalid_address_returns_form_error(self):
form = AddressForm(data={
'full_name': 'John Doe',
'email': 'john@example.com',
# Wrong street address
'street_address_1': '1579',
'street_address_2': '',
'city': 'Logan',
'state': 'UT',
# Wrong Zip code
'postal_code': '84321'
})
self.assertFalse(form.is_valid())
def test_process_full_name_with_two_given_names(self):
form = AddressForm(data={
'full_name': 'John Doe',
'email': 'john@example.com',
'street_address_1': '1579 Talon Dr',
'street_address_2': '',
'city': 'Logan',
'state': 'UT',
'postal_code': '84321'
})
if form.is_valid():
cleaned_data = form.cleaned_data
first_name, last_name = form.process_full_name(
cleaned_data.get('full_name')
)
self.assertEqual(first_name, 'John')
self.assertEqual(last_name, 'Doe')
def test_process_full_name_with_more_than_two_given_names(self):
form = AddressForm(data={
'full_name': 'John Franklin Rosevelt Doe',
'email': 'john@example.com',
'street_address_1': '1579 Talon Dr',
'street_address_2': '',
'city': 'Logan',
'state': 'UT',
'postal_code': '84321'
})
if form.is_valid():
cleaned_data = form.cleaned_data
first_name, last_name = form.process_full_name(
cleaned_data.get('full_name')
)
self.assertEqual(first_name, 'John Franklin Rosevelt')
self.assertEqual(last_name, 'Doe')

View File

@ -0,0 +1,32 @@
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__)
class CheckoutAddressViewTest(TestCase):
def setUp(self):
self.client = Client()
def test_view_uses_correct_template(self):
response = self.client.get(reverse('storefront:checkout-address'))
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, 'storefront/checkout_address.html')
def test_view_has_correct_form(self):
response = self.client.get(reverse('storefront:checkout-address'))
self.assertTrue(response.context['form'])
self.assertTrue(isinstance(response.context['form'], AddressForm))

View File

@ -160,8 +160,7 @@ class CheckoutAddressView(FormView):
if user.is_authenticated and user.default_shipping_address:
address = user.default_shipping_address
initial = {
'first_name': address.first_name,
'last_name': address.last_name,
'full_name': address.first_name+' '+address.last_name,
'email': user.email,
'street_address_1': address.street_address_1,
'street_address_2': address.street_address_2,
@ -172,8 +171,7 @@ class CheckoutAddressView(FormView):
elif self.request.session.get('shipping_address'):
address = self.request.session.get('shipping_address')
initial = {
'first_name': address['first_name'],
'last_name': address['last_name'],
'full_name': address['first_name']+' '+address['last_name'],
'email': address['email'],
'street_address_1': address['street_address_1'],
'street_address_2': address['street_address_2'],
@ -185,7 +183,21 @@ class CheckoutAddressView(FormView):
def form_valid(self, form):
# save address data to session
self.request.session['shipping_address'] = form.cleaned_data
cleaned_data = form.cleaned_data
first_name, last_name = form.process_full_name(
cleaned_data.get('full_name')
)
address = {
'first_name': first_name,
'last_name': last_name,
'email': cleaned_data['email'],
'street_address_1': cleaned_data['street_address_1'],
'street_address_2': cleaned_data['street_address_2'],
'city': cleaned_data['city'],
'state': cleaned_data['state'],
'postal_code': cleaned_data['postal_code']
}
self.request.session['shipping_address'] = address
return super().form_valid(form)
class OrderCreateView(CreateView):
@ -197,7 +209,9 @@ class OrderCreateView(CreateView):
def get(self, request, *args, **kwargs):
if not self.request.session.get("shipping_address"):
messages.warning(request, 'Please add a shipping address.')
return HttpResponseRedirect(reverse('storefront:checkout-address'))
return HttpResponseRedirect(
reverse('storefront:checkout-address')
)
else:
return super().get(request, *args, **kwargs)