Commits

Mikhail Korobov committed 810e399

Slug is removed, tests and migrations are added, syntax errors are fixed

Comments (0)

Files changed (18)

 
 #misc files
 pip-log.txt
+netcash_tests/db.sqlite
 
 #os files
 .DS_Store
 build/
 dist/
 .build/
-MANIFEST
+MANIFEST
 include *.txt
 include *.rst
-recursive-include docs *.txt
+recursive-include docs *.txt
+recursive-include netcash_tests *.py
+recursive-include netcash_tests/templates/ *.html
 from netcash.models import NetcashGateway, NetcashOrder
 
 class NetcashGatewayAdmin(admin.ModelAdmin):
-    list_display = ['name', 'data_url']
-    prepopulated_fields = {"slug": ("name",)}
-
+    list_display = ['name', 'data_url', 'accept_url', 'reject_url', 'netcash_ip']
 
 class NetcashOrderAdmin(admin.ModelAdmin):
-    list_display = ['Reference', 'created_at', 'Amount', 'RETC', 'TransactionAccepted', ]
+    list_display = ['Reference', 'created_at', 'Amount', 'RETC', 'TransactionAccepted']
     list_filter = ['gateway']
 
-
 admin.site.register(NetcashGateway, NetcashGatewayAdmin)
 admin.site.register(NetcashOrder, NetcashOrderAdmin)
 
 NETCASH_TARGET_URL = 'https://gateway.netcash.co.za/vvonline/ccnetcash.asp'
 
-NETCASH_USERNAME = getattr(settings, 'NETCASH_USERNAME', 'testuser')
-NETCASH_PASSWORD = getattr(settings, 'NETCASH_PASSWORD', 'testpass')
-NETCASH_PIN = getattr(settings, 'NETCASH_PIN', '654321')
-NETCASH_TERMINAL_NUMBER = getattr(settings, 'NETCASH_TERMINAL_NUMBER', '0291')
+NETCASH_USERNAME = settings.NETCASH_USERNAME
+NETCASH_PASSWORD = settings.NETCASH_PASSWORD
+NETCASH_PIN = settings.NETCASH_PIN
+NETCASH_TERMINAL_NUMBER = settings.NETCASH_TERMINAL_NUMBER
 
 # request.META key with client ip address
 NETCASH_IP_HEADER = getattr(settings, 'NETCASH_IP_HEADER', 'REMOTE_ADDR')
 
-
 # NETCASH_TEST_MODE = getattr(settings, 'NETCASH_TEST_MODE', False)
 from netcash.conf import *
 from netcash.models import NetcashOrder
 
-
 class HiddenForm(forms.Form):
     """ A form with all fields hidden """
     def __init__(self, *args, **kwargs):
     p3 = forms.CharField(max_length = 50)
 
     # Transactional Amount that is to be settled to the Card
-    p4 = forms.DecimalField(min_value=0, max_decimal_places=2, max_digits=8)
+    p4 = forms.DecimalField(min_value=0, decimal_places=2, max_digits=8)
 
     # Cancel Button URL. This is the URL the client will be directed
     # to when at anytime the client clicks the cancel button.
     m_9 = forms.CharField(max_length=100, required=False)
 
     # Any text sent in this parameter is returned to the Accept and Reject
-    # return URLs. This is usually used with basket products like
+    # return URL's. This is usually used with basket products like
     # OSCommerce and VirtueMart
     m_10 = forms.CharField(max_length=100, required=False)
 
     m_5 = forms.CharField(max_length=50, required=False)
     m_6 = forms.CharField(max_length=50, required=False)
 
+    TransactionAccepted = forms.CharField()
+
+    def clean_TransactionAccepted(self):
+        accepted = self.cleaned_data['TransactionAccepted']
+        if accepted == 'true':
+            return True
+        elif accepted == 'false':
+            return False
+        else:
+            raise forms.ValidationError('Invalid value')
+
     def save(self, *args, **kwargs):
         self.cleaned_data['Extra1'] = self.cleaned_data['m_4']
         self.cleaned_data['Extra2'] = self.cleaned_data['m_5']
         self.cleaned_data['Extra3'] = self.cleaned_data['m_6']
-        super(DataHandlerForm, self).save(*args, **kwargs)
+        return super(DataHandlerForm, self).save(*args, **kwargs)
 
     class Meta:
         model = NetcashOrder
-        exclude = ['created_at']
+        exclude = ['created_at', 'updated_at']

netcash/migrations/0001_initial.py

