From 83be442aeb30b1c00c0b45f391eb9048d9bcdf19 Mon Sep 17 00:00:00 2001 From: Anton Kuzmichev Date: Wed, 25 May 2016 11:49:12 +0400 Subject: [PATCH 1/9] updated .gitignore --- .gitignore | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 7849784..c538c7a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,23 @@ +# OS & Editors folders +.DS_Store +.cache +.directory +.project +.settings +.tmproj +.ropeproject +.pydevproject +nbproject +Thumbs.db +.idea +*.orig +.c9 +.~c9_invoke* + +# Python +__pycache__ *.pyc -*.DS_Store + +# DBs +/*.db +/*.sqlite3 \ No newline at end of file From e6e692b0a584cbfb1f191f31c83cb65b289b8714 Mon Sep 17 00:00:00 2001 From: Anton Kuzmichev Date: Wed, 25 May 2016 11:58:16 +0400 Subject: [PATCH 2/9] fix swappable User model; pep8fy and sixify --- django_braintree/models.py | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/django_braintree/models.py b/django_braintree/models.py index a1e260f..b2af68d 100644 --- a/django_braintree/models.py +++ b/django_braintree/models.py @@ -2,7 +2,8 @@ from decimal import Decimal from django.db import models -from django.contrib.auth.models import User +from django.conf import settings +from django.utils.six import python_2_unicode_compatible from braintree import Transaction @@ -23,27 +24,29 @@ def is_in_vault(self, user): return True if self.filter(user=user) else False def charge(self, user, vault_id=None): - """If vault_id is not passed this will assume that there is only one instane of user and vault_id in the db.""" + """If vault_id is not passed this will assume that there is only one instance of user and vault_id in the db.""" assert self.is_in_vault(user) if vault_id: - user_vault = self.get(user=user, vault_id=vault_id) + self.get(user=user, vault_id=vault_id) else: - user_vault = self.get(user=user) + self.get(user=user) + +@python_2_unicode_compatible class UserVault(models.Model): """Keeping it open that one user can have multiple vault credentials, hence the FK to User and not a OneToOne.""" - user = models.ForeignKey(User, unique=True) + user = models.OneToOneField(settings.AUTH_USER_MODEL) vault_id = models.CharField(max_length=64, unique=True) objects = UserVaultManager() - def __unicode__(self): + def __str__(self): return self.user.username def charge(self, amount): """ - Charges the users credit card, with he passed $amount, if they are in the vault. Returns the payment_log instance - or None (if charge fails etc.) + Charges the users credit card, with he passed $amount, if they are in the vault. + Returns the payment_log instance or None (if charge fails etc.) """ try: result = Transaction.sale( @@ -58,23 +61,28 @@ def charge(self, amount): if result.is_success: # create a payment log - payment_log = PaymentLog.objects.create(user=self.user, amount=amount, transaction_id=result.transaction.id) + payment_log = PaymentLog.objects.create( + user=self.user, amount=amount, + transaction_id=result.transaction.id + ) return payment_log else: - raise Exception('Logical error in CC transaction') - except Exception: + raise ValueError('Logical error in CC transaction') + except ValueError: logging.error('Failed to charge $%s to user: %s with vault_id: %s' % (amount, self.user, self.vault_id)) return None + +@python_2_unicode_compatible class PaymentLog(models.Model): """ Captures raw charges made to a users credit card. Extra info related to this payment should be a OneToOneField referencing this model. """ - user = models.ForeignKey(User) + user = models.ForeignKey(settings.AUTH_USER_MODEL) amount = models.DecimalField(max_digits=7, decimal_places=2) timestamp = models.DateTimeField(auto_now=True) transaction_id = models.CharField(max_length=128) - def __unicode__(self): + def __str__(self): return '%s charged $%s - %s' % (self.user, self.amount, self.transaction_id) From fb6200216e8c0a78d87ad41c99c6fada33dc1910 Mon Sep 17 00:00:00 2001 From: Anton Kuzmichev Date: Wed, 25 May 2016 12:05:42 +0400 Subject: [PATCH 3/9] model migrations for Django>=1.7 --- django_braintree/migrations/0001_initial.py | 37 +++++++++++++++++++++ django_braintree/migrations/__init__.py | 0 2 files changed, 37 insertions(+) create mode 100644 django_braintree/migrations/0001_initial.py create mode 100644 django_braintree/migrations/__init__.py diff --git a/django_braintree/migrations/0001_initial.py b/django_braintree/migrations/0001_initial.py new file mode 100644 index 0000000..156533f --- /dev/null +++ b/django_braintree/migrations/0001_initial.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.6 on 2016-05-25 08:03 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='PaymentLog', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('amount', models.DecimalField(decimal_places=2, max_digits=7)), + ('timestamp', models.DateTimeField(auto_now=True)), + ('transaction_id', models.CharField(max_length=128)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='UserVault', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('vault_id', models.CharField(max_length=64, unique=True)), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/django_braintree/migrations/__init__.py b/django_braintree/migrations/__init__.py new file mode 100644 index 0000000..e69de29 From cdcd5fecc24aed66aa90ac3d232cfe89a0c6a6e2 Mon Sep 17 00:00:00 2001 From: Anton Kuzmichev Date: Wed, 25 May 2016 13:23:23 +0400 Subject: [PATCH 4/9] added apps.py --- django_braintree/__init__.py | 3 +++ django_braintree/apps.py | 8 ++++++++ 2 files changed, 11 insertions(+) create mode 100644 django_braintree/apps.py diff --git a/django_braintree/__init__.py b/django_braintree/__init__.py index e69de29..fe29afc 100644 --- a/django_braintree/__init__.py +++ b/django_braintree/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +default_app_config = 'django_braintree.apps.Config' diff --git a/django_braintree/apps.py b/django_braintree/apps.py new file mode 100644 index 0000000..73aed3f --- /dev/null +++ b/django_braintree/apps.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.apps import AppConfig + + +class Config(AppConfig): + name = 'django_braintree' From f431b04fab71acae22c72bf46c14a8f026b264e4 Mon Sep 17 00:00:00 2001 From: Anton Kuzmichev Date: Wed, 25 May 2016 13:24:26 +0400 Subject: [PATCH 5/9] fixed invalid requirement --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 5795e51..2fdcd6f 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ 'Django>=1.4.0', 'South>=0.7.2', 'braintree>=2.10.0', - 'django-common>=0.1', + 'django-common-helpers>=0.8.0', 'fudge==1.0.3' ], #dependency_links=["git://github.com/Tivix/django-common.git@91e23cd5e0e8b420e8d4#egg=django_common-0.1"], From 653779dd638608067418c5ae5d2abc35ba538945 Mon Sep 17 00:00:00 2001 From: Anton Kuzmichev Date: Wed, 25 May 2016 13:27:32 +0400 Subject: [PATCH 6/9] bumped version to 0.1.4 --- setup.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/setup.py b/setup.py index 2fdcd6f..481d47e 100644 --- a/setup.py +++ b/setup.py @@ -20,13 +20,13 @@ setup( name='tivix-django-braintree', - version='0.1.2', + version='0.1.4', author='Sumit Chachra', author_email='chachra@tivix.com', url='http://github.com/tivix/django-braintree', - description = 'An easy way to integrate with Braintree Payment Solutions from Django.', + description='An easy way to integrate with Braintree Payment Solutions from Django.', long_description=long_description, - keywords = 'django braintree payment', + keywords='django braintree payment', packages=find_packages(), zip_safe=False, install_requires=[ @@ -36,8 +36,8 @@ 'django-common-helpers>=0.8.0', 'fudge==1.0.3' ], - #dependency_links=["git://github.com/Tivix/django-common.git@91e23cd5e0e8b420e8d4#egg=django_common-0.1"], - test_suite = 'django_braintree.tests', + # dependency_links=["git://github.com/Tivix/django-common.git@91e23cd5e0e8b420e8d4#egg=django_common-0.1"], + test_suite='django_braintree.tests', include_package_data=True, # cmdclass={}, classifiers=[ From 13f915fad68133fc78634ea9e34e86610c492ee5 Mon Sep 17 00:00:00 2001 From: Anton Kuzmichev Date: Wed, 25 May 2016 13:46:53 +0400 Subject: [PATCH 7/9] MANIFEST.in --- MANIFEST.in | 4 ++++ setup.py | 4 +--- 2 files changed, 5 insertions(+), 3 deletions(-) create mode 100644 MANIFEST.in diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..32e4cfc --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,4 @@ +include README.rst +include LICENSE +include AUTHORS +recursive-include django_braintree/templates * diff --git a/setup.py b/setup.py index 481d47e..c3ff750 100644 --- a/setup.py +++ b/setup.py @@ -28,6 +28,7 @@ long_description=long_description, keywords='django braintree payment', packages=find_packages(), + include_package_data=True, zip_safe=False, install_requires=[ 'Django>=1.4.0', @@ -36,10 +37,7 @@ 'django-common-helpers>=0.8.0', 'fudge==1.0.3' ], - # dependency_links=["git://github.com/Tivix/django-common.git@91e23cd5e0e8b420e8d4#egg=django_common-0.1"], test_suite='django_braintree.tests', - include_package_data=True, - # cmdclass={}, classifiers=[ 'Framework :: Django', 'Intended Audience :: Developers', From a6ede2e764a9083f27d83a23cb580f5fc48fd279 Mon Sep 17 00:00:00 2001 From: Anton Kuzmichev Date: Wed, 25 May 2016 14:01:52 +0400 Subject: [PATCH 8/9] fix username string representation --- django_braintree/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/django_braintree/models.py b/django_braintree/models.py index b2af68d..74eec4d 100644 --- a/django_braintree/models.py +++ b/django_braintree/models.py @@ -3,7 +3,7 @@ from django.db import models from django.conf import settings -from django.utils.six import python_2_unicode_compatible +from django.utils.six import python_2_unicode_compatible, text_type from braintree import Transaction @@ -41,7 +41,7 @@ class UserVault(models.Model): objects = UserVaultManager() def __str__(self): - return self.user.username + return text_type(self.user) def charge(self, amount): """ From 2669464ddceb525552499afd2151b0d36d4dbc4a Mon Sep 17 00:00:00 2001 From: Anton Kuzmichev Date: Wed, 25 May 2016 14:20:03 +0400 Subject: [PATCH 9/9] refacts --- django_braintree/forms.py | 17 ++++++++--------- django_braintree/models.py | 24 ++++++------------------ django_braintree/views.py | 5 +++-- 3 files changed, 17 insertions(+), 29 deletions(-) diff --git a/django_braintree/forms.py b/django_braintree/forms.py index ce4ac8f..aee9b35 100644 --- a/django_braintree/forms.py +++ b/django_braintree/forms.py @@ -25,17 +25,16 @@ class UserCCDetailsForm(forms.Form): ) __YEAR_CHOICES = ( - (2010, '2010'), - (2011, '2011'), - (2012, '2012'), - (2013, '2013'), - (2014, '2014'), - (2015, '2015'), (2016, '2016'), (2017, '2017'), (2018, '2018'), (2019, '2019'), (2020, '2020'), + (2021, '2021'), + (2022, '2022'), + (2023, '2023'), + (2024, '2024'), + (2025, '2025'), ) name = forms.CharField(max_length=64, label='Name as on card') @@ -55,7 +54,7 @@ def __init__(self, user, post_to_update=False, *args, **kwargs): this form is meant for rendering to the user, hence initialize with braintree data (if any). """ self.__user = user - self.__user_vault = UserVault.objects.get_user_vault_instance_or_none(user) + self.__user_vault = UserVault.objects.for_user(user) if not post_to_update and self.__user_vault and not args: logging.debug('Looking up payment info for vault_id: %s' % self.__user_vault.vault_id) @@ -72,7 +71,7 @@ def __init__(self, user, post_to_update=False, *args, **kwargs): 'zip_code': info.billing_address.postal_code, } super(UserCCDetailsForm, self).__init__(initial=initial, *args, **kwargs) - except Exception, e: + except Exception as e: logging.error('Was not able to get customer from vault. %s' % e) super(UserCCDetailsForm, self).__init__(initial = {'name': '%s %s' % (user.first_name, user.last_name)}, *args, **kwargs) @@ -116,7 +115,7 @@ def save(self, prepend_vault_id=''): response = Customer.find(self.__user_vault.vault_id) cc_info = response.credit_cards[0] return CreditCard.update(cc_info.token, params=cc_details_map) - except Exception, e: + except Exception as e: logging.error('Was not able to get customer from vault. %s' % e) self.__user_vault.delete() # delete the stale instance from our db diff --git a/django_braintree/models.py b/django_braintree/models.py index 74eec4d..dfe0bbd 100644 --- a/django_braintree/models.py +++ b/django_braintree/models.py @@ -9,27 +9,15 @@ class UserVaultManager(models.Manager): - def get_user_vault_instance_or_none(self, user): - """Returns a vault_id string or None""" - qset = self.filter(user=user) - if not qset: + def for_user(self, user): + """ Returns UserVault object for user or None""" + try: + return self.get(user=user) + except UserVault.DoesNotExists: return None - - if qset.count() > 1: - raise Exception('This app does not currently support multiple vault ids') - - return qset.get() def is_in_vault(self, user): - return True if self.filter(user=user) else False - - def charge(self, user, vault_id=None): - """If vault_id is not passed this will assume that there is only one instance of user and vault_id in the db.""" - assert self.is_in_vault(user) - if vault_id: - self.get(user=user, vault_id=vault_id) - else: - self.get(user=user) + return True if self.filter(user=user).count() > 0 else False @python_2_unicode_compatible diff --git a/django_braintree/views.py b/django_braintree/views.py index 5dd1586..6b72178 100644 --- a/django_braintree/views.py +++ b/django_braintree/views.py @@ -15,6 +15,7 @@ BAD_CC_ERROR_MSG = 'Oops! Doesn\'t seem like your Credit Card details are correct. Please re-check and try again.' + @ssl_required() @login_required def payments_billing(request, template='django_braintree/payments_billing.html'): @@ -39,9 +40,9 @@ def payments_billing(request, template='django_braintree/payments_billing.html') else: if UserVault.objects.is_in_vault(request.user): try: - response = Customer.find(UserVault.objects.get_user_vault_instance_or_none(request.user).vault_id) + response = Customer.find(UserVault.objects.for_user(request.user).vault_id) d['current_cc_info'] = response.credit_cards[0] - except Exception, e: + except Exception as e: logging.error('Unable to get vault information for user from braintree. %s' % e) d['cc_form'] = UserCCDetailsForm(request.user)