Merge branch 'release/1.3.16'

This commit is contained in:
Nathan Chapman 2022-07-09 08:13:19 -06:00
commit 3b7377f765
13 changed files with 213 additions and 86 deletions

1
.blacklist Normal file
View File

@ -0,0 +1 @@
mchretien@forum.dk

View File

@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
## [1.3.16] - 2022-07-09
### Added
- Blacklist to reduce spam emails from contact form
## [1.3.14] - 2022-06-11 ## [1.3.14] - 2022-06-11

View File

@ -2,103 +2,105 @@ from django.conf import settings
class DiscountValueType: class DiscountValueType:
FIXED = "fixed" FIXED = 'fixed'
PERCENTAGE = "percentage" PERCENTAGE = 'percentage'
CHOICES = [ CHOICES = [
(FIXED, settings.DEFAULT_CURRENCY), (FIXED, settings.DEFAULT_CURRENCY),
(PERCENTAGE, "%"), (PERCENTAGE, '%'),
] ]
class VoucherType: class VoucherType:
SHIPPING = "shipping" SHIPPING = 'shipping'
ENTIRE_ORDER = "entire_order" ENTIRE_ORDER = 'entire_order'
SPECIFIC_PRODUCT = "specific_product" SPECIFIC_PRODUCT = 'specific_product'
CHOICES = [ CHOICES = [
(ENTIRE_ORDER, "Entire order"), (ENTIRE_ORDER, 'Entire order'),
(SHIPPING, "Shipping"), (SHIPPING, 'Shipping'),
(SPECIFIC_PRODUCT, "Specific products, collections and categories"), (SPECIFIC_PRODUCT, 'Specific products, collections and categories'),
] ]
class OrderStatus: class OrderStatus:
DRAFT = "draft" # fully editable, not finalized order created by staff users DRAFT = 'draft' # fully editable, not finalized order created by staff users
UNFULFILLED = "unfulfilled" # order with no items marked as fulfilled UNFULFILLED = 'unfulfilled' # order with no items marked as fulfilled
PARTIALLY_FULFILLED = ( PARTIALLY_FULFILLED = (
"partially_fulfilled" # order with some items marked as fulfilled 'partially_fulfilled' # order with some items marked as fulfilled
) )
FULFILLED = "fulfilled" # order with all items marked as fulfilled FULFILLED = 'fulfilled' # order with all items marked as fulfilled
PARTIALLY_RETURNED = ( PARTIALLY_RETURNED = (
"partially_returned" # order with some items marked as returned 'partially_returned' # order with some items marked as returned
) )
RETURNED = "returned" # order with all items marked as returned RETURNED = 'returned' # order with all items marked as returned
CANCELED = "canceled" # permanently canceled order CANCELED = 'canceled' # permanently canceled order
CHOICES = [ CHOICES = [
(DRAFT, "Draft"), (DRAFT, 'Draft'),
(UNFULFILLED, "Unfulfilled"), (UNFULFILLED, 'Unfulfilled'),
(PARTIALLY_FULFILLED, "Partially fulfilled"), (PARTIALLY_FULFILLED, 'Partially fulfilled'),
(PARTIALLY_RETURNED, "Partially returned"), (PARTIALLY_RETURNED, 'Partially returned'),
(RETURNED, "Returned"), (RETURNED, 'Returned'),
(FULFILLED, "Fulfilled"), (FULFILLED, 'Fulfilled'),
(CANCELED, "Canceled"), (CANCELED, 'Canceled'),
] ]
class TransactionStatus: class TransactionStatus:
CREATED = "CREATED" # The order was created with the specified context. CREATED = 'CREATED' # The order was created with the specified context.
SAVED = "SAVED" # The order was saved and persisted. The order status continues to be in progress until a capture is made with final_capture = true for all purchase units within the order. SAVED = 'SAVED' # The order was saved and persisted. The order status continues to be in progress until a capture is made with final_capture = true for all purchase units within the order.
APPROVED = "APPROVED" # The customer approved the payment through the PayPal wallet or another form of guest or unbranded payment. For example, a card, bank account, or so on. APPROVED = 'APPROVED' # The customer approved the payment through the PayPal wallet or another form of guest or unbranded payment. For example, a card, bank account, or so on.
VOIDED = "VOIDED" # All purchase units in the order are voided. VOIDED = 'VOIDED' # All purchase units in the order are voided.
COMPLETED = "COMPLETED" # The payment was authorized or the authorized payment was captured for the order. COMPLETED = 'COMPLETED' # The payment was authorized or the authorized payment was captured for the order.
PAYER_ACTION_REQUIRED = "PAYER_ACTION_REQUIRED" # The order requires an action from the payer (e.g. 3DS authentication). Redirect the payer to the "rel":"payer-action" HATEOAS link returned as part of the response prior to authorizing or capturing the order. PAYER_ACTION_REQUIRED = 'PAYER_ACTION_REQUIRED' # The order requires an action from the payer (e.g. 3DS authentication). Redirect the payer to the 'rel':'payer-action' HATEOAS link returned as part of the response prior to authorizing or capturing the order.
CHOICES = [ CHOICES = [
(CREATED, "Created"), (CREATED, 'Created'),
(SAVED, "Saved"), (SAVED, 'Saved'),
(APPROVED, "Approved"), (APPROVED, 'Approved'),
(VOIDED, "Voided"), (VOIDED, 'Voided'),
(COMPLETED, "Completed"), (COMPLETED, 'Completed'),
(PAYER_ACTION_REQUIRED, "Payer action required") (PAYER_ACTION_REQUIRED, 'Payer action required')
] ]
class ShippingMethodType: class ShippingMethodType:
PRICE_BASED = "price" PRICE_BASED = 'price'
WEIGHT_BASED = "weight" WEIGHT_BASED = 'weight'
CHOICES = [ CHOICES = [
(PRICE_BASED, "Price based shipping"), (PRICE_BASED, 'Price based shipping'),
(WEIGHT_BASED, "Weight based shipping"), (WEIGHT_BASED, 'Weight based shipping'),
] ]
class ShippingService: class ShippingService:
FIRST_CLASS = "FIRST CLASS" FIRST_CLASS = 'FIRST CLASS'
PRIORITY = "PRIORITY" PRIORITY = 'PRIORITY'
PRIORITY_COMMERCIAL = "PRIORITY COMMERCIAL" PRIORITY_COMMERCIAL = 'PRIORITY COMMERCIAL'
CHOICES = [ CHOICES = [
(FIRST_CLASS, "First Class"), (FIRST_CLASS, 'First Class'),
(PRIORITY, "Priority"), (PRIORITY, 'Priority'),
(PRIORITY_COMMERCIAL, "Priority Commercial") (PRIORITY_COMMERCIAL, 'Priority Commercial')
] ]
class ShippingContainer: class ShippingContainer:
LG_FLAT_RATE_BOX = "LG FLAT RATE BOX" LG_FLAT_RATE_BOX = 'LG FLAT RATE BOX'
REGIONAL_RATE_BOX_A = "REGIONALRATEBOXA" MD_FLAT_RATE_BOX = 'MD FLAT RATE BOX'
REGIONAL_RATE_BOX_B = "REGIONALRATEBOXB" REGIONAL_RATE_BOX_A = 'REGIONALRATEBOXA'
VARIABLE = "VARIABLE" REGIONAL_RATE_BOX_B = 'REGIONALRATEBOXB'
VARIABLE = 'VARIABLE'
CHOICES = [ CHOICES = [
(LG_FLAT_RATE_BOX, "Flate Rate Box - Large"), (LG_FLAT_RATE_BOX, 'Flate Rate Box - Large'),
(REGIONAL_RATE_BOX_A, "Regional Rate Box A"), (MD_FLAT_RATE_BOX, 'Flate Rate Box - Medium'),
(REGIONAL_RATE_BOX_B, "Regional Rate Box B"), (REGIONAL_RATE_BOX_A, 'Regional Rate Box A'),
(VARIABLE, "Variable") (REGIONAL_RATE_BOX_B, 'Regional Rate Box B'),
(VARIABLE, 'Variable')
] ]

