From a62a40f27f44f5d5d3d3226185ea9ffb5df6ae2c Mon Sep 17 00:00:00 2001 From: racitup Date: Wed, 14 Jun 2017 01:45:18 +0100 Subject: [PATCH 1/3] basic mechanics for dynamic update payment modifier options --- shop/forms/checkout.py | 29 +++++++++++++++++++++++++++-- shop/static/shop/js/dialogs.js | 1 + 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/shop/forms/checkout.py b/shop/forms/checkout.py index 76ca6cb18..cca3feec9 100644 --- a/shop/forms/checkout.py +++ b/shop/forms/checkout.py @@ -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 @@ -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 put 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): @@ -309,6 +326,14 @@ 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 + """ + choices = [choice[0] for choice in self.base_fields['payment_modifier'].choices] + return {'choices': choices} + class ShippingMethodForm(DialogForm): scope_prefix = 'data.shipping_method' diff --git a/shop/static/shop/js/dialogs.js b/shop/static/shop/js/dialogs.js index 4759dac4b..6722df76a 100644 --- a/shop/static/shop/js/dialogs.js +++ b/shop/static/shop/js/dialogs.js @@ -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); From 7aa136f36d70359c5f1198fa40828e92f302ffc8 Mon Sep 17 00:00:00 2001 From: racitup Date: Mon, 19 Jun 2017 17:16:51 +0100 Subject: [PATCH 2/3] fixed bug in modifier is_active() causing payment modifier dynamic updates to be old --- shop/forms/checkout.py | 1 + shop/modifiers/base.py | 16 ++++++++++++---- shop/views/checkout.py | 5 ++++- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/shop/forms/checkout.py b/shop/forms/checkout.py index cca3feec9..e50a7d3d8 100644 --- a/shop/forms/checkout.py +++ b/shop/forms/checkout.py @@ -330,6 +330,7 @@ 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} diff --git a/shop/modifiers/base.py b/shop/modifiers/base.py index a45bf4ce2..d2f0dab5b 100644 --- a/shop/modifiers/base.py +++ b/shop/modifiers/base.py @@ -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): """ @@ -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): """ diff --git a/shop/views/checkout.py b/shop/views/checkout.py index 9718e9bcc..e91b66487 100644 --- a/shop/views/checkout.py +++ b/shop/views/checkout.py @@ -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 @@ -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 From 82c60001fd5190fd7b08a3e4df3a5793a1c0751f Mon Sep 17 00:00:00 2001 From: racitup Date: Mon, 19 Jun 2017 17:24:16 +0100 Subject: [PATCH 3/3] fixed comment typo --- shop/forms/checkout.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shop/forms/checkout.py b/shop/forms/checkout.py index e50a7d3d8..b25c1b372 100644 --- a/shop/forms/checkout.py +++ b/shop/forms/checkout.py @@ -294,7 +294,7 @@ class ValueInsertRenderer(RadioFieldRenderer): class PaymentMethodForm(DialogForm): # from 2nd element of prefix used, always in response after 'data' - # is put assigned using `$rootScope.data = response.data;` + # is assigned using `$rootScope.data = response.data;` scope_prefix = 'data.payment_method' payment_modifier = fields.ChoiceField(label=_("Payment Method"),