From 2c4823f54f175310f134ec447bedfa6592bd3a1c Mon Sep 17 00:00:00 2001 From: dmpayton Date: Tue, 11 Mar 2014 12:43:05 -0700 Subject: [PATCH 01/23] Rejigger how settings work. Making changes to `django.conf.settings` is *bad* and can have negative side-affects on other apps. This has been yanked, and all settings can now be set via environment cariables. Additionally, automatically configuring balanced has been moved into models.py. This is historically where you do things that need to be done on startup, but after settings have been created (see also: registering signal listeners). This can be revisited when the app refactor lands in Django 1.7. --- django_balanced/__init__.py | 2 -- django_balanced/models.py | 5 +++++ django_balanced/settings.py | 37 +++++++++---------------------------- 3 files changed, 14 insertions(+), 30 deletions(-) diff --git a/django_balanced/__init__.py b/django_balanced/__init__.py index 33d45d8..850505a 100644 --- a/django_balanced/__init__.py +++ b/django_balanced/__init__.py @@ -1,3 +1 @@ __version__ = '0.1.10' - -from . import settings diff --git a/django_balanced/models.py b/django_balanced/models.py index 05f3d94..4cb499c 100644 --- a/django_balanced/models.py +++ b/django_balanced/models.py @@ -9,6 +9,11 @@ from django.db import models from django.db.models.signals import post_save +from .settings import BALANCED + +if BALANCED.get('API_KEY'): + balanced.configure(BALANCED['API_KEY']) + class BalancedException(Exception): pass diff --git a/django_balanced/settings.py b/django_balanced/settings.py index 290ae47..4eefec1 100644 --- a/django_balanced/settings.py +++ b/django_balanced/settings.py @@ -6,34 +6,15 @@ LOGGER = logging.getLogger(__name__) -BALANCED = getattr(settings, 'BALANCED', {}) -BALANCED.setdefault('DASHBOARD_URL', 'https://www.balancedpayments.com') -BALANCED.setdefault('API_URL', 'https://api.balancedpayments.com') +BALANCED = { + 'DASHBOARD_URL': os.environ.get('BALANCED_DASHBOARD_URL', + 'https://www.balancedpayments.com'), + 'API_URL': os.environ.get('BALANCED_API_URL', + 'https://api.balancedpayments.com'), + 'API_KEY': os.environ.get('BALANCED_API_KEY'), +} -installed_apps = getattr(settings, 'INSTALLED_APPS', ()) -ctx_processors = getattr(settings, 'TEMPLATE_CONTEXT_PROCESSORS', []) -middlware_clss = getattr(settings, 'MIDDLEWARE_CLASSES', ()) +BALANCED.update(getattr(settings, 'BALANCED', {})) -installed_apps += ( - 'django_balanced', -) -ctx_processors = [ - 'django_balanced.context_processors.balanced_library', - 'django_balanced.context_processors.balanced_settings', - 'django.contrib.auth.context_processors.auth', -] -middlware_clss += ( - 'django_balanced.middleware.BalancedMiddleware', -) - -settings.INSTALLED_APPS = installed_apps -settings.TEMPLATE_CONTEXT_PROCESSORS = ctx_processors -settings.MIDDLEWARE_CLASSES = middlware_clss - -PROJECT_PATH = os.path.realpath(os.path.dirname(__file__)) -settings.TEMPLATE_DIRS += ( - PROJECT_PATH + '/' + 'templates', -) - -if not BALANCED.get('API_KEY'): +if BALANCED['API_KEY'] is None: LOGGER.error('You must set the BALANCED_API_KEY environment variable.') From e2fd0409ac954335920c3b994e6f78d651ed07e1 Mon Sep 17 00:00:00 2001 From: dmpayton Date: Tue, 11 Mar 2014 12:50:22 -0700 Subject: [PATCH 02/23] Remove BalancedMiddleware. Balanced should be configured when things start running, there's really no reason to configure it on every request. --- django_balanced/middleware.py | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 django_balanced/middleware.py diff --git a/django_balanced/middleware.py b/django_balanced/middleware.py deleted file mode 100644 index eac0932..0000000 --- a/django_balanced/middleware.py +++ /dev/null @@ -1,10 +0,0 @@ -from __future__ import unicode_literals - -import balanced -from django.conf import settings - - -class BalancedMiddleware(object): - - def process_request(*_): - balanced.configure(settings.BALANCED['API_KEY']) From 8895f69b7b946cc0884e0c9076d7b1ad8f5d1b91 Mon Sep 17 00:00:00 2001 From: dmpayton Date: Tue, 11 Mar 2014 13:39:04 -0700 Subject: [PATCH 03/23] Change tests to use py.test with coverage and pep8 integration. To run the tests: $ py.test tests/ --cov django_balanced --cov-report term-missing --pep8 django_balanced --- conftest.py | 7 +++ test-requirements.txt | 4 ++ tests/__init__.py | 0 tests/settings.py | 45 +++++++++++++++++++ .../tests.py => tests/test_suite.py | 20 +++------ tests/urls.py | 21 +++++++++ 6 files changed, 84 insertions(+), 13 deletions(-) create mode 100644 conftest.py create mode 100644 tests/__init__.py create mode 100755 tests/settings.py rename django_balanced/tests.py => tests/test_suite.py (77%) create mode 100755 tests/urls.py diff --git a/conftest.py b/conftest.py new file mode 100644 index 0000000..bd9d5d7 --- /dev/null +++ b/conftest.py @@ -0,0 +1,7 @@ +import os +from django.conf import settings + + +def pytest_configure(): + if not settings.configured: + os.environ['DJANGO_SETTINGS_MODULE'] = 'tests.settings' diff --git a/test-requirements.txt b/test-requirements.txt index e69de29..67caf7c 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -0,0 +1,4 @@ +pytest +pytest-cov +pytest-django +pytest-pep8 diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/settings.py b/tests/settings.py new file mode 100755 index 0000000..e4d0439 --- /dev/null +++ b/tests/settings.py @@ -0,0 +1,45 @@ +import balanced +import os + +PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__)) + +DEBUG = True +TEMPLATE_DEBUG = DEBUG + +ADMINS = MANAGERS = (('Admin User', 'admin@example.com')) + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': ':memory:', + } +} + +LANGUAGE_CODE = 'en-us' +ROOT_URLCONF = 'tests.urls' +SECRET_KEY = 'local' +SITE_ID = 1 +TIME_ZONE = 'UTC' +USE_I18N = True +USE_L10N = True +USE_TZ = True + +MEDIA_ROOT = os.path.join(PROJECT_ROOT, 'media') +MEDIA_URL = '/media/' + +STATIC_ROOT = os.path.join(PROJECT_ROOT, 'static') +STATIC_URL = '/static/' + +INSTALLED_APPS = ( + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + + 'django_balanced', +) + +BALANCED = { + 'API_KEY': balanced.APIKey().save().secret +} diff --git a/django_balanced/tests.py b/tests/test_suite.py similarity index 77% rename from django_balanced/tests.py rename to tests/test_suite.py index 8cbd2e7..ebbfb92 100644 --- a/django_balanced/tests.py +++ b/tests/test_suite.py @@ -1,7 +1,6 @@ from __future__ import unicode_literals import balanced -import mock from django.test import TestCase from django.contrib.auth.models import User @@ -26,24 +25,19 @@ class ModelsTest(TestCase): - @classmethod def setUpClass(cls): - with balanced.key_switcher(None): - cls.api_key = balanced.APIKey().save() - balanced.configure(cls.api_key.secret) cls.marketplace = balanced.Marketplace().save() - cls.user = User.objects.create_user('john', 'john@test.com', 'pass') - cls.user.save() + + def setUp(self): + self.user = User.objects.create_user('john', 'john@test.com', 'pass') + self.user.save() card = balanced.Card(**FIXTURES['card']).save() - cls.card = models.Card.create_from_card_uri(cls.user, card.uri) - cls.buyer = cls.card.user.balanced_account + self.card = models.Card.create_from_card_uri(self.user, card.uri) + self.buyer = self.card.user.balanced_account # put some money in the escrow account - cls.buyer.debit(100 * 100, 'test') # $100.00 - - def setUp(self): - pass + self.buyer.debit(100 * 100, 'test') # $100.00 def test_create_credit(self): bank_account = models.BankAccount(**FIXTURES['bank_account']) diff --git a/tests/urls.py b/tests/urls.py new file mode 100755 index 0000000..2061a28 --- /dev/null +++ b/tests/urls.py @@ -0,0 +1,21 @@ +try: + from django.conf.urls import patterns, include, url +except ImportError: # django < 1.4 + from django.conf.urls.defaults import patterns, include, url + +# Uncomment the next two lines to enable the admin: +from django.contrib import admin +admin.autodiscover() + +urlpatterns = patterns('', + # Examples: + # url(r'^$', '{{ project_name }}.views.home', name='home'), + # url(r'^{{ project_name }}/', include('playground.foo.urls')), + + # Uncomment the admin/doc line below to enable admin documentation: + # url(r'^admin/doc/', include('django.contrib.admindocs.urls')), + + # Uncomment the next line to enable the admin: + url(r'^admin/', include(admin.site.urls)), + url(r'^admin/balanced/', include('django_balanced.urls')), +) From 4b3b97f7333b4377f9da6634ed732490c01f776d Mon Sep 17 00:00:00 2001 From: dmpayton Date: Tue, 11 Mar 2014 14:02:36 -0700 Subject: [PATCH 04/23] PEP8 fixes. --- django_balanced/management/__init__.py | 8 ++++---- setup.cfg | 3 +++ 2 files changed, 7 insertions(+), 4 deletions(-) create mode 100644 setup.cfg diff --git a/django_balanced/management/__init__.py b/django_balanced/management/__init__.py index 27809ed..fdd3485 100644 --- a/django_balanced/management/__init__.py +++ b/django_balanced/management/__init__.py @@ -19,16 +19,16 @@ def sync_balanced(app, created_models, verbosity, db, **kwargs): Credit.sync() signals.post_syncdb.connect( - sync_balanced, - sender=models, + sync_balanced, + sender=models, dispatch_uid="django_balanced.management.sync_balanced" ) # the pre_syncdb signal is supported in a later version # only connect to it when it exists if hasattr(signals, 'pre_syncdb'): signals.pre_syncdb.connect( - configure_balanced, - sender=models, + configure_balanced, + sender=models, dispatch_uid="django_balanced.management.pre_syncdb" ) else: diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..bb2d3b1 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,3 @@ +[pytest] +pep8ignore = + *.py E128 From 1e7ebe3149b68a23ede37bbfc3425e1ecea83cb3 Mon Sep 17 00:00:00 2001 From: dmpayton Date: Tue, 11 Mar 2014 14:35:52 -0700 Subject: [PATCH 05/23] Pin balanced to <1.0. --- requirements.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 9abc881..a297e86 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,2 @@ -balanced>=0.9.0 +balanced<1.0 django>=1.4 - From f77b74adba74becb9dd914367eefda545c009510 Mon Sep 17 00:00:00 2001 From: dmpayton Date: Tue, 11 Mar 2014 15:46:13 -0700 Subject: [PATCH 06/23] First pass at Travis-CI and Tox. --- .travis.yml | 20 ++++++++++++++++++++ tox.ini | 13 +++++++++++++ 2 files changed, 33 insertions(+) create mode 100755 .travis.yml create mode 100644 tox.ini diff --git a/.travis.yml b/.travis.yml new file mode 100755 index 0000000..16a2a07 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,20 @@ +language: python + +python: + - "2.7" + +env: + - DJANGO=1.7 + - DJANGO=1.6 + - DJANGO=1.5 + +install: + - pip install -q -r requirements.txt + - pip install -q -r test-requirements.txt + - pip install -q -I "django<$DJANGO" + - pip install python-coveralls + - python setup.py -q install + +script: py.test tests/ --cov django_balanced --cov-config .coveragerc --cov-report term-missing --pep8 django_balanced + +after_success: coveralls diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..17574fb --- /dev/null +++ b/tox.ini @@ -0,0 +1,13 @@ +# Tox (http://tox.testrun.org/) is a tool for running tests +# in multiple virtualenvs. This configuration file will run the +# test suite on all supported python versions. To use it, "pip install tox" +# and then run "tox" from this directory. + +[tox] +envlist = py27 + +[testenv] +commands = py.test tests/ --cov django_balanced --cov-config .coveragerc --cov-report term-missing +deps = + -r{toxinidir}/requirements.txt + -r{toxinidir}/test-requirements.txt From 2409327a5c1c747f1ab25186079c7086f169d44c Mon Sep 17 00:00:00 2001 From: dmpayton Date: Wed, 12 Mar 2014 02:31:18 -0700 Subject: [PATCH 07/23] Add support for custom user models. --- django_balanced/admin.py | 8 +++++--- django_balanced/models.py | 19 ++++++++++--------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/django_balanced/admin.py b/django_balanced/admin.py index 95734e4..ef6ce47 100644 --- a/django_balanced/admin.py +++ b/django_balanced/admin.py @@ -1,10 +1,11 @@ from __future__ import unicode_literals import balanced + from django import forms from django.conf.urls import patterns, url from django.contrib import admin -from django.contrib.auth.models import User +from django.contrib.auth import get_user_model from django.core import urlresolvers from django.shortcuts import render, redirect @@ -38,7 +39,8 @@ class BankAccountAdminForm(forms.ModelForm): type = forms.ChoiceField(choices=( ('savings', 'savings'), ('checking', 'checking') )) - user = forms.ModelChoiceField(queryset=User.objects, required=False) + user = forms.ModelChoiceField(queryset=get_user_model().objects, + required=False) class Meta: model = BankAccount @@ -118,7 +120,7 @@ def save_model(self, request, obj, form, change): obj.routing_number = data['routing_number'] obj.type = data['type'] if data['user']: - obj.user = User.objects.get(pk=data['user']) + obj.user = get_user_model().objects.get(pk=data['user']) super(BalancedAdmin, self).save_model(request, obj, form, change) diff --git a/django_balanced/models.py b/django_balanced/models.py index 4cb499c..eb1a4be 100644 --- a/django_balanced/models.py +++ b/django_balanced/models.py @@ -4,7 +4,7 @@ import balanced from django.conf import settings -from django.contrib.auth.models import User +from django.contrib.auth import get_user_model from django.core.exceptions import ObjectDoesNotExist from django.db import models from django.db.models.signals import post_save @@ -14,6 +14,8 @@ if BALANCED.get('API_KEY'): balanced.configure(BALANCED['API_KEY']) +AUTH_USER_MODEL = getattr(settings, 'AUTH_USER_MODEL', 'auth.User') + class BalancedException(Exception): pass @@ -30,7 +32,7 @@ class Meta: def dashboard_link(self): return 'View on Balanced' % ( - settings.BALANCED['DASHBOARD_URL'], + BALANCED['DASHBOARD_URL'], self.uri[3:] ) dashboard_link.allow_tags = True @@ -59,7 +61,7 @@ def _sync(self, obj): class BankAccount(BalancedResource): _resource = balanced.BankAccount - user = models.ForeignKey(User, + user = models.ForeignKey(AUTH_USER_MODEL, related_name='bank_accounts', null=True) account_number = models.CharField(editable=False, max_length=255) @@ -117,7 +119,7 @@ def credit(self, amount, description=None): class Card(BalancedResource): _resource = balanced.Card - user = models.ForeignKey(User, + user = models.ForeignKey(AUTH_USER_MODEL, related_name='cards', null=False) name = models.CharField(editable=False, max_length=255) @@ -166,7 +168,7 @@ def debit(self, amount, description): class Credit(BalancedResource): _resource = balanced.Credit - user = models.ForeignKey(User, + user = models.ForeignKey(AUTH_USER_MODEL, related_name='credits', editable=False, null=True) @@ -213,7 +215,7 @@ def delete(self, using=None): class Debit(BalancedResource): _resource = balanced.Debit - user = models.ForeignKey(User, + user = models.ForeignKey(AUTH_USER_MODEL, related_name='debits', null=False) amount = models.DecimalField(editable=False, @@ -257,7 +259,7 @@ def delete(self, using=None): class Account(BalancedResource): _resource = balanced.Account - user = models.OneToOneField(User, related_name='balanced_account') + user = models.OneToOneField(AUTH_USER_MODEL, related_name='balanced_account') class Meta: db_table = 'balanced_accounts' @@ -295,5 +297,4 @@ def delete(self, using=None): def create_user_profile(sender, instance, created, **kwargs): Account.objects.get_or_create(user=instance) - -post_save.connect(create_user_profile, sender=User) +post_save.connect(create_user_profile, sender=get_user_model()) From b1831f23c92b1d6176ea9c931940a58576c74a8a Mon Sep 17 00:00:00 2001 From: dmpayton Date: Wed, 12 Mar 2014 03:36:49 -0700 Subject: [PATCH 08/23] Rocket surgery on the admin: - Move Model forms out of admin.py and into forms.py. - Refactor how admin forms work. Instead of modifying fields directly, we now override .get_form() to return to appropriate add/change form. This allows us more flexibility in how the form is setup, validated, and saved. TODO: logic in model_save() should probably be moved to the respective models .save() - Refactor bulk payouts. The view logic has been moved out of the admin and into to a class-based view. The page is now driven by forms/formsets, giving it proper validation, and template has been updated. - Disable the Credit change form Since Credit's cannot be changed once created, the change_form template has been override to hide the form. --- django_balanced/admin.py | 197 +++++------------- django_balanced/forms.py | 60 ++++++ django_balanced/formsets.py | 21 ++ .../django_balanced/credit/change_form.html | 13 ++ .../admin_confirm_bulk_pay.html | 90 +++++--- django_balanced/views.py | 53 ++++- 6 files changed, 262 insertions(+), 172 deletions(-) create mode 100644 django_balanced/forms.py create mode 100644 django_balanced/formsets.py create mode 100644 django_balanced/templates/admin/django_balanced/credit/change_form.html diff --git a/django_balanced/admin.py b/django_balanced/admin.py index ef6ce47..fe58ba6 100644 --- a/django_balanced/admin.py +++ b/django_balanced/admin.py @@ -1,174 +1,83 @@ from __future__ import unicode_literals -import balanced - -from django import forms from django.conf.urls import patterns, url from django.contrib import admin -from django.contrib.auth import get_user_model -from django.core import urlresolvers -from django.shortcuts import render, redirect +from django.shortcuts import redirect -from django_balanced.models import BankAccount, Credit +from . import views +from .forms import BankAccountAddForm, BankAccountChangeForm, CreditAddForm +from .models import BankAccount, Credit """ TODO: - Generate merchant dashboard login links - Bulk pay a set of bank accounts Add account URI onto django users """ class BalancedAdmin(admin.ModelAdmin): - add_fields = () - edit_fields = () - - def add_view(self, *args, **kwargs): - self.fields = getattr(self, 'add_fields', self.fields) - return super(BalancedAdmin, self).add_view(*args, **kwargs) - - def change_view(self, *args, **kwargs): - self.fields = getattr(self, 'edit_fields', self.fields) - return super(BalancedAdmin, self).change_view(*args, **kwargs) - - -class BankAccountAdminForm(forms.ModelForm): - name = forms.CharField(max_length=255) - account_number = forms.CharField(max_length=255) - routing_number = forms.CharField(max_length=255) - type = forms.ChoiceField(choices=( - ('savings', 'savings'), ('checking', 'checking') - )) - user = forms.ModelChoiceField(queryset=get_user_model().objects, - required=False) - - class Meta: - model = BankAccount - -# -# def clean(self): -# data = self.cleaned_data -# # TODO: validate routing number -# # routing_number = balanced.BankAccount( -# # routing_number=data['routing_number'], -# # ) -# # try: -# # routing_number.validate() -# # except balanced.exc.HTTPError as ex: -# # if 'routing_number' in ex.message: -# # raise forms.ValidationError(ex.message) -# # raise -# return data + add_form = None + + def get_form(self, request, obj=None, **kwargs): + defaults = {} + if obj is None and self.add_form: + defaults.update({ + 'form': self.add_form, + }) + defaults.update(kwargs) + return super(BalancedAdmin, self).get_form(request, obj, **defaults) class BankAccountAdmin(BalancedAdmin): - add_fields = ('name', 'account_number', 'routing_number', 'type', 'user') - edit_fields = ('user',) - list_display = ['account_number', 'created_at', 'user', 'name', - 'bank_name', 'type', 'dashboard_link'] - list_filter = ['type', 'bank_name', 'user'] - search_fields = ['name', 'account_number'] - form = BankAccountAdminForm - actions = ['bulk_pay_action'] + actions = ('bulk_pay_action',) + list_display = ('account_number', 'created_at', 'user', 'name', + 'bank_name', 'type', 'dashboard_link') + list_filter = ('type', 'bank_name', 'user') + add_form = BankAccountAddForm + form = BankAccountChangeForm + raw_id_fields = ('user',) + search_fields = ('name', 'account_number') def bulk_pay_action(self, request, queryset): - return render(request, 'django_balanced/admin_confirm_bulk_pay.html', { - 'bank_accounts': enumerate(queryset), - }, current_app=self.admin_site.name) - + request.session['bank_account_bulk_pay'] = [bank_account.pk for + bank_account in queryset] + return redirect('admin:bank_account_bulk_pay') bulk_pay_action.short_description = 'Credit selected accounts' def get_urls(self): - urls = super(BankAccountAdmin, self).get_urls() - my_urls = patterns('django_balanced.admin', - url(r'^bulk_pay/$', - self.admin_site.admin_view(self.bulk_pay_view), - name='bank_account_bulk_pay') - ) - return my_urls + urls - - def bulk_pay_view(self, request): - charges = [] - index = 0 - total = 0 - while True: - prefix = 'bank_account_%s' % index - uri = request.POST.get(prefix) - if not uri: - break - description = request.POST.get('%s_description' % prefix) - amount = float(request.POST.get('%s_amount' % prefix)) - amount = int(amount * 100) - bank_account = BankAccount.objects.get(pk=uri) - total += amount - charges.append( - (bank_account, amount, description) - ) - index += 1 - balanced.bust_cache() - escrow = balanced.Marketplace.my_marketplace.in_escrow - if total > escrow: - raise Exception('You have insufficient funds.') - for bank_account, amount, description in charges: - bank_account.credit(amount, description) - return redirect(urlresolvers.reverse('admin:index')) + urlpatterns = patterns('django_balanced.admin', + url(r'^bulk_pay/$', views.AdminBulkPay.as_view(), + name='bank_account_bulk_pay') + ) + super(BankAccountAdmin, self).get_urls() + return urlpatterns def save_model(self, request, obj, form, change): - data = form.data - obj.name = data['name'] - obj.account_number = data['account_number'] - obj.routing_number = data['routing_number'] - obj.type = data['type'] - if data['user']: - obj.user = get_user_model().objects.get(pk=data['user']) - super(BalancedAdmin, self).save_model(request, obj, form, change) - - -class CreditAdminForm(forms.ModelForm): - amount = forms.DecimalField(max_digits=10, required=True) - description = forms.CharField(max_length=255, required=False) - bank_account = forms.ModelChoiceField(queryset=BankAccount.objects) - - class Meta: - model = Credit - - def clean(self): - if not self.is_valid(): - return self.cleaned_data - data = self.cleaned_data - balanced.bust_cache() - escrow = balanced.Marketplace.my_marketplace.in_escrow - amount = int(float(data['amount']) * 100) - if amount > escrow: - raise forms.ValidationError('You have insufficient funds to cover ' - 'this transfer.') - return data + if isinstance(form, self.add_form): + data = form.cleaned_data + obj.name = data['name'] + obj.account_number = data['account_number'] + obj.routing_number = data['routing_number'] + obj.type = data['type'] + if data['user']: + obj.user = data['user'] + super(BankAccountAdmin, self).save_model(request, obj, form, change) + +admin.site.register(BankAccount, BankAccountAdmin) class CreditAdmin(BalancedAdmin): - add_fields = ('amount', 'bank_account', 'description') - edit_fields = (None) - list_display = ['user', 'bank_account', 'amount', - 'description', 'status', 'dashboard_link'] - search_fields = ['amount', 'description', 'status'] - list_filter = ['user', 'status'] - form = CreditAdminForm - - def get_form(self, request, obj=None, **kwargs): - if obj: - self.exclude = ('amount',) - return super(CreditAdmin, self).get_form(request, obj=None, **kwargs) + add_form = CreditAddForm + list_display = ('user', 'bank_account', 'amount', 'description', + 'status', 'dashboard_link') + list_filter = ('user', 'status') + search_fields = ('amount', 'description', 'status') def save_model(self, request, obj, form, change): - data = form.data - amount = int(float(data['amount']) * 100) - bank_account = BankAccount.objects.get(pk=data['bank_account']) - obj.amount = amount - obj.bank_account = bank_account - obj.user = bank_account.user - obj.description = data['description'] - obj.save() + if isinstance(form, self.add_form): + data = form.cleaned_data + obj.amount = int(data['amount'] * 100) + obj.bank_account = data['bank_account'] + obj.user = data['bank_account'].user + obj.description = data['description'] + return super(CreditAdmin, self).save_model(request, obj, form, change) - -admin.site.register(BankAccount, BankAccountAdmin) admin.site.register(Credit, CreditAdmin) diff --git a/django_balanced/forms.py b/django_balanced/forms.py new file mode 100644 index 0000000..a374311 --- /dev/null +++ b/django_balanced/forms.py @@ -0,0 +1,60 @@ +import balanced + +from decimal import Decimal +from django import forms + +from .models import BankAccount, Credit + + +class BankAccountAddForm(forms.ModelForm): + name = forms.CharField(max_length=255) + account_number = forms.CharField(max_length=255) + routing_number = forms.CharField(max_length=255) + type = forms.ChoiceField(choices=( + ('savings', 'savings'), ('checking', 'checking') + )) + + class Meta: + fields = ('user',) + model = BankAccount + + +class BankAccountChangeForm(forms.ModelForm): + class Meta: + fields = ('user',) + model = BankAccount + + +class CreditAddForm(forms.ModelForm): + amount = forms.DecimalField(max_digits=10, decimal_places=2, required=True) + description = forms.CharField(max_length=255, required=False) + bank_account = forms.ModelChoiceField(queryset=BankAccount.objects) + + class Meta: + model = Credit + + def clean(self): + if not self.is_valid(): + return self.cleaned_data + data = self.cleaned_data + balanced.bust_cache() + escrow = balanced.Marketplace.my_marketplace.in_escrow + amount = int(data['amount'] * 100) + if amount > escrow: + error = 'You have insufficient funds to cover this transfer.' + raise forms.ValidationError(error) + return data + + +class PayoutForm(forms.Form): + bank_account = forms.ModelChoiceField(queryset=BankAccount.objects, + widget=forms.HiddenInput) + amount = forms.DecimalField(decimal_places=2, min_value=Decimal('.50')) + description = forms.CharField() + + def save(self): + assert self.is_valid() + return self.cleaned_data['bank_account'].credit( + self.cleaned_data['amount'] * 100, + self.cleaned_data['description'] + ) diff --git a/django_balanced/formsets.py b/django_balanced/formsets.py new file mode 100644 index 0000000..f842e4a --- /dev/null +++ b/django_balanced/formsets.py @@ -0,0 +1,21 @@ +import balanced + +from django import forms +from django.forms.models import BaseFormSet, formset_factory + +from .forms import PayoutForm + + +class BaseBulkPayoutFormSet(BaseFormSet): + def clean(self): + if any(self.errors): + return + balanced.bust_cache() + escrow = balanced.Marketplace.my_marketplace.in_escrow + total = sum([form.cleaned_data['amount'] for form in self.forms]) * 100 + if total > escrow: + error = 'You have insufficient funds to cover this payout.' + raise forms.ValidationError(error) + +BulkPayoutFormSet = formset_factory(PayoutForm, + formset=BaseBulkPayoutFormSet, extra=0) diff --git a/django_balanced/templates/admin/django_balanced/credit/change_form.html b/django_balanced/templates/admin/django_balanced/credit/change_form.html new file mode 100644 index 0000000..9038a12 --- /dev/null +++ b/django_balanced/templates/admin/django_balanced/credit/change_form.html @@ -0,0 +1,13 @@ +{% extends "admin/change_form.html" %} + +{% block content %} + {% if add %} + {{ block.super }} + {% else %} + {# You cannot make changes to Credit objects #} +
+

