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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,4 @@ nosetests.xml
coverage.xml
stats.dat
.DS_Store
.tox/
21 changes: 21 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
language: python

python:
- "2.7"

env:
- 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 python-coveralls
- python setup.py -q install

script: ./run-tests.sh

after_success: coveralls
88 changes: 76 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -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/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/).

* **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 :)
```
7 changes: 7 additions & 0 deletions conftest.py
Original file line number Diff line number Diff line change
@@ -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'
2 changes: 1 addition & 1 deletion django_balanced/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
__version__ = '0.1.10'

from . import settings
default_app_config = 'django_balanced.apps.DjangoBalancedConfig'
195 changes: 53 additions & 142 deletions django_balanced/admin.py
Original file line number Diff line number Diff line change
@@ -1,172 +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.models import User
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=User.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 = User.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)
class CreditAdmin(BalancedAdmin):
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)
9 changes: 9 additions & 0 deletions django_balanced/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from django.apps import AppConfig


class DjangoBalancedConfig(AppConfig):
name = 'django_balanced'

def ready(self):
print 'HELLO'
from . import listeners
2 changes: 1 addition & 1 deletion django_balanced/context_processors.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
},
}

Expand Down
Loading