View File

@ -6,7 +6,7 @@
"name": "Save 10%: Valid", "name": "Save 10%: Valid",
"code": "MAY2022", "code": "MAY2022",
"valid_from": "2022-05-01T06:00:00Z", "valid_from": "2022-05-01T06:00:00Z",
"valid_to": "2022-05-31T06:00:00Z", "valid_to": "2054-05-31T06:00:00Z",
"discount_value_type": "percentage", "discount_value_type": "percentage",
"discount_value": "10.00", "discount_value": "10.00",
"products": [], "products": [],

View File

@ -43,7 +43,7 @@ class AddressTests(StaticLiveServerTestCase):
state_select.select_by_value('UT') state_select.select_by_value('UT')
postal_code_input = self.browser.find_element_by_name('postal_code') postal_code_input = self.browser.find_element_by_name('postal_code')
postal_code_input.send_keys('37461') postal_code_input.send_keys('37461')
self.browser.find_element_by_xpath('//input[@value="Continue to Payment"]').click() self.browser.find_element_by_xpath('//input[@value="Continue"]').click()
# try: # try:
# WebDriverWait(self.browser, 4).until( # WebDriverWait(self.browser, 4).until(
# EC.presence_of_element_located((By.CLASS_NAME, 'errorlist')) # EC.presence_of_element_located((By.CLASS_NAME, 'errorlist'))
@ -77,7 +77,7 @@ class AddressTests(StaticLiveServerTestCase):
state_select.select_by_value('AK') state_select.select_by_value('AK')
postal_code_input = self.browser.find_element_by_name('postal_code') postal_code_input = self.browser.find_element_by_name('postal_code')
postal_code_input.send_keys('99801') postal_code_input.send_keys('99801')
self.browser.find_element_by_xpath('//input[@value="Continue to Payment"]').click() self.browser.find_element_by_xpath('//input[@value="Continue"]').click()
# try: # try:
# WebDriverWait(self.browser, 4).until( # WebDriverWait(self.browser, 4).until(
# EC.presence_of_element_located((By.CLASS_NAME, 'errorlist')) # EC.presence_of_element_located((By.CLASS_NAME, 'errorlist'))

