Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 28 additions & 2 deletions shop/forms/checkout.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from django.utils.functional import cached_property

from djng.styling.bootstrap3.forms import Bootstrap3ModelForm
from djng.styling.bootstrap3.widgets import RadioSelect, RadioFieldRenderer, CheckboxInput
from djng.styling.bootstrap3.widgets import RadioSelect, RadioFieldRenderer, CheckboxInput, RadioChoiceInput

from shop.models.address import ShippingAddressModel, BillingAddressModel
from shop.models.customer import CustomerModel
Expand Down Expand Up @@ -278,11 +278,28 @@ def set_address(self, cart, instance):
cart.billing_address = instance if not self['use_primary_address'].value() else None


class ValueInsertRadioChoiceInput(RadioChoiceInput):
"""replaces '$value$' with the input field value in all string attributes"""
def tag(self, attrs=None):
attrs = attrs or self.attrs
for key, value in attrs.items():
if isinstance(value, str):
attrs[key] = value.replace('$value$', self.choice_value)
return super(ValueInsertRadioChoiceInput, self).tag(attrs)


class ValueInsertRenderer(RadioFieldRenderer):
choice_input_class = ValueInsertRadioChoiceInput


class PaymentMethodForm(DialogForm):
# from 2nd element of prefix used, always in response after 'data'
# is assigned using `$rootScope.data = response.data;`
scope_prefix = 'data.payment_method'

payment_modifier = fields.ChoiceField(label=_("Payment Method"),
widget=RadioSelect(renderer=RadioFieldRenderer, attrs={'ng-change': 'upload()'})
widget=RadioSelect(renderer=ValueInsertRenderer, attrs={'ng-change': 'upload()',
'ng-disabled': "$root.data.payment_method.choices.indexOf('$value$') === -1"})
)

def __init__(self, *args, **kwargs):
Expand All @@ -309,6 +326,15 @@ def form_factory(cls, request, data, cart):
payment_extra_data=data.get('payment_data', {}))
return payment_method_form

def get_response_data(self):
"""
Override default that returns nothing.
Add 'choices' payment modifier list for dynamic client-side disable
Must match the __init__ choices, otherwise validation will differ between server & client
"""
choices = [choice[0] for choice in self.base_fields['payment_modifier'].choices]
return {'choices': choices}


class ShippingMethodForm(DialogForm):
scope_prefix = 'data.shipping_method'
Expand Down
16 changes: 12 additions & 4 deletions shop/modifiers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,11 +135,15 @@ def get_choice(self):
"""
raise NotImplemented("Must be implemented by the inheriting class")

def is_active(self, cart):
def is_active(self, cart, request=None):
"""
Returns true if this payment modifier is active.
Deprecation: calls should always supply a request so that '=None' can be removed
"""
return cart.extra.get('payment_modifier') == self.identifier
try:
return request.data['payment_method']['payment_modifier'] == self.identifier
except (AttributeError, KeyError):
return cart.extra.get('payment_modifier') == self.identifier

def is_disabled(self, cart):
"""
Expand Down Expand Up @@ -167,11 +171,15 @@ def get_choice(self):
"""
raise NotImplemented("Must be implemented by the inheriting class")

def is_active(self, cart):
def is_active(self, cart, request=None):
"""
Returns true if this shipping modifier is active.
Deprecation: calls should always supply a request so that '=None' can be removed
"""
return cart.extra.get('shipping_modifier') == self.identifier
try:
return request.data['shipping_method']['shipping_modifier'] == self.identifier
except (AttributeError, KeyError):
return cart.extra.get('shipping_modifier') == self.identifier

def is_disabled(self, cart):
"""
Expand Down
1 change: 1 addition & 0 deletions shop/static/shop/js/dialogs.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ djangoShopModule.controller('DialogController',
}
$rootScope.cart = response.cart;
$rootScope.checkout_summary = response.checkout_summary;
$rootScope.data = response.data;
}).error(function(errors) {
console.error("Unable to upload checkout forms:");
console.log(errors);
Expand Down
5 changes: 4 additions & 1 deletion shop/views/checkout.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,9 @@ def upload(self, request):
# save data, get text representation and collect potential errors
errors, checkout_summary, response_data = {}, {}, {'$valid': True}
with transaction.atomic():
# only validates the relevant submitted checkout page form data
for form_class, data in dialog_data:
# payment & shipping method forms call cart.update(request) which runs all modifiers
form = form_class.form_factory(request, data, cart)
if form.is_valid():
# empty error dict forces revalidation by the client side validation
Expand All @@ -85,8 +87,9 @@ def upload(self, request):
response_data[key] = update_data
cart.save()

# add possible form errors for giving feedback to the customer
# produces a response with correct cart contents by running all modifiers via cart.update(request)
response = self.list(request)
# adds the rest of the data for the checkout forms including customer feedback errors
response.data.update(errors=errors, checkout_summary=checkout_summary, data=response_data)
return response

Expand Down