Merge branch 'feature/addresses' into develop
This commit is contained in:
commit
2ce2b59118
60
src/functional_tests/test_address.py
Normal file
60
src/functional_tests/test_address.py
Normal 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.'
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
@ -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'
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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']
|
||||
|
||||
@ -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())
|
||||
|
||||
@ -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 %}
|
||||
|
||||
106
src/storefront/tests/test_forms.py
Normal file
106
src/storefront/tests/test_forms.py
Normal 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')
|
||||
32
src/storefront/tests/test_views.py
Normal file
32
src/storefront/tests/test_views.py
Normal 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))
|
||||
@ -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)
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user