View File

@ -85,7 +85,7 @@ class CouponTests(StaticLiveServerTestCase):
postal_code_input = self.browser.find_element_by_name('postal_code') postal_code_input = self.browser.find_element_by_name('postal_code')
postal_code_input.send_keys('84321') postal_code_input.send_keys('84321')
self.browser.find_element_by_xpath( self.browser.find_element_by_xpath(
'//input[@value="Continue to Payment"]' '//input[@value="Continue"]'
).click() ).click()
self.assertEqual( self.assertEqual(
@ -140,7 +140,7 @@ class CouponTests(StaticLiveServerTestCase):
postal_code_input = self.browser.find_element_by_name('postal_code') postal_code_input = self.browser.find_element_by_name('postal_code')
postal_code_input.send_keys('84321') postal_code_input.send_keys('84321')
self.browser.find_element_by_xpath( self.browser.find_element_by_xpath(
'//input[@value="Continue to Payment"]' '//input[@value="Continue"]'
).click() ).click()
self.assertEqual( self.assertEqual(

View File

@ -30,6 +30,7 @@ class Cart:
self.request = request self.request = request
self.session = request.session self.session = request.session
self.coupon_code = self.session.get('coupon_code') self.coupon_code = self.session.get('coupon_code')
self.container = self.session.get('shipping_container')
cart = self.session.get(settings.CART_SESSION_ID) cart = self.session.get(settings.CART_SESSION_ID)
if not cart: if not cart:
cart = self.session[settings.CART_SESSION_ID] = {} cart = self.session[settings.CART_SESSION_ID] = {}
@ -109,20 +110,26 @@ class Cart:
else: else:
return 0 return 0
def get_shipping_box(self): def get_shipping_box(self, container=None):
if container:
return container
if self.container:
return self.container
if len(self) > 6 and len(self) <= 10: if len(self) > 6 and len(self) <= 10:
return ShippingContainer.LG_FLAT_RATE_BOX return ShippingContainer.LG_FLAT_RATE_BOX
elif len(self) > 2 and len(self) <= 6: elif len(self) > 3 and len(self) <= 6:
return ShippingContainer.REGIONAL_RATE_BOX_B return ShippingContainer.REGIONAL_RATE_BOX_B
elif len(self) <= 2: elif len(self) <= 3:
return ShippingContainer.REGIONAL_RATE_BOX_A return ShippingContainer.REGIONAL_RATE_BOX_A
else: else:
return ShippingContainer.VARIABLE return ShippingContainer.VARIABLE
def get_shipping_cost(self): def get_shipping_cost(self, container=None):
if len(self) > 0 and self.session.get("shipping_address"): if len(self) > 0 and self.session.get("shipping_address"):
try: try:
usps_rate_request = self.build_usps_rate_request() usps_rate_request = self.build_usps_rate_request(container)
except TypeError as e: except TypeError as e:
return Decimal('0.00') return Decimal('0.00')
usps = USPSApi(settings.USPS_USER_ID, test=True) usps = USPSApi(settings.USPS_USER_ID, test=True)
@ -135,7 +142,7 @@ class Cart:
) )
logger.info(validation.result) logger.info(validation.result)
if not 'Error' in validation.result['RateV4Response']['Package']: if 'Error' not in validation.result['RateV4Response']['Package']:
rate = validation.result['RateV4Response']['Package']['Postage']['CommercialRate'] rate = validation.result['RateV4Response']['Package']['Postage']['CommercialRate']
else: else:
logger.error("USPS Rate error") logger.error("USPS Rate error")
@ -152,7 +159,7 @@ class Cart:
pass pass
self.session.modified = True self.session.modified = True
def build_usps_rate_request(self): def build_usps_rate_request(self, container=None):
return \ return \
{ {
'service': ShippingService.PRIORITY_COMMERCIAL, 'service': ShippingService.PRIORITY_COMMERCIAL,
@ -160,7 +167,7 @@ class Cart:
'zip_destination': f'{self.session.get("shipping_address")["postal_code"]}', 'zip_destination': f'{self.session.get("shipping_address")["postal_code"]}',
'pounds': '0', 'pounds': '0',
'ounces': f'{self.get_total_weight()}', 'ounces': f'{self.get_total_weight()}',
'container': f'{self.get_shipping_box()}', 'container': f'{self.get_shipping_box(container)}',
'width': '', 'width': '',
'length': '', 'length': '',
'height': '', 'height': '',
@ -168,7 +175,7 @@ class Cart:
'machinable': 'TRUE' 'machinable': 'TRUE'
} }
def build_order_params(self): def build_order_params(self, container=None):
return \ return \
{ {
'items': self, 'items': self,
@ -177,12 +184,14 @@ class Cart:
'discount': f'{self.get_discount()}', 'discount': f'{self.get_discount()}',
'shipping_price': f'{self.get_shipping_cost()}', 'shipping_price': f'{self.get_shipping_cost()}',
'tax_total': '0', 'tax_total': '0',
'shipping_method': 'US POSTAL SERVICE', 'shipping_method': 'US POSTAL SERVICE ' + (
container if container else ''
),
'shipping_address': self.build_shipping_address(self.session.get('shipping_address')), 'shipping_address': self.build_shipping_address(self.session.get('shipping_address')),
} }
def create_order(self): def create_order(self, container=None):
params = self.build_order_params() params = self.build_order_params(container)
logger.info(f'\nParams: {params}\n') logger.info(f'\nParams: {params}\n')
if settings.DEBUG: if settings.DEBUG:
response = CreateOrder().create_order(params, debug=True) response = CreateOrder().create_order(params, debug=True)

View File

@ -11,7 +11,7 @@ from usps import USPSApi, Address
from captcha.fields import CaptchaField from captcha.fields import CaptchaField
from core.models import Order from core.models import Order
from core import CoffeeGrind from core import CoffeeGrind, ShippingContainer
from .tasks import contact_form_email from .tasks import contact_form_email
@ -106,6 +106,18 @@ class AddressForm(forms.Form):
) )
class CheckoutShippingForm(forms.Form):
SHIPPING_CHOICES = [
(ShippingContainer.MD_FLAT_RATE_BOX, 'Flate Rate Box - Medium'),
(ShippingContainer.REGIONAL_RATE_BOX_B, 'Regional Rate Box B'),
]
shipping_method = forms.ChoiceField(
widget=forms.RadioSelect,
choices=SHIPPING_CHOICES
)
class OrderCreateForm(forms.ModelForm): class OrderCreateForm(forms.ModelForm):
email = forms.CharField(widget=forms.HiddenInput()) email = forms.CharField(widget=forms.HiddenInput())
first_name = forms.CharField(widget=forms.HiddenInput()) first_name = forms.CharField(widget=forms.HiddenInput())