+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        
+        # Adding model 'NetcashGateway'
+        db.create_table('netcash_netcashgateway', (
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('name', self.gf('django.db.models.fields.CharField')(default='gateway', max_length=50)),
+            ('slug', self.gf('django.db.models.fields.SlugField')(unique=True, max_length=50, db_index=True)),
+            ('secret', self.gf('django.db.models.fields.CharField')(max_length=40)),
+            ('netcash_ip', self.gf('django.db.models.fields.IPAddressField')(max_length=15, null=True, blank=True)),
+        ))
+        db.send_create_signal('netcash', ['NetcashGateway'])
+
+        # Adding model 'NetcashOrder'
+        db.create_table('netcash_netcashorder', (
+            ('Reference', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('TransactionAccepted', self.gf('django.db.models.fields.NullBooleanField')(default=None, null=True, blank=True)),
+            ('CardHolderIpAddr', self.gf('django.db.models.fields.IPAddressField')(max_length=15, null=True, blank=True)),
+            ('Amount', self.gf('django.db.models.fields.DecimalField')(null=True, max_digits=12, decimal_places=2, blank=True)),
+            ('Reason', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)),
+            ('RETC', self.gf('django.db.models.fields.CharField')(max_length=25, null=True, blank=True)),
+            ('Extra1', self.gf('django.db.models.fields.CharField')(max_length=50, null=True, blank=True)),
+            ('Extra2', self.gf('django.db.models.fields.CharField')(max_length=50, null=True, blank=True)),
+            ('Extra3', self.gf('django.db.models.fields.CharField')(max_length=50, null=True, blank=True)),
+            ('created_at', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)),
+            ('updated_at', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)),
+            ('gateway', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['netcash.NetcashGateway'], null=True, blank=True)),
+        ))
+        db.send_create_signal('netcash', ['NetcashOrder'])
+
+
+    def backwards(self, orm):
+        
+        # Deleting model 'NetcashGateway'
+        db.delete_table('netcash_netcashgateway')
+
+        # Deleting model 'NetcashOrder'
+        db.delete_table('netcash_netcashorder')
+
+
+    models = {
+        'netcash.netcashgateway': {
+            'Meta': {'object_name': 'NetcashGateway'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'default': "'gateway'", 'max_length': '50'}),
+            'netcash_ip': ('django.db.models.fields.IPAddressField', [], {'max_length': '15', 'null': 'True', 'blank': 'True'}),
+            'secret': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50', 'db_index': 'True'})
+        },
+        'netcash.netcashorder': {
+            'Amount': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '12', 'decimal_places': '2', 'blank': 'True'}),
+            'CardHolderIpAddr': ('django.db.models.fields.IPAddressField', [], {'max_length': '15', 'null': 'True', 'blank': 'True'}),
+            'Extra1': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
+            'Extra2': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
+            'Extra3': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
+            'Meta': {'object_name': 'NetcashOrder'},
+            'RETC': ('django.db.models.fields.CharField', [], {'max_length': '25', 'null': 'True', 'blank': 'True'}),
+            'Reason': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'Reference': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'TransactionAccepted': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'gateway': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['netcash.NetcashGateway']", 'null': 'True', 'blank': 'True'}),
+            'updated_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'})
+        }
+    }
+
+    complete_apps = ['netcash']

netcash/migrations/0002_add_initial_gateway.py

+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import DataMigration
+from django.db import models
+
+from netcash.models import random_secret
+
+class Migration(DataMigration):
+
+    def forwards(self, orm):
+        gateway = orm.NetcashGateway.objects.create()
+        gateway.secret = random_secret()
+        gateway.slug = 'gateway'
+        gateway.save()
+        "Write your forwards methods here."
+
+
+    def backwards(self, orm):
+        "Write your backwards methods here."
+
+
+    models = {
+        'netcash.netcashgateway': {
+            'Meta': {'object_name': 'NetcashGateway'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'default': "'gateway'", 'max_length': '50'}),
+            'netcash_ip': ('django.db.models.fields.IPAddressField', [], {'max_length': '15', 'null': 'True', 'blank': 'True'}),
+            'secret': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50', 'db_index': 'True'})
+        },
+        'netcash.netcashorder': {
+            'Amount': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '12', 'decimal_places': '2', 'blank': 'True'}),
+            'CardHolderIpAddr': ('django.db.models.fields.IPAddressField', [], {'max_length': '15', 'null': 'True', 'blank': 'True'}),
+            'Extra1': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
+            'Extra2': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
+            'Extra3': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
+            'Meta': {'object_name': 'NetcashOrder'},
+            'RETC': ('django.db.models.fields.CharField', [], {'max_length': '25', 'null': 'True', 'blank': 'True'}),
+            'Reason': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'Reference': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'TransactionAccepted': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'gateway': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['netcash.NetcashGateway']", 'null': 'True', 'blank': 'True'}),
+            'updated_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'})
+        }
+    }
+
+    complete_apps = ['netcash']

