Skip to content
This repository has been archived by the owner on Jan 25, 2018. It is now read-only.

Commit

Permalink
allow prices to be a list of dicts and make price optional in the tra…
Browse files Browse the repository at this point in the history
…nsaction (bug 821511)
  • Loading branch information
Andy McKay committed Dec 19, 2012
1 parent d7f945c commit 328c9cd
Show file tree
Hide file tree
Showing 9 changed files with 107 additions and 15 deletions.
29 changes: 25 additions & 4 deletions lib/bango/forms.py
Expand Up @@ -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):
Expand Down Expand Up @@ -95,20 +95,41 @@ 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
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')
Expand Down
12 changes: 8 additions & 4 deletions lib/bango/resources/billing.py
Expand Up @@ -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 = {
Expand Down
5 changes: 5 additions & 0 deletions lib/bango/tests/samples.py
Expand Up @@ -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 = {
Expand Down
52 changes: 50 additions & 2 deletions 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')
Expand All @@ -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())

This comment has been minimized.

Copy link
@cvan

cvan Dec 20, 2012

why do you like this over assert?

This comment has been minimized.

Copy link
@robhudson

robhudson Dec 20, 2012

Member

3 less key strokes!

This comment has been minimized.

Copy link
@andymckay

andymckay Dec 20, 2012

Contributor

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

This comment has been minimized.

Copy link
@cvan

cvan Dec 20, 2012

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

This comment has been minimized.

Copy link
@andymckay

andymckay Dec 20, 2012

Contributor

344 tests in 14.1s

6 changes: 5 additions & 1 deletion lib/bango/tests/test_resources.py
Expand Up @@ -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()
Expand All @@ -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)

Expand Down
6 changes: 4 additions & 2 deletions lib/transactions/models.py
Expand Up @@ -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')
Expand Down Expand Up @@ -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', ''),
Expand Down
1 change: 1 addition & 0 deletions migrations/26-amount-optional.sql
@@ -0,0 +1 @@
ALTER TABLE transaction MODIFY amount decimal(9,2);
3 changes: 1 addition & 2 deletions samples/bango-basic.py
Expand Up @@ -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',
Expand Down
8 changes: 8 additions & 0 deletions solitude/fields.py
Expand Up @@ -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.')

This comment has been minimized.

Copy link
@cvan

cvan Dec 20, 2012

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

return value

0 comments on commit 328c9cd

Please sign in to comment.