Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

allow prices to be a list of dicts and make price optional in the tra…

…nsaction (bug 821511)
  • Loading branch information...
commit 328c9cdd3c981e1e0686b637956480834a9226a3 1 parent d7f945c
@andymckay andymckay authored
View
29 lib/bango/forms.py
@@ -5,7 +5,7 @@
from lib.bango.constants import (COUNTRIES, CURRENCIES, PAYMENT_TYPES, RATINGS,
RATINGS_SCHEME)
from lib.sellers.models import SellerProductBango
-from solitude.fields import URLField
+from solitude.fields import ListField, URLField
class ProductForm(forms.ModelForm):
@@ -95,11 +95,9 @@ class UpdateRatingForm(SellerProductForm):
class CreateBillingConfigurationForm(SellerProductForm):
- price_amount = forms.DecimalField()
- price_currency = forms.ChoiceField(choices=([r, r] for r
- in CURRENCIES.keys()))
redirect_url_onsuccess = forms.URLField()
redirect_url_onerror = forms.URLField()
+ prices = ListField()
pageTitle = forms.CharField()
@property
@@ -107,8 +105,31 @@ def bango_data(self):
data = super(CreateBillingConfigurationForm, self).bango_data
data['typeFilter'] = PAYMENT_TYPES
data['externalTransactionId'] = uuid.uuid4()
+ del data['prices']
return data
+ def clean_prices(self):
+ # Remarkably like a formset, but without the drama.
+ prices = self.cleaned_data.get('prices', [])
+ results = []
+ for price in prices:
+ result = PriceForm(price)
+ try:
+ if not result.is_valid():
+ raise forms.ValidationError(result.errors)
+ except AttributeError:
+ raise forms.ValidationError('Invalid JSON.')
+ results.append(result)
+ if not results:
+ raise forms.ValidationError(self.fields['prices']
+ .error_messages['required'])
+ return results
+
+
+class PriceForm(forms.Form):
+ amount = forms.DecimalField()
+ currency = forms.ChoiceField(choices=([r, r] for r in CURRENCIES.keys()))
+
class CreateBankDetailsForm(forms.Form):
seller_bango = URLField(to='lib.bango.resources.package.PackageResource')
View
12 lib/bango/resources/billing.py
@@ -20,10 +20,14 @@ def obj_create(self, bundle, request, **kwargs):
billing = client.client('billing')
data = form.bango_data
- price = billing.factory.create('Price')
- price.amount = data.pop('price_amount')
- price.currency = data.pop('price_currency')
- data['priceList'] = [price]
+ price_list = []
+ for item in form.cleaned_data['prices']:
+ price = billing.factory.create('Price')
+ price.amount = item.cleaned_data['amount']
+ price.currency = item.cleaned_data['currency']
+ price_list.append(price)
+
+ data['priceList'] = price_list
config = billing.factory.create('ArrayOfBillingConfigurationOption')
configs = {
View
5 lib/bango/tests/samples.py
@@ -43,6 +43,11 @@
'pageTitle': 'wat!',
'redirect_url_onsuccess': 'https://nowhere.com/success',
'redirect_url_onerror': 'https://nowhere.com/error',
+ 'prices': [
+ {'amount': 2, 'currency': 'CAD'},
+ {'amount': 1, 'currency': 'EUR'},
+ ],
+ 'pageTitle': 'wat!'
}
good_bank_details = {
View
52 lib/bango/tests/test_forms.py
@@ -1,8 +1,13 @@
+import json
from unittest import TestCase
import mock
-from ..forms import CreateBankDetailsForm
-from .samples import good_bank_details
+from nose.tools import eq_, ok_
+
+from ..forms import (CreateBankDetailsForm,
+ CreateBillingConfigurationForm as BillingForm,
+ PriceForm)
+from .samples import good_bank_details, good_billing_request
@mock.patch('lib.bango.forms.URLField.clean')
@@ -23,3 +28,46 @@ def test_iban(self, clean):
del self.bank['bankAccountNumber']
self.bank['bankAccountIban'] = 'foo'
assert CreateBankDetailsForm(self.bank).is_valid()
+
+
+@mock.patch('lib.bango.forms.URLField.clean')
+class TestBilling(TestCase):
+
+ def setUp(self):
+ self.billing = good_billing_request.copy()
+ self.billing['seller_product_bango'] = '/blah/'
+
+ def test_form(self, clean):
+ ok_(PriceForm({'amount': 1, 'currency': 'NZD'}))
+
+ def test_billing(self, clean):
+ ok_(BillingForm(self.billing).is_valid())
+
+ def test_no_json(self, clean):
+ del self.billing['prices']
+ assert not BillingForm(self.billing).is_valid()
+
+ def test_bad_json(self, clean):
+ self.billing['prices'] = 'blargh'
+ assert not BillingForm(self.billing).is_valid()
+
+ self.billing['prices'] = json.dumps(['foo'])
+ assert not BillingForm(self.billing).is_valid()
+
+ def test_no_prices(self, clean):
+ self.billing['prices'] = []
+ form = BillingForm(self.billing)
+ form.is_valid()
+ eq_(form.errors['prices'], ['This field is required.'])
+
+ def test_price_error(self, clean):
+ self.billing['prices'] = [{'amount': 1, 'currency': 'FOO'}]
+ form = BillingForm(self.billing)
+ form.is_valid()
+ ok_('Select a valid choice' in form.errors['prices'][0])
+
+ def test_iterate(self, clean):
+ form = BillingForm(self.billing)
+ form.is_valid()
+ for price in form.cleaned_data['prices']:
+ ok_(price.is_valid())
@cvan Collaborator
cvan added a note

why do you like this over assert?

@robhudson Collaborator

3 less key strokes!

@andymckay Owner

its shorter and it feels like there's more symmetry in ok_ and eq_. there's no good reason.

@cvan Collaborator
cvan added a note

but then it's another import, and another function call!

@andymckay Owner

344 tests in 14.1s

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
View
6 lib/bango/tests/test_resources.py
@@ -264,8 +264,13 @@ def test_good(self):
def test_missing_price(self):
data = self.good()
del data['price_currency']
+
+ def test_missing(self):
+ data = samples.good_billing_request.copy()
+ del data['prices']
res = self.client.post(self.list_url, data=data)
eq_(res.status_code, 400)
+ assert 'prices' in json.loads(res.content)
def test_missing_success_url(self):
data = self.good()
@@ -283,7 +288,6 @@ def test_transaction(self):
data = self.good()
res = self.client.post(self.list_url, data=data)
eq_(res.status_code, 201, res.content)
-
tran = Transaction.objects.get()
eq_(tran.provider, 1)
View
6 lib/transactions/models.py
@@ -13,7 +13,10 @@
class Transaction(Model):
- amount = models.DecimalField(max_digits=9, decimal_places=2)
+ # In the case of some transactions (e.g. Bango) we don't know the amount
+ # until the transaction reaches a certain stage.
+ amount = models.DecimalField(max_digits=9, decimal_places=2, blank=True,
+ null=True)
buyer = models.ForeignKey('buyers.Buyer', blank=True, null=True,
db_index=True)
currency = models.CharField(max_length=3, default='USD')
@@ -106,7 +109,6 @@ def create_bango_transaction(sender, **kwargs):
seller_product = form.cleaned_data['seller_product_bango'].seller_product
transaction = Transaction.create(
- amount=form.cleaned_data['price_amount'],
provider=constants.SOURCE_BANGO,
seller_product=seller_product,
source=data.get('source', ''),
View
1  migrations/26-amount-optional.sql
@@ -0,0 +1 @@
+ALTER TABLE transaction MODIFY amount decimal(9,2);
View
3  samples/bango-basic.py
@@ -119,8 +119,7 @@ def call(url, method, data):
print 'Request billing configuration.'
call('/bango/billing/', 'post', {
'pageTitle': 'yep',
- 'price_currency': 'CAD',
- 'price_amount': 1,
+ 'prices': [{'amount': 1, 'currency': 'EUR'}],
'seller_product_bango': bango_product_uri,
'redirect_url_onerror': 'https://marketplace-dev.allizom.org/mozpay/err',
'redirect_url_onsuccess': 'https://marketplace-dev.allizom.org/mozpay/ok',
View
8 solitude/fields.py
@@ -34,3 +34,11 @@ def clean(self, value):
return self.to_instance().get_via_uri(value)
except (ObjectDoesNotExist, NotFound):
raise forms.ValidationError('Not a valid resource.')
+
+
+class ListField(forms.CharField):
+
+ def clean(self, value):
+ if not isinstance(value, list):
+ raise forms.ValidationError('Invalid list.')
@cvan Collaborator
cvan added a note

it's not just invalid. it's not a list!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ return value
@cvan

why do you like this over assert?

@cvan
Collaborator

it's not just invalid. it's not a list!

@robhudson

3 less key strokes!

@andymckay

its shorter and it feels like there's more symmetry in ok_ and eq_. there's no good reason.

@cvan

but then it's another import, and another function call!

@andymckay

344 tests in 14.1s

Please sign in to comment.
Something went wrong with that request. Please try again.