Credits cannot be changed once created.

+

Go back to the Credit listing.

+
+ {% endif %} +{% endblock %} diff --git a/django_balanced/templates/django_balanced/admin_confirm_bulk_pay.html b/django_balanced/templates/django_balanced/admin_confirm_bulk_pay.html index 320f198..668836e 100644 --- a/django_balanced/templates/django_balanced/admin_confirm_bulk_pay.html +++ b/django_balanced/templates/django_balanced/admin_confirm_bulk_pay.html @@ -1,32 +1,68 @@ {% extends "admin/base_site.html" %} +{% load static %} +{% load url from future %} -{% block content %} - -
- {% csrf_token %} -

How much do you want to pay to the following bank accounts:

- - - - - - - - - - {% for index, bank_account in bank_accounts %} - - - - - - - {% endfor %} - -
Bank accountAmountDescription
{{ bank_account }}
- +{% block title %}Bank Account Bulk Payout {{ block.super }}{% endblock %} -
+{% block extrastyle %} + {{ block.super }} + + +{% endblock %} +{% block content %} +
+

Bank Account Bulk Payout

+

How much do you want to pay to the following bank accounts:

+
+
+ {{ formset.non_form_errors }} + {% csrf_token %} + + + + + + + + + + + {% for form in formset %} + {{ form.bank_account }} + + + + + + + {% endfor %} + +
UserBank accountAmountDescription
+ {% if form.initial.bank_account.user %} +