View File

@ -8,15 +8,24 @@ from templated_email import send_templated_mail
logger = get_task_logger(__name__) logger = get_task_logger(__name__)
COTACT_FORM_TEMPLATE = 'storefront/contact_form' CONTACT_FORM_TEMPLATE = 'storefront/contact_form'
@shared_task(name='contact_form_email') @shared_task(name='contact_form_email')
def contact_form_email(formdata): def contact_form_email(formdata):
send_templated_mail( with open(f'{settings.BASE_DIR.parent}/.blacklist') as blacklist:
template_name=COTACT_FORM_TEMPLATE, if formdata.get('email_address') not in blacklist.read():
from_email=settings.DEFAULT_FROM_EMAIL, send_templated_mail(
recipient_list=[settings.DEFAULT_CONTACT_EMAIL], template_name=CONTACT_FORM_TEMPLATE,
context=formdata from_email=settings.DEFAULT_FROM_EMAIL,
) recipient_list=[settings.DEFAULT_CONTACT_EMAIL],
context=formdata
)
logger.info(f"Contact form email sent from {formdata['email_address']}") logger.info(
f"Contact form email sent from {formdata['email_address']}"
)
else:
logger.warn(
f"{formdata['email_address']} tried to send an email but was on the blacklist"
)

View File