netcash/migrations/0003_remove_slug.py

+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        
+        # Deleting field 'NetcashGateway.slug'
+        db.delete_column('netcash_netcashgateway', 'slug')
+
+
+    def backwards(self, orm):
+        
+        # We cannot add back in field 'NetcashGateway.slug'
+        raise RuntimeError(
+            "Cannot reverse this migration. 'NetcashGateway.slug' and its values cannot be restored.")
+
+
+    models = {
+        'netcash.netcashgateway': {
+            'Meta': {'object_name': 'NetcashGateway'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'default': "'gateway'", 'max_length': '50'}),
+            'netcash_ip': ('django.db.models.fields.IPAddressField', [], {'max_length': '15', 'null': 'True', 'blank': 'True'}),
+            'secret': ('django.db.models.fields.CharField', [], {'max_length': '40'})
+        },
+        'netcash.netcashorder': {
+            'Amount': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '12', 'decimal_places': '2', 'blank': 'True'}),
+            'CardHolderIpAddr': ('django.db.models.fields.IPAddressField', [], {'max_length': '15', 'null': 'True', 'blank': 'True'}),
+            'Extra1': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
+            'Extra2': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
+            'Extra3': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
+            'Meta': {'object_name': 'NetcashOrder'},
+            'RETC': ('django.db.models.fields.CharField', [], {'max_length': '25', 'null': 'True', 'blank': 'True'}),
+            'Reason': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'Reference': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'TransactionAccepted': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'gateway': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['netcash.NetcashGateway']", 'null': 'True', 'blank': 'True'}),
+            'updated_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'})
+        }
+    }
+
+    complete_apps = ['netcash']

netcash/migrations/__init__.py

Empty file added.

netcash/models.py

     """ Secure settings for payment gateway """
 
     name = models.CharField(max_length=50, default='gateway')
-    slug = models.SlugField('Public gateway URL name', unique=True,
-                            help_text='used for Accept URL and Reject URL')
-
     secret = models.CharField(max_length=40, editable=False,
                               help_text='Data URL secret')
 
         return data_url(self)
 
     def accept_url(self):
-        return full_url(reverse('netcash_accept', args=[self.slug]))
+        return full_url(reverse('netcash_accept'))
 
     def reject_url(self):
-        return full_url(reverse('netcash_reject', args=[self.slug]))
+        return full_url(reverse('netcash_reject'))
 
     def save(self, *args, **kwargs):
         if not self.secret:
 
     TransactionAccepted = models.NullBooleanField(default=None)
     CardHolderIpAddr = models.IPAddressField(u'Cardholder ip address', null=True, blank=True)