{{ form.initial.bank_account.user }}

+ {% else %} +

N/A

+ {% endif %} +
{{ form.initial.bank_account.name }}
{{ form.initial.bank_account.account_number }}
+ {{ form.amount.errors }} + {{ form.amount }} +

{{ form.amount.help_text }}

+
+ {{ form.description.errors }} + {{ form.description }} +
+
+
+ + {{ formset.management_form }} + + +
+
+
{% endblock %} diff --git a/django_balanced/views.py b/django_balanced/views.py index 6bc71d2..6f6cc3b 100644 --- a/django_balanced/views.py +++ b/django_balanced/views.py @@ -1,8 +1,59 @@ from __future__ import unicode_literals -from django.shortcuts import render +from django.contrib import messages +from django.contrib.admin.views.decorators import staff_member_required +from django.shortcuts import redirect, render +from django.utils.decorators import method_decorator +from django.views import generic + +from .formsets import BulkPayoutFormSet +from .models import BankAccount def bank_accounts(request): data = {} return render(request, 'django_balanced/bank_account_form.html', **data) + + +class AdminBulkPay(generic.TemplateView): + template_name = 'django_balanced/admin_confirm_bulk_pay.html' + + @method_decorator(staff_member_required) + def dispatch(self, request, *args, **kwargs): + if 'bank_account_bulk_pay' not in request.session: + return redirect('../') + self.formset = self.get_formset() + return super(AdminBulkPay, self).dispatch(request, *args, **kwargs) + + def post(self, request, *args, **kwargs): + if '_cancel' in request.POST: + message = 'Your payouts have been cancelled' + return self.payout_complete(message) + if self.formset.is_valid(): + for form in self.formset: + form.save() + message = 'Your payouts have been made.' + return self.payout_complete(message) + return self.get(request, *args, **kwargs) + + def get_queryset(self): + bank_account_list = self.request.session['bank_account_bulk_pay'] + queryset = BankAccount.objects.filter(pk__in=bank_account_list) + queryset = queryset.select_related('user') + return queryset + + def get_context_data(self, **kwargs): + context = super(AdminBulkPay, self).get_context_data(**kwargs) + context['formset'] = self.formset + return context + + def get_formset(self): + return BulkPayoutFormSet(self.request.POST or None, + initial=[{'bank_account': bank_account} for bank_account in + self.get_queryset()] + ) + + def payout_complete(self, message): + del self.request.session['bank_account_bulk_pay'] + messages.success(self.request, message) + return redirect('admin:django_balanced_bankaccount_changelist') From 56f65204a88cac3f379f303c16156a6b443aa28a Mon Sep 17 00:00:00 2001 From: dmpayton Date: Wed, 12 Mar 2014 12:45:32 -0700 Subject: [PATCH 09/23] Minor PEP8 fix. --- django_balanced/models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/django_balanced/models.py b/django_balanced/models.py index eb1a4be..7eb42ec 100644 --- a/django_balanced/models.py +++ b/django_balanced/models.py @@ -259,7 +259,8 @@ def delete(self, using=None): class Account(BalancedResource): _resource = balanced.Account - user = models.OneToOneField(AUTH_USER_MODEL, related_name='balanced_account') + user = models.OneToOneField(AUTH_USER_MODEL, + related_name='balanced_account') class Meta: db_table = 'balanced_accounts' From 8cbdea02f731216f4d7bd50f4b3df138cc104ed1 Mon Sep 17 00:00:00 2001 From: dmpayton Date: Wed, 12 Mar 2014 13:17:59 -0700 Subject: [PATCH 10/23] Backwards compatible get_user_model. --- django_balanced/models.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/django_balanced/models.py b/django_balanced/models.py index 7eb42ec..8c97e3b 100644 --- a/django_balanced/models.py +++ b/django_balanced/models.py @@ -4,11 +4,16 @@ import balanced from django.conf import settings -from django.contrib.auth import get_user_model from django.core.exceptions import ObjectDoesNotExist from django.db import models from django.db.models.signals import post_save +try: + from django.contrib.auth import get_user_model +except ImportError: + from django.contrib.auth.models import User + get_user_model = lambda: User + from .settings import BALANCED if BALANCED.get('API_KEY'): From 678163924ffc8bad77f6544809edbe4be9bf948c Mon Sep 17 00:00:00 2001 From: dmpayton Date: Fri, 6 Jun 2014 21:00:00 -0700 Subject: [PATCH 11/23] Fix API and Dashboard urls. --- django_balanced/context_processors.py | 2 +- django_balanced/settings.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/django_balanced/context_processors.py b/django_balanced/context_processors.py index 5dc8509..6ac3f86 100644 --- a/django_balanced/context_processors.py +++ b/django_balanced/context_processors.py @@ -9,7 +9,7 @@ def balanced_settings(request): 'BALANCED': { 'MARKETPLACE_URI': balanced.Marketplace.my_marketplace.uri, 'DASHBOARD_URL': settings.BALANCED['DASHBOARD_URL'], - 'API_URL': settings.BALANCED['DASHBOARD_URL'], + 'API_URL': settings.BALANCED['API_URL'], }, } diff --git a/django_balanced/settings.py b/django_balanced/settings.py index 4eefec1..bd099f5 100644 --- a/django_balanced/settings.py +++ b/django_balanced/settings.py @@ -8,7 +8,7 @@ BALANCED = { 'DASHBOARD_URL': os.environ.get('BALANCED_DASHBOARD_URL', - 'https://www.balancedpayments.com'), + 'https://dashboard.balancedpayments.com'), 'API_URL': os.environ.get('BALANCED_API_URL', 'https://api.balancedpayments.com'), 'API_KEY': os.environ.get('BALANCED_API_KEY'), From 01ed2e337dd42f7e4e3138c08981cf95828aeffd Mon Sep 17 00:00:00 2001 From: dmpayton Date: Fri, 6 Jun 2014 21:17:33 -0700 Subject: [PATCH 12/23] Put listeners into their own file. --- django_balanced/__init__.py | 2 ++ django_balanced/listeners.py | 31 +++++++++++++++++++++++++++++++ django_balanced/models.py | 15 --------------- 3 files changed, 33 insertions(+), 15 deletions(-) create mode 100644 django_balanced/listeners.py diff --git a/django_balanced/__init__.py b/django_balanced/__init__.py index 850505a..e66c7e9 100644 --- a/django_balanced/__init__.py +++ b/django_balanced/__init__.py @@ -1 +1,3 @@ __version__ = '0.1.10' + +from . import listeners diff --git a/django_balanced/listeners.py b/django_balanced/listeners.py new file mode 100644 index 0000000..0ab32d8 --- /dev/null +++ b/django_balanced/listeners.py @@ -0,0 +1,31 @@ +from __future__ import unicode_literals + +from django.dispatch import receiver +from django.db.models import signals + +try: + from django.contrib.auth import get_user_model +except ImportError: + from django.contrib.auth.models import User + get_user_model = lambda: User + +try: + post_sync = signals.post_migrate +except AttributeError: + post_sync = signals.post_syncdb + + +# This will create an account per user when they are next saved. Subsequent +# saves will not make a network call. +@receiver(signals.post_save, sender=get_user_model()) +def create_user_profile(sender, instance, created, **kwargs): + from .models import Account + Account.objects.get_or_create(user=instance) + + +# Sync with Balanced when the database is synced +@receiver(post_sync, dispatch_uid='django_balanced.listeners.sync_balanced') +def sync_balanced(app, created_models, verbosity, db, **kwargs): + from .models import BankAccount, Credit + BankAccount.sync() + Credit.sync() diff --git a/django_balanced/models.py b/django_balanced/models.py index 8c97e3b..d7064fe 100644 --- a/django_balanced/models.py +++ b/django_balanced/models.py @@ -6,13 +6,6 @@ from django.conf import settings from django.core.exceptions import ObjectDoesNotExist from django.db import models -from django.db.models.signals import post_save - -try: - from django.contrib.auth import get_user_model -except ImportError: - from django.contrib.auth.models import User - get_user_model = lambda: User from .settings import BALANCED @@ -296,11 +289,3 @@ def debit(self, amount, description, card=None): def delete(self, using=None): raise NotImplemented - - -# this will create an account per user when they are next saved. subsequent -# saves will not make a network call. -def create_user_profile(sender, instance, created, **kwargs): - Account.objects.get_or_create(user=instance) - -post_save.connect(create_user_profile, sender=get_user_model()) From 89fcf1a9999c48be409648c94ea86f31dd303145 Mon Sep 17 00:00:00 2001 From: dmpayton Date: Fri, 6 Jun 2014 21:17:43 -0700 Subject: [PATCH 13/23] PEP8 fixes. --- django_balanced/models.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/django_balanced/models.py b/django_balanced/models.py index d7064fe..be289a9 100644 --- a/django_balanced/models.py +++ b/django_balanced/models.py @@ -69,7 +69,7 @@ class BankAccount(BalancedResource): type = models.CharField(editable=False, max_length=255) class Meta: -# app_label = 'Balanced' + # app_label = 'Balanced' db_table = 'balanced_bank_accounts' def __unicode__(self): @@ -127,7 +127,7 @@ class Card(BalancedResource): brand = models.CharField(editable=False, max_length=255) class Meta: - # app_label = 'Balanced' + # app_label = 'Balanced' db_table = 'balanced_cards' @classmethod @@ -180,7 +180,7 @@ class Credit(BalancedResource): status = models.CharField(editable=False, max_length=255) class Meta: -# app_label = 'Balanced' + # app_label = 'Balanced' db_table = 'balanced_credits' def save(self, **kwargs): @@ -225,7 +225,7 @@ class Debit(BalancedResource): editable=False) class Meta: - # app_label = 'Balanced' + # app_label = 'Balanced' db_table = 'balanced_debits' def save(self, **kwargs): From 6308e829f8bcbcea76f128b8eda5a1bdaba07eb3 Mon Sep 17 00:00:00 2001 From: dmpayton Date: Fri, 6 Jun 2014 21:18:01 -0700 Subject: [PATCH 14/23] Test for PEP8 in tox. --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 17574fb..e2fffb7 100644 --- a/tox.ini +++ b/tox.ini @@ -7,7 +7,7 @@ envlist = py27 [testenv] -commands = py.test tests/ --cov django_balanced --cov-config .coveragerc --cov-report term-missing +commands = py.test tests/ --cov django_balanced --cov-config .coveragerc --cov-report term-missing --pep8 django_balanced deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt From e719339ff5e64d1f29340b74240edafc5050b847 Mon Sep 17 00:00:00 2001 From: dmpayton Date: Fri, 6 Jun 2014 21:21:33 -0700 Subject: [PATCH 15/23] Remove an unfinished and unused view. --- django_balanced/urls.py | 7 ------- django_balanced/views.py | 7 +------ 2 files changed, 1 insertion(+), 13 deletions(-) delete mode 100644 django_balanced/urls.py diff --git a/django_balanced/urls.py b/django_balanced/urls.py deleted file mode 100644 index adadeb2..0000000 --- a/django_balanced/urls.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.conf.urls import patterns, url - - -urlpatterns = patterns( - 'django_balanced.views', - url(r'^bank_accounts/$', 'bank_account'), -) diff --git a/django_balanced/views.py b/django_balanced/views.py index 6f6cc3b..0ae9dd2 100644 --- a/django_balanced/views.py +++ b/django_balanced/views.py @@ -2,7 +2,7 @@ from django.contrib import messages from django.contrib.admin.views.decorators import staff_member_required -from django.shortcuts import redirect, render +from django.shortcuts import redirect from django.utils.decorators import method_decorator from django.views import generic @@ -10,11 +10,6 @@ from .models import BankAccount -def bank_accounts(request): - data = {} - return render(request, 'django_balanced/bank_account_form.html', **data) - - class AdminBulkPay(generic.TemplateView): template_name = 'django_balanced/admin_confirm_bulk_pay.html' From 69eb613500664560fb63e1048182dfe9894dc4b3 Mon Sep 17 00:00:00 2001 From: dmpayton Date: Mon, 9 Jun 2014 16:00:25 -0700 Subject: [PATCH 16/23] Update tox to test Django 1.4-1.7b4 --- .travis.yml | 1 + requirements.txt | 1 - tox.ini | 30 +++++++++++++++++++++++++++++- 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 16a2a07..a898edf 100755 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,7 @@ env: - DJANGO=1.7 - DJANGO=1.6 - DJANGO=1.5 + - DJANGO=1.4 install: - pip install -q -r requirements.txt diff --git a/requirements.txt b/requirements.txt index a297e86..3633b2d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1 @@ balanced<1.0 -django>=1.4 diff --git a/tox.ini b/tox.ini index e2fffb7..d11eadb 100644 --- a/tox.ini +++ b/tox.ini @@ -4,10 +4,38 @@ # and then run "tox" from this directory. [tox] -envlist = py27 +envlist = + py27-django17, + py27-django16, + py27-django15, + py27-django14, [testenv] commands = py.test tests/ --cov django_balanced --cov-config .coveragerc --cov-report term-missing --pep8 django_balanced deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt + +[testenv:py27-django17] +basepython = python2.7 +deps = + https://www.djangoproject.com/m/releases/1.7/Django-1.7b4.tar.gz + {[testenv]deps} + +[testenv:py27-django16] +basepython = python2.7 +deps = + Django>=1.6,<1.7 + {[testenv]deps} + +[testenv:py27-django15] +basepython = python2.7 +deps = + Django>=1.5,<1.6 + {[testenv]deps} + +[testenv:py27-django14] +basepython = python2.7 +deps = + Django>=1.4,<1.5 + {[testenv]deps} From c9255064da7e4396a54a9e2afa90903b4921ebbd Mon Sep 17 00:00:00 2001 From: dmpayton Date: Mon, 9 Jun 2014 16:01:34 -0700 Subject: [PATCH 17/23] Add .tox/ to .gitignore. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 14ad287..a7ce173 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,4 @@ nosetests.xml coverage.xml stats.dat .DS_Store +.tox/ From 040a110526c4358e56532ee20e6fb438dfdf02fa Mon Sep 17 00:00:00 2001 From: dmpayton Date: Mon, 23 Jun 2014 03:11:34 -0700 Subject: [PATCH 18/23] Django 1.7 compat: - Use the apps registry (if available) to register listeners, with a fallback import in models.py - The post_sync listener doesn't care about any of the kwargs passed to it. Tests pass with Django 1.7b4. :thumbsup: --- django_balanced/__init__.py | 2 +- django_balanced/apps.py | 9 +++++++++ django_balanced/listeners.py | 2 +- django_balanced/models.py | 7 +++++++ 4 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 django_balanced/apps.py diff --git a/django_balanced/__init__.py b/django_balanced/__init__.py index e66c7e9..447b2c7 100644 --- a/django_balanced/__init__.py +++ b/django_balanced/__init__.py @@ -1,3 +1,3 @@ __version__ = '0.1.10' -from . import listeners +default_app_config = 'django_balanced.apps.DjangoBalancedConfig' diff --git a/django_balanced/apps.py b/django_balanced/apps.py new file mode 100644 index 0000000..da3ab9e --- /dev/null +++ b/django_balanced/apps.py @@ -0,0 +1,9 @@ +from django.apps import AppConfig + + +class DjangoBalancedConfig(AppConfig): + name = 'django_balanced' + + def ready(self): + print 'HELLO' + from . import listeners diff --git a/django_balanced/listeners.py b/django_balanced/listeners.py index 0ab32d8..02b3135 100644 --- a/django_balanced/listeners.py +++ b/django_balanced/listeners.py @@ -25,7 +25,7 @@ def create_user_profile(sender, instance, created, **kwargs): # Sync with Balanced when the database is synced @receiver(post_sync, dispatch_uid='django_balanced.listeners.sync_balanced') -def sync_balanced(app, created_models, verbosity, db, **kwargs): +def sync_balanced(sender, **kwargs): from .models import BankAccount, Credit BankAccount.sync() Credit.sync() diff --git a/django_balanced/models.py b/django_balanced/models.py index be289a9..13a02c2 100644 --- a/django_balanced/models.py +++ b/django_balanced/models.py @@ -9,6 +9,13 @@ from .settings import BALANCED +try: + from django import apps +except ImportError: + # We're in Django <1.7 and can't rely on the apps registry. + # Fall back to importing listeners here. + from . import listeners + if BALANCED.get('API_KEY'): balanced.configure(BALANCED['API_KEY']) From c28eb17c745964f17db7c025612f5a3837102984 Mon Sep 17 00:00:00 2001 From: dmpayton Date: Mon, 23 Jun 2014 03:46:30 -0700 Subject: [PATCH 19/23] Test updates: - Create run-tests.sh script for quickly running the tests with common options. Update Travis-CI and tox to use this. - Update how Travis-CI specifies Django versions for clarity. --- .travis.yml | 10 +++++----- run-tests.sh | 7 +++++++ tox.ini | 2 +- 3 files changed, 13 insertions(+), 6 deletions(-) create mode 100755 run-tests.sh diff --git a/.travis.yml b/.travis.yml index a898edf..2db5741 100755 --- a/.travis.yml +++ b/.travis.yml @@ -4,15 +4,15 @@ python: - "2.7" env: - - DJANGO=1.7 - - DJANGO=1.6 - - DJANGO=1.5 - - DJANGO=1.4 + - DJANGO=">=1.6,<1.7" + - DJANGO=">=1.5,<1.6" + - DJANGO=">=1.4,<1.5" + - DJANGO=">=1.3,<1.4" install: - pip install -q -r requirements.txt - pip install -q -r test-requirements.txt - - pip install -q -I "django<$DJANGO" + - pip install -q -I "Django $DJANGO" - pip install python-coveralls - python setup.py -q install diff --git a/run-tests.sh b/run-tests.sh new file mode 100755 index 0000000..20ce35b --- /dev/null +++ b/run-tests.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +py.test tests/ \ + --cov django_balanced \ + --cov-config .coveragerc \ + --cov-report term-missing \ + --pep8 django_balanced diff --git a/tox.ini b/tox.ini index d11eadb..a66e8a4 100644 --- a/tox.ini +++ b/tox.ini @@ -11,7 +11,7 @@ envlist = py27-django14, [testenv] -commands = py.test tests/ --cov django_balanced --cov-config .coveragerc --cov-report term-missing --pep8 django_balanced +commands = ./run-tests.sh deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt From dda05e000c50decde0978c69279312ec8218f003 Mon Sep 17 00:00:00 2001 From: dmpayton Date: Mon, 23 Jun 2014 04:29:43 -0700 Subject: [PATCH 20/23] Update travis to use new test script, apparently. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2db5741..fd76209 100755 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,6 @@ install: - pip install python-coveralls - python setup.py -q install -script: py.test tests/ --cov django_balanced --cov-config .coveragerc --cov-report term-missing --pep8 django_balanced +script: ./run-tests.sh after_success: coveralls From 415033cf83d699ed01e9f11277792a44a5899e5c Mon Sep 17 00:00:00 2001 From: dmpayton Date: Mon, 23 Jun 2014 04:30:24 -0700 Subject: [PATCH 21/23] Add version info, testing info, and badges to README. --- README.md | 88 +++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 76 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index d4a042a..7748eea 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,90 @@ -# django-balanced +django-balanced +=============== -## How to send ACH payments in 10 minutes +[![Build Status](https://secure.travis-ci.org/balanced/django-balanced.png)](http://travis-ci.org/dmpayton/django-balanced) +[![Coverage Status](https://coveralls.io/repos/balanced/django-balanced/badge.png?branch=develop)](https://coveralls.io/r/balanced/django-balanced) +[![Downloads](https://pypip.in/d/django-balanced/badge.png)](https://pypi.python.org/pypi/django-balanced) + +Django integration for [Balanced Payments](https://www.balancedpayments.com/). + +* **Version**: 0.1.10 +* **License**: BSD + +This version is compatible with the +[Balanced API v1.0](https://docs.balancedpayments.com/1.0/overview/) using +[balanced-python 0.11.15](https://pypi.python.org/pypi/balanced/0.11.15). + +How to send ACH payments in 10 minutes +-------------------------------------- 1. Visit www.balancedpayments.com and get yourself an API key + 2. `pip install django-balanced` + 3. Edit your `settings.py` and add the API key like so: - import os + ``` + import os - BALANCED = { - 'API_KEY': os.environ.get('BALANCED_API_KEY'), - } + BALANCED = { + 'API_KEY': os.environ.get('BALANCED_API_KEY'), + } + ``` 4. Add `django_balanced` to your `INSTALLED_APPS` in `settings.py` - INSTALLED_APPS = ( - ... - 'django.contrib.admin', # if you want to use the admin interface - 'django_balanced', - ... - ) + ``` + INSTALLED_APPS = ( + ... + 'django.contrib.admin', # if you want to use the admin interface + 'django_balanced', + ... + ) + ``` 5. Run `BALANCED_API_KEY=YOUR_API_KEY django-admin.py syncdb` + 6. Run `BALANCED_API_KEY=YOUR_API_KEY python manage.py runserver` + 7. Visit `http://127.0.0.1:8000/admin` and pay some people! + +Testing +------- + +Continuous integration provided by [Travis CI](https://travis-ci.org/). + +### Running the tests + +1. Install all requirements: + + ``` + $ pip install Django -r requirements.txt -r test-requirements.txt + ``` + +2. Run the tests: + + ``` + $ ./run-tests.py + + ... + + =============== 2 passed, 17 skipped in 15.95 seconds =============== + ``` + +### Testing with Tox + +For quickly testing against different Django versions, we use +[Tox](http://tox.readthedocs.org/). + +``` +$ tox + +... + +_______________ summary _______________ + py27-django17: commands succeeded + py27-django16: commands succeeded + py27-django15: commands succeeded + py27-django14: commands succeeded + congratulations :) +``` From d0d9121655e54ed50465ffaabd63f2f555a758bc Mon Sep 17 00:00:00 2001 From: dmpayton Date: Mon, 23 Jun 2014 04:41:08 -0700 Subject: [PATCH 22/23] Don't cache bust before escrow checks. --- django_balanced/forms.py | 1 - django_balanced/formsets.py | 1 - 2 files changed, 2 deletions(-) diff --git a/django_balanced/forms.py b/django_balanced/forms.py index a374311..89733bd 100644 --- a/django_balanced/forms.py +++ b/django_balanced/forms.py @@ -37,7 +37,6 @@ def clean(self): if not self.is_valid(): return self.cleaned_data data = self.cleaned_data - balanced.bust_cache() escrow = balanced.Marketplace.my_marketplace.in_escrow amount = int(data['amount'] * 100) if amount > escrow: diff --git a/django_balanced/formsets.py b/django_balanced/formsets.py index f842e4a..140e29f 100644 --- a/django_balanced/formsets.py +++ b/django_balanced/formsets.py @@ -10,7 +10,6 @@ class BaseBulkPayoutFormSet(BaseFormSet): def clean(self): if any(self.errors): return - balanced.bust_cache() escrow = balanced.Marketplace.my_marketplace.in_escrow total = sum([form.cleaned_data['amount'] for form in self.forms]) * 100 if total > escrow: From b9ea7a00d4776542f4c8496808020d1a251116ae Mon Sep 17 00:00:00 2001 From: Derek Payton Date: Wed, 25 Jun 2014 12:49:32 -0700 Subject: [PATCH 23/23] Fix badges in README. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7748eea..c82158f 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ django-balanced =============== -[![Build Status](https://secure.travis-ci.org/balanced/django-balanced.png)](http://travis-ci.org/dmpayton/django-balanced) -[![Coverage Status](https://coveralls.io/repos/balanced/django-balanced/badge.png?branch=develop)](https://coveralls.io/r/balanced/django-balanced) +[![Build Status](https://secure.travis-ci.org/balanced/django-balanced.png)](http://travis-ci.org/balanced/django-balanced) +[![Coverage Status](https://coveralls.io/repos/balanced/django-balanced/badge.png)](https://coveralls.io/r/balanced/django-balanced) [![Downloads](https://pypip.in/d/django-balanced/badge.png)](https://pypi.python.org/pypi/django-balanced) Django integration for [Balanced Payments](https://www.balancedpayments.com/).