@ -14,7 +14,7 @@
{% csrf_token %} {% csrf_token %}
{{form.as_p}} {{form.as_p}}
<p> <p>
<input type="submit" value="Continue to Payment"> <input type="submit" value="Continue">
</p> </p>
</form> </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> <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>

View File

@ -0,0 +1,39 @@
{% extends "base.html" %}
{% load static %}
{% block head_title %}Checkout | {% endblock %}
{% block content %}
<article>
<header>
<h1>Checkout</h1>
</header>
<section>
<h3>Shipping Method</h3>
<form action="{% url 'storefront:checkout-shipping' %}" method="POST">
{% csrf_token %}
{{ form.non_field_errors }}
<fieldset>
<legend>{{ form.shipping_method.label }}</legend>
{% for radio in form.shipping_method %}
<p>
<label for="{{ radio.id_for_label }}">
{{ radio.choice_label }}
{% if 'Flate Rate Box - Medium' in radio.choice_label %}
<strong>${{ MD_FLAT_RATE_BOX }}</strong>
{% elif 'Regional Rate Box B' in radio.choice_label %}
<strong>${{ REGIONAL_RATE_BOX_B }}</strong>
{% endif %}
</label>
{{ radio.tag }}
</p>
{% endfor %}
</fieldset>
<br>
<p>
<input type="submit" value="Continue to Payment">
</p>
</form>
</section>
</article>
{% endblock %}

View File