-    Amount = models.DecimalField(min_value=0, max_decimal_places=2, max_digits=12, null=True, blank=True)
+    Amount = models.DecimalField(decimal_places=2, max_digits=12, null=True, blank=True)
 
     Reason = models.CharField(max_length=255, null=True, blank=True,
                        help_text='If the transaction failed this is the reason '
 
     # utility fields
     created_at = models.DateTimeField(default=datetime.now)
+    updated_at = models.DateTimeField(default=datetime.now)
     gateway = models.ForeignKey(NetcashGateway, null=True, blank=True)
 
+    def save(self, *args, **kwargs):
+        self.updated_at = datetime.now()
+        return super(NetcashOrder, self).save(*args, **kwargs)
+
     def __unicode__(self):
         return u'NetCash #%s (%s)' % (self.pk, self.created_at)
 
+from django.test import TestCase
+from django.core.urlresolvers import reverse
+
+from netcash.models import NetcashGateway, NetcashOrder
+from netcash.forms import NetcashForm
+
+class NetcashTest(TestCase):
+
+    def setUp(self):
+        self.gateway = NetcashGateway.objects.all()[0]
+        self.gateway_url = reverse('netcash_data', args = [self.gateway.secret])
+
+    def test_invalid_data_request(self):
+        response = self.client.post(self.gateway_url, {})
+        self.assertEqual(response.status_code, 404)
+        self.assertEqual(NetcashOrder.objects.count(), 0)
+
+    def test_invalid_gateway(self):
+        response = self.client.post(self.gateway_url+'123', {})
+        self.assertEqual(response.status_code, 404)
+        self.assertEqual(NetcashOrder.objects.count(), 0)
+
+    def test_data_request(self):
+
+        # this will create an order
+        form = NetcashForm(initial={
+            'p3': 'description',
+            'p4': 100,
+        })
+        reference = str(form.order.pk)
+
+        # ... user posts the form and pays the purchase on netcash.co.za site
+        # ... then Netcash sends a notification to our Data URL
+
+        response = self.client.post(self.gateway_url, {
+           'TransactionAccepted': 'true',
+           'Reference': reference,
+           'RETC': '123',
+           'Reason': '',
+           'Amount': '100'
+        })
+
+        self.assertEqual(response.status_code, 200)
+
+        order = NetcashOrder.objects.get(pk=reference)
+        self.assertEqual(order.TransactionAccepted, True)
+        self.assertEqual(order.Amount, 100)
+        self.assertEqual(order.RETC, '123')
+        self.assertEqual(order.gateway, self.gateway)
 
 urlpatterns = patterns('netcash.views',
     url(
-          r'^data/(?P<secret>.+)/$',
+          r'^data/(?P<secret>.+)$',
           'data_handler',
           name='netcash_data'
     ),

netcash_tests/__init__.py

Empty file added.

netcash_tests/manage.py

+#!/usr/bin/env python
+from django.core.management import execute_manager
+try:
+    import settings # Assumed to be in the same directory.
+except ImportError:
+    import sys
+    sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
+    sys.exit(1)
+
+if __name__ == "__main__":
+    execute_manager(settings)

netcash_tests/runtests.py

+#!/usr/bin/env python
+import sys
+from django.core.management import execute_manager
+try:
+    import settings # Assumed to be in the same directory.
+except ImportError:
+    sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
+    sys.exit(1)
+
+sys.argv.insert(1, 'test')
+
+if len(sys.argv) == 2:
+    sys.argv.append('netcash')
+
+if __name__ == "__main__":
+    execute_manager(settings)
+

netcash_tests/settings.py

+# Django settings for merchant_api_tests project.
+import os, sys
+PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__))
+join = lambda p: os.path.abspath(os.path.join(PROJECT_ROOT, p))
+
+# use netcash from the folder above, not the installed version
+sys.path.insert(0, join('..'))
+
+# ===== netcash settings ====
+
+NETCASH_USERNAME = 'testuser'
+NETCASH_PASSWORD = 'testpass'
+NETCASH_PIN = '654321'
+NETCASH_TERMINAL_NUMBER = '0291'
+
+# ===========================
+
+DEBUG = True
+TEMPLATE_DEBUG = DEBUG
+
+ADMINS = ()
+MANAGERS = ADMINS
+
+DATABASES = {
+    'default': {
+        'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
+        'NAME': join('db.sqlite'),                      # Or path to database file if using sqlite3.
+    }
+}
+
+TIME_ZONE = 'America/Chicago'
+LANGUAGE_CODE = 'en-us'
+SITE_ID = 1
+USE_I18N = True
+USE_L10N = True
+MEDIA_ROOT = join('media')
+MEDIA_URL = '/media/'
+ADMIN_MEDIA_PREFIX = '/media/admin/'
+SECRET_KEY = '5mcs97ar-(nnxhfk67290+0^sr!e(ax=x$2-!8dqy25ff-l1*a='
+TEMPLATE_LOADERS = (
+    'django.template.loaders.filesystem.Loader',
+    'django.template.loaders.app_directories.Loader',
+)
+
+MIDDLEWARE_CLASSES = (
+    'django.middleware.common.CommonMiddleware',
+    'django.contrib.sessions.middleware.SessionMiddleware',
+    'django.middleware.csrf.CsrfViewMiddleware',
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
+    'django.contrib.messages.middleware.MessageMiddleware',
+)
+
+ROOT_URLCONF = 'urls'
+TEMPLATE_DIRS = (join('templates'),)
+
+INSTALLED_APPS = [
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.sessions',
+    'django.contrib.sites',
+    'django.contrib.admin',
+    'netcash',
+]
+
+# test migrations if South is available
+try:
+    import south
+    if 'south' not in INSTALLED_APPS:
+        INSTALLED_APPS += ['south']
+except ImportError:
+    pass

netcash_tests/templates/404.html

Empty file added.

netcash_tests/urls.py

+from django.conf.urls.defaults import *
+from django.contrib import admin
+admin.autodiscover()
+
+urlpatterns = patterns('',
+    (r'^admin/', include(admin.site.urls)),
+    (r'^netcash/', include('netcash.urls')),
+)