@ -48,6 +48,11 @@ urlpatterns = [
views.CheckoutAddressView.as_view(), views.CheckoutAddressView.as_view(),
name='checkout-address', name='checkout-address',
), ),
path(
'checkout/shipping/',
views.CheckoutShippingView.as_view(),
name='checkout-shipping',
),
path('checkout/', views.OrderCreateView.as_view(), name='order-create'), path('checkout/', views.OrderCreateView.as_view(), name='order-create'),
path('done/', views.PaymentDoneView.as_view(), name='payment-done'), path('done/', views.PaymentDoneView.as_view(), name='payment-done'),
path( path(

View File

@ -32,11 +32,11 @@ from accounts.forms import (
) )
from core.models import Product, Order, Transaction, OrderLine, Coupon from core.models import Product, Order, Transaction, OrderLine, Coupon
from core.forms import ShippingMethodForm from core.forms import ShippingMethodForm
from core import OrderStatus from core import OrderStatus, ShippingContainer
from .forms import ( from .forms import (
AddToCartForm, UpdateCartItemForm, OrderCreateForm, AddToCartForm, UpdateCartItemForm, OrderCreateForm,
AddressForm, CouponApplyForm, ContactForm AddressForm, CouponApplyForm, ContactForm, CheckoutShippingForm,
) )
from .cart import Cart from .cart import Cart
from .payments import CaptureOrder from .payments import CaptureOrder
@ -163,7 +163,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:order-create') success_url = reverse_lazy('storefront:checkout-shipping')
def get_initial(self): def get_initial(self):
user = self.request.user user = self.request.user
@ -212,6 +212,47 @@ class CheckoutAddressView(FormView):
return super().form_valid(form) return super().form_valid(form)
class CheckoutShippingView(FormView):
template_name = 'storefront/checkout_shipping_form.html'
form_class = CheckoutShippingForm
success_url = reverse_lazy('storefront:order-create')
def get(self, request, *args, **kwargs):
cart = Cart(request)
if len(cart) != 6:
if 'shipping_container' in self.request.session:
del self.request.session['shipping_container']
return HttpResponseRedirect(
reverse('storefront:order-create')
)
if not self.request.session.get("shipping_address"):
messages.warning(request, 'Please add a shipping address.')
return HttpResponseRedirect(
reverse('storefront:checkout-address')
)
return super().get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
cart = Cart(self.request)
context = super().get_context_data(**kwargs)
context['MD_FLAT_RATE_BOX'] = cart.get_shipping_cost(
ShippingContainer.MD_FLAT_RATE_BOX
)
context['REGIONAL_RATE_BOX_B'] = cart.get_shipping_cost(
ShippingContainer.REGIONAL_RATE_BOX_B
)
return context
def form_valid(self, form):
cleaned_data = form.cleaned_data
self.request.session['shipping_container'] = cleaned_data.get(
'shipping_method'
)
return super().form_valid(form)
class OrderCreateView(CreateView): class OrderCreateView(CreateView):
model = Order model = Order
template_name = 'storefront/order_form.html' template_name = 'storefront/order_form.html'
@ -219,6 +260,10 @@ class OrderCreateView(CreateView):
success_url = reverse_lazy('storefront:payment-done') success_url = reverse_lazy('storefront:payment-done')
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
cart = Cart(request)
if len(cart) != 6 and 'shipping_container' in self.request.session:
del self.request.session['shipping_container']
if not self.request.session.get("shipping_address"): if not self.request.session.get("shipping_address"):
messages.warning(request, 'Please add a shipping address.') messages.warning(request, 'Please add a shipping address.')
return HttpResponseRedirect( return HttpResponseRedirect(
@ -271,13 +316,14 @@ class OrderCreateView(CreateView):
def form_valid(self, form): def form_valid(self, form):
cart = Cart(self.request) cart = Cart(self.request)
shipping_address = self.request.session.get('shipping_address') shipping_address = self.request.session.get('shipping_address')
shipping_container = self.request.session.get('shipping_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() response = cart.create_order(shipping_container)
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
@ -339,8 +385,8 @@ class CustomerDetailView(UserPassesTestMixin, LoginRequiredMixin, DetailView):
permission_denied_message = 'Not authorized.' permission_denied_message = 'Not authorized.'
raise_exception = True raise_exception = True
def get_context_data(self, *args, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(*args, **kwargs) context = super().get_context_data(**kwargs)
context['order_list'] = Order.objects.without_drafts().filter( context['order_list'] = Order.objects.without_drafts().filter(
customer=self.object customer=self.object
).prefetch_related('lines') ).prefetch_related('lines')