Merge branch 'relaunch' of github.com:Gluejar/regluit into relaunch
Conflicts: core/signals.pypull/1/head
|
@ -0,0 +1,232 @@
|
|||
# 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 'Ebook.unglued'
|
||||
db.delete_column('core_ebook', 'unglued')
|
||||
|
||||
# Adding field 'Edition.unglued'
|
||||
db.add_column('core_edition', 'unglued', self.gf('django.db.models.fields.BooleanField')(default=False), keep_default=False)
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
|
||||
# Adding field 'Ebook.unglued'
|
||||
db.add_column('core_ebook', 'unglued', self.gf('django.db.models.fields.BooleanField')(default=False), keep_default=False)
|
||||
|
||||
# Deleting field 'Edition.unglued'
|
||||
db.delete_column('core_edition', 'unglued')
|
||||
|
||||
|
||||
models = {
|
||||
'auth.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
'auth.permission': {
|
||||
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
|
||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||
},
|
||||
'auth.user': {
|
||||
'Meta': {'object_name': 'User'},
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
|
||||
},
|
||||
'contenttypes.contenttype': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
|
||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||
},
|
||||
'core.author': {
|
||||
'Meta': {'object_name': 'Author'},
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'editions': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'authors'", 'symmetrical': 'False', 'to': "orm['core.Edition']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '500'})
|
||||
},
|
||||
'core.campaign': {
|
||||
'Meta': {'object_name': 'Campaign'},
|
||||
'activated': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
|
||||
'amazon_receiver': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'deadline': ('django.db.models.fields.DateTimeField', [], {}),
|
||||
'description': ('ckeditor.fields.RichTextField', [], {'null': 'True'}),
|
||||
'details': ('ckeditor.fields.RichTextField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'edition': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'campaigns'", 'null': 'True', 'to': "orm['core.Edition']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'left': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'license': ('django.db.models.fields.CharField', [], {'default': "'CC BY-NC-ND'", 'max_length': '255'}),
|
||||
'managers': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'campaigns'", 'symmetrical': 'False', 'to': "orm['auth.User']"}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '500', 'null': 'True'}),
|
||||
'paypal_receiver': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
|
||||
'status': ('django.db.models.fields.CharField', [], {'default': "'INITIALIZED'", 'max_length': '15', 'null': 'True'}),
|
||||
'target': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'campaigns'", 'to': "orm['core.Work']"})
|
||||
},
|
||||
'core.campaignaction': {
|
||||
'Meta': {'object_name': 'CampaignAction'},
|
||||
'campaign': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'actions'", 'to': "orm['core.Campaign']"}),
|
||||
'comment': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'type': ('django.db.models.fields.CharField', [], {'max_length': '15'})
|
||||
},
|
||||
'core.celerytask': {
|
||||
'Meta': {'object_name': 'CeleryTask'},
|
||||
'active': ('django.db.models.fields.NullBooleanField', [], {'default': 'True', 'null': 'True', 'blank': 'True'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 9, 10, 14, 9, 45, 330452)', 'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'description': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'null': 'True'}),
|
||||
'function_args': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
|
||||
'function_name': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'task_id': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'tasks'", 'null': 'True', 'to': "orm['auth.User']"})
|
||||
},
|
||||
'core.claim': {
|
||||
'Meta': {'object_name': 'Claim'},
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'rights_holder': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'claim'", 'to': "orm['core.RightsHolder']"}),
|
||||
'status': ('django.db.models.fields.CharField', [], {'default': "'pending'", 'max_length': '7'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'claim'", 'to': "orm['auth.User']"}),
|
||||
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'claim'", 'to': "orm['core.Work']"})
|
||||
},
|
||||
'core.ebook': {
|
||||
'Meta': {'object_name': 'Ebook'},
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'edition': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ebooks'", 'to': "orm['core.Edition']"}),
|
||||
'format': ('django.db.models.fields.CharField', [], {'max_length': '25'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'provider': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
|
||||
'rights': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
|
||||
'url': ('django.db.models.fields.URLField', [], {'max_length': '1024'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'})
|
||||
},
|
||||
'core.edition': {
|
||||
'Meta': {'object_name': 'Edition'},
|
||||
'cover_image': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'public_domain': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'publication_date': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
|
||||
'publisher': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'title': ('django.db.models.fields.CharField', [], {'max_length': '1000'}),
|
||||
'unglued': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'editions'", 'null': 'True', 'to': "orm['core.Work']"})
|
||||
},
|
||||
'core.identifier': {
|
||||
'Meta': {'unique_together': "(('type', 'value'),)", 'object_name': 'Identifier'},
|
||||
'edition': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'identifiers'", 'null': 'True', 'to': "orm['core.Edition']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'type': ('django.db.models.fields.CharField', [], {'max_length': '4'}),
|
||||
'value': ('django.db.models.fields.CharField', [], {'max_length': '31'}),
|
||||
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'identifiers'", 'to': "orm['core.Work']"})
|
||||
},
|
||||
'core.key': {
|
||||
'Meta': {'object_name': 'Key'},
|
||||
'encrypted_value': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'})
|
||||
},
|
||||
'core.premium': {
|
||||
'Meta': {'object_name': 'Premium'},
|
||||
'amount': ('django.db.models.fields.DecimalField', [], {'max_digits': '10', 'decimal_places': '0'}),
|
||||
'campaign': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'premiums'", 'null': 'True', 'to': "orm['core.Campaign']"}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'description': ('django.db.models.fields.TextField', [], {'null': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'limit': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'type': ('django.db.models.fields.CharField', [], {'max_length': '2'})
|
||||
},
|
||||
'core.rightsholder': {
|
||||
'Meta': {'object_name': 'RightsHolder'},
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'email': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'rights_holder'", 'to': "orm['auth.User']"}),
|
||||
'rights_holder_name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||
},
|
||||
'core.subject': {
|
||||
'Meta': {'ordering': "['name']", 'object_name': 'Subject'},
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '200'}),
|
||||
'works': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'subjects'", 'symmetrical': 'False', 'to': "orm['core.Work']"})
|
||||
},
|
||||
'core.userprofile': {
|
||||
'Meta': {'object_name': 'UserProfile'},
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'facebook_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}),
|
||||
'goodreads_auth_secret': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'goodreads_auth_token': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'goodreads_user_id': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
|
||||
'goodreads_user_link': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
|
||||
'goodreads_user_name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
|
||||
'home_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'librarything_id': ('django.db.models.fields.CharField', [], {'max_length': '31', 'blank': 'True'}),
|
||||
'pic_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
|
||||
'tagline': ('django.db.models.fields.CharField', [], {'max_length': '140', 'blank': 'True'}),
|
||||
'twitter_id': ('django.db.models.fields.CharField', [], {'max_length': '15', 'blank': 'True'}),
|
||||
'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'profile'", 'unique': 'True', 'to': "orm['auth.User']"})
|
||||
},
|
||||
'core.waswork': {
|
||||
'Meta': {'object_name': 'WasWork'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'moved': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'}),
|
||||
'was': ('django.db.models.fields.IntegerField', [], {'unique': 'True'}),
|
||||
'work': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Work']"})
|
||||
},
|
||||
'core.wishes': {
|
||||
'Meta': {'object_name': 'Wishes', 'db_table': "'core_wishlist_works'"},
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'source': ('django.db.models.fields.CharField', [], {'max_length': '15', 'blank': 'True'}),
|
||||
'wishlist': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Wishlist']"}),
|
||||
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'wishes'", 'to': "orm['core.Work']"})
|
||||
},
|
||||
'core.wishlist': {
|
||||
'Meta': {'object_name': 'Wishlist'},
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'wishlist'", 'unique': 'True', 'to': "orm['auth.User']"}),
|
||||
'works': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'wishlists'", 'symmetrical': 'False', 'through': "orm['core.Wishes']", 'to': "orm['core.Work']"})
|
||||
},
|
||||
'core.work': {
|
||||
'Meta': {'ordering': "['title']", 'object_name': 'Work'},
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'language': ('django.db.models.fields.CharField', [], {'default': "'en'", 'max_length': '2'}),
|
||||
'num_wishes': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'openlibrary_lookup': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
|
||||
'title': ('django.db.models.fields.CharField', [], {'max_length': '1000'})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['core']
|
|
@ -16,12 +16,6 @@ class Migration(SchemaMigration):
|
|||
))
|
||||
db.send_create_signal('core', ['Badge'])
|
||||
|
||||
# Changing field 'Campaign.description'
|
||||
db.alter_column('core_campaign', 'description', self.gf('ckeditor.fields.RichTextField')(null=True))
|
||||
|
||||
# Changing field 'Campaign.details'
|
||||
db.alter_column('core_campaign', 'details', self.gf('ckeditor.fields.RichTextField')(null=True))
|
||||
|
||||
# Adding M2M table for field badges on 'UserProfile'
|
||||
db.create_table('core_userprofile_badges', (
|
||||
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
|
||||
|
@ -36,12 +30,6 @@ class Migration(SchemaMigration):
|
|||
# Deleting model 'Badge'
|
||||
db.delete_table('core_badge')
|
||||
|
||||
# Changing field 'Campaign.description'
|
||||
db.alter_column('core_campaign', 'description', self.gf('django.db.models.fields.TextField')(null=True))
|
||||
|
||||
# Changing field 'Campaign.details'
|
||||
db.alter_column('core_campaign', 'details', self.gf('django.db.models.fields.TextField')(null=True))
|
||||
|
||||
# Removing M2M table for field badges on 'UserProfile'
|
||||
db.delete_table('core_userprofile_badges')
|
||||
|
||||
|
@ -62,7 +50,7 @@ class Migration(SchemaMigration):
|
|||
},
|
||||
'auth.user': {
|
||||
'Meta': {'object_name': 'User'},
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 9, 17, 15, 54, 11, 910150)'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
|
@ -70,7 +58,7 @@ class Migration(SchemaMigration):
|
|||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 9, 17, 15, 54, 11, 910015)'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
|
@ -126,7 +114,7 @@ class Migration(SchemaMigration):
|
|||
'core.celerytask': {
|
||||
'Meta': {'object_name': 'CeleryTask'},
|
||||
'active': ('django.db.models.fields.NullBooleanField', [], {'default': 'True', 'null': 'True', 'blank': 'True'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 8, 6, 16, 7, 34, 319207)', 'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 9, 17, 15, 54, 11, 494504)', 'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'description': ('django.db.models.fields.CharField', [], {'max_length': '2048', 'null': 'True'}),
|
||||
'function_args': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
|
||||
'function_name': ('django.db.models.fields.CharField', [], {'max_length': '1024'}),
|
||||
|
@ -163,6 +151,7 @@ class Migration(SchemaMigration):
|
|||
'publication_date': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
|
||||
'publisher': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'title': ('django.db.models.fields.CharField', [], {'max_length': '1000'}),
|
||||
'unglued': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'editions'", 'null': 'True', 'to': "orm['core.Work']"})
|
||||
},
|
||||
'core.identifier': {
|
|
@ -16,7 +16,7 @@ from django.utils.translation import ugettext_lazy as _
|
|||
|
||||
import regluit
|
||||
import regluit.core.isbn
|
||||
from regluit.core.signals import successful_campaign, unsuccessful_campaign
|
||||
from regluit.core.signals import successful_campaign, unsuccessful_campaign, wishlist_added
|
||||
import binascii
|
||||
|
||||
from regluit.payment.parameters import TRANSACTION_STATUS_ACTIVE, TRANSACTION_STATUS_COMPLETE, TRANSACTION_STATUS_CANCELED, TRANSACTION_STATUS_ERROR, TRANSACTION_STATUS_FAILED, TRANSACTION_STATUS_INCOMPLETE
|
||||
|
@ -111,6 +111,16 @@ class Premium(models.Model):
|
|||
t_model=get_model('payment','Transaction')
|
||||
return self.limit - t_model.objects.filter(premium=self).count()
|
||||
|
||||
class PledgeExtra:
|
||||
premium=None
|
||||
anonymous=False
|
||||
ack_name=''
|
||||
ack_dedication=''
|
||||
def __init__(self,premium=None,anonymous=False,ack_name='',ack_dedication=''):
|
||||
self.premium=premium
|
||||
self.anonymous=anonymous
|
||||
self.ack_name=ack_name
|
||||
self.ack_dedication=ack_dedication
|
||||
|
||||
class CampaignAction(models.Model):
|
||||
timestamp = models.DateTimeField(auto_now_add=True)
|
||||
|
@ -131,8 +141,8 @@ class CCLicense():
|
|||
)
|
||||
CHOICES = CCCHOICES+(('PD-US', 'Public Domain, US'),)
|
||||
|
||||
@classmethod
|
||||
def url(klass, license):
|
||||
@staticmethod
|
||||
def url(license):
|
||||
if license == 'PD-US':
|
||||
return 'http://creativecommons.org/publicdomain/mark/1.0/'
|
||||
elif license == 'CC0':
|
||||
|
@ -152,8 +162,8 @@ class CCLicense():
|
|||
else:
|
||||
return ''
|
||||
|
||||
@classmethod
|
||||
def badge(klass,license):
|
||||
@staticmethod
|
||||
def badge(license):
|
||||
if license == 'PD-US':
|
||||
return 'https://i.creativecommons.org/p/mark/1.0/88x31.png'
|
||||
elif license == 'CC0':
|
||||
|
@ -264,7 +274,12 @@ class Campaign(models.Model):
|
|||
|
||||
def transactions(self, **kwargs):
|
||||
p = PaymentManager()
|
||||
return p.query_campaign(self, summary=False, campaign_total=True, **kwargs)
|
||||
|
||||
# handle default parameter values
|
||||
kw = {'summary':False, 'campaign_total':True}
|
||||
kw.update(kwargs)
|
||||
|
||||
return p.query_campaign(self, **kw)
|
||||
|
||||
|
||||
def activate(self):
|
||||
|
@ -404,7 +419,7 @@ class Campaign(models.Model):
|
|||
|
||||
def custom_premiums(self):
|
||||
"""returns only the active custom premiums for the Campaign"""
|
||||
return Premium.objects.filter(campaign=self).filter(type='CU')
|
||||
return Premium.objects.filter(campaign=self).filter(type='CU').order_by('amount')
|
||||
|
||||
@property
|
||||
def rightsholder(self):
|
||||
|
@ -439,8 +454,8 @@ class Identifier(models.Model):
|
|||
class Meta:
|
||||
unique_together = ("type", "value")
|
||||
|
||||
@classmethod
|
||||
def get_or_add(klass, type='goog', value=None, edition=None, work=None):
|
||||
@staticmethod
|
||||
def get_or_add( type='goog', value=None, edition=None, work=None):
|
||||
try:
|
||||
return Identifier.objects.get(type=type, value=value)
|
||||
except Identifier.DoesNotExist:
|
||||
|
@ -468,6 +483,9 @@ class Work(models.Model):
|
|||
|
||||
@property
|
||||
def googlebooks_id(self):
|
||||
preferred_id=self.preferred_edition.googlebooks_id
|
||||
if preferred_id:
|
||||
return preferred_id
|
||||
try:
|
||||
return self.identifiers.filter(type='goog')[0].value
|
||||
except IndexError:
|
||||
|
@ -482,6 +500,9 @@ class Work(models.Model):
|
|||
|
||||
@property
|
||||
def goodreads_id(self):
|
||||
preferred_id=self.preferred_edition.goodreads_id
|
||||
if preferred_id:
|
||||
return preferred_id
|
||||
try:
|
||||
return self.identifiers.filter(type='gdrd')[0].value
|
||||
except IndexError:
|
||||
|
@ -633,6 +654,9 @@ class Work(models.Model):
|
|||
self.save()
|
||||
|
||||
def first_isbn_13(self):
|
||||
preferred_id=self.preferred_edition.isbn_13
|
||||
if preferred_id:
|
||||
return preferred_id
|
||||
try:
|
||||
return self.identifiers.filter(type='isbn')[0].value
|
||||
except IndexError:
|
||||
|
@ -645,6 +669,13 @@ class Work(models.Model):
|
|||
return edition.publication_date
|
||||
return ''
|
||||
|
||||
@property
|
||||
def publication_date_year(self):
|
||||
try:
|
||||
return self.publication_date[:4]
|
||||
except IndexError:
|
||||
return 'unknown'
|
||||
|
||||
def __unicode__(self):
|
||||
return self.title
|
||||
|
||||
|
@ -678,6 +709,7 @@ class Edition(models.Model):
|
|||
public_domain = models.NullBooleanField(null=True, blank=True)
|
||||
work = models.ForeignKey("Work", related_name="editions", null=True)
|
||||
cover_image = models.URLField(null=True, blank=True)
|
||||
unglued = models.BooleanField(blank=True)
|
||||
|
||||
def __unicode__(self):
|
||||
if self.isbn_13:
|
||||
|
@ -744,8 +776,8 @@ class Edition(models.Model):
|
|||
except IndexError:
|
||||
return ''
|
||||
|
||||
@classmethod
|
||||
def get_by_isbn(klass, isbn):
|
||||
@staticmethod
|
||||
def get_by_isbn( isbn):
|
||||
if len(isbn)==10:
|
||||
isbn=regluit.core.isbn.convert_10_to_13(isbn)
|
||||
try:
|
||||
|
@ -767,7 +799,6 @@ class Ebook(models.Model):
|
|||
created = models.DateTimeField(auto_now_add=True)
|
||||
format = models.CharField(max_length=25, choices = FORMAT_CHOICES)
|
||||
provider = models.CharField(max_length=255)
|
||||
unglued = models.BooleanField()
|
||||
|
||||
# use 'PD-US', 'CC BY', 'CC BY-NC-SA', 'CC BY-NC-ND', 'CC BY-NC', 'CC BY-ND', 'CC BY-SA', 'CC0'
|
||||
rights = models.CharField(max_length=255, null=True, choices = RIGHTS_CHOICES)
|
||||
|
@ -784,8 +815,8 @@ class Ebook(models.Model):
|
|||
return CCLicense.badge('PD-US')
|
||||
return CCLicense.badge(self.rights)
|
||||
|
||||
@classmethod
|
||||
def infer_provider(klass, url):
|
||||
@staticmethod
|
||||
def infer_provider( url):
|
||||
if not url:
|
||||
return None
|
||||
# provider derived from url. returns provider value. remember to call save() afterward
|
||||
|
|
|
@ -174,6 +174,7 @@ def handle_pledge_modified(sender, transaction=None, up_or_down=None, **kwargs):
|
|||
# we need to know if pledges were modified up or down because Amazon handles the
|
||||
# transactions in different ways, resulting in different user-visible behavior;
|
||||
# we need to set expectations appropriately
|
||||
# up_or_down is 'increased', 'decreased', or 'canceled'
|
||||
if transaction==None or up_or_down==None:
|
||||
return
|
||||
notification.queue([transaction.user], "pledge_status_change", {
|
||||
|
@ -220,7 +221,7 @@ def handle_wishlist_added(supporter, work, **kwargs):
|
|||
"""send notification to confirmed rights holder when someone wishes for their work"""
|
||||
claim = work.claim.filter(status="active")
|
||||
if claim:
|
||||
notification.queue([claim.user], "new_wisher", {
|
||||
notification.queue([claim[0].user], "new_wisher", {
|
||||
'supporter': supporter,
|
||||
'work': work
|
||||
}, True)
|
||||
|
|
|
@ -11,12 +11,14 @@ from django.conf import settings
|
|||
from django.contrib.comments.models import Comment
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.contrib.sites.models import Site
|
||||
from django.http import Http404
|
||||
|
||||
from regluit.payment.models import Transaction
|
||||
from regluit.core.models import Campaign, Work, UnglueitError, Edition, RightsHolder, Claim, Key
|
||||
from regluit.core.models import Campaign, Work, UnglueitError, Edition, RightsHolder, Claim, Key, Ebook
|
||||
from regluit.core import bookloader, models, search, goodreads, librarything
|
||||
from regluit.core import isbn
|
||||
from regluit.payment.parameters import PAYMENT_TYPE_AUTHORIZATION
|
||||
from regluit.frontend.views import safe_get_work
|
||||
|
||||
from regluit.core import tasks
|
||||
from celery.task.sets import TaskSet
|
||||
|
@ -591,3 +593,46 @@ class EncryptedKeyTest(TestCase):
|
|||
# just checking that the encrypted value is not the same as the value
|
||||
self.assertNotEqual(key.encrypted_value, value) # is this always true?
|
||||
|
||||
class SafeGetWorkTest(TestCase):
|
||||
def test_good_work(self):
|
||||
w1 = models.Work()
|
||||
w1.save()
|
||||
w2 = models.Work()
|
||||
w2.save()
|
||||
w2_id = w2.id
|
||||
bookloader.merge_works(w1, w2)
|
||||
|
||||
work = safe_get_work(w1.id)
|
||||
self.assertEqual(work, w1)
|
||||
work = safe_get_work(w2_id)
|
||||
self.assertEqual(work, w1)
|
||||
self.assertRaises(Http404, safe_get_work, 3)
|
||||
|
||||
class DownloadPageTest(TestCase):
|
||||
def test_download_page(self):
|
||||
w = models.Work()
|
||||
w.save()
|
||||
|
||||
e1 = models.Edition()
|
||||
e1.work = w
|
||||
e2 = models.Edition()
|
||||
e2.work = w
|
||||
e1.save()
|
||||
e2.save()
|
||||
|
||||
eb1 = models.Ebook()
|
||||
eb1.url = "http://example.com"
|
||||
eb1.edition = e1
|
||||
eb1.unglued = True
|
||||
|
||||
eb2 = models.Ebook()
|
||||
eb2.url = "http://example2.com"
|
||||
eb2.edition = e2
|
||||
|
||||
eb1.save()
|
||||
eb2.save()
|
||||
|
||||
anon_client = Client()
|
||||
response = anon_client.get("/work/%s/download/" % w.id)
|
||||
self.assertContains(response, "http://example.com", count=2)
|
||||
self.assertContains(response, "http://example2.com", count=2)
|
|
@ -13,7 +13,7 @@ from decimal import Decimal as D
|
|||
from selectable.forms import AutoCompleteSelectMultipleWidget,AutoCompleteSelectMultipleField
|
||||
from selectable.forms import AutoCompleteSelectWidget,AutoCompleteSelectField
|
||||
|
||||
from regluit.core.models import UserProfile, RightsHolder, Claim, Campaign, Premium, Ebook, Edition
|
||||
from regluit.core.models import UserProfile, RightsHolder, Claim, Campaign, Premium, Ebook, Edition, PledgeExtra
|
||||
from regluit.core.lookups import OwnerLookup
|
||||
|
||||
from regluit.utils.localdatetime import now
|
||||
|
@ -45,12 +45,13 @@ class EditionForm(forms.ModelForm):
|
|||
'title': forms.TextInput(attrs={'size': 40}),
|
||||
'add_author': forms.TextInput(attrs={'size': 30}),
|
||||
'add_subject': forms.TextInput(attrs={'size': 30}),
|
||||
'unglued': forms.CheckboxInput(),
|
||||
}
|
||||
|
||||
class EbookForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Ebook
|
||||
exclude =( 'created', 'unglued',)
|
||||
exclude =( 'created',)
|
||||
widgets = {
|
||||
'edition': forms.HiddenInput,
|
||||
'user': forms.HiddenInput,
|
||||
|
@ -296,59 +297,68 @@ class CampaignPledgeForm(forms.Form):
|
|||
)
|
||||
anonymous = forms.BooleanField(required=False, label=_("Don't display my name in the acknowledgements"))
|
||||
ack_name = forms.CharField(required=False, max_length=64, label=_("What name should we display?"))
|
||||
ack_link = forms.URLField(required=False, label=_("Your web site:"))
|
||||
ack_dedication = forms.CharField(required=False, max_length=140, label=_("Your dedication:"))
|
||||
|
||||
premium_id = forms.IntegerField(required=False)
|
||||
premium=None
|
||||
|
||||
@property
|
||||
def pledge_extra(self):
|
||||
return PledgeExtra( anonymous=self.cleaned_data['anonymous'],
|
||||
ack_name=self.cleaned_data['ack_name'],
|
||||
ack_dedication=self.cleaned_data['ack_dedication'],
|
||||
premium=self.premium)
|
||||
|
||||
def clean_preapproval_amount(self):
|
||||
data = self.cleaned_data['preapproval_amount']
|
||||
if data is None:
|
||||
preapproval_amount = self.cleaned_data['preapproval_amount']
|
||||
if preapproval_amount is None:
|
||||
raise forms.ValidationError(_("Please enter a pledge amount."))
|
||||
return data
|
||||
return preapproval_amount
|
||||
|
||||
# should we do validation on the premium_id here?
|
||||
# can see whether it corresponds to a real premium -- do that here?
|
||||
# can also figure out moreover whether it's one of the allowed premiums for that campaign....
|
||||
def clean_premium_id(self):
|
||||
premium_id = self.cleaned_data['premium_id']
|
||||
try:
|
||||
self.premium= Premium.objects.get(id=premium_id)
|
||||
if self.premium.limit>0:
|
||||
if self.premium.limit<=self.premium.premium_count:
|
||||
raise forms.ValidationError(_("Sorry, that premium is fully subscribed."))
|
||||
except Premium.DoesNotExist:
|
||||
raise forms.ValidationError(_("Sorry, that premium is not valid."))
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = self.cleaned_data
|
||||
# check on whether the preapproval amount is < amount for premium tier. If so, put an error message
|
||||
try:
|
||||
preapproval_amount = cleaned_data.get("preapproval_amount")
|
||||
premium_id = int(cleaned_data.get("premium_id"))
|
||||
premium_amount = Premium.objects.get(id=premium_id).amount
|
||||
logger.info("preapproval_amount: {0}, premium_id: {1}, premium_amount:{2}".format(preapproval_amount, premium_id, premium_amount))
|
||||
if preapproval_amount < premium_amount:
|
||||
preapproval_amount = self.cleaned_data.get("preapproval_amount")
|
||||
logger.info("preapproval_amount: {0}, premium_id: {1}, premium_amount:{2}".format(preapproval_amount, self.premium.id, self.premium.amount))
|
||||
if preapproval_amount < self.premium.amount:
|
||||
logger.info("raising form validating error")
|
||||
raise forms.ValidationError(_("Sorry, you must pledge at least $%s to select that premium." % (premium_amount)))
|
||||
try:
|
||||
premium= Premium.objects.get(id=premium_id)
|
||||
if premium.limit>0:
|
||||
if premium.limit<=premium.premium_count:
|
||||
raise forms.ValidationError(_("Sorry, that premium is fully subscribed."))
|
||||
except Premium.DoesNotExist:
|
||||
raise forms.ValidationError(_("Sorry, that premium is not valid."))
|
||||
raise forms.ValidationError(_("Sorry, you must pledge at least $%s to select that premium." % (self.premium.amount)))
|
||||
|
||||
except Exception, e:
|
||||
if isinstance(e, forms.ValidationError):
|
||||
raise e
|
||||
|
||||
return cleaned_data
|
||||
return self.cleaned_data
|
||||
|
||||
class DonateForm(forms.Form):
|
||||
donation_amount = forms.DecimalField(
|
||||
class CCForm(forms.Form):
|
||||
username = forms.CharField(max_length=30, required=True )
|
||||
work_id = forms.IntegerField(required=False, widget=forms.HiddenInput() )
|
||||
stripe_token = forms.CharField(required=False, widget=forms.HiddenInput())
|
||||
preapproval_amount= forms.DecimalField(
|
||||
required=False,
|
||||
min_value=D('1.00'),
|
||||
max_value=D('100000.00'),
|
||||
decimal_places=2,
|
||||
label="Donation",
|
||||
label="Pledge",
|
||||
)
|
||||
anonymous = forms.BooleanField(required=False, label=_("Don't display my username in the donors' list"))
|
||||
retain_cc_info = forms.BooleanField(required=False, initial=True, label=_("Keep my credit card on record"))
|
||||
|
||||
class DonateForm(forms.Form):
|
||||
preapproval_amount = forms.DecimalField( widget=forms.HiddenInput() )
|
||||
username = forms.CharField(max_length=30, required=True, widget=forms.HiddenInput() )
|
||||
work_id = forms.IntegerField(required=False, widget=forms.HiddenInput() )
|
||||
title = forms.CharField(max_length=30, required=False, widget=forms.HiddenInput() )
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = self.cleaned_data
|
||||
return cleaned_data
|
||||
|
||||
class GoodreadsShelfLoadingForm(forms.Form):
|
||||
goodreads_shelf_name_number = forms.CharField(widget=forms.Select(choices=(
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="nonlightbox">
|
||||
<div id="lightbox_content">
|
||||
<div class="clearfix about_page">
|
||||
<p>
|
||||
Do you have a <b>book you love so much</b> you'd like to give it to the world? These are the books that <span class="ungluer"></span> is helping to give to the world, right now.
|
||||
</p>
|
||||
|
@ -13,3 +19,9 @@
|
|||
<p class="last">
|
||||
What if you could <b>unglue it</b>?
|
||||
</p>
|
||||
|
||||
{% include "about_lightbox_footer.html" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -1,3 +1,9 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="nonlightbox">
|
||||
<div id="lightbox_content">
|
||||
<div class="clearfix about_page">
|
||||
<p>
|
||||
Do you have a <b>book you love so much</b> you'd like to give it to the world? At Unglue.it, you can.
|
||||
</p>
|
||||
|
@ -13,3 +19,9 @@
|
|||
<p class="last">
|
||||
What if you could <b>unglue it</b>?
|
||||
</p>
|
||||
|
||||
{% include "about_lightbox_footer.html" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -0,0 +1,8 @@
|
|||
<div class="right_border"><a href="{{ faqurl }}">Read the FAQ</a></div>
|
||||
<div class="right_border"><a href="{% url faq_location 'rightsholders' %}">Read the FAQ for authors and publishers</a></div>
|
||||
<div class="right_border"><a href="{% url press %}">Our press coverage</a></div>
|
||||
{% if not user.is_authenticated %}
|
||||
<div class="signuptoday"><a href="{% url registration_register %}">Sign up today</a></div>
|
||||
{% else %}
|
||||
<div class="signuptoday"><a href="{% url campaign_list 'ending' %}">Our campaigns</a></div>
|
||||
{% endif %}
|
|
@ -1,3 +1,9 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="nonlightbox">
|
||||
<div id="lightbox_content">
|
||||
<div class="clearfix about_page">
|
||||
<p>
|
||||
<b>What if you could</b> give a book to everyone on earth? Get an ebook and read it on any device, in any format, forever? Give an ebook to your library, for them to share? Own DRM-free ebooks, legally? Read free ebooks, and know their creators had been fairly paid?
|
||||
</p>
|
||||
|
@ -13,3 +19,9 @@
|
|||
<p class="last">
|
||||
What if you could <b>unglue it</b>?
|
||||
</p>
|
||||
|
||||
{% include "about_lightbox_footer.html" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -1,3 +1,9 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="nonlightbox">
|
||||
<div id="lightbox_content">
|
||||
<div class="clearfix about_page">
|
||||
<p>
|
||||
Imagine if <b>you could give your favorite book</b> to everyone on earth. Imagine if they all had a copy that they could read anywhere, anytime, on their favorite devices, with no DRM. That they could share with their friends, freely and legally. And imagine that your favorite book's creators still got paid.
|
||||
</p>
|
||||
|
@ -13,3 +19,9 @@
|
|||
<p class="last">
|
||||
What if you could <b>unglue it</b>?
|
||||
</p>
|
||||
|
||||
{% include "about_lightbox_footer.html" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -1,3 +1,9 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="nonlightbox">
|
||||
<div id="lightbox_content">
|
||||
<div class="clearfix about_page">
|
||||
<p>
|
||||
Imagine if <b>you could give your favorite book</b> to everyone on earth. Imagine if they all had a copy that they could read anywhere, anytime, on their favorite devices, with no DRM. That they could share with their friends, freely and legally. And imagine that your favorite book's creators still got paid.
|
||||
</p>
|
||||
|
@ -13,3 +19,9 @@
|
|||
<p class="last">
|
||||
What if you could <b>unglue it</b>?
|
||||
</p>
|
||||
|
||||
{% include "about_lightbox_footer.html" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -1,4 +1,10 @@
|
|||
<p>
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="nonlightbox">
|
||||
<div id="lightbox_content">
|
||||
<div class="clearfix about_page">
|
||||
<p>
|
||||
Do you have a <b>book you love so much</b> you'd like to give it to the world? These are the books that <span class="ungluer"></span> wants to give to the world.
|
||||
</p>
|
||||
<p>
|
||||
|
@ -13,3 +19,9 @@
|
|||
<p class="last">
|
||||
What if you could <b>unglue it</b>?
|
||||
</p>
|
||||
|
||||
{% include "about_lightbox_footer.html" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -1,3 +1,9 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="nonlightbox">
|
||||
<div id="lightbox_content">
|
||||
<div class="clearfix about_page">
|
||||
<p>
|
||||
Do you have a <b>book you love so much</b> you'd like to give it to the world?
|
||||
</p>
|
||||
|
@ -13,3 +19,9 @@
|
|||
<p class="last">
|
||||
What if you could <b>unglue it</b>?
|
||||
</p>
|
||||
|
||||
{% include "about_lightbox_footer.html" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -22,7 +22,7 @@
|
|||
{% block base_js %}
|
||||
<script type="text/javascript" src="{{ jquery_home }}"></script>
|
||||
{% endblock %}
|
||||
<script type="text/javascript" src="/static/js/expand_about.js"></script>
|
||||
<script type="text/javascript" src="/static/js/hijax.js"></script>
|
||||
{% block extra_js %}
|
||||
{% endblock %}
|
||||
<script type="text/javascript" src="/static/js/watermark_init.js"></script>
|
||||
|
@ -50,16 +50,8 @@
|
|||
<div id="about_expandable">
|
||||
<div id="about_collapser" class="collapser_x">X</div>
|
||||
<div id="lightbox">
|
||||
<!-- content will be inserted here by expand_about.js -->
|
||||
<!-- content will be inserted here by hijax.js -->
|
||||
</div>
|
||||
<div class="right_border"><a href="{{ faqurl }}">Read the FAQ</a></div>
|
||||
<div class="right_border"><a href="{% url faq_location 'rightsholders' %}">Read the FAQ for authors and publishers</a></div>
|
||||
<div class="right_border"><a href="{% url press %}">Our press coverage</a></div>
|
||||
{% if not user.is_authenticated %}
|
||||
<div class="signuptoday"><a href="{% url registration_register %}">Sign up today</a></div>
|
||||
{% else %}
|
||||
<div class="signuptoday"><a href="{% url campaign_list 'ending' %}">Our campaigns</a></div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div id="js-page-wrap">
|
||||
|
@ -97,7 +89,7 @@
|
|||
{% else %}
|
||||
<li class="first"><a href="{% url auth_login %}?next={% firstof request.path '/' %}"><span>Sign In</span></a></li>
|
||||
{% endif %}
|
||||
<li class="about_expander" id="about_main"><a href="#"><span>About</span></a></li>
|
||||
<li><a href="/about/main/" class="hijax"><span>About</span></a></li>
|
||||
<li><a href="{{ landingurl }}"><span>Home</span></a></li>
|
||||
{% if not user.is_authenticated %}
|
||||
<li class="last" id="expander"><a href="{% url registration_register %}"><span>sign up</span></a></li>
|
||||
|
@ -107,8 +99,8 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="preview">
|
||||
Amazon has forced us to suspend crowdfunding. The <a href="http://wp.me/p2omBl-3B">full story is on our blog</a>! The <a href="/work/81724/">first unglued book</a> is almost ready.
|
||||
<div class="launch_top">
|
||||
It's here: our first unglued edition. You can now <a href="/work/81724/">download <I>Oral Literature in Africa</I></a>.<br /><br />Campaigns to unglue more books <a href="http://blog.unglue.it/2012/09/13/update-on-unglue-it-relaunch/">will relaunch soon</a>.
|
||||
</div>
|
||||
{% block topsection %}{% endblock %}
|
||||
{% block content %}{% endblock %}
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
<p><b>On:</b> {{ deadline|date:"M d, Y" }}</p>
|
||||
<p><b>Raised:</b> {{ work.last_campaign.current_total|floatformat:0|intcomma }}</p>
|
||||
{% if first_ebook %}
|
||||
<a href="{{ work.ebooks.0.url }}"><div class="read_itbutton"><span>Read it Now</span></div></a>
|
||||
<a href="{% url download workid %}" class="hijax"><div class="read_itbutton"><span>Read it Now</span></div></a>
|
||||
{% else %}
|
||||
<div class="read_itbutton"><span>Coming Soon</span></div>
|
||||
{% endif %}
|
||||
|
|
|
@ -2,22 +2,6 @@
|
|||
{% load humanize %}
|
||||
{% url privacy as privacyurl %}
|
||||
|
||||
{% block extra_head %}
|
||||
<script src ='https://www.paypalobjects.com/js/external/dg.js' type='text/javascript'></script>
|
||||
|
||||
{% if embedded %}
|
||||
<script>
|
||||
//
|
||||
// This page is also used as the completion URL for purchases. Close the lightbox
|
||||
// whenever we are opened
|
||||
//
|
||||
dgFlow = top.dgFlow || top.opener.top.dgFlow;
|
||||
dgFlow.closeFlow();
|
||||
top.close()
|
||||
</script>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
|
@ -52,13 +36,5 @@
|
|||
<p>No associated transactions</p>
|
||||
{% endif %}
|
||||
|
||||
{% if embedded %}
|
||||
<script>
|
||||
//
|
||||
// This has to be included AFTER the definition of the button
|
||||
//
|
||||
var dgFlow = new PAYPAL.apps.DGFlow({ trigger: 'pledgeBtn' });
|
||||
</script>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
{% extends "basedocumentation.html" %}
|
||||
|
||||
{% block title %}Donate{% endblock %}
|
||||
|
||||
{% block extra_extra_head %}
|
||||
{% endblock %}
|
||||
|
||||
{% block doccontent %}
|
||||
|
||||
<h2>Donate</h2>
|
||||
<p>Wonderful: We're glad that you would like to donate to our <b>partnering non-profit</b>.</p>
|
||||
|
||||
<form method="post" action="#">
|
||||
{% csrf_token %}
|
||||
{{form.as_p}}
|
||||
<input type="submit" value="Donate" />
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
{% extends "basepledge.html" %}
|
||||
{% load humanize %}
|
||||
|
||||
{% block title %}You Have Donation Credits{% endblock %}
|
||||
|
||||
{% block extra_extra_head %}
|
||||
<link type="text/css" rel="stylesheet" href="/static/css/campaign.css" />
|
||||
<link type="text/css" rel="stylesheet" href="/static/css/pledge.css" />
|
||||
|
||||
<script type="text/javascript" src="/static/js/reconcile_pledge.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block doccontent %}
|
||||
<div style="height:15px"></div>
|
||||
|
||||
<div class="jsmodule rounded clearfix">
|
||||
<div class="jsmod-content">
|
||||
|
||||
<div>
|
||||
<h2> Donation Credited </h2>
|
||||
<p>{% if error %}
|
||||
Your donation credit of ${{ envelope.amount }}.{{ envelope.cents }} has already been registered! {% if work %} If you want to contribute more to <a href="{% url work work.id %}">{{ work.title }}</a>, you can! {% endif %}
|
||||
{% else %}
|
||||
Congratulations, your donation credit of ${{ envelope.amount }}.{{ envelope.cents }} has been registered! {% if transaction.campaign %} ${{transaction.amount}} of that had been pledged to {{ transaction.campaign.name }}. If you want to contribute more to <a href="{% url work work.id %}">{{ work.title }}</a>, you can! {% endif %}
|
||||
{% endif %}
|
||||
</p>
|
||||
<!-- sent log: {{ envelope.sent }} -->
|
||||
<!-- error: {{ error }} -->
|
||||
<!-- {{ transaction }} -->
|
||||
<div>
|
||||
<h2> Your donation credits </h2>
|
||||
<p>
|
||||
You have a balance of {{ request.user.credit.balance }} donation credits. <br />
|
||||
You have pledged {{ request.user.credit.pledged }} donation credits to ungluing campaigns.<br />
|
||||
You have {{ request.user.credit.available }} donation credits available to pledge or <a href="{% url donation %}">transfer</a>.<br />
|
||||
</p>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
{% extends "basepledge.html" %}
|
||||
{% load humanize %}
|
||||
|
||||
{% block title %}Please Log in as...{% endblock %}
|
||||
|
||||
{% block extra_extra_head %}
|
||||
<link type="text/css" rel="stylesheet" href="/static/css/campaign.css" />
|
||||
<link type="text/css" rel="stylesheet" href="/static/css/pledge.css" />
|
||||
|
||||
<script type="text/javascript" src="/static/js/reconcile_pledge.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block doccontent %}
|
||||
<div style="height:15px"></div>
|
||||
|
||||
<div class="jsmodule rounded clearfix">
|
||||
<div class="jsmod-content">
|
||||
|
||||
<div><h2>Wrong user for donation credit</h2>
|
||||
<div>
|
||||
<p>Unglue.it would like to process your donation credit, but you are currently logged in as <code>{{request.user.username}}</code>. Your donation credit from {{nonprofit.name}} for ${{ envelope.amount }}.{{ envelope.cents }} is designated for <code>{{ envelope.username }}</code>. Do record your credit, you need to <a href='{% url auth_logout %}?next={{ request.get_full_path|urlencode }}'>log out</a>, and then <a href='{% url auth_login %}?next={{ request.get_full_path|urlencode }}'>log in</a> as <code>{{ envelope.username }}</code>. If you have any problem, don't hesitate to contact <a href="mailto:support@gluejar.com?subject={{ request.get_full_path|urlencode }}">unglue.it support</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% with work.title as title %}
|
||||
{% block title %}
|
||||
— Downloads for {{ work.title }}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script type="text/javascript" src="/static/js/wishlist.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="download_container">
|
||||
<div id="lightbox_content">
|
||||
<div class="border">
|
||||
<h2>Downloads for <I><a href="{% url work work.id %}">{{ work.title }}</a></i></h2>
|
||||
{% if unglued_ebooks %}
|
||||
<div class="unglued">
|
||||
<h3>Read the unglued edition!</h3>
|
||||
<div class="ebook_download">
|
||||
{% for ebook in unglued_ebooks %}
|
||||
{% with ebook.url as url %}
|
||||
<a href="{{ url }}"><img src="{{ ebook.rights_badge }}"></a>
|
||||
<a href="{{ url }}">{{ ebook.format }}</a>
|
||||
{% ifequal ebook.format 'mobi' %} (use for Kindle){% endifequal %}
|
||||
{% ifequal ebook.format 'epub' %} (use for Nook, Apple, Sony){% endifequal %}
|
||||
{% if not forloop.last %}<br /><br />{% endif %}
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if other_ebooks %}
|
||||
<div class="not_unglued">
|
||||
{% if unglued_ebook %}
|
||||
<h4>Other freely available editions</h4>
|
||||
{% else %}
|
||||
<h4>Freely available editions</h4>
|
||||
{% endif %}
|
||||
{% for ebook in other_ebooks %}
|
||||
{% with ebook.url as url %}
|
||||
<a href="{{ url }}"><img src="{{ ebook.rights_badge }}"></a>
|
||||
<a href="{{ url }}">{{ ebook.format }}</a>
|
||||
{% ifequal ebook.format 'mobi' %} (use for Kindle){% endifequal %}
|
||||
{% ifequal ebook.format 'epub' %} (use for Nook, Apple, Sony){% endifequal %}
|
||||
{% if not forloop.last %}<br /><br />{% endif %}
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if unglued_ebooks or other_ebooks %}
|
||||
<div class="border">
|
||||
<h3>How to download</h3>
|
||||
<p>Ebooks you find at Unglue.it may be read on any device, and you're welcome to convert them to whatever electronic format is most useful to you. While we can't cover every possible combination of readers, software, and formats you might use, here's some help for the most common cases.</p>
|
||||
|
||||
<h4>Any device</h4>
|
||||
<p>You may already have an app which reads our ebook formats. If so, when you download the file, you will be given an option to open the file using that app. You're done!</p>
|
||||
|
||||
<p>If this doesn't work, use the instructions below for your device.</p>
|
||||
|
||||
<h4>Android devices</h4>
|
||||
<p class="ebook_download logo"><img src="/static/images/aldiko_logo.png">Aldiko</p>
|
||||
|
||||
<ul>
|
||||
<li><a href="http://www.aldiko.com/download.html">Download the free Aldiko app.</a></li>
|
||||
<li>Download your book from this page using your device's web browser.</li>
|
||||
<li>You can read HTML files right in the browser. For other formats, you will be given the option of opening the file in Aldiko.</li>
|
||||
</ul>
|
||||
{% comment %}
|
||||
this doesn't seem to work. i get only browser and IndieBound reader as options on my phone. perhaps I should recommend IBReader instead?
|
||||
{% endcomment %}
|
||||
|
||||
<h4>iOS devices</h4>
|
||||
<p class="ebook_download logo"><img src="/static/images/ibooks_logo.jpeg">iBooks</p>
|
||||
{% comment %}test{% endcomment %}
|
||||
<ul>
|
||||
<li><a href="http://itunes.apple.com/us/app/ibooks/id364709193?mt=8">Download the free iBooks app</a> from the App Store.</li>
|
||||
<li>Download your book from this page using your device's web browser.</li>
|
||||
<li>You can read HTML files right in the browser. For other formats, you will be given the option of opening the file in iBooks.</li>
|
||||
</ul>
|
||||
|
||||
<h4>PC, Mac, or Linux</h4>
|
||||
<p class="ebook_download logo"><img src="/static/images/calibre_logo.png">Calibre</p>
|
||||
<ul>
|
||||
<li><a href="http://calibre-ebook.com/download">Download the free Calibre app.</a></li>
|
||||
<li>Download your book from this page using your device's web browser.</li>
|
||||
<li>Open the ebook file in Calibre.</li>
|
||||
</ul>
|
||||
|
||||
<h4>Ereaders</h4>
|
||||
<p>If you'd like to read epub files on a Kindle or Kindle Fire, you'll need to follow the "Converting epubs for Kindle" instructions below, and then the instructions here. To read mobi files on a Kindle, or any ebooks on other ereaders, use these instructions.</p>
|
||||
|
||||
<ul>
|
||||
<li>Download the ebook to a laptop or desktop computer.</li>
|
||||
<li>Plug the ereader into your computer with a USB cable.</li>
|
||||
<li>Using the Finder (Mac) or Windows Explorer (Windows), drag and drop the ebook file into the Documents folder on your device. (It may also be called My Documents or My Stuff, depending on your ereader.) If you have downloaded Calibre, you can also use its "Send to Device" button.</li>
|
||||
<li>Eject your device from the Finder or Explorer and disconnect the USB.</li>
|
||||
<li>You may need to reboot your device to see the new book.</li>
|
||||
</ul>
|
||||
|
||||
<h4>Converting epubs for Kindle</h4>
|
||||
<p>Kindles can read plain text, MOBI, PDF, and HTML files. They cannot read epub books (the format unglued ebooks are released in). However, you can convert epubs to mobi files using free software.</p>
|
||||
|
||||
<ul>
|
||||
<li><a href="http://calibre-ebook.com/download">Download the free Calibre app</a> to a desktop or laptop computer.</li>
|
||||
<li>Download your book from this page onto the desktop or laptop.</li>
|
||||
<li>Open the ebook file in Calibre.</li>
|
||||
<li>Convert the epub to a mobi file: select the file; click on "convert books"; and set the output format to mobi.</li>
|
||||
<li>When the conversion is done, put the file on your device using the instructions for ereaders above. If you've set up your Kindle email address at Amazon.com, you can also email it to yourself using the "Connect/share" button, without needing to plug your Kindle in. Calibre will walk you through the process of setting up your Kindle email in Calibre.</li>
|
||||
</ul>
|
||||
|
||||
<h4>Need more help?</h4>
|
||||
<p>Need help with something not listed here? Email us at <a href="mailto:support@gluejar.com?Subject=Ebook%20downloading%20help">support@gluejar.com</a>.</p>
|
||||
</div>
|
||||
{% else %}
|
||||
<p id="content-block">There are no freely available downloads of <I>{{ work.title }}</I> right now. {% if not work in request.user.wishlist.works.all %}Would you like there to be? <a class="add-wishlist"><span class="work_id" id="w{{ work.id }}">Add this book to your wishlist.</span></a>{% else %}Ask your friends to add it to their wishlists!{% endif %}</p>
|
||||
|
||||
<p>If you know of a Creative-Commons-licensed or US public domain edition of this book, you can add it through the <a href="{% url work work.id %}?tab=4">Rights tab of the book page</a>.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% endwith %}
|
|
@ -0,0 +1,77 @@
|
|||
{% extends "basepledge.html" %}
|
||||
{% load humanize %}
|
||||
|
||||
{% block title %}Fund Your Pledge{% endblock %}
|
||||
|
||||
{% block extra_extra_head %}
|
||||
<link type="text/css" rel="stylesheet" href="/static/css/campaign.css" />
|
||||
<link type="text/css" rel="stylesheet" href="/static/css/pledge.css" />
|
||||
<link href="/static/stripe/tag.css" rel="stylesheet" type="text/css">
|
||||
|
||||
<script type="text/javascript" src="/static/stripe/tag.js"></script>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block doccontent %}
|
||||
<div style="height:15px"></div>
|
||||
|
||||
<div class="jsmodule rounded clearfix">
|
||||
<div class="jsmod-content">
|
||||
|
||||
<div><h2>Funding Your Pledge</h2>
|
||||
<div>We're so happy that you've decided to {% if modified %}increase your pledge{% else %}join{% endif %} this campaign. We have two ways we can fund your pledge of ${{preapproval_amount}}.
|
||||
<ol>
|
||||
<li>You can <a href="#donate">make a donation now</a> to {{nonprofit.name}}, a non-profit charity that's working with Unglue.it to give books to the world.</li>
|
||||
<li>You can <a href="#authorize">give us your credit card</a> information now; we'll charge your card only if the campaign succeeds.</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
<div id="donate" class="clearfix">
|
||||
<h3>Donate to Charity</h3>
|
||||
{% if request.user.credit.balance %}You have a donation credit of ${{ request.user.credit.balance }}. The amount that's uncommitted is ${{ request.user.credit.available }}, so you need to add at least ${{ needed }}.{% endif %}
|
||||
<p> Click the button to be sent to the {{nonprofit.name}} donation site. When you complete your donation of at least ${{ needed }}, you'll be sent back to Unglue.it and your pledge will automatically be entered. You can move your donation credit to another campaign or campaigns at any time. {{nonprofit.name}} will hold all funds and earn any interest until a campaign you support succeeds.
|
||||
</p>
|
||||
<div id="donate_charity">
|
||||
|
||||
<form method="GET" action="{{nonprofit.link}}">
|
||||
{{ donate_form.non_field_errors }}
|
||||
{{donate_form}}
|
||||
<input name="donate_submit" type="submit" value="Go Donate!" id="donate_submit" />
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div id="authorize" class="clearfix">
|
||||
<h3>Pledge by Credit Card</h3>
|
||||
<p>Unglue.it uses Stripe to securely manage your Credit Card information.
|
||||
</p>
|
||||
{% if request.user.credit.available %}<p>Although you have ${{request.user.credit.available}} in donation credits, you can't support a campaign with a mixture of credit card pledges and donations.{% endif %}
|
||||
<div id="cc_pledge">
|
||||
<span class="payment-errors"></span>
|
||||
<form action="" method="post" id="payment-form">
|
||||
{% csrf_token %}
|
||||
{{ form.non_field_errors }}
|
||||
{{form}}
|
||||
<payment key="{{STRIPE_PK}}"></payment>
|
||||
<input name="cc_submit" type="submit" value="Verify Credit Card" id="cc_submit" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="application/x-javascript">
|
||||
|
||||
var $j = jQuery.noConflict();
|
||||
console.debug('setting up handlers in stripe.html');
|
||||
|
||||
$j('payment').bind('success.payment', function () {
|
||||
console.debug('success.payment ev');
|
||||
});
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
|
@ -1,30 +1,35 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div style="width: 80%; margin: 15px auto;" class="clearfix"><img style="float:left; margin-right: 10px;" width="100" height="85" src="/static/images/LOCKSSPreserved.png"></span><span>We are partnering with LOCKSS to safeguard unglued ebooks. The LOCKSS Program (Lots of Copies Keep Stuff Safe) is an open source, cooperative, distributed system for preserving and providing access to digital content. For more information, see <a href="http://www.lockss.org/">the LOCKSS web site</a>.
|
||||
</div>
|
||||
<div style="width: 80%; margin: 15px auto;" >
|
||||
{% if ebook %}
|
||||
<I>{{ work.title }}</i><br />
|
||||
{% if authors|length = 1 %}
|
||||
{{ authors.0 }}
|
||||
{% else %}
|
||||
{% if authors|length = 2 %}
|
||||
{{ authors.0 }} and {{ authors.1 }}
|
||||
{% else % }
|
||||
{% for author in authors %}
|
||||
{% if forloop.last %} and {{ author }}{% else %}{{ author }}, {% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<p>
|
||||
<a rel="license" href="{{ work.last_campaign.license_url }}"><img alt="Creative Commons License" style="border-width:0" src="{{ ebook.rights_badge }}" /></a><br />This work is licensed under a <a rel="license" href="{{ work.last_campaign.license_url }}">{{ work.last_campaign.license }} License</a>.
|
||||
</p>
|
||||
<p>
|
||||
<a href="{{ ebook.url }}">Download.</a>
|
||||
</p>
|
||||
{% else %}
|
||||
When an unglued ebook for <I>{{ work.title }}</I> is available, the LOCKSS harvester will be able to download it here.
|
||||
{% endif %}
|
||||
</div>
|
||||
<div style="width: 80%; margin: 15px auto;" class="clearfix">
|
||||
<img style="float:left; margin-right: 10px;" width="100" height="85" src="/static/images/LOCKSSpreserved.png"><span>We are partnering with LOCKSS to safeguard unglued ebooks. The LOCKSS Program (Lots of Copies Keep Stuff Safe) is an open source, cooperative, distributed system for preserving and providing access to digital content. For more information, see <a href="http://www.lockss.org/">the LOCKSS web site</a>.</span>
|
||||
</div>
|
||||
<div style="width: 80%; margin: 15px auto;">
|
||||
{% if ebooks %}
|
||||
{% for ebook in ebooks %}
|
||||
<div>
|
||||
<I>{{ work.title }}</i><br />
|
||||
{% if authors|length = 1 %}
|
||||
{{ authors.0 }}
|
||||
{% else %}
|
||||
{% if authors|length = 2 %}
|
||||
{{ authors.0 }} and {{ authors.1 }}
|
||||
{% else % }
|
||||
{% for author in authors %}
|
||||
{% if forloop.last %} and {{ author }}{% else %}{{ author }}, {% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<p>
|
||||
<a rel="license" href="{{ work.last_campaign.license_url }}"><img alt="Creative Commons License" style="border-width:0" src="{{ ebook.rights_badge }}" /></a><br />This work is licensed under a <a rel="license" href="{{ work.last_campaign.license_url }}">{{ work.last_campaign.license }} License</a>.
|
||||
</p>
|
||||
<p>
|
||||
<a href="{{ ebook.url }}">Download.</a>
|
||||
</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
When an unglued ebook for <I>{{ work.title }}</I> is available, the LOCKSS harvester will be able to download it here.
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -72,7 +72,7 @@ Please fix the following before launching your campaign:
|
|||
<div>
|
||||
<div class="pubinfo">
|
||||
<h3 class="book-author">{{ work.author }}</h3>
|
||||
<h3 class="book-year">{{ pubdate }}</h3>
|
||||
<h3 class="book-year">{{ work.publication_date_year }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -91,7 +91,7 @@ Please fix the following before launching your campaign:
|
|||
{% ifequal campaign_status 'INITIALIZED' %}
|
||||
<a href="{% url work_preview campaign.work.id %}" class="manage" target="_blank">Preview Your Campaign</a>
|
||||
{% else %}
|
||||
<a href="{% url work_preview campaign.work.id %}" class="manage" target="_blank">See Your Campaign</a>
|
||||
<a href="{% url work campaign.work.id %}" class="manage" target="_blank">See Your Campaign</a>
|
||||
{% endifequal %}
|
||||
</div>
|
||||
|
||||
|
@ -111,18 +111,30 @@ Please fix the following before launching your campaign:
|
|||
{% csrf_token %}
|
||||
{{ form.media }}
|
||||
<h3>Select the edition</h3>
|
||||
<p> Please choose the edition that most closely matches the edition to be unglued:</p>
|
||||
<p> Please choose the edition that most closely matches the edition to be unglued. This is the edition whose cover image will display on your book's page. Your unglued edition should be identical to this edition if possible; you should note any differences under Rights Details below.</p>
|
||||
|
||||
{{ form.edition.errors }}{{ form.edition }}
|
||||
{% if campaign.edition %}
|
||||
<p>You can edit the campaign's <a href="{% url rh_edition work.id campaign.edition.id %}">preferred edition</a>.</p>
|
||||
<p>If the details of the edition you've chosen aren't accurate, you can <a href="{% url rh_edition work.id campaign.edition.id %}">edit the edition</a>.</p>
|
||||
{% else %}
|
||||
<p>If none of the existing editions matches what you want to release, you can <a href="{% url rh_edition work.id '' %}">create a new edition</a>.</p>
|
||||
<p>If the edition details are inaccurate (e.g. wrong cover image), go ahead and set up the campaign with that edition anyway. You'll be able to edit the edition details from this page later on.</p>
|
||||
{% endif %}
|
||||
<p>If you don't see an edition that matches what you want to release, you can <a href="{% url rh_edition work.id '' %}">create a new edition</a>.</p>
|
||||
|
||||
<h3>Make Your Pitch</h3>
|
||||
<p>This will be displayed in the Campaign tab for your work. It's your main pitch to supporters. It should include:</p>
|
||||
<p>This will be displayed in the Campaign tab for your work. It's your main pitch to supporters.</p>
|
||||
|
||||
<p>This isn't a dry synopsis in a catalog. It's your chance to be creative, to share your passion for this work, and to inspire readers. A strong pitch:</p>
|
||||
|
||||
<ul class="terms">
|
||||
<li>A synopsis of the work.</li>
|
||||
<li>Introduces the work. What's this book like?</li>
|
||||
<li>Shows why it matters. How will someone or something -- the world, readers, some cause that matters -- be better off if this book is freely available? What kind of impact has the book had already? What will ungluers get out of supporting this campaign?</li>
|
||||
<li>Has visual appeal (photos and/or videos).</li>
|
||||
<li>Defines important but potentially unfamiliar things. What's an ungluing campaign, and why are you running one? Is there anything unusual about the book, its genre, et cetera? For those who aren't already familiar with you (or the author), who are you? Are you offering any particularly great premiums you want to call people's attention to?</li>
|
||||
<li>Gives examples of the author's work. This could be links to your site or places people can find more information about you or the book. You can also add quotes, embed a free sample chapter or story, display images, et cetera. These work samples might be from the campaign book itself, or from your (or the author's) other creative works.</li>
|
||||
<li>Has personality. The writing should be thoroughly proofread but it should have a point of view. This is the place to be conversational, opinionated, funny, quirky -- whatever reflects your style. Be you.</li>
|
||||
<li>Optionally, provides extra incentives (like new or improved premiums) that will kick in if the campaign is unusually successful. Will you do something special when you reach 10% or 50% or 75% of your goal? Or if you reach that milestone before a particular deadline (e.g. in the first week of your campaign)?</li>
|
||||
{% comment %}
|
||||
<li>Hyperlinks for the author(s), publisher making the offer, or for the work itself. <span class="rh_help" id="helpHyperlink">(How do I hyperlink?)</span>
|
||||
<div class="rh_answer" id="helpHyperlink2">
|
||||
<p>Format a hyperlink like this:</p>
|
||||
|
@ -144,23 +156,41 @@ Please fix the following before launching your campaign:
|
|||
<p>If you'd like to change the size of the video, feel free; just don't make it more than 445px wide. Leaving it the default size is fine, too.</p>
|
||||
<p>If you'd like to embed a video from another source, please talk to us. You certainly can; we just want to make sure we've given you the right instructions.</p>
|
||||
</div></li>
|
||||
{% endcomment %}
|
||||
</ul>
|
||||
<p>Make it concise and emotionally appealing. The point here is not to tell ungluers everything about your book; it's to remind them why they love it.</p>
|
||||
<p>Above all, be engaging. The point here is not to tell ungluers everything about your book; it's to remind them why they love it.</p>
|
||||
|
||||
<p>Looking for inspiration? Check out the all-time most-funded projects on crowdfunding sites <a href="http://www.kickstarter.com/discover/most-funded">Kickstarter</a> or <a href="http://www.indiegogo.com/projects?filter_quick=most_funded">IndieGogo</a>, or have a look at <a href="http://www.kickstarter.com/discover/categories/publishing">Kickstarter's Publishing category</a> or <a href="http://www.indiegogo.com/projects?filter_category=Writing">IndieGogo's Writing category</a>.</p>
|
||||
{{ form.description.errors }}{{ form.description }}
|
||||
<h3>Offer details</h3>
|
||||
<p>This will be displayed on the Rights tab for your work. It's the fine print for your offer. For example, if your unglued edition will exclude certain illustrations due to rights issues, or otherwise differ from existing editions, this is the place to disclose that. If your offer doesn't have any fine print, you can leave this blank.</p>
|
||||
<h3>Edition and Rights Details</h3>
|
||||
<p>This will be displayed on the Rights tab for your work. It's the fine print for your campaign. Make sure to disclose any ways the unglued edition will differ from the existing edition; for example:
|
||||
<ul>
|
||||
<li>Any material that may have to be excluded due to permissions issues: illustrations, forewords, etc.</li>
|
||||
<li>Any additional materials that will be included, if not already covered in your pitch -- but we encourage you to cover them there to show supporters the value of ungluing!</li>
|
||||
<li>If the cover image will differ.</li>
|
||||
</ul>
|
||||
|
||||
<p>In short, is there a fact about this campaign that you think would matter to your agent or another publishing wonk, but that no one else is likely to care about? Put it here. If your campaign doesn't have any fine print, you can leave this blank.</p>
|
||||
{{ form.details.errors }}{{ form.details }}
|
||||
|
||||
{% ifnotequal campaign_status 'ACTIVE' %}
|
||||
<h3>Target Price</h3>
|
||||
<p> This is the target price for your campaign. Once you launch the campaign, you won't be able to increase it. The <i>mimimum</i> target is ${{form.minimum_target|intcomma}} .</p>
|
||||
<p>This is the target price for your campaign. The <i>mimimum</i> target is ${{form.minimum_target|intcomma}}.</p>
|
||||
|
||||
<p>Your target should be high enough to compensate you for potential lost future revenue from sales of this edition and reflect well on your brand, but low enough to seem realistic to supporters; people are more likely to back campaigns that they think will succeed.</p>
|
||||
|
||||
<p>Once you launch the campaign, you'll be able to decrease your target, but not increase it.</p>
|
||||
|
||||
<p>Please email us if you want to talk about pricing strategy.</p>
|
||||
|
||||
{{ form.target.errors }}${{ form.target }}
|
||||
<h3>License being offered</h3>
|
||||
<p> This is the license you are offering to use once the campaign succeeds. For more info on the licenses you can use, see <a href="http://creativecommons.org/licenses">Creative Commons: About the Licenses</a>.</p>
|
||||
<p> This is the license you are offering to use once the campaign succeeds. For more information on the licenses you can use, see <a href="http://creativecommons.org/licenses">Creative Commons: About the Licenses</a>. Once your campaign is active, you'll be able to switch to a less restrictive license, but not a more restrictive one.</p>
|
||||
{{ form.license.errors }}{{ form.license }}
|
||||
<h3>Ending date</h3>
|
||||
<p> This is the ending date of your campaign. Once you launch the campaign, you won't be able to change it.
|
||||
The ending date can't be more than six months away- that's a practical limit for credit card authorizations. The <i>latest</i> ending you can choose <i>right now</i> is {{ form.latest_ending }}</p>
|
||||
<p> This is the ending date of your campaign. Once you launch the campaign, you won't be able to change it.</p>
|
||||
|
||||
<p>The ending date can't be more than six months away- that's a practical limit for credit card authorizations. The <i>latest</i> ending you can choose <i>right now</i> is {{ form.latest_ending }}</p>
|
||||
|
||||
{{ form.deadline.errors }}{{ form.deadline }}
|
||||
|
||||
|
@ -171,7 +201,7 @@ Please fix the following before launching your campaign:
|
|||
|
||||
<h3>License being offered</h3>
|
||||
<p>If your campaign succeeds, you will be offering your ebook under a <b><a href="{{campaign.license_url }}">{{ campaign.license }}</a></b> license.</p>
|
||||
<p>During a campaign, you may only change the license to remove restrictions. For more info on the licenses you can use, see <a href="http://creativecommons.org/licenses">Creative Commons: About the Licenses</a>.</p></p>
|
||||
<p>Since your campaign is active, you may only change the license to remove restrictions. For more information on the licenses you can use, see <a href="http://creativecommons.org/licenses">Creative Commons: About the Licenses</a>.</p></p>
|
||||
{{ form.license.errors }}<span>{{ form.license }}</span>
|
||||
<h3>Ending date</h3>
|
||||
<p>The ending date of your campaign is <b>{{ campaign.deadline }}</b>. Your campaign will conclude on this date or when you meet your target price, whichever is earlier. You may not change the ending date of an active campaign.</p>
|
||||
|
@ -179,7 +209,9 @@ Please fix the following before launching your campaign:
|
|||
{% endifnotequal %}
|
||||
|
||||
<h3>Paypal collection address</h3>
|
||||
<p> If your campaign succeeds, the funds raised (less commission and fees) will be deposited in a paypal account bearing this email address.</p>
|
||||
<p>Enter the email address associated with your PayPal account.</p>
|
||||
|
||||
<p>We don't support PayPal yet; if your campaign succeeds we'll be paying you by check. However, we've applied PayPal for a merchant account, and should that application be approved PayPal expects us to have this information.</p>
|
||||
<p>{{ form.paypal_receiver.errors }}{{ form.paypal_receiver }}</p>
|
||||
|
||||
{% ifequal campaign_status 'ACTIVE' %}
|
||||
|
|
|
@ -10,48 +10,76 @@
|
|||
|
||||
{% block doccontent %}
|
||||
{% if edition.pk %}
|
||||
<h2>Edit Edition {{edition.pk}}</h2>
|
||||
<h2>Edit Edition</h2>
|
||||
{% else %}
|
||||
<h2>Create New Edition</h2>
|
||||
{% endif %}
|
||||
|
||||
<p>Title and ISBN 13 are required; the rest is optional, though a cover image link is strongly recommended.</p>
|
||||
<form method="POST" action="#">
|
||||
{% csrf_token %}
|
||||
{{ form.work }}
|
||||
<div>
|
||||
Title: {{ form.title.errors }}{{ form.title }}<br />
|
||||
Authors:<ul>
|
||||
{% if edition.pk and edition.authors %}
|
||||
{% for author in edition.authors.all %}
|
||||
<li>{{ author.name }}</li>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% for author in edition.new_author_names %}
|
||||
<li>{{ author }}<input type="hidden" name="new_author" value="{{ author }}" /></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
Add an Author: {{ form.add_author.errors }}{{ form.add_author }}
|
||||
<input type="submit" name="add_author_submit" value="Add Author" id="submit_author"><br />
|
||||
Language: {{ form.language.errors }}{{ form.language }}<br />
|
||||
ISBN: {{ form.isbn_13.errors }}{{ form.isbn_13 }}<br />
|
||||
Description: <br />{{ form.description.errors }}{{ form.description }}<br />
|
||||
Publisher: {{ form.publisher.errors }}{{ form.publisher }}<br />
|
||||
Publish Date: {{ form.publication_date.errors }}{{ form.publication_date }}<br />
|
||||
Public Domain?: {{ form.public_domain.errors }}{{ form.public_domain }}<br />
|
||||
Subjects:<ul>
|
||||
{% if edition.work.subjects %}
|
||||
{% for subject in edition.work.subjects.all %}
|
||||
<li>{{ subject.name }}</li>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% for new_subject in edition.new_subjects %}
|
||||
<li>{{ new_subject }}<input type="hidden" name="new_subject" value="{{ new_subject }}" /></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
Add a Subject: {{ form.add_subject.errors }}{{ form.add_subject }}
|
||||
<input type="submit" name="add_subject_submit" value="Add Subject" id="submit_subject"><br />
|
||||
Cover Image (Small): {{ form.cover_image.errors }}{{ form.cover_image }}{{ form.cover_image.help_text }}<br />
|
||||
</div>
|
||||
<input type="submit" name="create_new_edition" value="{% if edition.pk %}Save Edits{% else %}Create Edition{% endif %}" id="submit">
|
||||
{% csrf_token %}
|
||||
{{ form.work }}
|
||||
<div>
|
||||
<p><b>Title</b>: {{ form.title.errors }}{{ form.title }}</p>
|
||||
|
||||
<p>
|
||||
<b>Authors</b>:
|
||||
{% if edition.authors or edition.new_author_names %}
|
||||
<ul>
|
||||
{% if edition.pk and edition.authors %}
|
||||
{% for author in edition.authors.all %}
|
||||
<li>{{ author.name }}</li>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% for author in edition.new_author_names %}
|
||||
<li>{{ author }}<input type="hidden" name="new_author" value="{{ author }}" /></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
(None listed)
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
<p><b>Add an Author</b> (<I>Firstname Lastname</I>): {{ form.add_author.errors }}{{ form.add_author }}
|
||||
<input type="submit" name="add_author_submit" value="Add Author" id="submit_author"></p>
|
||||
|
||||
<p><b>Language</b>: {{ form.language.errors }}{{ form.language }}</p>
|
||||
|
||||
<p><b>ISBN-13</b>: {{ form.isbn_13.errors }}{{ form.isbn_13 }}</p>
|
||||
|
||||
<p><b>Description</b>: <br />
|
||||
{{ form.description.errors }}{{ form.description }}<br />
|
||||
(<I>This will appear in the Description tab on the book page. If you create a campaign for this work, the campaign pitch will override this description</i>)</p>
|
||||
|
||||
<p><b>Publisher</b>: {{ form.publisher.errors }}{{ form.publisher }}</p>
|
||||
|
||||
<p><b>Publish Date</b> (<I>four-digit year</I>): {{ form.publication_date.errors }}{{ form.publication_date }}</p>
|
||||
|
||||
<p><b>Public Domain?</b>: {{ form.public_domain.errors }}{{ form.public_domain }}</p>
|
||||
{% comment %}
|
||||
this has been removed since there's no point in exposing subject functionality when we're not doing anything with it -- will just confuse people.
|
||||
|
||||
<p><b>Subjects</b>:
|
||||
<ul>
|
||||
{% if edition.work.subjects %}
|
||||
{% for subject in edition.work.subjects.all %}
|
||||
<li>{{ subject.name }}</li>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% for new_subject in edition.new_subjects %}
|
||||
<li>{{ new_subject }}<input type="hidden" name="new_subject" value="{{ new_subject }}" /></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<b>Add a Subject</b>: {{ form.add_subject.errors }}{{ form.add_subject }}
|
||||
<input type="submit" name="add_subject_submit" value="Add Subject" id="submit_subject"></p>
|
||||
{% endcomment %}
|
||||
<p><b>Cover Image</b>: {{ form.cover_image.errors }}{{ form.cover_image }}{{ form.cover_image.help_text }}<br />
|
||||
(<i>Enter a link to an image, ideally 120px wide. You can't upload an image through this form at this time. If you have a cover image file and you're not sure how to put it online, email us and we can upload it for you.</I>)</p>
|
||||
{% if request.user.is_staff %}
|
||||
<p><b>Is this the unglued edition?</b> {{ form.unglued }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<input type="submit" name="create_new_edition" value="{% if edition.pk %}Save Edits{% else %}Create Edition{% endif %}" id="submit">
|
||||
</form>
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
{% extends "nonprofit_base.html" %}
|
||||
{% load humanize %}
|
||||
|
||||
{% block title %}Donate to {{nonprofit.name}}{% endblock %}
|
||||
|
||||
{% block extra_extra_head %}
|
||||
<link type="text/css" rel="stylesheet" href="/static/css/campaign.css" />
|
||||
<link type="text/css" rel="stylesheet" href="/static/css/pledge.css" />
|
||||
|
||||
<script type="text/javascript" src="/static/js/reconcile_pledge.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block doccontent %}
|
||||
<div style="height:15px"></div>
|
||||
|
||||
<div class="jsmodule rounded clearfix">
|
||||
<div class="jsmod-content">
|
||||
|
||||
<div><h1>Supporting <i>{{ get.title }}</i> through {{nonprofit.name}}</h1>
|
||||
<div><p>{{nonprofit.name}} is cooperating with Unglue.it to make books free to the world. On this page, you can make a donation to us, and we'll hold the money until the ungluing campaign you've selected succeeds. Then we'll make a payment to the rightsholder along side unglue.it. Once you've made the donation, we'll ad donation credits to your unglue.it account, <code>{{get.username}}</code>.
|
||||
</p>
|
||||
<p>
|
||||
To fund the pledge of ${{get.preapproval_amount|intcomma}} that you've made at unglue.it, you'll need to make a donation of at least that amount. You can donate more than that of course- you'll get additional donation credits to use on other campaigns. The larger your donation, the less percentage-wise that gets eaten up by processing fees.
|
||||
</p>
|
||||
<p>Any interest we earn will be used for our public purpose of helping libraries remain relevant into the future. If your unglue.it donation credits don't get used for 5 years, we'll use it in the same way. Your donation to {{nonprofit.name}} is deductible as a Charitable donation in the US.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="donate" class="clearfix">
|
||||
<h2>Donate by Credit Card</h2>
|
||||
<p>{{nonprofit.name}} uses Stripe to securely manage your Credit Card information.
|
||||
</p>
|
||||
<div id="cc_pledge">
|
||||
<form method="POST" action="#">
|
||||
{% csrf_token %}
|
||||
{{ form.non_field_errors }}
|
||||
{{form}}
|
||||
<input name="cc_submit" type="submit" value="Verify Credit Card" id="cc_submit" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
|
@ -0,0 +1,200 @@
|
|||
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" dir="ltr" lang="en-US">
|
||||
|
||||
<head profile="http://gmpg.org/xfn/11">
|
||||
<script type="text/javascript">var _sf_startpt=(new Date()).getTime()</script>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
|
||||
<title>{% block title %}Join the Movement | Library Renewal{% endblock %}</title>
|
||||
|
||||
<link href="http://libraryrenewal.org/wp-content/themes/library-renewal/assets/stylesheets/blueprint/screen.css" media="screen, projection" rel="stylesheet" type="text/css" />
|
||||
<link href="http://libraryrenewal.org/wp-content/themes/library-renewal/assets/stylesheets/blueprint/print.css" media="print" rel="stylesheet" type="text/css" />
|
||||
<!--[if IE]><link href="http://libraryrenewal.org/wp-content/themes/library-renewal/assets/stylesheets/blueprint/ie.css" media="screen, projection" rel="stylesheet" type="text/css" /><![endif]-->
|
||||
<link rel="stylesheet" href="http://libraryrenewal.org/wp-content/themes/library-renewal/style.css" type="text/css" media="screen" />
|
||||
|
||||
|
||||
<link rel="alternate" type="application/rss+xml" title="Library Renewal RSS Feed" href="http://libraryrenewal.org/feed/" />
|
||||
<link rel="alternate" type="application/atom+xml" title="Library Renewal Atom Feed" href="http://libraryrenewal.org/feed/atom/" />
|
||||
<link rel="pingback" href="http://libraryrenewal.org/xmlrpc.php" />
|
||||
|
||||
|
||||
<link rel='stylesheet' id='twitter-pro-css' href='http://libraryrenewal.org/wp-content/plugins/twitter-widget-premium/resources/style.css?ver=3.4.1' type='text/css' media='all' />
|
||||
<link rel='stylesheet' id='sp-gallery-css' href='http://libraryrenewal.org/wp-content/plugins/simple_wp_gallery/resources/sp-gallery.css?ver=3.4.1' type='text/css' media='all' />
|
||||
<link rel='stylesheet' id='NextGEN-css' href='http://libraryrenewal.org/wp-content/plugins/nextgen-gallery/css/nggallery.css?ver=1.0.0' type='text/css' media='screen' />
|
||||
<link rel='stylesheet' id='shutter-css' href='http://libraryrenewal.org/wp-content/plugins/nextgen-gallery/shutter/shutter-reloaded.css?ver=1.3.2' type='text/css' media='screen' />
|
||||
<script type='text/javascript' src='http://libraryrenewal.org/wp-includes/js/jquery/jquery.js?ver=1.7.2'></script>
|
||||
<script type='text/javascript' src='http://libraryrenewal.org/wp-content/plugins/simple_wp_gallery/resources/jquery.cycle.min.js?ver=2.86'></script>
|
||||
<script type='text/javascript'>
|
||||
/* <![CDATA[ */
|
||||
var shutterSettings = {"msgLoading":"L O A D I N G","msgClose":"Click to Close","imageCount":"1"};
|
||||
/* ]]> */
|
||||
</script>
|
||||
<script type='text/javascript' src='http://libraryrenewal.org/wp-content/plugins/nextgen-gallery/shutter/shutter-reloaded.js?ver=1.3.2'></script>
|
||||
<script type='text/javascript' src='http://libraryrenewal.org/wp-content/plugins/nextgen-gallery/js/ngg.slideshow.min.js?ver=1.05'></script>
|
||||
<script type='text/javascript' src='http://libraryrenewal.org/wp-includes/js/comment-reply.js?ver=3.4.1'></script>
|
||||
<link rel="EditURI" type="application/rsd+xml" title="RSD" href="http://libraryrenewal.org/xmlrpc.php?rsd" />
|
||||
<link rel="wlwmanifest" type="application/wlwmanifest+xml" href="http://libraryrenewal.org/wp-includes/wlwmanifest.xml" />
|
||||
<link rel='prev' title='About Us' href='http://libraryrenewal.org/about/' />
|
||||
<link rel='next' title='Events/Research' href='http://libraryrenewal.org/events/' />
|
||||
<meta name="generator" content="WordPress 3.4.1" />
|
||||
|
||||
<!-- All in One SEO Pack 1.6.13.3 by Michael Torbert of Semper Fi Web Design[275,324] -->
|
||||
<link rel="canonical" href="http://libraryrenewal.org/join-the-movement/" />
|
||||
<!-- /all in one seo pack -->
|
||||
<!--[if lt IE 8]><link rel="stylesheet" href="http://libraryrenewal.org/wp-content/plugins/simple_wp_gallery/resources/ie7.css" type="text/css" media="screen"><![endif]-->
|
||||
<!--[if IE 8]><link rel="stylesheet" href="http://libraryrenewal.org/wp-content/plugins/simple_wp_gallery/resources/ie8.css" type="text/css" media="screen"><![endif]-->
|
||||
<link rel="stylesheet" href="http://libraryrenewal.org/wp-content/plugins/socialpop/socialpop-styles.css" type="text/css" media="screen" title="no title" charset="utf-8" />
|
||||
<script type="text/javascript">
|
||||
<!--
|
||||
var sps_style = "popup";
|
||||
var sps_box = "no";
|
||||
var sps_boxfloat = "left";
|
||||
var sps_title = "Share!";
|
||||
var sps_notitle = "no";
|
||||
var sps_holder = "gray";
|
||||
var sps_bgcolor = "ffffff";
|
||||
-->
|
||||
</script>
|
||||
<script src="http://libraryrenewal.org/wp-content/plugins/socialpop/socialpop.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="http://libraryrenewal.org/wp-content/plugins/socialpop/script.js" type="text/javascript" charset="utf-8"></script>
|
||||
|
||||
<meta name='NextGEN' content='1.8.2' />
|
||||
{% block extra_extra_head %}{% endblock %}
|
||||
|
||||
</head>
|
||||
|
||||
<body class="page page-id-7 page-template-default" >
|
||||
|
||||
<div id="header" class="container">
|
||||
<h2 id="logo"><a href="http://libraryrenewal.org">Library Renewal</a></h2>
|
||||
<ul id="global-menu">
|
||||
<li class="page_item page-item-2"><a href="http://libraryrenewal.org/about/" title="About Us">About Us</a></li>
|
||||
<li class="page_item page-item-7"><a href="http://libraryrenewal.org/join-the-movement/" title="Join the Movement">Join</a></li>
|
||||
<li class="page_item page-item-7"><a href="http://libraryrenewal.myshopify.com/" title="Store & Donation Center">Store</a></li>
|
||||
<li class="page_item page-item-9"><a href="http://libraryrenewal.org/events/" title="Events">Events</a></li>
|
||||
<li class="page_item page-item-11"><a href="http://libraryrenewal.org/support/" title="Support">Support Us</a></li>
|
||||
<li class="page_item page-item-53"><a href="http://libraryrenewal.org/blog/" title="Blog">Blog</a></li>
|
||||
<li class="page_item page-item-13"><a href="http://libraryrenewal.org/contact/" title="Contact">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="content" class="container" style="border:none">
|
||||
<div class="post" id="post-7">
|
||||
|
||||
<!-- SIDEBAR -->
|
||||
|
||||
<div id="sidebar" class="span-4 append-1">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="callout">
|
||||
<h3>Join the Movement</h3>
|
||||
<p>We are making library renewal happen together. Give us feedback and interact with your colleagues to create the library access your community needs today.</p>
|
||||
<a href="/join-the-movement" class="btn">Join the Movement</a>
|
||||
</div>
|
||||
|
||||
<div class="callout">
|
||||
<h3>Search the Site</h3>
|
||||
<form method="get" action="http://www.libraryrenewal.org/?">
|
||||
<input class="text" type="text" name="s" style="width:130px" >
|
||||
<p class="clear clearfix"><button type="submit" class="button positive"><img alt="Search the Site" height="16" src="http://libraryrenewal.org/wp-content/themes/library-renewal/assets/images/folder_magnify.png" title="" width="16" />Search</button></p>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="callout">
|
||||
<h3>Library Renewal Posters</h3>
|
||||
<a href="http://libraryrenewal.myshopify.com/collections/posters/"><img src="http://www.libraryrenewal.org/wp-content/uploads/2010/12/Cricket-Press-LR-sketches-2-197x300.jpg" style="width:150px"></a>
|
||||
<a href="http://libraryrenewal.myshopify.com/collections/posters/" class="btn">Browse Posters</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="span-19 last">
|
||||
|
||||
{% block doccontent %}{% endblock %}
|
||||
|
||||
<div id="lk-plugin"><div class="socialpop-container sps-solo">
|
||||
<ul class="socialpop-list">
|
||||
<li><a href="http://delicious.com/save?url={{ nonprofit.link|urlencode }}" onclick="window.open(this.href);return false;"><img src="http://libraryrenewal.org/wp-content/plugins/socialpop/images/icons/24px_altered/delicious.png" width="32" height="32" alt="Delicious" title="Delicious" /></a></li>
|
||||
<li><a href="http://digg.com/submit?url={{ nonprofit.link|urlencode }}" onclick="window.open(this.href);return false;"><img src="http://libraryrenewal.org/wp-content/plugins/socialpop/images/icons/24px_altered/digg.png" width="32" height="32" alt="Digg" title="Digg" /></a></li>
|
||||
<li><a href="mailto:?subject=Join the Movement&body={{ nonprofit.link|urlencode }}"><img src="http://libraryrenewal.org/wp-content/plugins/socialpop/images/icons/24px_altered/email.png" width="32" height="32" alt="E-Mail" title="E-Mail" /></a></li>
|
||||
<li><a href="http://www.facebook.com/sharer.php?u={{ nonprofit.link|urlencode }}"><img src="http://libraryrenewal.org/wp-content/plugins/socialpop/images/icons/24px_altered/facebook.png" width="32" height="32" alt="Facebook" title="Facebook" /></a></li>
|
||||
<li><a href="http://www.google.com/buzz/post?url={{ nonprofit.link|urlencode }}"><img src="http://libraryrenewal.org/wp-content/plugins/socialpop/images/icons/24px_altered/google-buzz.png" width="32" height="32" alt="Google Buzz" title="Google Buzz" /></a></li>
|
||||
<li><a href="http://www.linkedin.com/shareArticle?mini=true&url={{ nonprofit.link|urlencode }}&message=Join+the+Movement"><img src="http://libraryrenewal.org/wp-content/plugins/socialpop/images/icons/24px_altered/linkedin.png" width="32" height="32" alt="LinkedIn" title="LinkedIn" /></a></li>
|
||||
<li><a href="http://www.stumbleupon.com/submit?url={{ nonprofit.link|urlencode }}&title=Join+the+Movement"><img src="http://libraryrenewal.org/wp-content/plugins/socialpop/images/icons/24px_altered/stumbleupon.png" width="32" height="32" alt="StumbleUpon" title="StumbleUpon" /></a></li>
|
||||
<li><a href="http://twitter.com/home?status=Join+the+Movement+{{ nonprofit.link|urlencode }}"><img src="http://libraryrenewal.org/wp-content/plugins/socialpop/images/icons/24px_altered/twitter.png" width="32" height="32" alt="Twitter" title="Twitter" /></a></li>
|
||||
|
||||
</ul>
|
||||
</div></div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div id="footer" class="container">
|
||||
|
||||
<ul>
|
||||
|
||||
<li class="page_item page-item-2"><a href="http://libraryrenewal.org/about/" title="About Us">About Us</a></li>
|
||||
|
||||
<li class="page_item page-item-7"><a href="http://libraryrenewal.org/posters/" title="Join the Movement">Join the Movement</a></li>
|
||||
|
||||
<li class="page_item page-item-9"><a href="http://libraryrenewal.org/events/" title="Events">Events</a></li>
|
||||
|
||||
<li class="page_item page-item-11"><a href="http://libraryrenewal.org/support/" title="Support">Support Us</a></li>
|
||||
|
||||
<li class="page_item page-item-53"><a href="http://libraryrenewal.org/blog/" title="Blog">Blog</a></li>
|
||||
|
||||
<li class="page_item page-item-13"><a href="http://libraryrenewal.org/contact/" title="Contact">Contact</a></li>
|
||||
|
||||
</ul>
|
||||
|
||||
<p>Copyright © 2010 Library Renewal | Built With Pride by <a href="http://www.commercekitchen.com" target="_new">www.commercekitchen.com</a></p>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div id="share-container-outer">
|
||||
|
||||
<div id="share-container-inner">
|
||||
|
||||
<ul>
|
||||
|
||||
<li><a href="http://www.facebook.com/libraryrenewal" target="_new"><img src="http://libraryrenewal.org/wp-content/themes/library-renewal/assets/images/social-media-icons/facebook.png" width="16" height="16" alt="" title="" /></a></li>
|
||||
|
||||
<li><a href="http://twitter.com/libraryrenewal" target="_new"><img src="http://libraryrenewal.org/wp-content/themes/library-renewal/assets/images/social-media-icons/twitter.png" width="16" height="16" alt="" title="" /></a></li>
|
||||
|
||||
<li><a href="http://libraryrenewal.org/feed/" target="_new"><img src="http://libraryrenewal.org/wp-content/themes/library-renewal/assets/images/social-media-icons/feed.png" width="16" height="16" alt="" title="" /></a></li>
|
||||
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<script type='text/javascript' src='http://libraryrenewal.org/wp-content/plugins/simple_wp_gallery/resources/sp-gallery.js?ver=3.4.1'></script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
{% if work.last_campaign_status == 'SUCCESSFUL' %}
|
||||
Great news, {{ user.username }}! {{ work.title }}, which you have supported on Unglue.it, is now available for download as an Unglued Ebook.
|
||||
Great news! {{ work.title }}, which you have supported on Unglue.it, is now available for download as an Unglued Ebook.
|
||||
|
||||
{% else %}
|
||||
Good News, {{ user.username }}! {{ work.title }}, which is on your wishlist, is available for download as a {{ work.ebooks.0.get_rights_display }} ebook.
|
||||
Good News! {{ work.title }}, which is on your wishlist, is available for download as a {{ work.ebooks.0.get_rights_display }} ebook.
|
||||
|
||||
{% if work.ebooks.0.user %}
|
||||
We'd like to thank Ungluer {{work.ebooks.0.user}} for adding the link(s).
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
<div>
|
||||
<div class="pubinfo">
|
||||
<h3 class="book-author">{{ work.author }}</h3>
|
||||
<h3 class="book-year">{{ pubdate }}</h3>
|
||||
<h3 class="book-year">{{ work.publication_date_year }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -52,8 +52,8 @@
|
|||
{% if faqmenu == 'modify' %}
|
||||
<div class="modify_notification clearfix"><h4>You've already pledged to this campaign:</h4>
|
||||
<div>
|
||||
Amount: ${{preapproval_amount|intcomma}}.<br />
|
||||
Your premium: {% if premium_description %}{{ premium_description }}{% else %}You did not request a premium for this campaign.{% endif %}<br />
|
||||
Amount: ${{transaction.amount|intcomma}}.<br />
|
||||
Your premium: {% if transaction.premium %}{{ transaction.premium.description }}{% else %}You did not request a premium for this campaign.{% endif %}<br />
|
||||
</div>
|
||||
<br /> You can modify your pledge below.
|
||||
</div>
|
||||
|
@ -108,8 +108,8 @@
|
|||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cancel_notice">(You will be sent to <b>{{ payment_processor|capfirst }}</b>
|
||||
{% if faqmenu == 'modify' %}, if needed, to modify your pledge. We hope you won't, but of course you're also free to <a href="{% url pledge_cancel campaign_id=work.last_campaign.id %}">cancel your pledge</a>{% else %} to complete your pledge.{% endif %}.)</div>
|
||||
<div class="cancel_notice">
|
||||
{% if faqmenu == 'modify' %}We hope you won't, but of course you're also free to <a href="{% url pledge_cancel campaign_id=work.last_campaign.id %}">cancel your pledge</a>.{% endif %}</div>
|
||||
<div id="pass_supporter_name" style="display: none;">{{ request.user.username }}</div>
|
||||
{% if transaction.ack_name %}
|
||||
<div id="pass_ack_name" style="display: none;">{{ transaction.ack_name }}</div>
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
<link type="text/css" rel="stylesheet" href="/static/css/campaign.css" />
|
||||
<link type="text/css" rel="stylesheet" href="/static/css/searchandbrowse.css" />
|
||||
<link type="text/css" rel="stylesheet" href="/static/css/book_panel.css" />
|
||||
<link type="text/css" rel="stylesheet" href="/static/css/pledge.css" />
|
||||
|
||||
|
||||
<script src="/static/js/slides.min.jquery.js"></script>
|
||||
<script src="/static/js/slideshow.js"></script>
|
||||
|
@ -29,7 +31,14 @@
|
|||
|
||||
<h2 class="thank-you">Thank you!</h2>
|
||||
|
||||
<p class="pledge_complete">You've just pledged ${{ transaction.amount|intcomma }} to <a href="{% url work work.id %}">{{ work.title }}</a>. If it reaches its goal of ${{ campaign.target|intcomma }} by {{ campaign.deadline|date:"M d Y"}}, it will be unglued for all to enjoy.</p>
|
||||
<p class="pledge_complete">You've just {% if modified %}modified your pledge for{% else %}pledged{% endif %} ${{ transaction.amount|intcomma }} to <a href="{% url work work.id %}">{{ work.title }}</a>. If it reaches its goal of ${{ campaign.target|intcomma }} by {{ campaign.deadline|date:"M d Y"}}, it will be unglued for all to enjoy.</p>
|
||||
<div class="modify_notification clearfix">
|
||||
<div>
|
||||
Amount: ${{transaction.amount|intcomma}}.<br />
|
||||
Your premium: {% if transaction.premium %}{{ transaction.premium.description }}{% else %}You did not request a premium for this campaign.{% endif %}<br />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="pledge_complete">You can help even more by sharing this campaign with your friends:</p>
|
||||
|
||||
<div id="widgetcode">Copy/paste this into your site:<br /><textarea rows="7" cols="22"><iframe src="https://{{request.META.HTTP_HOST}}/api/widget/{{work.first_isbn_13}}/" width="152" height="325" frameborder="0"></iframe></textarea></div>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<a href="#overview">Overview</a><br />
|
||||
</div>
|
||||
<div>
|
||||
<a href="#press">All Press Coverage</a><br />
|
||||
<a href="#press">Press Coverage</a><br />
|
||||
<a href="#blogs">Blog Coverage (Highlights)</a><br />
|
||||
</div>
|
||||
<div>
|
||||
|
@ -17,6 +17,7 @@
|
|||
</div>
|
||||
<div>
|
||||
<a href="#images">Logos & Images</a><br />
|
||||
<a href="#releases">Press Releases</a><br />
|
||||
</div>
|
||||
<div class="pressemail">
|
||||
Additional press questions? Please email <a href="mailto:press@gluejar.com">press@gluejar.com</a>.
|
||||
|
@ -105,7 +106,7 @@ Creative Commons offers a variety of other licenses, many of them with even less
|
|||
Die Zeit - July 9, 2012<br />
|
||||
</div>
|
||||
<div>
|
||||
<a href="hhttp://mashable.com/2012/06/14/unglueit/">Unglue.it Wants to Make a Creative Commons for Ebooks</a><br />
|
||||
<a href="http://mashable.com/2012/06/14/unglueit/">Unglue.it Wants to Make a Creative Commons for Ebooks</a><br />
|
||||
Mashable - June 14, 2012
|
||||
</div>
|
||||
<div>
|
||||
|
@ -180,6 +181,10 @@ Creative Commons offers a variety of other licenses, many of them with even less
|
|||
|
||||
<a id="blogs"></a><h2>Blog Coverage (Highlights)</h2>
|
||||
<div class="pressarticles">
|
||||
<div>
|
||||
<a href="http://www.thedigitalshift.com/2012/09/roy-tennant-digital-libraries/first-book-comes-unglued/">First Book Comes Unglued</a><br />
|
||||
The Digital Shift (Library Journal/School Library Journal) - September 12, 2012<br />
|
||||
</div>
|
||||
<div>
|
||||
<a href="http://davidbrin.blogspot.com/2012/07/transparency-secrecy-and-copyright-for.html">Transparency, Secrecy, and Copyright for the Modern Age</a><br />
|
||||
Contrary Brin - July 1, 2012<br />
|
||||
|
@ -339,7 +344,7 @@ Creative Commons offers a variety of other licenses, many of them with even less
|
|||
<p>You can also <a href="http://eepurl.com/fKLfI">subscribe</a> to our newsletter.</p>
|
||||
|
||||
<a id="images"></a><h2>Logos & Images</h2>
|
||||
<div class="pressimages">
|
||||
<div class="pressimages clearfix">
|
||||
<div class="outer">
|
||||
<div><a href="/static/images/logo.png"><img src="/static/images/logo.png" alt="big logo" /></a></div>
|
||||
<div class="text"><p>Full logo, 161px x 70px</p></div>
|
||||
|
@ -369,11 +374,30 @@ Creative Commons offers a variety of other licenses, many of them with even less
|
|||
<div class="text"><p>300 ppi screenshot of a search result page, in panel view. As with the supporter page, users can toggle between views.</p></div>
|
||||
</div>
|
||||
</div>
|
||||
<br /><br /><br /><br /><br /><br />
|
||||
<div>
|
||||
<a id="releases"></a><h2>Press Releases</h2>
|
||||
<div class="pressarticles">
|
||||
<div>
|
||||
<a href="/static/pdfs/OLAUngluedEbookRelease.pdf">Oral Literature in Africa unglued ebook released</a><br />September 12, 2012
|
||||
</div>
|
||||
<div>
|
||||
<a href="/static/pdfs/PressReleaselaunchoverall.pdf">Main launch announcement</a><br />May 17, 2012
|
||||
</div>
|
||||
<div>
|
||||
<a href="/static/pdfs/PressReleaseBuddingReader.pdf">Launch announcement: Budding Reader campaign</a><br />May 17, 2012
|
||||
</div>
|
||||
<div>
|
||||
<a href="/static/pdfs/PressReleaseMikeLaser.pdf">Launch announcement: Michael Laser campaign</a><br />May 17, 2012
|
||||
</div>
|
||||
<div>
|
||||
<a href="/static/pdfs/PressReleaseJoeNassise.pdf">Launch announcement: Joseph Nassise campaign</a><br />May 17, 2012
|
||||
</div>
|
||||
<div>
|
||||
<a href="/static/pdfs/PressReleaseOpenBook.pdf">Launch announcement: Open Book Publishers campaign</a><br />May 17, 2012
|
||||
</div>
|
||||
<div>
|
||||
<a href="/static/pdfs/PressReleaseNancyRawles.pdf">Launch announcement: Nancy Rawles campaign</a><br />May 17, 2012
|
||||
</div>
|
||||
|
||||
{% comment %}
|
||||
should have logos at a variety of resolutions and also in greyscale
|
||||
{% endcomment %}
|
||||
</div>
|
||||
<br /><br /><br /><br /><br /><br />
|
||||
{% endblock %}
|
|
@ -1,7 +1,6 @@
|
|||
{% extends "base.html" %}
|
||||
{% load endless %}
|
||||
{% load truncatechars %}
|
||||
{% comment %} we don't need "with request.user.wishlist.works.all as wishlist" here to make book_panel work, because wishlist is already in the context {% endcomment %}
|
||||
{% block title %} — {{ supporter.username }}{% endblock %}
|
||||
{% block extra_css %}
|
||||
<link type="text/css" rel="stylesheet" href="/static/css/supporter_layout.css" />
|
||||
|
@ -321,23 +320,23 @@ there's no tab for seeing ALL my books, only the filters! huh.
|
|||
{% if request.user.is_anonymous %}
|
||||
<div class="tabs-1 anon_about">
|
||||
{% if works_unglued %}
|
||||
{{ supporter }} is sharing these books with you. <a href="#" class="about_expander" id="about_unglued">Find out how.</a>
|
||||
{{ supporter }} is sharing these books with you. <a href="/about/unglued/" class="hijax">Find out how.</a>
|
||||
{% else %}
|
||||
{{ supporter }} isn't sharing any books with the world yet. <a href="#" class="about_expander" id="about_unglued_empty">Find out how you can.</a>
|
||||
{{ supporter }} isn't sharing any books with the world yet. <a href="/about/unglued_empty/" class="hijax">Find out how you can.</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="tabs-2 anon_about">
|
||||
{% if works_active %}
|
||||
{{ supporter }} is giving these books to you. <a href="#" class="about_expander" id="about_active">Find out how.</a>
|
||||
{{ supporter }} is giving these books to you. <a href="/about/active/" class="hijax">Find out how.</a>
|
||||
{% else %}
|
||||
{{ supporter }} isn't giving any books to the world right now. <a href="#" class="about_expander" id="about_active_empty">Find out how you can.</a>
|
||||
{{ supporter }} isn't giving any books to the world right now. <a href="/about/active_empty" class="hijax">Find out how you can.</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="tabs-3 anon_about">
|
||||
{% if works_wished %}
|
||||
{{ supporter }} wants to give these books to you. <a href="#" class="about_expander" id="about_wishlist">Find out how.</a>
|
||||
{{ supporter }} wants to give these books to you. <a href="/about/wishlist/" class="hijax">Find out how.</a>
|
||||
{% else %}
|
||||
{{ supporter }} hasn't decided which books to give the world yet. <a href="#" class="about_expander" id="about_wishlist_empty">Learn more.</a>
|
||||
{{ supporter }} hasn't decided which books to give the world yet. <a href="/about/wishlist_empty/" class="hijax">Learn more.</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
@ -348,8 +347,9 @@ there's no tab for seeing ALL my books, only the filters! huh.
|
|||
{% with work.last_campaign_status as status %}
|
||||
{% with work.last_campaign.deadline as deadline %}
|
||||
{% with work.googlebooks_id as googlebooks_id %}
|
||||
{% with request.user.wishlist.works.all as wishlist %}
|
||||
{% include "book_panel.html" %}
|
||||
{% endwith %}{% endwith %}{% endwith %}
|
||||
{% endwith %}{% endwith %}{% endwith %}{% endwith %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="pagination content-block-heading tabs-1">
|
||||
|
@ -365,8 +365,9 @@ there's no tab for seeing ALL my books, only the filters! huh.
|
|||
{% with work.last_campaign_status as status %}
|
||||
{% with work.last_campaign.deadline as deadline %}
|
||||
{% with work.googlebooks_id as googlebooks_id %}
|
||||
{% with request.user.wishlist.works.all as wishlist %}
|
||||
{% include "book_panel.html" %}
|
||||
{% endwith %}{% endwith %}{% endwith %}
|
||||
{% endwith %}{% endwith %}{% endwith %}{% endwith %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="pagination content-block-heading tabs-2">
|
||||
|
@ -382,8 +383,9 @@ there's no tab for seeing ALL my books, only the filters! huh.
|
|||
{% with work.last_campaign_status as status %}
|
||||
{% with work.last_campaign.deadline as deadline %}
|
||||
{% with work.googlebooks_id as googlebooks_id %}
|
||||
{% with request.user.wishlist.works.all as wishlist %}
|
||||
{% include "book_panel.html" %}
|
||||
{% endwith %}{% endwith %}{% endwith %}
|
||||
{% endwith %}{% endwith %}{% endwith %}{% endwith %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="pagination content-block-heading tabs-3">
|
||||
|
|
|
@ -28,16 +28,6 @@ $j(document).ready(function(){
|
|||
$j(this).next().toggle();
|
||||
});
|
||||
});
|
||||
$j(document).ready(function(){
|
||||
$j('.show_more_ebooks').click(function(){
|
||||
if ($j(this).html() == '<br>hide downloads') {
|
||||
$j(this).html('<br>more downloads...')
|
||||
} else {
|
||||
$j(this).html('<br>hide downloads')
|
||||
}
|
||||
$j(this).next().toggle();
|
||||
});
|
||||
});
|
||||
$j(document).ready(function(){
|
||||
var img = $j('#book-detail-img');
|
||||
var googimg = $j('#find-google img');
|
||||
|
@ -126,7 +116,7 @@ $j(document).ready(function(){
|
|||
<div>
|
||||
<div class="pubinfo">
|
||||
<h3 class="book-author">{{ work.author }}</h3>
|
||||
<h3 class="book-year">{{ pubdate }}</h3>
|
||||
<h3 class="book-year">{{ work.publication_date_year }}</h3>
|
||||
</div>
|
||||
{% if status == 'ACTIVE' %}
|
||||
{% if pledged %}
|
||||
|
@ -134,33 +124,15 @@ $j(document).ready(function(){
|
|||
{% else %}
|
||||
<div class="btn_support"><form action="{% url pledge work_id %}" method="get"><input type="submit" value="Support" /></form></div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% if work.first_ebook %}
|
||||
<div class="btn_support">
|
||||
<a href="{% url download work_id %}" class="fakeinput hijax">Download</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% if work.first_ebook %}
|
||||
<div class="get-book">
|
||||
<label>Read it now!</label>
|
||||
<span class="find-link">
|
||||
{% for ebook in work.ebooks %}
|
||||
{% if forloop.first %}
|
||||
<span class="first_ebook">
|
||||
{% endif %}
|
||||
{% if forloop.counter == 2 %}
|
||||
</span>
|
||||
<span class="show_more_ebooks"><br />More downloads...</span>
|
||||
<span class="more_ebooks">
|
||||
{% endif %}
|
||||
{% if not forloop.first %}
|
||||
<br />
|
||||
{% endif %}
|
||||
<a href="{{ ebook.url }}"><img src="/static/images/{{ ebook.format }}32.png" height="32" alt=" {{ ebook.format }} at {{ebook.provider}}" title=" {{ ebook.format }} at {{ebook.provider}}" /><img src="{{ebook.rights_badge}}" height="31" width="88" alt="{{ebook.rights}}" title="{{ebook.rights}}" /></a>
|
||||
{% if forloop.last %}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="find-book">
|
||||
<label>Learn more at...</label>
|
||||
<div class="find-link">
|
||||
|
@ -344,45 +316,53 @@ $j(document).ready(function(){
|
|||
{% endif %}
|
||||
|
||||
{% if alert %}<div class="alert"><b>Ebook Contribution:</b><br />{{ alert }}</div>{% endif %}
|
||||
{% for edition in editions %}
|
||||
<div class="editions">{% if edition.googlebooks_id %}<div class="image"><img src="{{ edition.cover_image_small }}" title="edition cover" alt="edition cover" /></div>{% endif %}
|
||||
<div class="metadata" id="edition_{{edition.id}}">{% if edition.publisher %}Publisher: {{edition.publisher}}<br />{% endif %}
|
||||
{% if edition.publication_date %}Published: {{edition.publication_date}}<br />{% endif %}
|
||||
{% if edition.isbn_13 %}
|
||||
ISBN: {{ edition.isbn_13 }}<br />
|
||||
{% endif %}
|
||||
{% if edition.oclc %}
|
||||
OCLC: <a href="http://www.worldcat.org/oclc/{{ edition.oclc }}">{{ edition.oclc }}</a><br />
|
||||
{% endif %}
|
||||
{% if user.is_staff %}
|
||||
<a href="{% url new_edition work_id edition.id %}">Edit this edition</a><br />
|
||||
{% for edition in editions %}
|
||||
<div class="clearfix">
|
||||
<div class="editions">
|
||||
{% if edition.googlebooks_id %}
|
||||
<div class="image">
|
||||
<img src="{{ edition.cover_image_small }}" title="edition cover" alt="edition cover" />
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if edition.googlebooks_id %}
|
||||
See <a href="https://encrypted.google.com/books?id={{ edition.googlebooks_id }}">this edition on Google Books</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% if edition.ebook_form %}{% ifnotequal status 'ACTIVE' %}
|
||||
{% if edition.hide_details %}<div class="show_more_edition" >more...</div>{% endif %}
|
||||
<div {% if edition.hide_details %} class="more_edition" {% endif %}>
|
||||
{% if edition.ebooks.count %}
|
||||
<h5>eBooks for this Edition</h5>
|
||||
{% for ebook in edition.ebooks.all %}
|
||||
<a href="{{ebook.url}}">{{ ebook.format }}</a> {{ebook.rights}} at {{ebook.provider}}<br />
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
<h5>Add an eBook for this Edition:</h5>
|
||||
<span>If you know that this edition is available as a public domain or Creative Commons ebook, you can enter the link here and "unglue" it. Right now, we're only accepting URLs that point to Internet Archive, Wikisources, Hathitrust, Project Gutenberg, or Google Books.</span>
|
||||
<form method="POST" action="#edition_{{edition.id}}">
|
||||
{% csrf_token %}{{ edition.ebook_form.edition.errors }}{{ edition.ebook_form.edition }}{{ edition.ebook_form.user.errors }}{{ edition.ebook_form.user }}{{ edition.ebook_form.provider.errors }}{{ edition.ebook_form.provider }}
|
||||
{{ edition.ebook_form.url.errors }}<span>URL: {{ edition.ebook_form.url }}</span><br />
|
||||
{{ edition.ebook_form.format.errors }}<span>File Format: {{ edition.ebook_form.format }}</span>
|
||||
{{ edition.ebook_form.rights.errors }}<span>License: {{ edition.ebook_form.rights }}</span><br />
|
||||
<input type="submit" name="add_ebook" value="add ebook" />
|
||||
</form>
|
||||
<div class="metadata" id="edition_{{edition.id}}">{% if edition.publisher %}Publisher: {{edition.publisher}}<br />{% endif %}
|
||||
{% if edition.publication_date %}Published: {{edition.publication_date}}<br />{% endif %}
|
||||
{% if edition.isbn_13 %}
|
||||
ISBN: {{ edition.isbn_13 }}<br />
|
||||
{% endif %}
|
||||
{% if edition.oclc %}
|
||||
OCLC: <a href="http://www.worldcat.org/oclc/{{ edition.oclc }}">{{ edition.oclc }}</a><br />
|
||||
{% endif %}
|
||||
{% if user.is_staff %}
|
||||
<a href="{% url new_edition work_id edition.id %}">Edit this edition</a><br />
|
||||
{% endif %}
|
||||
{% if edition.googlebooks_id %}
|
||||
See <a href="https://encrypted.google.com/books?id={{ edition.googlebooks_id }}">this edition on Google Books</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endifnotequal %}{% endif %}
|
||||
|
||||
{% if edition.ebook_form %}{% ifnotequal status 'ACTIVE' %}
|
||||
{% if edition.hide_details %}
|
||||
<div class="show_more_edition" >more...</div>
|
||||
{% endif %}
|
||||
<div {% if edition.hide_details %} class="more_edition" {% endif %}>
|
||||
{% if edition.ebooks.count %}
|
||||
<h5>eBooks for this Edition</h5>
|
||||
{% for ebook in edition.ebooks.all %}
|
||||
<a href="{{ebook.url}}">{{ ebook.format }}</a> {{ebook.rights}} at {{ebook.provider}}<br />
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
<h5>Add an eBook for this Edition:</h5>
|
||||
<span>If you know that this edition is available as a public domain or Creative Commons ebook, you can enter the link here and "unglue" it. Right now, we're only accepting URLs that point to Internet Archive, Wikisources, Hathitrust, Project Gutenberg, or Google Books.</span>
|
||||
<form method="POST" action="#edition_{{edition.id}}">
|
||||
{% csrf_token %}{{ edition.ebook_form.edition.errors }}{{ edition.ebook_form.edition }}{{ edition.ebook_form.user.errors }}{{ edition.ebook_form.user }}{{ edition.ebook_form.provider.errors }}{{ edition.ebook_form.provider }}
|
||||
{{ edition.ebook_form.url.errors }}<span>URL: {{ edition.ebook_form.url }}</span><br />
|
||||
{{ edition.ebook_form.format.errors }}<span>File Format: {{ edition.ebook_form.format }}</span>
|
||||
{{ edition.ebook_form.rights.errors }}<span>License: {{ edition.ebook_form.rights }}</span><br />
|
||||
<input type="submit" name="add_ebook" value="add ebook" />
|
||||
</form>
|
||||
</div>
|
||||
{% endifnotequal %}{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -415,7 +395,7 @@ $j(document).ready(function(){
|
|||
{% if premium.limit == 0 or premium.limit > premium.premium_count %}
|
||||
<li class="{% if forloop.first %}first{% else %}{% if forloop.last %}last{% endif %}{% endif %}">
|
||||
<a href="{% url pledge_modify work_id %}?premium_id={{premium.id}}">
|
||||
<span class="menu-item-price">${{ premium.amount|floatformat:0|intcomma }}</span>{% if pledged.0.premium == premium %}<div class="you_pledged">Pledged!</div>{% endif %}
|
||||
<span class="menu-item-price">{% if premium.amount %}${{ premium.amount|floatformat:0|intcomma }}{% else %}Any amount{% endif %}</span>{% if pledged.0.premium == premium %}<div class="you_pledged">Pledged!</div>{% endif %}
|
||||
<span class="menu-item-desc">{{ premium.description }}</span>
|
||||
{% ifnotequal premium.limit 0 %}<br /> Only {{ premium.premium_remaining }} remaining! {% endifnotequal %}
|
||||
</a></li>
|
||||
|
|
|
@ -3,12 +3,14 @@ from django.views.generic.simple import direct_to_template
|
|||
from django.views.generic.base import TemplateView
|
||||
from django.views.generic import ListView, DetailView
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.conf import settings
|
||||
|
||||
from regluit.core.feeds import SupporterWishlistFeed
|
||||
from regluit.core.models import Campaign
|
||||
from regluit.frontend.views import GoodreadsDisplayView, LibraryThingView, PledgeView, PledgeCompleteView, PledgeModifyView, PledgeCancelView, PledgeNeverMindView, PledgeRechargeView, FAQView
|
||||
from regluit.frontend.views import CampaignListView, DonateView, WorkListView, UngluedListView, InfoPageView, InfoLangView, DonationView
|
||||
from regluit.frontend.views import GoodreadsDisplayView, LibraryThingView, PledgeView, PledgeCompleteView, PledgeCancelView, PledgeRechargeView, FAQView
|
||||
from regluit.frontend.views import CampaignListView, WorkListView, UngluedListView, InfoPageView, InfoLangView, DonationView, FundPledgeView
|
||||
from regluit.frontend.views import NonprofitCampaign, DonationCredit, PledgeModifiedView
|
||||
|
||||
urlpatterns = patterns(
|
||||
"regluit.frontend.views",
|
||||
|
@ -44,6 +46,7 @@ urlpatterns = patterns(
|
|||
url(r"^work/(?P<work_id>\d+)/preview/$", "work", {'action': 'preview'}, name="work_preview"),
|
||||
url(r"^work/(?P<work_id>\d+)/acks/$", "work", {'action': 'acks'}, name="work_acks"),
|
||||
url(r"^work/(?P<work_id>\d+)/lockss/$", "lockss", name="lockss"),
|
||||
url(r"^work/(?P<work_id>\d+)/download/$", "download", name="download"),
|
||||
url(r"^work/\d+/acks/images/(?P<file_name>[\w\.]*)$", "static_redirect_view",{'dir': 'images'}),
|
||||
url(r"^work/(?P<work_id>\d+)/librarything/$", "work_librarything", name="work_librarything"),
|
||||
url(r"^work/(?P<work_id>\d+)/goodreads/$", "work_goodreads", name="work_goodreads"),
|
||||
|
@ -52,12 +55,15 @@ urlpatterns = patterns(
|
|||
url(r"^new_edition/(?P<work_id>\d*)/(?P<edition_id>\d*)$", "new_edition", name="new_edition"),
|
||||
url(r"^googlebooks/(?P<googlebooks_id>.+)/$", "googlebooks", name="googlebooks"),
|
||||
url(r"^donation/$", login_required(DonationView.as_view()), name="donation"),
|
||||
url(r"^donation/credit/(?P<token>.+)/$", login_required(DonationCredit.as_view()), name="donation_credit"),
|
||||
url(r"^pledge/(?P<work_id>\d+)/$", login_required(PledgeView.as_view()), name="pledge"),
|
||||
url(r"^pledge/cancel/(?P<campaign_id>\d+)$", login_required(PledgeCancelView.as_view()), name="pledge_cancel"),
|
||||
url(r"^pledge/complete/$", login_required(PledgeCompleteView.as_view()), name="pledge_complete"),
|
||||
url(r"^pledge/nevermind/$", login_required(PledgeNeverMindView.as_view()), name="pledge_nevermind"),
|
||||
url(r"^pledge/modify/(?P<work_id>\d+)$", login_required(PledgeModifyView.as_view()), name="pledge_modify"),
|
||||
url(r"^pledge/modified/$", login_required(PledgeModifiedView.as_view()), name="pledge_modified"),
|
||||
url(r"^pledge/modify/(?P<work_id>\d+)$", login_required(PledgeView.as_view()), name="pledge_modify"),
|
||||
url(r"^pledge/fund/(?P<t_id>\d+)$", login_required(FundPledgeView.as_view()), name="fund_pledge"),
|
||||
url(r"^pledge/recharge/(?P<work_id>\d+)$", login_required(PledgeRechargeView.as_view()), name="pledge_recharge"),
|
||||
url(r"^donate_to_campaign/$", csrf_exempt(NonprofitCampaign.as_view()), name="nonprofit"),
|
||||
url(r"^subjects/$", "subjects", name="subjects"),
|
||||
url(r"^librarything/$", LibraryThingView.as_view(), name="librarything"),
|
||||
url(r"^librarything/load/$","librarything_load", name="librarything_load"),
|
||||
|
@ -75,7 +81,8 @@ urlpatterns = patterns(
|
|||
url(r"^info/(?P<template_name>[\w\.]*)$", InfoPageView.as_view()),
|
||||
url(r"^info/languages/(?P<template_name>[\w\.]*)$", InfoLangView.as_view()),
|
||||
url(r'^supporter/(?P<supporter>[^/]+)/feed/$', SupporterWishlistFeed()),
|
||||
url(r'^campaign_archive.js/$', "campaign_archive_js", name='campaign_archive_js'),
|
||||
url(r'^campaign_archive.js/$', "campaign_archive_js", name="campaign_archive_js"),
|
||||
url(r"^about/(?P<facet>\w*)/$", "about", name="about"),
|
||||
)
|
||||
|
||||
if settings.DEBUG:
|
||||
|
@ -83,6 +90,5 @@ if settings.DEBUG:
|
|||
"regluit.frontend.views",
|
||||
url(r"^goodreads/$", login_required(GoodreadsDisplayView.as_view()), name="goodreads_display"),
|
||||
url(r"^goodreads/clear_wishlist/$","clear_wishlist", name="clear_wishlist"),
|
||||
url(r"^donate/$", DonateView.as_view(), name="donate"),
|
||||
url(r"^celery/clear/$","clear_celery_tasks", name="clear_celery_tasks"),
|
||||
)
|
|
@ -17,6 +17,7 @@ import oauth2 as oauth
|
|||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.core import signing
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.contrib import messages
|
||||
|
@ -47,17 +48,19 @@ from regluit.frontend.forms import UserData, UserEmail, ProfileForm, CampaignPle
|
|||
from regluit.frontend.forms import RightsHolderForm, UserClaimForm, LibraryThingForm, OpenCampaignForm
|
||||
from regluit.frontend.forms import getManageCampaignForm, DonateForm, CampaignAdminForm, EmailShareForm, FeedbackForm
|
||||
from regluit.frontend.forms import EbookForm, CustomPremiumForm, EditManagersForm, EditionForm, PledgeCancelForm
|
||||
from regluit.frontend.forms import getTransferCreditForm
|
||||
from regluit.frontend.forms import getTransferCreditForm, CCForm
|
||||
from regluit.payment.manager import PaymentManager
|
||||
from regluit.payment.models import Transaction
|
||||
from regluit.payment.parameters import TARGET_TYPE_CAMPAIGN, TARGET_TYPE_DONATION, PAYMENT_TYPE_AUTHORIZATION
|
||||
from regluit.payment.parameters import TRANSACTION_STATUS_ACTIVE, TRANSACTION_STATUS_COMPLETE, TRANSACTION_STATUS_CANCELED, TRANSACTION_STATUS_ERROR, TRANSACTION_STATUS_FAILED, TRANSACTION_STATUS_INCOMPLETE
|
||||
from regluit.payment.paypal import Preapproval
|
||||
from regluit.payment.models import Transaction, Account
|
||||
from regluit.payment.parameters import TRANSACTION_STATUS_ACTIVE, TRANSACTION_STATUS_COMPLETE, TRANSACTION_STATUS_CANCELED, TRANSACTION_STATUS_ERROR, TRANSACTION_STATUS_FAILED, TRANSACTION_STATUS_INCOMPLETE, TRANSACTION_STATUS_NONE, TRANSACTION_STATUS_MODIFIED
|
||||
from regluit.payment.parameters import PAYMENT_TYPE_AUTHORIZATION, PAYMENT_TYPE_INSTANT
|
||||
from regluit.payment.parameters import PAYMENT_HOST_STRIPE
|
||||
from regluit.payment.credit import credit_transaction
|
||||
from regluit.core import goodreads
|
||||
from tastypie.models import ApiKey
|
||||
from regluit.payment.models import Transaction
|
||||
from regluit.payment.models import Transaction, Sent, CreditLog
|
||||
from notification import models as notification
|
||||
|
||||
from regluit.payment import stripelib
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -96,6 +99,19 @@ def next(request):
|
|||
else:
|
||||
return HttpResponseRedirect('/')
|
||||
|
||||
def safe_get_work(work_id):
|
||||
"""
|
||||
use this rather than querying the db directly for a work by id
|
||||
"""
|
||||
try:
|
||||
work = models.Work.objects.get(id = work_id)
|
||||
except models.Work.DoesNotExist:
|
||||
try:
|
||||
work = models.WasWork.objects.get(was = work_id).work
|
||||
except models.WasWork.DoesNotExist:
|
||||
raise Http404
|
||||
return work
|
||||
|
||||
def home(request, landing=False):
|
||||
if request.user.is_authenticated() and landing == False:
|
||||
return HttpResponseRedirect(reverse('supporter',
|
||||
|
@ -115,17 +131,11 @@ def stub(request):
|
|||
def acks(request, work):
|
||||
return render(request,'front_matter.html', {'campaign': work.last_campaign()})
|
||||
|
||||
|
||||
def work(request, work_id, action='display'):
|
||||
try:
|
||||
work = models.Work.objects.get(id = work_id)
|
||||
except models.Work.DoesNotExist:
|
||||
try:
|
||||
work = models.WasWork.objects.get(was = work_id).work
|
||||
except models.WasWork.DoesNotExist:
|
||||
raise Http404
|
||||
work = safe_get_work(work_id)
|
||||
if action == "acks":
|
||||
return acks( request, work)
|
||||
|
||||
if request.method == 'POST' and not request.user.is_anonymous():
|
||||
activetab = '4'
|
||||
else:
|
||||
|
@ -136,8 +146,10 @@ def work(request, work_id, action='display'):
|
|||
activetab = '1';
|
||||
except:
|
||||
activetab = '1';
|
||||
|
||||
context = {}
|
||||
campaign = work.last_campaign()
|
||||
if campaign and campaign.edition:
|
||||
if campaign and campaign.edition and not request.user.is_staff:
|
||||
editions = [campaign.edition]
|
||||
else:
|
||||
editions = work.editions.all().order_by('-publication_date')
|
||||
|
@ -146,7 +158,14 @@ def work(request, work_id, action='display'):
|
|||
except:
|
||||
pledged = None
|
||||
|
||||
logger.info("pledged: {0}".format(pledged))
|
||||
countdown = ""
|
||||
|
||||
try:
|
||||
assert not (work.last_campaign_status() == 'ACTIVE' and work.first_ebook())
|
||||
except:
|
||||
logger.warning("Campaign running for %s when ebooks are already available: why?" % work.title )
|
||||
|
||||
if work.last_campaign_status() == 'ACTIVE':
|
||||
from math import ceil
|
||||
time_remaining = campaign.deadline - now()
|
||||
|
@ -167,13 +186,10 @@ def work(request, work_id, action='display'):
|
|||
countdown = "in %s minutes" % str(time_remaining.seconds/60 + 1)
|
||||
else:
|
||||
countdown = "right now"
|
||||
|
||||
if action == 'preview':
|
||||
work.last_campaign_status = 'ACTIVE'
|
||||
|
||||
try:
|
||||
pubdate = work.publication_date[:4]
|
||||
except IndexError:
|
||||
pubdate = 'unknown'
|
||||
if not request.user.is_anonymous():
|
||||
claimform = UserClaimForm( request.user, data={'claim-work':work.pk, 'claim-user': request.user.id}, prefix = 'claim')
|
||||
for edition in editions:
|
||||
|
@ -192,6 +208,7 @@ def work(request, work_id, action='display'):
|
|||
edition.ebook_form = EbookForm( instance= models.Ebook(user = request.user, edition = edition, provider = 'x' ), prefix = 'ebook_%d'%edition.id)
|
||||
else:
|
||||
claimform = None
|
||||
|
||||
if campaign:
|
||||
# pull up premiums explicitly tied to the campaign
|
||||
# mandatory premiums are only displayed in pledge process
|
||||
|
@ -226,7 +243,6 @@ def work(request, work_id, action='display'):
|
|||
'wishers': wishers,
|
||||
'base_url': base_url,
|
||||
'editions': editions,
|
||||
'pubdate': pubdate,
|
||||
'pledged': pledged,
|
||||
'activetab': activetab,
|
||||
'alert': alert,
|
||||
|
@ -394,11 +410,6 @@ def manage_campaign(request, id):
|
|||
|
||||
work = campaign.work
|
||||
|
||||
try:
|
||||
pubdate = work.publication_date[:4]
|
||||
except IndexError:
|
||||
pubdate = 'unknown'
|
||||
|
||||
return render(request, 'manage_campaign.html', {
|
||||
'campaign': campaign,
|
||||
'form':form,
|
||||
|
@ -406,7 +417,6 @@ def manage_campaign(request, id):
|
|||
'alerts': alerts,
|
||||
'premiums' : campaign.effective_premiums(),
|
||||
'premium_form' : new_premium_form,
|
||||
'pubdate': pubdate,
|
||||
'work': work,
|
||||
'activetab': activetab,
|
||||
})
|
||||
|
@ -584,281 +594,313 @@ class DonationView(TemplateView):
|
|||
class PledgeView(FormView):
|
||||
template_name="pledge.html"
|
||||
form_class = CampaignPledgeForm
|
||||
embedded = False
|
||||
transaction = None
|
||||
campaign = None
|
||||
work = None
|
||||
premiums = None
|
||||
data = None
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
# change the default behavior from https://code.djangoproject.com/browser/django/tags/releases/1.3.1/django/views/generic/edit.py#L129
|
||||
# don't automatically bind the data to the form on GET, only on POST
|
||||
# compare with https://code.djangoproject.com/browser/django/tags/releases/1.3.1/django/views/generic/edit.py#L34
|
||||
form_class = self.get_form_class()
|
||||
form = form_class()
|
||||
|
||||
context_data = self.get_context_data(form=form)
|
||||
# if there is already an active campaign pledge for user, redirect to the pledge modify page
|
||||
if context_data.get('redirect_to_modify_pledge'):
|
||||
work = context_data['work']
|
||||
return HttpResponseRedirect(reverse('pledge_modify', args=[work.id]))
|
||||
else:
|
||||
return self.render_to_response(context_data)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
"""set up the pledge page"""
|
||||
|
||||
# the following should be true since PledgeModifyView.as_view is wrapped in login_required
|
||||
assert self.request.user.is_authenticated()
|
||||
user = self.request.user
|
||||
|
||||
context = super(PledgeView, self).get_context_data(**kwargs)
|
||||
|
||||
work = get_object_or_404(models.Work, id=self.kwargs["work_id"])
|
||||
campaign = work.last_campaign()
|
||||
|
||||
# if there is no campaign or if campaign is not active, we should raise an error
|
||||
|
||||
if campaign is None or campaign.status != 'ACTIVE':
|
||||
raise Http404
|
||||
|
||||
custom_premiums = campaign.custom_premiums()
|
||||
# need to also include the no-premiums default in the queryset we send the page
|
||||
premiums = custom_premiums | models.Premium.objects.filter(id=150)
|
||||
premium_id = self.request.REQUEST.get('premium_id', None)
|
||||
def get_preapproval_amount(self):
|
||||
preapproval_amount = self.request.REQUEST.get('preapproval_amount', None)
|
||||
|
||||
if premium_id is not None and preapproval_amount is None:
|
||||
if preapproval_amount:
|
||||
return preapproval_amount
|
||||
premium_id = self.request.REQUEST.get('premium_id', None)
|
||||
if premium_id != None:
|
||||
try:
|
||||
preapproval_amount = D(models.Premium.objects.get(id=premium_id).amount)
|
||||
except:
|
||||
preapproval_amount = None
|
||||
if self.transaction:
|
||||
if preapproval_amount:
|
||||
preapproval_amount = preapproval_amount if preapproval_amount>self.transaction.amount else self.transaction.amount
|
||||
else:
|
||||
preapproval_amount = self.transaction.amount
|
||||
return preapproval_amount
|
||||
|
||||
data = {'preapproval_amount':preapproval_amount, 'premium_id':premium_id}
|
||||
|
||||
form_class = self.get_form_class()
|
||||
|
||||
# no validation errors, please, when we're only doing a GET
|
||||
# to avoid validation errors, don't bind the form
|
||||
|
||||
if preapproval_amount is not None:
|
||||
form = form_class(data)
|
||||
else:
|
||||
form = form_class()
|
||||
|
||||
try:
|
||||
pubdate = work.publication_date[:4]
|
||||
except IndexError:
|
||||
pubdate = 'unknown'
|
||||
|
||||
context.update({
|
||||
'redirect_to_modify_pledge':False,
|
||||
'work':work,'campaign':campaign,
|
||||
'premiums':premiums, 'form':form,
|
||||
'premium_id':premium_id,
|
||||
'faqmenu': 'pledge',
|
||||
'pubdate':pubdate,
|
||||
'payment_processor':settings.PAYMENT_PROCESSOR,
|
||||
})
|
||||
|
||||
# check whether the user already has an ACTIVE transaction for the given campaign.
|
||||
# if so, we should redirect the user to modify pledge page
|
||||
# BUGBUG: but what about Completed Transactions?
|
||||
transactions = campaign.transactions().filter(user=user, status=TRANSACTION_STATUS_ACTIVE)
|
||||
if transactions.count() > 0:
|
||||
context.update({'redirect_to_modify_pledge':True})
|
||||
else:
|
||||
context.update({'redirect_to_modify_pledge':False})
|
||||
|
||||
return context
|
||||
|
||||
def form_valid(self, form):
|
||||
work_id = self.kwargs["work_id"]
|
||||
preapproval_amount = form.cleaned_data["preapproval_amount"]
|
||||
anonymous = form.cleaned_data["anonymous"]
|
||||
ack_name = form.cleaned_data["ack_name"]
|
||||
ack_link = form.cleaned_data["ack_link"]
|
||||
ack_dedication = form.cleaned_data["ack_dedication"]
|
||||
|
||||
# right now, if there is a non-zero pledge amount, go with that. otherwise, do the pre_approval
|
||||
campaign = models.Work.objects.get(id=int(work_id)).last_campaign()
|
||||
|
||||
premium_id = form.cleaned_data["premium_id"]
|
||||
# confirm that the premium_id is a valid one for the campaign in question
|
||||
try:
|
||||
premium = models.Premium.objects.get(id=premium_id)
|
||||
if not (premium.campaign is None or premium.campaign == campaign):
|
||||
premium = None
|
||||
except models.Premium.DoesNotExist, e:
|
||||
premium = None
|
||||
|
||||
p = PaymentManager(embedded=self.embedded)
|
||||
|
||||
# PledgeView is wrapped in login_required -- so in theory, user should never be None -- but I'll keep this logic here for now.
|
||||
if self.request.user.is_authenticated():
|
||||
user = self.request.user
|
||||
else:
|
||||
user = None
|
||||
|
||||
if not self.embedded:
|
||||
|
||||
return_url = None
|
||||
nevermind_url = None
|
||||
|
||||
# the recipients of this authorization is not specified here but rather by the PaymentManager.
|
||||
# set the expiry date based on the campaign deadline
|
||||
expiry = campaign.deadline + timedelta( days=settings.PREAPPROVAL_PERIOD_AFTER_CAMPAIGN )
|
||||
|
||||
paymentReason = "Unglue.it Pledge for {0}".format(campaign.name)
|
||||
t, url = p.authorize('USD', TARGET_TYPE_CAMPAIGN, preapproval_amount, expiry=expiry, campaign=campaign, list=None, user=user,
|
||||
return_url=return_url, nevermind_url=nevermind_url, anonymous=anonymous, premium=premium,
|
||||
paymentReason=paymentReason, ack_name=ack_name, ack_link=ack_link, ack_dedication=ack_dedication)
|
||||
else: # embedded view -- which we're not actively using right now.
|
||||
# embedded view triggerws instant payment: send to the partnering RH
|
||||
receiver_list = [{'email':settings.PAYPAL_NONPROFIT_PARTNER_EMAIL, 'amount':preapproval_amount}]
|
||||
|
||||
return_url = None
|
||||
nevermind_url = None
|
||||
|
||||
t, url = p.pledge('USD', TARGET_TYPE_CAMPAIGN, receiver_list, campaign=campaign, list=None, user=user,
|
||||
return_url=return_url, nevermind_url=nevermind_url, anonymous=anonymous, premium=premium,
|
||||
ack_name=ack_name, ack_link=ack_link, ack_dedication=ack_dedication)
|
||||
|
||||
if url:
|
||||
logger.info("PledgeView url: " + url)
|
||||
return HttpResponseRedirect(url)
|
||||
else:
|
||||
logger.error("Attempt to produce transaction id {0} failed".format(t.id))
|
||||
return HttpResponse("Our attempt to enable your transaction failed. We have logged this error.")
|
||||
|
||||
class PledgeModifyView(FormView):
|
||||
"""
|
||||
A view to handle request to change an existing pledge
|
||||
"""
|
||||
|
||||
template_name="pledge.html"
|
||||
form_class = CampaignPledgeForm
|
||||
embedded = False
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
||||
context = super(PledgeModifyView, self).get_context_data(**kwargs)
|
||||
|
||||
# the following should be true since PledgeModifyView.as_view is wrapped in login_required
|
||||
def get_form_kwargs(self):
|
||||
assert self.request.user.is_authenticated()
|
||||
user = self.request.user
|
||||
|
||||
work = get_object_or_404(models.Work, id=self.kwargs["work_id"])
|
||||
self.work = get_object_or_404(models.Work, id=self.kwargs["work_id"])
|
||||
|
||||
# if there is no campaign or if campaign is not active, we should raise an error
|
||||
try:
|
||||
campaign = work.last_campaign()
|
||||
premiums = campaign.custom_premiums() | models.Premium.objects.filter(id=150)
|
||||
|
||||
# which combination of campaign and transaction status required?
|
||||
self.campaign = self.work.last_campaign()
|
||||
# TODO need to sort the premiums
|
||||
self.premiums = self.campaign.custom_premiums() | models.Premium.objects.filter(id=150)
|
||||
# Campaign must be ACTIVE
|
||||
assert campaign.status == 'ACTIVE'
|
||||
|
||||
transactions = campaign.transactions().filter(user=user, status=TRANSACTION_STATUS_ACTIVE)
|
||||
assert transactions.count() == 1
|
||||
transaction = transactions[0]
|
||||
assert transaction.type == PAYMENT_TYPE_AUTHORIZATION and transaction.status == TRANSACTION_STATUS_ACTIVE
|
||||
|
||||
assert self.campaign.status == 'ACTIVE'
|
||||
except Exception, e:
|
||||
raise e
|
||||
|
||||
# what stuff do we need to pull out to populate form?
|
||||
# preapproval_amount, premium_id (which we don't have stored yet)
|
||||
if transaction.premium is not None:
|
||||
premium_id = transaction.premium.id
|
||||
premium_description = transaction.premium.description
|
||||
transactions = self.campaign.transactions().filter(user=self.request.user, status=TRANSACTION_STATUS_ACTIVE, type=PAYMENT_TYPE_AUTHORIZATION)
|
||||
premium_id = self.request.REQUEST.get('premium_id', None)
|
||||
if transactions.count() == 0:
|
||||
ack_name=''
|
||||
ack_dedication=''
|
||||
anonymous=''
|
||||
else:
|
||||
premium_id = None
|
||||
premium_description = None
|
||||
self.transaction = transactions[0]
|
||||
if premium_id == None and self.transaction.premium is not None:
|
||||
premium_id = self.transaction.premium.id
|
||||
ack_name=self.transaction.ack_name
|
||||
ack_dedication=self.transaction.ack_dedication
|
||||
anonymous=self.transaction.anonymous
|
||||
|
||||
# is there a Transaction for an ACTIVE campaign for this
|
||||
# should make sure Transaction is modifiable.
|
||||
self.data = {'preapproval_amount':self.get_preapproval_amount(), 'premium_id':premium_id,
|
||||
'ack_name':ack_name, 'ack_dedication':ack_dedication, 'anonymous':anonymous}
|
||||
if self.request.method == 'POST':
|
||||
self.data.update(self.request.POST.dict())
|
||||
return {'data':self.data}
|
||||
else:
|
||||
return {'initial':self.data}
|
||||
|
||||
preapproval_amount = transaction.amount
|
||||
data = {'preapproval_amount':preapproval_amount, 'premium_id':premium_id}
|
||||
def get_context_data(self, **kwargs):
|
||||
"""set up the pledge page"""
|
||||
|
||||
# initialize form with the current state of the transaction if the current values empty
|
||||
form = kwargs['form']
|
||||
|
||||
if not(form.is_bound):
|
||||
form_class = self.get_form_class()
|
||||
form = form_class(initial=data)
|
||||
context = super(PledgeView, self).get_context_data(**kwargs)
|
||||
|
||||
context.update({
|
||||
'work':work,
|
||||
'campaign':campaign,
|
||||
'premiums':premiums,
|
||||
'form':form,
|
||||
'preapproval_amount':preapproval_amount,
|
||||
'premium_id':premium_id,
|
||||
'premium_description': premium_description,
|
||||
'faqmenu': 'modify',
|
||||
'tid': transaction.id,
|
||||
'payment_processor':settings.PAYMENT_PROCESSOR,
|
||||
'transaction': transaction,
|
||||
})
|
||||
'work':self.work,
|
||||
'campaign':self.campaign,
|
||||
'premiums':self.premiums,
|
||||
'premium_id':self.data['premium_id'],
|
||||
'faqmenu': 'modify' if self.transaction else 'pledge',
|
||||
'transaction': self.transaction,
|
||||
'tid': self.transaction.id if self.transaction else None,
|
||||
})
|
||||
|
||||
return context
|
||||
|
||||
|
||||
def form_invalid(self, form):
|
||||
logger.info("form.non_field_errors: {0}".format(form.non_field_errors()))
|
||||
response = self.render_to_response(self.get_context_data(form=form))
|
||||
return response
|
||||
|
||||
def form_valid(self, form):
|
||||
# right now, if there is a non-zero pledge amount, go with that. otherwise, do the pre_approval
|
||||
|
||||
# What are the situations we need to deal with?
|
||||
# 2 main situations: if the new amount is less than max_amount, no need to go out to PayPal again
|
||||
# if new amount is greater than max_amount...need to go out and get new approval.
|
||||
# to start with, we can use the standard pledge_complete, pledge_cancel machinery
|
||||
# might have to modify the pledge_complete, pledge_cancel because the messages are going to be
|
||||
# different because we're modifying a pledge rather than a new one.
|
||||
p = PaymentManager()
|
||||
if self.transaction:
|
||||
# modifying the transaction...
|
||||
assert self.transaction.type == PAYMENT_TYPE_AUTHORIZATION and self.transaction.status == TRANSACTION_STATUS_ACTIVE
|
||||
status, url = p.modify_transaction(self.transaction, form.cleaned_data["preapproval_amount"],
|
||||
paymentReason="Unglue.it Pledge for {0}".format(self.campaign.name),
|
||||
pledge_extra=form.pledge_extra
|
||||
)
|
||||
logger.info("status: {0}, url:{1}".format(status, url))
|
||||
|
||||
work_id = self.kwargs["work_id"]
|
||||
preapproval_amount = form.cleaned_data["preapproval_amount"]
|
||||
ack_name = form.cleaned_data["ack_name"]
|
||||
ack_link = form.cleaned_data["ack_link"]
|
||||
ack_dedication = form.cleaned_data["ack_dedication"]
|
||||
anonymous = form.cleaned_data["anonymous"]
|
||||
if status and url is not None:
|
||||
logger.info("PledgeView (Modify): " + url)
|
||||
return HttpResponseRedirect(url)
|
||||
elif status and url is None:
|
||||
return HttpResponseRedirect("{0}?tid={1}".format(reverse('pledge_modified'), self.transaction.id))
|
||||
else:
|
||||
return HttpResponse("No modification made")
|
||||
else:
|
||||
t, url = p.process_transaction('USD', form.cleaned_data["preapproval_amount"],
|
||||
host = None,
|
||||
campaign=self.campaign,
|
||||
user=self.request.user,
|
||||
paymentReason="Unglue.it Pledge for {0}".format(self.campaign.name),
|
||||
pledge_extra=form.pledge_extra
|
||||
)
|
||||
if url:
|
||||
logger.info("PledgeView url: " + url)
|
||||
return HttpResponseRedirect(url)
|
||||
else:
|
||||
logger.error("Attempt to produce transaction id {0} failed".format(t.id))
|
||||
return HttpResponse("Our attempt to enable your transaction failed. We have logged this error.")
|
||||
|
||||
class FundPledgeView(FormView):
|
||||
template_name="fund_the_pledge.html"
|
||||
form_class = CCForm
|
||||
transaction = None
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super(FundPledgeView, self).get_form_kwargs()
|
||||
|
||||
assert self.request.user.is_authenticated()
|
||||
user = self.request.user
|
||||
if self.transaction is None:
|
||||
self.transaction = get_object_or_404(Transaction, id=self.kwargs["t_id"])
|
||||
|
||||
# right now, if there is a non-zero pledge amount, go with that. otherwise, do the pre_approval
|
||||
campaign = models.Work.objects.get(id=int(work_id)).last_campaign()
|
||||
assert campaign.status == 'ACTIVE'
|
||||
|
||||
premium_id = form.cleaned_data["premium_id"]
|
||||
# confirm that the premium_id is a valid one for the campaign in question
|
||||
try:
|
||||
premium = models.Premium.objects.get(id=premium_id)
|
||||
if not (premium.campaign is None or premium.campaign == campaign):
|
||||
premium = None
|
||||
except models.Premium.DoesNotExist, e:
|
||||
premium = None
|
||||
|
||||
transactions = campaign.transactions().filter(user=user, status=TRANSACTION_STATUS_ACTIVE)
|
||||
assert transactions.count() == 1
|
||||
transaction = transactions[0]
|
||||
assert transaction.type == PAYMENT_TYPE_AUTHORIZATION and transaction.status == TRANSACTION_STATUS_ACTIVE
|
||||
|
||||
p = PaymentManager(embedded=self.embedded)
|
||||
paymentReason = "Unglue.it Pledge for {0}".format(campaign.name)
|
||||
status, url = p.modify_transaction(transaction=transaction, amount=preapproval_amount, premium=premium,
|
||||
paymentReason=paymentReason, ack_name=ack_name, ack_link=ack_link,
|
||||
ack_dedication=ack_dedication, anonymous=anonymous)
|
||||
|
||||
logger.info("status: {0}, url:{1}".format(status, url))
|
||||
|
||||
if status and url is not None:
|
||||
logger.info("PledgeModifyView paypal: " + url)
|
||||
return HttpResponseRedirect(url)
|
||||
elif status and url is None:
|
||||
# let's use the pledge_complete template for now and maybe look into customizing it.
|
||||
return HttpResponseRedirect("{0}?tid={1}".format(reverse('pledge_complete'), transaction.id))
|
||||
if kwargs.has_key('data'):
|
||||
data = kwargs['data'].copy()
|
||||
else:
|
||||
return HttpResponse("No modification made")
|
||||
data = {}
|
||||
|
||||
data.update(
|
||||
{'preapproval_amount':self.transaction.max_amount,
|
||||
'username':self.request.user.username,
|
||||
'work_id':self.transaction.campaign.work.id,
|
||||
'title':self.transaction.campaign.work.title}
|
||||
)
|
||||
|
||||
kwargs['data'] = data
|
||||
|
||||
return kwargs
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(FundPledgeView, self).get_context_data(**kwargs)
|
||||
context['modified'] = self.transaction.status==TRANSACTION_STATUS_MODIFIED
|
||||
context['preapproval_amount']=self.transaction.max_amount
|
||||
context['needed'] = self.transaction.max_amount - self.request.user.credit.available
|
||||
context['transaction']=self.transaction
|
||||
context['nonprofit'] = settings.NONPROFIT
|
||||
context['STRIPE_PK'] = stripelib.STRIPE_PK
|
||||
# note that get_form_kwargs() will already have been called once
|
||||
donate_args=self.get_form_kwargs()
|
||||
donate_args['data']['preapproval_amount']=context['needed']
|
||||
context['donate_form'] = DonateForm(**donate_args)
|
||||
return context
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
logger.info('request.POST: {0}'.format(request.POST))
|
||||
return super(FundPledgeView, self).post(request, *args, **kwargs)
|
||||
|
||||
def form_valid(self, form):
|
||||
""" note desire to pledge; make sure there is a credit card to charge"""
|
||||
|
||||
# first pass -- we have a token -- also do more direct coupling to stripelib -- then move to
|
||||
# abstraction of payment.manager / payment.baseprocessor
|
||||
|
||||
# demonstrate two possibilities: 1) token -> charge or 2) token->customer->charge
|
||||
|
||||
stripe_token = form.cleaned_data["stripe_token"]
|
||||
preapproval_amount = form.cleaned_data["preapproval_amount"]
|
||||
retain_cc_info = form.cleaned_data["retain_cc_info"]
|
||||
|
||||
sc = stripelib.StripeClient()
|
||||
|
||||
# let's figure out what part of transaction can be used to store info
|
||||
# try placing charge id in transaction.pay_key
|
||||
# need to set amount
|
||||
# how does max_amount get set? -- coming from /pledge/xxx/?
|
||||
# max_amount is set -- but I don't think we need it for stripe
|
||||
|
||||
if retain_cc_info:
|
||||
# create customer and charge id and then charge the customer
|
||||
customer = sc.create_customer(card=stripe_token, description=self.request.user.username,
|
||||
email=self.request.user.email)
|
||||
|
||||
account = Account(host = PAYMENT_HOST_STRIPE,
|
||||
account_id = customer.id,
|
||||
card_last4 = customer.active_card.last4,
|
||||
card_type = customer.active_card.type,
|
||||
card_exp_month = customer.active_card.exp_month,
|
||||
card_exp_year = customer.active_card.exp_year,
|
||||
card_fingerprint = customer.active_card.fingerprint,
|
||||
card_country = customer.active_card.country,
|
||||
user = self.request.user
|
||||
)
|
||||
|
||||
account.save()
|
||||
|
||||
charge = sc.create_charge(preapproval_amount, customer=customer, description="${0} for test / retain cc".format(preapproval_amount))
|
||||
|
||||
else:
|
||||
customer = None
|
||||
|
||||
charge = sc.create_charge(preapproval_amount, card=stripe_token, description="${0} for test / cc not retained".format(preapproval_amount))
|
||||
|
||||
# set True for now -- wondering whether we should actually wait for a webhook -- don't think so.
|
||||
|
||||
## settings to apply to transaction for TRANSACTION_STATUS_COMPLETE
|
||||
#self.transaction.type = PAYMENT_TYPE_INSTANT
|
||||
#self.transaction.approved = True
|
||||
#self.transaction.status = TRANSACTION_STATUS_COMPLETE
|
||||
#self.transaction.pay_key = charge.id
|
||||
|
||||
# settings to apply to transaction for TRANSACTION_STATUS_ACTIVE
|
||||
# should approved be set to False and wait for a webhook?
|
||||
self.transaction.type = PAYMENT_TYPE_AUTHORIZATION
|
||||
self.transaction.approved = True
|
||||
self.transaction.status = TRANSACTION_STATUS_ACTIVE
|
||||
self.transaction.preapproval_key = charge.id
|
||||
|
||||
self.transaction.currency = 'USD'
|
||||
self.transaction.amount = preapproval_amount
|
||||
self.transaction.date_payment = now()
|
||||
|
||||
self.transaction.save()
|
||||
|
||||
return HttpResponse("charge id: {0} / customer: {1}".format(charge.id, customer))
|
||||
|
||||
|
||||
class NonprofitCampaign(FormView):
|
||||
template_name="nonprofit.html"
|
||||
form_class = CCForm
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(NonprofitCampaign, self).get_context_data(**kwargs)
|
||||
context['nonprofit'] = settings.NONPROFIT
|
||||
context['get'] = self.request.GET
|
||||
return context
|
||||
|
||||
def get_form_kwargs(self):
|
||||
if self.request.method == 'POST':
|
||||
return {'data':self.request.POST}
|
||||
else:
|
||||
return {'initial':self.request.GET }
|
||||
|
||||
|
||||
|
||||
def form_valid(self, form):
|
||||
username=form.cleaned_data['username']
|
||||
forward={'username':username}
|
||||
forward['work_id']= form.cleaned_data['work_id']
|
||||
amount=form.cleaned_data['preapproval_amount']
|
||||
forward['cents']=int(100*(amount-int(amount)))
|
||||
forward['amount']= int(amount)
|
||||
forward['sent']= Sent.objects.create(user=username,amount=form.cleaned_data['preapproval_amount']).pk
|
||||
token=signing.dumps(forward)
|
||||
return HttpResponseRedirect(settings.BASE_URL + reverse('donation_credit',kwargs={'token':token}))
|
||||
|
||||
class DonationCredit(TemplateView):
|
||||
template_name="donation_credit.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(DonationCredit, self).get_context_data(**kwargs)
|
||||
context['faqmenu']="donation"
|
||||
context['nonprofit'] = settings.NONPROFIT
|
||||
try:
|
||||
envelope=signing.loads(kwargs['token'])
|
||||
context['envelope']=envelope
|
||||
except signing.BadSignature:
|
||||
self.template_name="donation_error.html"
|
||||
return context
|
||||
try:
|
||||
work = models.Work.objects.get(id=envelope['work_id'])
|
||||
campaign=work.last_campaign()
|
||||
except models.Work.DoesNotExist:
|
||||
campaign = None
|
||||
context['work']=work
|
||||
try:
|
||||
user = User.objects.get(username=envelope['username'])
|
||||
except User.DoesNotExist:
|
||||
self.template_name="donation_user_error.html"
|
||||
context['error']='user does not exist'
|
||||
return context
|
||||
if user != self.request.user:
|
||||
self.template_name="donation_user_error.html"
|
||||
context['error']='wrong user logged in'
|
||||
return context
|
||||
try:
|
||||
# check token not used
|
||||
CreditLog.objects.get(sent=envelope['sent'])
|
||||
context['error']='credit already registered'
|
||||
return context
|
||||
except CreditLog.DoesNotExist:
|
||||
#not used yet!
|
||||
amount=envelope['amount']+envelope['cents']/D(100)
|
||||
CreditLog.objects.create(user=user,amount=amount,action='deposit',sent=envelope['sent'])
|
||||
ts=Transaction.objects.filter(user=user,campaign=campaign,status=TRANSACTION_STATUS_NONE).order_by('-pk')
|
||||
if ts.count()==0:
|
||||
ts=Transaction.objects.filter(user=user,campaign=campaign,status=TRANSACTION_STATUS_MODIFIED).order_by('-pk')
|
||||
if ts.count()>0:
|
||||
t=ts[0]
|
||||
credit_transaction(t,user, amount)
|
||||
for t in ts[1:]:
|
||||
t.status=TRANSACTION_STATUS_CANCELED
|
||||
t.save()
|
||||
context['transaction']=t
|
||||
return context
|
||||
else:
|
||||
user.credit.add_to_balance(amount)
|
||||
return context
|
||||
|
||||
|
||||
class PledgeRechargeView(TemplateView):
|
||||
|
@ -871,7 +913,7 @@ class PledgeRechargeView(TemplateView):
|
|||
|
||||
context = super(PledgeRechargeView, self).get_context_data(**kwargs)
|
||||
|
||||
# the following should be true since PledgeModifyView.as_view is wrapped in login_required
|
||||
# the following should be true since PledgeView.as_view is wrapped in login_required
|
||||
assert self.request.user.is_authenticated()
|
||||
user = self.request.user
|
||||
|
||||
|
@ -890,18 +932,10 @@ class PledgeRechargeView(TemplateView):
|
|||
|
||||
if transaction is not None:
|
||||
# the recipients of this authorization is not specified here but rather by the PaymentManager.
|
||||
# set the expiry date based on the campaign deadline
|
||||
expiry = campaign.deadline + timedelta( days=settings.PREAPPROVAL_PERIOD_AFTER_CAMPAIGN )
|
||||
ack_name = transaction.ack_name
|
||||
ack_link = transaction.ack_link
|
||||
ack_dedication = transaction.ack_dedication
|
||||
|
||||
paymentReason = "Unglue.it Recharge for {0}".format(campaign.name)
|
||||
|
||||
p = PaymentManager(embedded=False)
|
||||
t, url = p.authorize('USD', TARGET_TYPE_CAMPAIGN, transaction.amount, expiry=expiry, campaign=campaign, list=None, user=user,
|
||||
return_url=return_url, nevermind_url=nevermind_url, anonymous=transaction.anonymous, premium=transaction.premium,
|
||||
paymentReason=paymentReason, ack_name=ack_name, ack_link=ack_link, ack_dedication=ack_dedication)
|
||||
p = PaymentManager()
|
||||
t, url = p.authorize(transaction, return_url=return_url, paymentReason=paymentReason)
|
||||
logger.info("Recharge url: {0}".format(url))
|
||||
else:
|
||||
url = None
|
||||
|
@ -933,10 +967,6 @@ class PledgeCompleteView(TemplateView):
|
|||
# pick up all get and post parameters and display
|
||||
context = super(PledgeCompleteView, self).get_context_data()
|
||||
|
||||
output = "pledge complete"
|
||||
output += self.request.method + "\n" + str(self.request.REQUEST.items())
|
||||
context["output"] = output
|
||||
|
||||
if self.request.user.is_authenticated():
|
||||
user = self.request.user
|
||||
else:
|
||||
|
@ -955,12 +985,8 @@ class PledgeCompleteView(TemplateView):
|
|||
work = None
|
||||
|
||||
# we need to check whether the user tied to the transaction is indeed the authenticated user.
|
||||
|
||||
correct_user = False
|
||||
try:
|
||||
if user.id == transaction.user.id:
|
||||
correct_user = True
|
||||
else:
|
||||
if user.id != transaction.user.id:
|
||||
# should be 403 -- but let's try 404 for now -- 403 exception coming in Django 1.4
|
||||
raise Http404
|
||||
except Exception, e:
|
||||
|
@ -978,7 +1004,7 @@ class PledgeCompleteView(TemplateView):
|
|||
correct_transaction_type = False
|
||||
|
||||
# add the work corresponding to the Transaction on the user's wishlist if it's not already on the wishlist
|
||||
if user is not None and correct_user and correct_transaction_type and (campaign is not None) and (work is not None):
|
||||
if user is not None and correct_transaction_type and (campaign is not None) and (work is not None):
|
||||
# ok to overwrite Wishes.source?
|
||||
user.wishlist.add_work(work, 'pledging')
|
||||
|
||||
|
@ -987,8 +1013,6 @@ class PledgeCompleteView(TemplateView):
|
|||
works2 = worklist[4:8]
|
||||
|
||||
context["transaction"] = transaction
|
||||
context["correct_user"] = correct_user
|
||||
context["correct_transaction_type"] = correct_transaction_type
|
||||
context["work"] = work
|
||||
context["campaign"] = campaign
|
||||
context["faqmenu"] = "complete"
|
||||
|
@ -998,6 +1022,11 @@ class PledgeCompleteView(TemplateView):
|
|||
|
||||
return context
|
||||
|
||||
class PledgeModifiedView(PledgeCompleteView):
|
||||
def get_context_data(self):
|
||||
context = super(PledgeModifiedView, self).get_context_data()
|
||||
context['modified']=True
|
||||
return context
|
||||
|
||||
class PledgeCancelView(FormView):
|
||||
"""A view for allowing a user to cancel the active transaction for specified campaign"""
|
||||
|
@ -1093,114 +1122,6 @@ class PledgeCancelView(FormView):
|
|||
logger.error("Exception from attempt to cancel pledge for campaign id {0} for username {1}: {2}".format(campaign_id, user.username, e))
|
||||
return HttpResponse("Sorry, something went wrong in canceling your campaign pledge. We have logged this error.")
|
||||
|
||||
|
||||
class PledgeNeverMindView(TemplateView):
|
||||
"""A callback for PayPal to tell unglue.it that a payment transaction has been canceled by the user"""
|
||||
template_name="pledge_nevermind.html"
|
||||
|
||||
def get_context_data(self):
|
||||
context = super(PledgeNeverMindView, self).get_context_data()
|
||||
|
||||
if self.request.user.is_authenticated():
|
||||
user = self.request.user
|
||||
else:
|
||||
user = None
|
||||
|
||||
# pull out the transaction id and try to get the corresponding Transaction
|
||||
transaction_id = self.request.REQUEST.get("tid")
|
||||
transaction = Transaction.objects.get(id=transaction_id)
|
||||
|
||||
# work and campaign in question
|
||||
try:
|
||||
campaign = transaction.campaign
|
||||
work = campaign.work
|
||||
except Exception, e:
|
||||
campaign = None
|
||||
work = None
|
||||
|
||||
# we need to check whether the user tied to the transaction is indeed the authenticated user.
|
||||
|
||||
correct_user = False
|
||||
try:
|
||||
if user.id == transaction.user.id:
|
||||
correct_user = True
|
||||
except Exception, e:
|
||||
pass
|
||||
|
||||
# check that the user had not already approved the transaction
|
||||
# do we need to first run PreapprovalDetails to check on the status
|
||||
|
||||
# is it of type=PAYMENT_TYPE_AUTHORIZATION and status is NONE or ACTIVE (but approved is false)
|
||||
|
||||
if transaction.type == PAYMENT_TYPE_AUTHORIZATION:
|
||||
correct_transaction_type = True
|
||||
else:
|
||||
correct_transaction_type = False
|
||||
|
||||
# status?
|
||||
|
||||
# give the user an opportunity to approved the transaction again
|
||||
# provide a URL to click on.
|
||||
# https://www.sandbox.paypal.com/?cmd=_ap-preapproval&preapprovalkey=PA-6JV656290V840615H
|
||||
try_again_url = '%s?cmd=_ap-preapproval&preapprovalkey=%s' % (settings.PAYPAL_PAYMENT_HOST, transaction.preapproval_key)
|
||||
|
||||
context["transaction"] = transaction
|
||||
context["correct_user"] = correct_user
|
||||
context["correct_transaction_type"] = correct_transaction_type
|
||||
context["try_again_url"] = try_again_url
|
||||
context["work"] = work
|
||||
context["campaign"] = campaign
|
||||
context["faqmenu"] = "cancel"
|
||||
|
||||
return context
|
||||
|
||||
|
||||
class DonateView(FormView):
|
||||
template_name="donate.html"
|
||||
form_class = DonateForm
|
||||
embedded = False
|
||||
|
||||
#def get_context_data(self, **kwargs):
|
||||
# context = super(DonateView, self).get_context_data(**kwargs)
|
||||
#
|
||||
# form = CampaignPledgeForm(data)
|
||||
#
|
||||
# context.update({'work':work,'campaign':campaign, 'premiums':premiums, 'form':form, 'premium_id':premium_id})
|
||||
# return context
|
||||
|
||||
def form_valid(self, form):
|
||||
donation_amount = form.cleaned_data["donation_amount"]
|
||||
anonymous = form.cleaned_data["anonymous"]
|
||||
|
||||
# right now, if there is a non-zero pledge amount, go with that. otherwise, do the pre_approval
|
||||
campaign = None
|
||||
|
||||
p = PaymentManager(embedded=self.embedded)
|
||||
|
||||
# we should force login at this point -- or if no account, account creation, login, and return to this spot
|
||||
if self.request.user.is_authenticated():
|
||||
user = self.request.user
|
||||
else:
|
||||
user = None
|
||||
|
||||
# instant payment: send to the partnering RH
|
||||
receiver_list = [{'email':settings.PAYPAL_NONPROFIT_PARTNER_EMAIL, 'amount':donation_amount}]
|
||||
|
||||
#redirect the page back to campaign page on success
|
||||
return_url = self.request.build_absolute_uri(reverse('donate'))
|
||||
|
||||
t, url = p.pledge('USD', TARGET_TYPE_DONATION, receiver_list, campaign=campaign, list=None, user=user,
|
||||
return_url=return_url, anonymous=anonymous, ack_name=ack_name, ack_link=ack_link,
|
||||
ack_dedication=ack_dedication)
|
||||
|
||||
if url:
|
||||
return HttpResponseRedirect(url)
|
||||
else:
|
||||
response = t.reference
|
||||
logger.info("PledgeView paypal: Error " + str(t.reference))
|
||||
return HttpResponse(response)
|
||||
|
||||
|
||||
def claim(request):
|
||||
if request.method == 'GET':
|
||||
data = request.GET
|
||||
|
@ -2071,14 +1992,34 @@ def campaign_archive_js(request):
|
|||
return response
|
||||
|
||||
def lockss(request, work_id):
|
||||
"""
|
||||
manifest pages for lockss harvester
|
||||
"""
|
||||
work = safe_get_work(work_id)
|
||||
try:
|
||||
work = models.Work.objects.get(id = work_id)
|
||||
except models.Work.DoesNotExist:
|
||||
try:
|
||||
work = models.WasWork.objects.get(was = work_id).work
|
||||
except models.WasWork.DoesNotExist:
|
||||
raise Http404
|
||||
ebook = work.ebooks().filter(unglued=True)[0]
|
||||
ebooks = work.ebooks().filter(edition__unglued=True)
|
||||
except:
|
||||
ebooks = None
|
||||
authors = list(models.Author.objects.filter(editions__work=work).all())
|
||||
|
||||
return render(request, "lockss.html", {'work':work, 'ebook':ebook, 'authors':authors})
|
||||
return render(request, "lockss.html", {'work':work, 'ebooks':ebooks, 'authors':authors})
|
||||
|
||||
def download(request, work_id):
|
||||
context = {}
|
||||
work = safe_get_work(work_id)
|
||||
context.update({'work': work})
|
||||
|
||||
unglued_ebooks = work.ebooks().filter(edition__unglued=True)
|
||||
other_ebooks = work.ebooks().filter(edition__unglued=False)
|
||||
|
||||
context.update({
|
||||
'unglued_ebooks': unglued_ebooks,
|
||||
'other_ebooks': other_ebooks
|
||||
})
|
||||
|
||||
return render(request, "download.html", context)
|
||||
|
||||
def about(request, facet):
|
||||
template = "about_" + facet + ".html"
|
||||
return render(request, template)
|
||||
|
||||
|
|
|
@ -1,870 +0,0 @@
|
|||
from regluit.payment.parameters import *
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.conf import settings
|
||||
from regluit.payment.models import Transaction, PaymentResponse
|
||||
from boto.fps.connection import FPSConnection
|
||||
from django.http import HttpResponse, HttpRequest, HttpResponseRedirect, HttpResponseBadRequest, HttpResponseForbidden
|
||||
from datetime import timedelta
|
||||
from regluit.utils.localdatetime import now, zuluformat
|
||||
from boto import handler
|
||||
from boto.resultset import ResultSet
|
||||
from boto.exception import FPSResponseError
|
||||
import xml.sax
|
||||
|
||||
import traceback
|
||||
import datetime
|
||||
import logging
|
||||
import urlparse
|
||||
import time
|
||||
import urllib
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
AMAZON_STATUS_SUCCESS_ABT = 'SA'
|
||||
AMAZON_STATUS_SUCCESS_ACH = 'SB'
|
||||
AMAZON_STATUS_SUCCESS_CREDIT = 'SC'
|
||||
AMAZON_STATUS_ERROR = 'SE'
|
||||
AMAZON_STATUS_ADBANDONED = 'A'
|
||||
AMAZON_STATUS_EXCEPTION = 'CE'
|
||||
AMAZON_STATUS_PAYMENT_MISMATCH = 'PE'
|
||||
AMAZON_STATUS_INCOMPLETE = 'NP'
|
||||
AMAZON_STATUS_NOT_REGISTERED = 'NM'
|
||||
|
||||
AMAZON_STATUS_CANCELED = 'Canceled'
|
||||
AMAZON_STATUS_FAILURE = 'Failure'
|
||||
AMAZON_STATUS_PENDING = 'Pending'
|
||||
AMAZON_STATUS_RESERVED = 'Reserved'
|
||||
AMAZON_STATUS_SUCCESS = 'Success'
|
||||
|
||||
AMAZON_IPN_STATUS_CANCELED = 'CANCELED'
|
||||
AMAZON_IPN_STATUS_FAILURE = 'FAILURE'
|
||||
AMAZON_IPN_STATUS_PENDING = 'PENDING'
|
||||
AMAZON_IPN_STATUS_RESERVED = 'RESERVED'
|
||||
AMAZON_IPN_STATUS_SUCCESS = 'SUCCESS'
|
||||
|
||||
AMAZON_NOTIFICATION_TYPE_STATUS = 'TransactionStatus'
|
||||
AMAZON_NOTIFICATION_TYPE_CANCEL = 'TokenCancellation'
|
||||
|
||||
AMAZON_OPERATION_TYPE_PAY = 'PAY'
|
||||
AMAZON_OPERATION_TYPE_REFUND = 'REFUND'
|
||||
AMAZON_OPERATION_TYPE_CANCEL = 'CANCEL'
|
||||
|
||||
# load FPS_ACCESS_KEY and FPS_SECRET_KEY from the database if possible
|
||||
try:
|
||||
from regluit.core.models import Key
|
||||
FPS_ACCESS_KEY = Key.objects.get(name="FPS_ACCESS_KEY").value
|
||||
FPS_SECRET_KEY = Key.objects.get(name="FPS_SECRET_KEY").value
|
||||
logger.info('Successful loading of FPS_*_KEYs')
|
||||
except Exception, e:
|
||||
logger.info('EXCEPTION: unsuccessful loading of FPS_*_KEYs: {0}'.format(e))
|
||||
|
||||
def get_ipn_url():
|
||||
|
||||
if settings.IPN_SECURE_URL:
|
||||
return settings.BASE_URL_SECURE + reverse('HandleIPN', args=["amazon"])
|
||||
else:
|
||||
return settings.BASE_URL + reverse('HandleIPN', args=["amazon"])
|
||||
|
||||
def ProcessIPN(request):
|
||||
'''
|
||||
IPN handler for amazon. Here is a litle background on amazon IPNS
|
||||
http://docs.amazonwebservices.com/AmazonFPS/latest/FPSAdvancedGuide/APPNDX_IPN.html
|
||||
|
||||
notificationType: Can either be TransactionStatus of TokenCancellation
|
||||
status: One of the defined IPN status codes
|
||||
operation: The type of operation
|
||||
callerReference: The reference to find the transaction
|
||||
|
||||
The IPN is called for the following cases:
|
||||
|
||||
A payment or reserve succeeds
|
||||
A payment or reserve fails
|
||||
A payment or reserve goes into a pending state
|
||||
A reserved payment is settled successfully
|
||||
A reserved payment is not settled successfully
|
||||
A refund succeeds
|
||||
A refund fails
|
||||
A refund goes into a pending state
|
||||
A payment is canceled
|
||||
A reserve is canceled
|
||||
A token is canceled successfully
|
||||
|
||||
'''
|
||||
try:
|
||||
logging.debug("Amazon IPN called")
|
||||
logging.debug(request.POST)
|
||||
|
||||
uri = request.build_absolute_uri()
|
||||
parsed_url = urlparse.urlparse(uri)
|
||||
|
||||
connection = FPSConnection(FPS_ACCESS_KEY, FPS_SECRET_KEY, host=settings.AMAZON_FPS_HOST)
|
||||
|
||||
# Check the validity of the IPN
|
||||
resp = connection.verify_signature("%s://%s%s" %(parsed_url.scheme,
|
||||
parsed_url.netloc,
|
||||
parsed_url.path),
|
||||
request.raw_post_data)
|
||||
|
||||
if not resp[0].VerificationStatus == "Success":
|
||||
# Error, ignore this IPN
|
||||
logging.error("Amazon IPN cannot be verified with post data: ")
|
||||
logging.error(request.raw_post_data)
|
||||
return HttpResponseForbidden()
|
||||
|
||||
logging.debug("Amazon IPN post data:")
|
||||
logging.debug(request.POST)
|
||||
|
||||
reference = request.POST['callerReference']
|
||||
type = request.POST['notificationType']
|
||||
|
||||
# In the case of cancelling a token, there is no transaction, so this info is not set
|
||||
transactionId = request.POST.get('transactionId', None)
|
||||
date = request.POST.get('transactionDate', None)
|
||||
operation = request.POST.get('operation', None)
|
||||
status = request.POST.get('transactionStatus', None)
|
||||
|
||||
logging.info("Received Amazon IPN with the following data:")
|
||||
logging.info("type = %s" % type)
|
||||
logging.info("operation = %s" % operation)
|
||||
logging.info("reference = %s" % reference)
|
||||
logging.info("status = %s" % status)
|
||||
|
||||
# We should always find the transaction by the token
|
||||
transaction = Transaction.objects.get(secret=reference)
|
||||
|
||||
if type == AMAZON_NOTIFICATION_TYPE_STATUS:
|
||||
|
||||
|
||||
# status update for the token, save the actual value
|
||||
transaction.local_status = status
|
||||
|
||||
# Now map our local status to the global status codes
|
||||
if operation == AMAZON_OPERATION_TYPE_PAY:
|
||||
|
||||
|
||||
if status == AMAZON_IPN_STATUS_SUCCESS:
|
||||
transaction.status = TRANSACTION_STATUS_COMPLETE
|
||||
|
||||
elif status == AMAZON_IPN_STATUS_PENDING:
|
||||
|
||||
if transaction.status == TRANSACTION_STATUS_CREATED:
|
||||
#
|
||||
# Per the amazon documentation:
|
||||
# If your IPN receiving service is down for some time, it is possible that our retry mechanism will deliver the IPNs out of order.
|
||||
# If you receive an IPN for TransactionStatus (IPN), as SUCCESS or FAILURE or RESERVED,
|
||||
# then after that time ignore any IPN that gives the PENDING status for the transaction
|
||||
#
|
||||
transaction.status = TRANSACTION_STATUS_PENDING
|
||||
else:
|
||||
transaction.status = TRANSACTION_STATUS_ERROR
|
||||
|
||||
elif operation == AMAZON_OPERATION_TYPE_REFUND:
|
||||
|
||||
if status == AMAZON_IPN_STATUS_SUCCESS:
|
||||
transaction.status = TRANSACTION_STATUS_REFUNDED
|
||||
elif status == AMAZON_IPN_STATUS_PENDING:
|
||||
transaction.status = TRANSACTION_STATUS_PENDING
|
||||
else:
|
||||
transaction.status = TRANSACTION_STATUS_ERROR
|
||||
|
||||
elif operation == AMAZON_OPERATION_TYPE_CANCEL:
|
||||
|
||||
if status == AMAZON_IPN_STATUS_SUCCESS:
|
||||
transaction.status = TRANSACTION_STATUS_COMPLETE
|
||||
else:
|
||||
transaction.status = TRANSACTION_STATUS_ERROR
|
||||
|
||||
|
||||
elif type == AMAZON_NOTIFICATION_TYPE_CANCEL:
|
||||
#
|
||||
# The cancel IPN does not have a transaction ID or transaction status, so make them up
|
||||
#
|
||||
transaction.local_status = AMAZON_IPN_STATUS_CANCELED
|
||||
transaction.status = TRANSACTION_STATUS_CANCELED
|
||||
status = AMAZON_IPN_STATUS_CANCELED
|
||||
|
||||
|
||||
transaction.save()
|
||||
|
||||
#
|
||||
# This is currently not done in paypal land, but log this IPN since the amazon IPN has good info
|
||||
#
|
||||
PaymentResponse.objects.create(api="IPN",
|
||||
correlation_id = transactionId,
|
||||
timestamp = date,
|
||||
info = str(request.POST),
|
||||
status=status,
|
||||
transaction=transaction)
|
||||
|
||||
return HttpResponse("Complete")
|
||||
|
||||
except:
|
||||
traceback.print_exc()
|
||||
return HttpResponseForbidden()
|
||||
|
||||
|
||||
def amazonPaymentReturn(request):
|
||||
'''
|
||||
This is the complete view called after the co-branded API completes. It is called whenever the user
|
||||
approves a preapproval or a pledge. This URL is set via the PAY api.
|
||||
'''
|
||||
try:
|
||||
transaction = None
|
||||
|
||||
# pick up all get and post parameters and display
|
||||
output = "payment complete"
|
||||
output += request.method + "\n" + str(request.REQUEST.items())
|
||||
|
||||
|
||||
signature = request.GET['signature']
|
||||
status = request.GET['status']
|
||||
reference = request.GET['callerReference']
|
||||
token = request.GET['tokenID']
|
||||
|
||||
# validate the signature
|
||||
|
||||
uri = request.build_absolute_uri()
|
||||
parsed_url = urlparse.urlparse(uri)
|
||||
|
||||
connection = FPSConnection(FPS_ACCESS_KEY, FPS_SECRET_KEY, host=settings.AMAZON_FPS_HOST)
|
||||
|
||||
# Check the validity of the IPN
|
||||
resp = connection.verify_signature("%s://%s%s" %(parsed_url.scheme,
|
||||
parsed_url.netloc,
|
||||
parsed_url.path),
|
||||
urllib.urlencode(request.GET))
|
||||
|
||||
if not resp[0].VerificationStatus == "Success":
|
||||
# Error, ignore this
|
||||
logging.error("amazonPaymentReturn cannot be verified with get data: ")
|
||||
logging.error(request.GET)
|
||||
return HttpResponseForbidden()
|
||||
|
||||
logging.debug("amazonPaymentReturn sig verified:")
|
||||
logging.debug(request.GET)
|
||||
|
||||
# validation of signature ok
|
||||
# Find the transaction by reference, there should only be one
|
||||
try:
|
||||
transaction = Transaction.objects.get(secret=reference)
|
||||
except:
|
||||
logging.info("transaction with secret {0}".format(reference))
|
||||
return HttpResponseForbidden()
|
||||
|
||||
logging.info("Amazon Co-branded Return URL called for transaction id: %d" % transaction.id)
|
||||
logging.info(request.GET)
|
||||
|
||||
#
|
||||
# BUGBUG, for now lets map amazon status code to paypal, just to keep things uninform
|
||||
#
|
||||
if transaction.type == PAYMENT_TYPE_INSTANT:
|
||||
# Instant payments need to be executed now
|
||||
|
||||
# Log the authorize transaction
|
||||
r = PaymentResponse.objects.create(api="Authorize",
|
||||
correlation_id = "None",
|
||||
timestamp = str(datetime.datetime.now()),
|
||||
info = str(request.GET),
|
||||
status=status,
|
||||
transaction=transaction)
|
||||
|
||||
if status == AMAZON_STATUS_SUCCESS_ABT or status == AMAZON_STATUS_SUCCESS_ACH or status == AMAZON_STATUS_SUCCESS_CREDIT:
|
||||
# The above status code are unique to the return URL and are different than the pay API codes
|
||||
|
||||
# Store the token, we need this for the IPN.
|
||||
transaction.pay_key = token
|
||||
|
||||
#
|
||||
# BUGBUG, need to handle multiple recipients
|
||||
# Send the pay request now to ourselves
|
||||
#
|
||||
e = Execute(transaction=transaction)
|
||||
|
||||
if e.success() and not e.error():
|
||||
# Success case, save the ID. Our IPN will update the status
|
||||
print "Amazon Execute returned succesfully"
|
||||
|
||||
else:
|
||||
logging.error("Amazon payment execution failed: ")
|
||||
logging.error(e.envelope())
|
||||
transaction.status = TRANSACTION_STATUS_ERROR
|
||||
|
||||
# Log the pay transaction
|
||||
r = PaymentResponse.objects.create(api="Pay",
|
||||
correlation_id = e.correlation_id(),
|
||||
timestamp = e.timestamp(),
|
||||
info = e.envelope(),
|
||||
status = e.status,
|
||||
transaction=transaction)
|
||||
|
||||
else:
|
||||
# We may never see an IPN, set the status here
|
||||
logging.error("Amazon payment authorization failed: ")
|
||||
logging.error(request.GET)
|
||||
transaction.status = TRANSACTION_STATUS_ERROR
|
||||
|
||||
|
||||
elif transaction.type == PAYMENT_TYPE_AUTHORIZATION:
|
||||
#
|
||||
# Future payments, we only need to store the token. The authorization was requested with the default expiration
|
||||
# date set in our settings. When we are ready, we can call execute on this
|
||||
#
|
||||
transaction.local_status = status
|
||||
|
||||
if status == AMAZON_STATUS_SUCCESS_ABT or status == AMAZON_STATUS_SUCCESS_ACH or status == AMAZON_STATUS_SUCCESS_CREDIT:
|
||||
|
||||
# The above status code are unique to the return URL and are different than the pay API codes
|
||||
transaction.status = TRANSACTION_STATUS_ACTIVE
|
||||
transaction.approved = True
|
||||
transaction.pay_key = token
|
||||
transaction.save()
|
||||
|
||||
print "Calling CANCEL RELATED"
|
||||
|
||||
# clear out any other active transactions for this user and this campaign
|
||||
from regluit.payment.manager import PaymentManager
|
||||
p = PaymentManager()
|
||||
p.cancel_related_transaction(transaction, status=TRANSACTION_STATUS_ACTIVE, campaign=transaction.campaign)
|
||||
|
||||
else:
|
||||
# We may never see an IPN, set the status here
|
||||
transaction.status = TRANSACTION_STATUS_ERROR
|
||||
|
||||
# Log the trasaction
|
||||
r = PaymentResponse.objects.create(api="Authorize",
|
||||
correlation_id = "None",
|
||||
timestamp = str(datetime.datetime.now()),
|
||||
info = str(request.GET),
|
||||
status = status,
|
||||
transaction=transaction)
|
||||
|
||||
else:
|
||||
# Corrupt transaciton, unknown type
|
||||
transaction.status = TRANSACTION_STATUS_ERROR
|
||||
|
||||
transaction.save()
|
||||
|
||||
if transaction.status == TRANSACTION_STATUS_ERROR:
|
||||
# We failed, redirect to a page to allow the user to try again
|
||||
return_path = "{0}?{1}".format(reverse('pledge_nevermind'),
|
||||
urllib.urlencode({'tid':transaction.id}))
|
||||
return_url = urlparse.urljoin(settings.BASE_URL, return_path)
|
||||
return HttpResponseRedirect(return_url)
|
||||
|
||||
else:
|
||||
# Not a failure, exact status will be updated by IPN
|
||||
# Redirect to our pledge success URL
|
||||
return_path = "{0}?{1}".format(reverse('pledge_complete'),
|
||||
urllib.urlencode({'tid':transaction.id}))
|
||||
return_url = urlparse.urljoin(settings.BASE_URL, return_path)
|
||||
return HttpResponseRedirect(return_url)
|
||||
|
||||
except Exception, e:
|
||||
logging.error("Amazon co-branded return-url FAILED with exception:")
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
if transaction:
|
||||
|
||||
# We failed, redirect to a page to allow the user to try again
|
||||
return_path = "{0}?{1}".format(reverse('pledge_nevermind'),
|
||||
urllib.urlencode({'tid':transaction.id}))
|
||||
return_url = urlparse.urljoin(settings.BASE_URL, return_path)
|
||||
return HttpResponseRedirect(return_url)
|
||||
|
||||
else:
|
||||
|
||||
#
|
||||
# If we are here, amazon did not give us a caller reference, so we don't know what transaction was cancelled.
|
||||
# Some kind of cleanup is required. This can happen if there is an error in the amazon API or if the user clicks
|
||||
# the cancel button. If the case where the user closes the co-branded window or hits the back button, we will never arrive here.
|
||||
#
|
||||
return HttpResponseRedirect(settings.BASE_URL)
|
||||
|
||||
class AmazonRequest:
|
||||
'''
|
||||
Handles common information that is processed from the response envelope of the amazon request.
|
||||
|
||||
'''
|
||||
|
||||
# Global values for the class
|
||||
response = None
|
||||
raw_response = None
|
||||
errorMessage = None
|
||||
status = None
|
||||
url = None
|
||||
|
||||
def ack( self ):
|
||||
return None
|
||||
|
||||
def success(self):
|
||||
|
||||
if self.errorMessage:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def error(self):
|
||||
if self.errorMessage:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def error_data(self):
|
||||
return None
|
||||
|
||||
def error_id(self):
|
||||
return None
|
||||
|
||||
def error_string(self):
|
||||
|
||||
return self.errorMessage
|
||||
|
||||
def envelope(self):
|
||||
|
||||
# The envelope is used to store info about this request
|
||||
if self.response:
|
||||
return str(self.response)
|
||||
else:
|
||||
return None
|
||||
|
||||
def correlation_id(self):
|
||||
# The correlation ID is unique to each API call
|
||||
if self.response:
|
||||
return self.response.TransactionId
|
||||
else:
|
||||
return None
|
||||
|
||||
def timestamp(self):
|
||||
return str(datetime.datetime.now())
|
||||
|
||||
|
||||
class Pay( AmazonRequest ):
|
||||
|
||||
'''
|
||||
The pay function generates a redirect URL to approve the transaction
|
||||
'''
|
||||
|
||||
def __init__( self, transaction, return_url=None, nevermind_url=None, amount=None, paymentReason=""):
|
||||
|
||||
try:
|
||||
logging.debug("Amazon PAY operation for transaction ID %d" % transaction.id)
|
||||
|
||||
# Replace our return URL with a redirect through our internal URL
|
||||
self.original_return_url = return_url
|
||||
return_url = settings.BASE_URL + reverse('AmazonPaymentReturn')
|
||||
|
||||
self.connection = FPSConnection(FPS_ACCESS_KEY, FPS_SECRET_KEY, host=settings.AMAZON_FPS_HOST)
|
||||
|
||||
receiver_list = []
|
||||
receivers = transaction.receiver_set.all()
|
||||
|
||||
if not amount:
|
||||
amount = 0
|
||||
for r in receivers:
|
||||
amount += r.amount
|
||||
|
||||
logger.info(receiver_list)
|
||||
|
||||
# Data fields for amazon
|
||||
|
||||
expiry = now() + timedelta( days=settings.PREAPPROVAL_PERIOD )
|
||||
|
||||
data = {
|
||||
'amountType':'Maximum', # The transaction amount is the maximum amount
|
||||
'callerReference': transaction.secret,
|
||||
'currencyCode': 'USD',
|
||||
'globalAmountLimit': str(amount),
|
||||
'validityExpiry': str(int(time.mktime(expiry.timetuple()))), # use the preapproval date by default
|
||||
}
|
||||
|
||||
self.url = self.connection.make_url(return_url, paymentReason, "MultiUse", str(amount), **data)
|
||||
|
||||
logging.debug("Amazon PAY redirect url was: %s" % self.url)
|
||||
|
||||
except FPSResponseError as (responseStatus, responseReason, body):
|
||||
logging.error("Amazon PAY api failed with status: %s, reason: %s and data:" % (responseStatus, responseReason))
|
||||
logging.error(body)
|
||||
self.errorMessage = body
|
||||
|
||||
except:
|
||||
logging.error("Amazon PAY FAILED with exception:")
|
||||
traceback.print_exc()
|
||||
self.errorMessage = "Error: Server Error"
|
||||
|
||||
def api(self):
|
||||
return "Amazon Co-branded PAY request"
|
||||
|
||||
def exec_status( self ):
|
||||
return None
|
||||
|
||||
def amount( self ):
|
||||
return None
|
||||
|
||||
def key( self ):
|
||||
return None
|
||||
|
||||
def next_url( self ):
|
||||
return self.url
|
||||
|
||||
def embedded_url(self):
|
||||
return None
|
||||
|
||||
class Preapproval(Pay):
|
||||
|
||||
def __init__( self, transaction, amount, expiry=None, return_url=None, nevermind_url=None, paymentReason=""):
|
||||
|
||||
# set the expiration date for the preapproval if not passed in. This is what the paypal library does
|
||||
now_val = now()
|
||||
if expiry is None:
|
||||
expiry = now_val + timedelta( days=settings.PREAPPROVAL_PERIOD )
|
||||
transaction.date_authorized = now_val
|
||||
transaction.date_expired = expiry
|
||||
transaction.save()
|
||||
|
||||
# Call into our parent class
|
||||
Pay.__init__(self, transaction, return_url=return_url, nevermind_url=nevermind_url, amount=amount, paymentReason=paymentReason)
|
||||
|
||||
|
||||
class Execute(AmazonRequest):
|
||||
|
||||
'''
|
||||
The Execute function sends an existing token(generated via the URL from the pay operation), and collects
|
||||
the money.
|
||||
'''
|
||||
|
||||
def __init__(self, transaction=None):
|
||||
|
||||
try:
|
||||
logging.debug("Amazon EXECUTE action for transaction id: %d" % transaction.id)
|
||||
|
||||
# Use the boto class top open a connection
|
||||
self.connection = FPSConnection(FPS_ACCESS_KEY, FPS_SECRET_KEY, host=settings.AMAZON_FPS_HOST)
|
||||
self.transaction = transaction
|
||||
|
||||
# BUGBUG, handle multiple receivers! For now we just send the money to ourselves
|
||||
global_params = {"OverrideIPNURL": get_ipn_url()}
|
||||
self.raw_response = self.connection.pay(transaction.amount,
|
||||
transaction.pay_key,
|
||||
recipientTokenId=None,
|
||||
callerReference=transaction.secret,
|
||||
senderReference=None,
|
||||
recipientReference=None,
|
||||
senderDescription=None,
|
||||
recipientDescription=None,
|
||||
callerDescription=None,
|
||||
metadata=None,
|
||||
transactionDate=None,
|
||||
reserve=False,
|
||||
extra_params=global_params)
|
||||
|
||||
#
|
||||
# BUGBUG:
|
||||
# The boto FPS library throws an exception if an error is generated, we need to do a better
|
||||
# job of reporting the error when this occurs
|
||||
#
|
||||
|
||||
self.response = self.raw_response[0]
|
||||
logging.debug("Amazon EXECUTE response for transaction id: %d" % transaction.id)
|
||||
logging.debug(str(self.response))
|
||||
|
||||
self.status = self.response.TransactionStatus
|
||||
|
||||
#
|
||||
# For amazon, the transactionID is per transaction, not per receiver. For now we will store it in the preapproval key field
|
||||
# so we can use it to refund or get status later
|
||||
#
|
||||
transaction.preapproval_key = self.response.TransactionId
|
||||
|
||||
logging.debug("Amazon EXECUTE API returning with variables:")
|
||||
logging.debug(locals())
|
||||
|
||||
except FPSResponseError as (responseStatus, responseReason, body):
|
||||
|
||||
logging.error("Amazon EXECUTE api failed with status: %s, reason: %s and data:" % (responseStatus, responseReason))
|
||||
logging.error(body)
|
||||
self.errorMessage = body
|
||||
|
||||
except:
|
||||
logging.error("Amazon EXECUTE FAILED with exception:")
|
||||
traceback.print_exc()
|
||||
self.errorMessage = "Error: Server Error"
|
||||
|
||||
def api(self):
|
||||
return "Amazon API Pay"
|
||||
|
||||
def key(self):
|
||||
# IN paypal land, our key is updated from a preapproval to a pay key here, just return the existing key
|
||||
return self.transaction.pay_key
|
||||
|
||||
|
||||
|
||||
class Finish(AmazonRequest):
|
||||
'''
|
||||
The Finish function handles the secondary receiver in a chained payment. Currently not implemented
|
||||
for amazon
|
||||
'''
|
||||
def __init__(self, transaction):
|
||||
|
||||
try:
|
||||
|
||||
print "Finish"
|
||||
|
||||
except:
|
||||
traceback.print_exc()
|
||||
self.errorMessage = "Error: Server Error"
|
||||
|
||||
class PaymentDetails(AmazonRequest):
|
||||
'''
|
||||
Get details about executed PAY operation
|
||||
|
||||
This api must set the following class variables to work with the code in manager.py
|
||||
|
||||
status - one of the global transaction status codes
|
||||
transactions -- Not supported for amazon, used by paypal
|
||||
|
||||
'''
|
||||
def __init__(self, transaction=None):
|
||||
|
||||
try:
|
||||
logging.debug("Amazon PAYMENTDETAILS API for transaction id: %d" % transaction.id)
|
||||
|
||||
# Use the boto class top open a connection
|
||||
self.connection = FPSConnection(FPS_ACCESS_KEY, FPS_SECRET_KEY, host=settings.AMAZON_FPS_HOST)
|
||||
self.transaction = transaction
|
||||
|
||||
if not transaction.preapproval_key:
|
||||
# This is where we store the transaction ID
|
||||
self.errorMessage = "No Valid Transaction ID"
|
||||
return
|
||||
|
||||
#
|
||||
# We need to reference the transaction ID here, this is stored in the preapproval_key as this
|
||||
# field is not used for amazon
|
||||
#
|
||||
self.raw_response = self.connection.get_transaction_status(transaction.preapproval_key)
|
||||
self.response = self.raw_response[0]
|
||||
|
||||
logging.debug("Amazon PAYMENTDETAILS API for transaction id: %d returned response:")
|
||||
logging.debug(self.response)
|
||||
|
||||
#
|
||||
# Now we need to build values to match the paypal response.
|
||||
# The two we need are status and and array of transactions.
|
||||
#
|
||||
|
||||
# Check our status codes, note that these are different than the IPN status codes
|
||||
self.local_status = self.response.StatusCode
|
||||
self.message = self.response.StatusMessage
|
||||
|
||||
|
||||
if self.local_status == 'Canceled':
|
||||
self.status = TRANSACTION_STATUS_CANCELED
|
||||
|
||||
elif self.local_status == 'Success':
|
||||
#
|
||||
# Note, there is a limitation here. If the current status is refunded, this API will return "Success".
|
||||
# We must be careful to not overwrite refunded status codes. There is no way that I can find to poll
|
||||
# to see if a transaction is refunded. I need to investigate all of the data fields and see if we can find
|
||||
# that information
|
||||
#
|
||||
if transaction.status != TRANSACTION_STATUS_REFUNDED:
|
||||
self.status = TRANSACTION_STATUS_COMPLETE
|
||||
else:
|
||||
self.status = TRANSACTION_STATUS_REFUNDED
|
||||
|
||||
elif self.local_status == 'PendingNetworkResponse' or self.local_status == 'PendingVerification':
|
||||
self.status = TRANSACTION_STATUS_PENDING
|
||||
elif self.local_status == 'TransactionDenied':
|
||||
self.status = TRANSACTION_STATUS_FAILED
|
||||
else:
|
||||
self.status = TRANSACTION_STATUS_ERROR
|
||||
|
||||
# Amazon does not support receivers at this point
|
||||
self.transactions = []
|
||||
|
||||
logging.debug("Amazon PAYMENTDETAILS API returning with variables:")
|
||||
logging.debug(locals())
|
||||
|
||||
except FPSResponseError as (responseStatus, responseReason, body):
|
||||
|
||||
logging.error("Amazon PAYMENTDETAILS api failed with status: %s, reason: %s and data:" % (responseStatus, responseReason))
|
||||
logging.error(body)
|
||||
self.errorMessage = body
|
||||
|
||||
except:
|
||||
logging.error("Amazon PAYMENTDETAILS FAILED with exception:")
|
||||
self.errorMessage = "Error: ServerError"
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
|
||||
class CancelPreapproval(AmazonRequest):
|
||||
'''
|
||||
Cancels an exisiting token. The current boto FPS library does not directly support
|
||||
the CancelToken API, just the Cancel API(for real money in-flight or reserved).
|
||||
'''
|
||||
|
||||
def __init__(self, transaction):
|
||||
|
||||
try:
|
||||
logging.debug("Amazon CANCELPREAPPROVAL api called for transaction id: %d" % transaction.id)
|
||||
|
||||
# Use the boto class top open a connection
|
||||
self.connection = FPSConnection(FPS_ACCESS_KEY, FPS_SECRET_KEY, host=settings.AMAZON_FPS_HOST)
|
||||
self.transaction = transaction
|
||||
|
||||
global_params = {"OverrideIPNURL": get_ipn_url()}
|
||||
params = global_params
|
||||
params['TokenId'] = transaction.pay_key
|
||||
params['ReasonText'] = "Cancel Reason"
|
||||
|
||||
fps_response = self.connection.make_request("CancelToken", params)
|
||||
|
||||
body = fps_response.read()
|
||||
|
||||
if(fps_response.status == 200):
|
||||
|
||||
rs = ResultSet()
|
||||
h = handler.XmlHandler(rs, self)
|
||||
xml.sax.parseString(body, h)
|
||||
|
||||
if rs:
|
||||
self.raw_response = rs
|
||||
self.response = self.raw_response[0]
|
||||
self.status = self.response.TransactionStatus
|
||||
self.errorMessage = None
|
||||
|
||||
else:
|
||||
#
|
||||
# Set an error message and failure status for
|
||||
# our success() and error() functions
|
||||
#
|
||||
self.status = AMAZON_STATUS_FAILURE
|
||||
self.errorMessage = "%s - %s" % (fps_response.reason, body)
|
||||
|
||||
logging.debug("Amazon CANCELPREAPPROVAL API returning with variables:")
|
||||
logging.debug(locals())
|
||||
|
||||
except FPSResponseError as (responseStatus, responseReason, body):
|
||||
|
||||
logging.error("Amazon CANCELPREAPPROVAL api failed with status: %s, reason: %s and data:" % (responseStatus, responseReason))
|
||||
logging.error(body)
|
||||
self.errorMessage = body
|
||||
|
||||
except:
|
||||
logging.error("Amazon CANCELPREAPPROVAL FAILED with exception:")
|
||||
traceback.print_exc()
|
||||
self.errorMessage = "Error: Server Error"
|
||||
|
||||
|
||||
class RefundPayment(AmazonRequest):
|
||||
|
||||
def __init__(self, transaction):
|
||||
|
||||
try:
|
||||
logging.debug("Amazon REFUNDPAYMENT API called for transaction id: %d", transaction.id)
|
||||
|
||||
# Use the boto class top open a connection
|
||||
self.connection = FPSConnection(FPS_ACCESS_KEY, FPS_SECRET_KEY, host=settings.AMAZON_FPS_HOST)
|
||||
self.transaction = transaction
|
||||
|
||||
if not transaction.preapproval_key:
|
||||
# This is where we store the transaction ID
|
||||
self.errorMessage = "No Valid Transaction ID"
|
||||
return
|
||||
|
||||
#
|
||||
# We need to reference the transaction ID here, this is stored in the preapproval_key as this
|
||||
# field is not used for amazon
|
||||
#
|
||||
global_params = {"OverrideIPNURL": get_ipn_url()}
|
||||
self.raw_response = self.connection.refund(transaction.secret, transaction.preapproval_key, extra_params=global_params)
|
||||
self.response = self.raw_response[0]
|
||||
|
||||
logging.debug("Amazon REFUNDPAYMENT response was:")
|
||||
logging.debug(str(self.response))
|
||||
|
||||
self.status = self.response.TransactionStatus
|
||||
|
||||
logging.debug("Amazon REFUNDPAYMENT API returning with variables:")
|
||||
logging.debug(locals())
|
||||
|
||||
except FPSResponseError as (responseStatus, responseReason, body):
|
||||
|
||||
logging.error("Amazon REFUNDPAYMENT api failed with status: %s, reason: %s and data:" % (responseStatus, responseReason))
|
||||
logging.error(body)
|
||||
self.errorMessage = body
|
||||
|
||||
except:
|
||||
logging.error("Amazon REFUNDPAYMENT FAILED with exception:")
|
||||
traceback.print_exc()
|
||||
self.errorMessage = "Error: Server Error"
|
||||
|
||||
|
||||
class PreapprovalDetails(AmazonRequest):
|
||||
'''
|
||||
Get details about an authorized token
|
||||
|
||||
This api must set 4 different class variables to work with the code in manager.py
|
||||
|
||||
status - one of the global transaction status codes
|
||||
approved - boolean value
|
||||
currency - not used in this API, but we can get some more info via other APIs - TODO
|
||||
amount - not used in this API, but we can get some more info via other APIs - TODO
|
||||
|
||||
'''
|
||||
def __init__(self, transaction=None):
|
||||
|
||||
try:
|
||||
logging.debug("Amazon PREAPPROVALDETAILS API called for transaction id: %d", transaction.id)
|
||||
|
||||
# Use the boto class top open a connection
|
||||
self.connection = FPSConnection(FPS_ACCESS_KEY, FPS_SECRET_KEY, host=settings.AMAZON_FPS_HOST)
|
||||
self.transaction = transaction
|
||||
|
||||
|
||||
#
|
||||
# We need to reference the caller reference here, we may not have a token if the return URL failed
|
||||
#
|
||||
self.raw_response = self.connection.get_token_by_caller_reference(transaction.secret)
|
||||
self.response = self.raw_response
|
||||
|
||||
logging.debug("Amazon PREAPPROVALDETAILS response:")
|
||||
logging.debug(str(self.response))
|
||||
|
||||
#
|
||||
# Look for a token, we store this in the pay_key field
|
||||
#
|
||||
self.pay_key = self.response.TokenId
|
||||
self.local_status = self.response.TokenStatus
|
||||
|
||||
# Possible status for the Token object are Active and Inactive
|
||||
if self.local_status == 'Active':
|
||||
self.status = TRANSACTION_STATUS_ACTIVE
|
||||
self.approved = True
|
||||
else:
|
||||
# It is not clear here if this should be failed or cancelled, but we have no way to know
|
||||
# the token is only active or now, so we will assume it is canceled.
|
||||
self.status = TRANSACTION_STATUS_CANCELED
|
||||
self.approved = False
|
||||
|
||||
# Set the other fields that are expected. We don't have values for these now, so just copy the transaction
|
||||
self.currency = transaction.currency
|
||||
self.amount = transaction.amount
|
||||
|
||||
logging.debug("Amazon PREAPPROVALDETAILS API returning with variables:")
|
||||
logging.debug(locals())
|
||||
|
||||
except FPSResponseError as (responseStatus, responseReason, body):
|
||||
|
||||
logging.error("Amazon PREAPPROVALDETAILS api failed with status: %s, reason: %s and data:" % (responseStatus, responseReason))
|
||||
logging.error(body)
|
||||
self.errorMessage = body
|
||||
|
||||
except:
|
||||
# If the boto API fails, it also throws an exception and we end up here
|
||||
logging.error("Amazon PREAPPROVALDETAILS FAILED with exception:")
|
||||
self.errorMessage = "Error: ServerError"
|
||||
traceback.print_exc()
|
||||
|
|
@ -0,0 +1,182 @@
|
|||
from regluit.payment.models import Transaction, PaymentResponse
|
||||
from django.http import HttpResponseForbidden
|
||||
from datetime import timedelta
|
||||
from regluit.utils.localdatetime import now, zuluformat
|
||||
|
||||
import datetime
|
||||
import time
|
||||
|
||||
|
||||
|
||||
def ProcessIPN(request):
|
||||
return HttpResponseForbidden()
|
||||
|
||||
|
||||
class BasePaymentRequest:
|
||||
'''
|
||||
Handles common information incident to payment processing
|
||||
|
||||
'''
|
||||
|
||||
# Global values for the class
|
||||
response = None
|
||||
raw_response = None
|
||||
errorMessage = None
|
||||
status = None
|
||||
url = None
|
||||
|
||||
def ack( self ):
|
||||
return None
|
||||
|
||||
def success(self):
|
||||
|
||||
if self.errorMessage:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def error(self):
|
||||
if self.errorMessage:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def error_data(self):
|
||||
return None
|
||||
|
||||
def error_id(self):
|
||||
return None
|
||||
|
||||
def error_string(self):
|
||||
return self.errorMessage
|
||||
|
||||
def envelope(self):
|
||||
# The envelope is used to store info about this request
|
||||
if self.response:
|
||||
return str(self.response)
|
||||
else:
|
||||
return None
|
||||
|
||||
def correlation_id(self):
|
||||
return None
|
||||
|
||||
def timestamp(self):
|
||||
return str(datetime.datetime.now())
|
||||
|
||||
|
||||
class Pay( BasePaymentRequest ):
|
||||
|
||||
'''
|
||||
The pay function generates a redirect URL to approve the transaction
|
||||
'''
|
||||
|
||||
def __init__( self, transaction, return_url=None, amount=None, paymentReason=""):
|
||||
self.transaction=transaction
|
||||
|
||||
def api(self):
|
||||
return "null api"
|
||||
|
||||
def exec_status( self ):
|
||||
return None
|
||||
|
||||
def amount( self ):
|
||||
return None
|
||||
|
||||
def key( self ):
|
||||
return None
|
||||
|
||||
def next_url( self ):
|
||||
return self.url
|
||||
|
||||
class Preapproval(Pay):
|
||||
|
||||
def __init__( self, transaction, amount, expiry=None, return_url=None, paymentReason=""):
|
||||
|
||||
# set the expiration date for the preapproval if not passed in. This is what the paypal library does
|
||||
now_val = now()
|
||||
if expiry is None:
|
||||
expiry = now_val + timedelta( days=settings.PREAPPROVAL_PERIOD )
|
||||
transaction.date_authorized = now_val
|
||||
transaction.date_expired = expiry
|
||||
transaction.save()
|
||||
|
||||
# Call into our parent class
|
||||
Pay.__init__(self, transaction, return_url=return_url, amount=amount, paymentReason=paymentReason)
|
||||
|
||||
|
||||
class Execute(BasePaymentRequest):
|
||||
|
||||
'''
|
||||
The Execute function sends an existing token(generated via the URL from the pay operation), and collects
|
||||
the money.
|
||||
'''
|
||||
|
||||
def __init__(self, transaction=None):
|
||||
self.transaction = transaction
|
||||
|
||||
def api(self):
|
||||
return "Base Pay"
|
||||
|
||||
def key(self):
|
||||
# IN paypal land, our key is updated from a preapproval to a pay key here, just return the existing key
|
||||
return self.transaction.pay_key
|
||||
|
||||
|
||||
|
||||
class Finish(BasePaymentRequest):
|
||||
'''
|
||||
The Finish function handles the secondary receiver in a chained payment.
|
||||
'''
|
||||
def __init__(self, transaction):
|
||||
|
||||
print "Finish"
|
||||
|
||||
|
||||
class PaymentDetails(BasePaymentRequest):
|
||||
'''
|
||||
Get details about executed PAY operation
|
||||
|
||||
This api must set the following class variables to work with the code in manager.py
|
||||
|
||||
status - one of the global transaction status codes
|
||||
transactions -- Not supported for amazon, used by paypal
|
||||
|
||||
'''
|
||||
def __init__(self, transaction=None):
|
||||
self.transaction = transaction
|
||||
|
||||
|
||||
|
||||
class CancelPreapproval(BasePaymentRequest):
|
||||
'''
|
||||
Cancels an exisiting token.
|
||||
'''
|
||||
|
||||
def __init__(self, transaction):
|
||||
self.transaction = transaction
|
||||
|
||||
|
||||
class RefundPayment(BasePaymentRequest):
|
||||
|
||||
def __init__(self, transaction):
|
||||
self.transaction = transaction
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class PreapprovalDetails(BasePaymentRequest):
|
||||
'''
|
||||
Get details about an authorized token
|
||||
|
||||
This api must set 4 different class variables to work with the code in manager.py
|
||||
|
||||
status - one of the global transaction status codes
|
||||
approved - boolean value
|
||||
currency - not used in this API, but we can get some more info via other APIs - TODO
|
||||
amount - not used in this API, but we can get some more info via other APIs - TODO
|
||||
|
||||
'''
|
||||
def __init__(self, transaction=None):
|
||||
self.transaction = transaction
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
from datetime import timedelta
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.conf import settings
|
||||
|
||||
from regluit.payment.parameters import *
|
||||
from regluit.payment.baseprocessor import BasePaymentRequest
|
||||
|
||||
|
||||
def pledge_transaction(t,user,amount):
|
||||
"""commit <amount> from a <user>'s credit to a specified transaction <t>"""
|
||||
|
||||
if t.amount and t.host == PAYMENT_HOST_CREDIT:
|
||||
#changing the pledge_transaction
|
||||
user.credit.add_to_pledged(amount-t.amount)
|
||||
else:
|
||||
user.credit.add_to_pledged(amount)
|
||||
t.max_amount=amount
|
||||
t.set_credit_approved(amount)
|
||||
|
||||
def credit_transaction(t,user,amount):
|
||||
'''user has new credit, use it to fund the transaction'''
|
||||
# first, credit the user's account
|
||||
user.credit.add_to_balance(amount)
|
||||
|
||||
# now pledge to the transaction
|
||||
pledge_amount = t.max_amount if t.max_amount <= user.credit.available else amount
|
||||
user.credit.add_to_pledged(pledge_amount)
|
||||
t.set_credit_approved(pledge_amount)
|
||||
|
||||
class CancelPreapproval(BasePaymentRequest):
|
||||
'''
|
||||
Cancels an exisiting token.
|
||||
'''
|
||||
|
||||
def __init__(self, transaction):
|
||||
self.transaction = transaction
|
||||
if transaction.user.credit.add_to_pledged(-transaction.amount):
|
||||
#success
|
||||
transaction.status=TRANSACTION_STATUS_CANCELED
|
||||
transaction.save()
|
||||
else:
|
||||
self.errorMessage="couldn't cancel the transaction"
|
||||
self.status = 'Credit Cancel Failure'
|
||||
|
||||
class PreapprovalDetails(BasePaymentRequest):
|
||||
status = None
|
||||
approved = None
|
||||
currency = None
|
||||
amount = None
|
||||
def __init__(self, transaction):
|
||||
self.status = transaction.status
|
||||
self.approved = transaction.approved
|
||||
self.currency = transaction.currency
|
||||
self.amount = transaction.amount
|
|
@ -0,0 +1,7 @@
|
|||
from django import forms
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class StripePledgeForm(forms.Form):
|
||||
stripe_token = forms.CharField(required=False, widget=forms.HiddenInput())
|
|
@ -4,15 +4,11 @@ from django.contrib.auth.models import User
|
|||
from django.core.urlresolvers import reverse
|
||||
from django.conf import settings
|
||||
from regluit.payment.parameters import *
|
||||
from regluit.payment.paypal import IPN_SENDER_STATUS_COMPLETED
|
||||
from regluit.payment.signals import transaction_charged, pledge_modified, pledge_created
|
||||
from regluit.payment import credit
|
||||
|
||||
if settings.PAYMENT_PROCESSOR == 'paypal':
|
||||
from regluit.payment.paypal import Pay, Finish, Preapproval, ProcessIPN, CancelPreapproval, PaymentDetails, PreapprovalDetails, RefundPayment
|
||||
from regluit.payment.paypal import Pay as Execute
|
||||
from regluit.payment.baseprocessor import Pay, Finish, Preapproval, ProcessIPN, CancelPreapproval, PaymentDetails, PreapprovalDetails, RefundPayment
|
||||
|
||||
elif settings.PAYMENT_PROCESSOR == 'amazon':
|
||||
from regluit.payment.amazon import Pay, Execute, Finish, Preapproval, ProcessIPN, CancelPreapproval, PaymentDetails, PreapprovalDetails, RefundPayment
|
||||
import uuid
|
||||
import traceback
|
||||
from regluit.utils.localdatetime import now
|
||||
|
@ -21,6 +17,8 @@ import logging
|
|||
from decimal import Decimal as D
|
||||
from xml.dom import minidom
|
||||
import urllib, urlparse
|
||||
from datetime import timedelta
|
||||
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
|
@ -38,9 +36,6 @@ def append_element(doc, parent, name, text):
|
|||
# at this point, there is no internal context and therefore, the methods of PaymentManager can be recast into static methods
|
||||
class PaymentManager( object ):
|
||||
|
||||
def __init__( self, embedded=False):
|
||||
self.embedded = embedded
|
||||
|
||||
def processIPN(self, request, module):
|
||||
|
||||
# Forward to our payment processor
|
||||
|
@ -541,58 +536,28 @@ class PaymentManager( object ):
|
|||
logger.info("Cancel Transaction " + str(transaction.id) + " Failed with error: " + p.error_string())
|
||||
return False
|
||||
|
||||
def authorize(self, currency, target, amount, expiry=None, campaign=None, list=None, user=None,
|
||||
return_url=None, nevermind_url=None, anonymous=False, premium=None,
|
||||
paymentReason="unglue.it Pledge", ack_name=None, ack_link=None, ack_dedication=None,
|
||||
modification=False):
|
||||
def authorize(self, transaction, expiry= None, return_url=None, paymentReason="unglue.it Pledge", modification=False):
|
||||
'''
|
||||
authorize
|
||||
|
||||
authorizes a set amount of money to be collected at a later date
|
||||
|
||||
currency: a 3-letter paypal currency code, i.e. USD
|
||||
target: a defined target type, i.e. TARGET_TYPE_CAMPAIGN, TARGET_TYPE_LIST, TARGET_TYPE_NONE
|
||||
amount: the amount to authorize
|
||||
campaign: optional campaign object(to be set with TARGET_TYPE_CAMPAIGN)
|
||||
list: optional list object(to be set with TARGET_TYPE_LIST)
|
||||
user: optional user object
|
||||
return_url: url to redirect supporter to after a successful PayPal transaction
|
||||
nevermind_url: url to send supporter to if support hits cancel while in middle of PayPal transaction
|
||||
anonymous: whether this pledge is anonymous
|
||||
premium: the premium selected by the supporter for this transaction
|
||||
paymentReason: a memo line that will show up in the Payer's Amazon (and Paypal?) account
|
||||
modification: whether this authorize call is part of a modification of an existing pledge
|
||||
ack_name, ack_link, ack_dedication: how the user will be credited in the unglued ebook, if applicable
|
||||
|
||||
return value: a tuple of the new transaction object and a re-direct url. If the process fails,
|
||||
the redirect url will be None
|
||||
|
||||
'''
|
||||
|
||||
t = Transaction.objects.create(amount=amount,
|
||||
max_amount=amount,
|
||||
type=PAYMENT_TYPE_AUTHORIZATION,
|
||||
execution = EXECUTE_TYPE_CHAINED_DELAYED,
|
||||
target=target,
|
||||
currency=currency,
|
||||
status='NONE',
|
||||
campaign=campaign,
|
||||
list=list,
|
||||
user=user,
|
||||
anonymous=anonymous,
|
||||
premium=premium,
|
||||
ack_name=ack_name,
|
||||
ack_link=ack_link,
|
||||
ack_dedication=ack_dedication
|
||||
)
|
||||
if host==None:
|
||||
#TODO send user to select a payment processor
|
||||
pass
|
||||
|
||||
# we might want to not allow for a return_url or nevermind_url to be passed in but calculated
|
||||
# we might want to not allow for a return_url to be passed in but calculated
|
||||
# here because we have immediate access to the Transaction object.
|
||||
|
||||
if nevermind_url is None:
|
||||
nevermind_path = "{0}?{1}".format(reverse('pledge_nevermind'),
|
||||
urllib.urlencode({'tid':t.id}))
|
||||
nevermind_url = urlparse.urljoin(settings.BASE_URL, nevermind_path)
|
||||
|
||||
if return_url is None:
|
||||
return_path = "{0}?{1}".format(reverse('pledge_complete'),
|
||||
|
@ -600,7 +565,7 @@ class PaymentManager( object ):
|
|||
return_url = urlparse.urljoin(settings.BASE_URL, return_path)
|
||||
|
||||
method = getattr(t.get_payment_class(), "Preapproval")
|
||||
p = method(t, amount, expiry, return_url=return_url, nevermind_url=nevermind_url, paymentReason=paymentReason)
|
||||
p = method(transaction, transaction.max_amount, expiry, return_url=return_url, paymentReason=paymentReason)
|
||||
|
||||
# Create a response for this
|
||||
envelope = p.envelope()
|
||||
|
@ -610,11 +575,11 @@ class PaymentManager( object ):
|
|||
correlation_id = p.correlation_id(),
|
||||
timestamp = p.timestamp(),
|
||||
info = p.raw_response,
|
||||
transaction=t)
|
||||
transaction=transaction)
|
||||
|
||||
if p.success() and not p.error():
|
||||
t.preapproval_key = p.key()
|
||||
t.save()
|
||||
transaction.preapproval_key = p.key()
|
||||
transaction.save()
|
||||
|
||||
url = p.next_url()
|
||||
|
||||
|
@ -634,16 +599,62 @@ class PaymentManager( object ):
|
|||
# send the notice here for now
|
||||
# this is actually premature since we're only about to send the user off to the payment system to
|
||||
# authorize a charge
|
||||
pledge_created.send(sender=self, transaction=t)
|
||||
pledge_created.send(sender=self, transaction=transaction)
|
||||
|
||||
return t, url
|
||||
return transaction, url
|
||||
|
||||
|
||||
else:
|
||||
t.error = p.error_string()
|
||||
t.save()
|
||||
transaction.error = p.error_string()
|
||||
transaction.save()
|
||||
logger.info("Authorize Error: " + p.error_string())
|
||||
return t, None
|
||||
return transaction, None
|
||||
|
||||
def process_transaction(self, currency, amount, host=None, campaign=None, user=None,
|
||||
return_url=None, paymentReason="unglue.it Pledge", pledge_extra=None,
|
||||
modification=False):
|
||||
'''
|
||||
process
|
||||
|
||||
saves and processes a proposed transaction; decides if the transaction should be processed immediately.
|
||||
|
||||
currency: a 3-letter currency code, i.e. USD
|
||||
amount: the amount to authorize
|
||||
host: the name of the processing module; if none, send user back to decide!
|
||||
campaign: required campaign object
|
||||
user: optional user object
|
||||
return_url: url to redirect supporter to after a successful transaction
|
||||
paymentReason: a memo line that will show up in the Payer's Amazon (and Paypal?) account
|
||||
modification: whether this authorize call is part of a modification of an existing pledge
|
||||
pledge_extra: extra pledge stuff
|
||||
|
||||
return value: a tuple of the new transaction object and a re-direct url. If the process fails,
|
||||
the redirect url will be None
|
||||
'''
|
||||
# set the expiry date based on the campaign deadline
|
||||
expiry = campaign.deadline + timedelta( days=settings.PREAPPROVAL_PERIOD_AFTER_CAMPAIGN )
|
||||
|
||||
t = Transaction.create(amount=0,
|
||||
max_amount=amount,
|
||||
currency=currency,
|
||||
campaign=campaign,
|
||||
user=user,
|
||||
pledge_extra=pledge_extra
|
||||
)
|
||||
t.save()
|
||||
# does user have enough credit to pledge now?
|
||||
if user.credit.available >= amount :
|
||||
# YES!
|
||||
credit.pledge_transaction(t,user,amount)
|
||||
return_path = "{0}?{1}".format(reverse('pledge_complete'),
|
||||
urllib.urlencode({'tid':t.id}))
|
||||
return_url = urlparse.urljoin(settings.BASE_URL, return_path)
|
||||
pledge_created.send(sender=self, transaction=t)
|
||||
return t, return_url
|
||||
else:
|
||||
# send user to choose payment path
|
||||
return t, reverse('fund_pledge', args=[t.id])
|
||||
|
||||
|
||||
def cancel_related_transaction(self, transaction, status=TRANSACTION_STATUS_ACTIVE, campaign=None):
|
||||
'''
|
||||
|
@ -687,26 +698,26 @@ class PaymentManager( object ):
|
|||
|
||||
return canceled
|
||||
|
||||
def modify_transaction(self, transaction, amount, expiry=None, anonymous=None, premium=None,
|
||||
return_url=None, nevermind_url=None, paymentReason=None,
|
||||
ack_name=None, ack_link=None, ack_dedication=None):
|
||||
def modify_transaction(self, transaction, amount, expiry=None, pledge_extra=None,
|
||||
return_url=None, nevermind_url=None, paymentReason=None):
|
||||
'''
|
||||
modify
|
||||
|
||||
Modifies a transaction. The only type of modification allowed is to the amount and expiration date
|
||||
Modifies a transaction.
|
||||
2 main situations: if the new amount is less than max_amount, no need to go out to PayPal again
|
||||
if new amount is greater than max_amount...need to go out and get new approval.
|
||||
to start with, we can use the standard pledge_complete, pledge_cancel machinery
|
||||
might have to modify the pledge_complete, pledge_cancel because the messages are going to be
|
||||
different because we're modifying a pledge rather than a new one.
|
||||
|
||||
amount: the new amount
|
||||
expiry: the new expiration date, or if none the current expiration date will be used
|
||||
anonymous: new anonymous value; if None, then keep old value
|
||||
premium: new premium selected; if None, then keep old value
|
||||
return_url: the return URL after the preapproval(if needed)
|
||||
nevermind_url: the cancel url after the preapproval(if needed)
|
||||
paymentReason: a memo line that will show up in the Payer's Amazon (and Paypal?) account
|
||||
|
||||
return value: True if successful, False otherwise. An optional second parameter for the forward URL if a new authorhization is needed
|
||||
'''
|
||||
|
||||
# Can only modify the amount of a preapproval for now
|
||||
if transaction.type != PAYMENT_TYPE_AUTHORIZATION:
|
||||
logger.info("Error, attempt to modify an invalid transaction type")
|
||||
return False, None
|
||||
|
@ -717,34 +728,57 @@ class PaymentManager( object ):
|
|||
logger.info("Error, attempt to modify a transaction that is not active")
|
||||
return False, None
|
||||
|
||||
# if any of expiry, anonymous, or premium is None, use the existing value
|
||||
if expiry is None:
|
||||
expiry = transaction.date_expired
|
||||
if anonymous is None:
|
||||
anonymous = transaction.anonymous
|
||||
if premium is None:
|
||||
premium = transaction.premium
|
||||
if transaction.host == PAYMENT_HOST_CREDIT:
|
||||
# does user have enough credit to pledge now?
|
||||
if transaction.user.credit.available >= amount-transaction.amount :
|
||||
# YES!
|
||||
transaction.set_pledge_extra(pledge_extra)
|
||||
credit.pledge_transaction(transaction,transaction.user,amount)
|
||||
return_path = "{0}?{1}".format(reverse('pledge_complete'),
|
||||
urllib.urlencode({'tid':transaction.id}))
|
||||
return_url = urlparse.urljoin(settings.BASE_URL, return_path)
|
||||
|
||||
if amount > transaction.max_amount or expiry != transaction.date_expired:
|
||||
logger.info("Updated amount of transaction to %f" % amount)
|
||||
pledge_modified.send(sender=self, transaction=transaction,up_or_down="decreased" if amount-transaction.amount<0 else "increased")
|
||||
return transaction, return_url
|
||||
else:
|
||||
# cancel old transaction, send user to choose new payment path
|
||||
# set the expiry date based on the campaign deadline
|
||||
expiry = transaction.campaign.deadline + timedelta( days=settings.PREAPPROVAL_PERIOD_AFTER_CAMPAIGN )
|
||||
t = Transaction.create(amount=0,
|
||||
max_amount=amount,
|
||||
currency=transaction.currency,
|
||||
status=TRANSACTION_STATUS_MODIFIED,
|
||||
campaign=transaction.campaign,
|
||||
user=transaction.user,
|
||||
pledge_extra=pledge_extra
|
||||
)
|
||||
t.save()
|
||||
credit.CancelPreapproval(transaction)
|
||||
return t, reverse('fund_pledge', args=[t.id])
|
||||
|
||||
# Start a new authorization for the new amount
|
||||
elif amount > transaction.max_amount or expiry != transaction.date_expired:
|
||||
|
||||
t, url = self.authorize(transaction.currency,
|
||||
transaction.target,
|
||||
amount,
|
||||
expiry,
|
||||
transaction.campaign,
|
||||
transaction.list,
|
||||
transaction.user,
|
||||
return_url,
|
||||
nevermind_url,
|
||||
anonymous,
|
||||
premium,
|
||||
paymentReason,
|
||||
ack_name,
|
||||
ack_link,
|
||||
ack_dedication,
|
||||
True)
|
||||
# set the expiry date based on the campaign deadline
|
||||
expiry = transaction.campaign.deadline + timedelta( days=settings.PREAPPROVAL_PERIOD_AFTER_CAMPAIGN )
|
||||
|
||||
# Start a new transaction for the new amount
|
||||
t = Transaction.create(amount=amount,
|
||||
max_amount=amount,
|
||||
host=transaction.host,
|
||||
currency=transaction.currency,
|
||||
status=TRANSACTION_STATUS_CREATED,
|
||||
campaign=transaction.campaign,
|
||||
user=transaction.user,
|
||||
pledge_extra=pledge_extra
|
||||
)
|
||||
t.save()
|
||||
t, url = self.authorize(transaction,
|
||||
expiry=expiry if expiry else transaction.date_expired,
|
||||
return_url=return_url,
|
||||
paymentReason=paymentReason,
|
||||
modification=True
|
||||
)
|
||||
|
||||
if t and url:
|
||||
# Need to re-direct to approve the transaction
|
||||
|
@ -769,11 +803,7 @@ class PaymentManager( object ):
|
|||
elif amount <= transaction.max_amount:
|
||||
# Update transaction but leave the preapproval alone
|
||||
transaction.amount = amount
|
||||
transaction.anonymous = anonymous
|
||||
transaction.premium = premium
|
||||
transaction.ack_name = ack_name
|
||||
transaction.ack_link = ack_link
|
||||
transaction.ack_dedication = ack_dedication
|
||||
transaction.set_pledge_extra(pledge_extra)
|
||||
|
||||
transaction.save()
|
||||
logger.info("Updated amount of transaction to %f" % amount)
|
||||
|
@ -830,30 +860,18 @@ class PaymentManager( object ):
|
|||
logger.info("Refund Transaction " + str(transaction.id) + " Failed with error: " + p.error_string())
|
||||
return False
|
||||
|
||||
def pledge(self, currency, target, receiver_list, campaign=None, list=None, user=None,
|
||||
return_url=None, nevermind_url=None, anonymous=False, premium=None, ack_name=None,
|
||||
ack_link=None, ack_dedication=None):
|
||||
def pledge(self, currency, campaign=None, user=None,
|
||||
return_url=None, nevermind_url=None, pledge_extra=None):
|
||||
'''
|
||||
pledge
|
||||
|
||||
Performs an instant payment
|
||||
|
||||
currency: a 3-letter paypal currency code, i.e. USD
|
||||
target: a defined target type, i.e. TARGET_TYPE_CAMPAIGN, TARGET_TYPE_LIST, TARGET_TYPE_NONE
|
||||
receiver_list: a list of receivers for the transaction, in this format:
|
||||
|
||||
[
|
||||
{'email':'email-1', 'amount':amount1},
|
||||
{'email':'email-2', 'amount':amount2}
|
||||
]
|
||||
|
||||
campaign: optional campaign object(to be set with TARGET_TYPE_CAMPAIGN)
|
||||
list: optional list object(to be set with TARGET_TYPE_LIST)
|
||||
campaign: required campaign object
|
||||
user: optional user object
|
||||
return_url: url to redirect supporter to after a successful PayPal transaction
|
||||
nevermind_url: url to send supporter to if support hits cancel while in middle of PayPal transaction
|
||||
anonymous: whether this pledge is anonymous
|
||||
premium: the premium selected by the supporter for this transaction
|
||||
|
||||
return value: a tuple of the new transaction object and a re-direct url. If the process fails,
|
||||
the redirect url will be None
|
||||
|
@ -862,28 +880,18 @@ class PaymentManager( object ):
|
|||
|
||||
amount = D('0.00')
|
||||
|
||||
# for chained payments, first amount is the total amount
|
||||
amount = D(receiver_list[0]['amount'])
|
||||
t = Transaction.create(amount=amount,
|
||||
max_amount=amount,
|
||||
currency=currency,
|
||||
status=TRANSACTION_STATUS_NONE,
|
||||
campaign=campaign,
|
||||
user=user,
|
||||
pledge_extra=pledge_extra
|
||||
)
|
||||
|
||||
t = Transaction.objects.create(amount=amount,
|
||||
max_amount=amount,
|
||||
type=PAYMENT_TYPE_INSTANT,
|
||||
execution=EXECUTE_TYPE_CHAINED_INSTANT,
|
||||
target=target,
|
||||
currency=currency,
|
||||
status='NONE',
|
||||
campaign=campaign,
|
||||
list=list,
|
||||
user=user,
|
||||
date_payment=now(),
|
||||
anonymous=anonymous,
|
||||
premium=premium,
|
||||
ack_name=ack_name,
|
||||
ack_link=ack_link,
|
||||
ack_dedication=ack_dedication
|
||||
)
|
||||
|
||||
t.create_receivers(receiver_list)
|
||||
t.date_payment=now()
|
||||
t.execution=EXECUTE_TYPE_CHAINED_INSTANT
|
||||
t.type=PAYMENT_TYPE_INSTANT
|
||||
method = getattr(t.get_payment_class(), "Pay")
|
||||
p = method(t,return_url=return_url, nevermind_url=nevermind_url)
|
||||
|
||||
|
@ -903,11 +911,7 @@ class PaymentManager( object ):
|
|||
t.status = TRANSACTION_STATUS_CREATED
|
||||
t.save()
|
||||
|
||||
if self.embedded:
|
||||
url = p.embedded_url()
|
||||
logger.info(url)
|
||||
else:
|
||||
url = p.next_url()
|
||||
url = p.next_url()
|
||||
|
||||
logger.info("Pledge Success: " + url)
|
||||
return t, url
|
||||
|
|
|
@ -0,0 +1,224 @@
|
|||
# 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 'Credit'
|
||||
db.create_table('payment_credit', (
|
||||
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
('user', self.gf('django.db.models.fields.related.OneToOneField')(related_name='credit', unique=True, to=orm['auth.User'])),
|
||||
('balance', self.gf('django.db.models.fields.DecimalField')(default='0.00', max_digits=14, decimal_places=2)),
|
||||
('pledged', self.gf('django.db.models.fields.DecimalField')(default='0.00', max_digits=14, decimal_places=2)),
|
||||
('last_activity', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)),
|
||||
))
|
||||
db.send_create_signal('payment', ['Credit'])
|
||||
|
||||
# Adding model 'CreditLog'
|
||||
db.create_table('payment_creditlog', (
|
||||
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True)),
|
||||
('amount', self.gf('django.db.models.fields.DecimalField')(default='0.00', max_digits=14, decimal_places=2)),
|
||||
('timestamp', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)),
|
||||
('action', self.gf('django.db.models.fields.CharField')(max_length=16)),
|
||||
))
|
||||
db.send_create_signal('payment', ['CreditLog'])
|
||||
|
||||
# Deleting field 'Transaction.target'
|
||||
db.delete_column('payment_transaction', 'target')
|
||||
|
||||
# Deleting field 'Transaction.list'
|
||||
db.delete_column('payment_transaction', 'list_id')
|
||||
|
||||
# Adding field 'Transaction.ack_name'
|
||||
db.add_column('payment_transaction', 'ack_name', self.gf('django.db.models.fields.CharField')(max_length=64, null=True), keep_default=False)
|
||||
|
||||
# Adding field 'Transaction.ack_dedication'
|
||||
db.add_column('payment_transaction', 'ack_dedication', self.gf('django.db.models.fields.CharField')(max_length=140, null=True), keep_default=False)
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
|
||||
# Deleting model 'Credit'
|
||||
db.delete_table('payment_credit')
|
||||
|
||||
# Deleting model 'CreditLog'
|
||||
db.delete_table('payment_creditlog')
|
||||
|
||||
# Adding field 'Transaction.target'
|
||||
db.add_column('payment_transaction', 'target', self.gf('django.db.models.fields.IntegerField')(default=0), keep_default=False)
|
||||
|
||||
# Adding field 'Transaction.list'
|
||||
db.add_column('payment_transaction', 'list', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['core.Wishlist'], null=True), keep_default=False)
|
||||
|
||||
# Deleting field 'Transaction.ack_name'
|
||||
db.delete_column('payment_transaction', 'ack_name')
|
||||
|
||||
# Deleting field 'Transaction.ack_dedication'
|
||||
db.delete_column('payment_transaction', 'ack_dedication')
|
||||
|
||||
|
||||
models = {
|
||||
'auth.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
'auth.permission': {
|
||||
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
|
||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||
},
|
||||
'auth.user': {
|
||||
'Meta': {'object_name': 'User'},
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 8, 31, 2, 10, 24, 467332)'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 8, 31, 2, 10, 24, 467190)'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
|
||||
},
|
||||
'contenttypes.contenttype': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
|
||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||
},
|
||||
'core.campaign': {
|
||||
'Meta': {'object_name': 'Campaign'},
|
||||
'activated': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
|
||||
'amazon_receiver': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'deadline': ('django.db.models.fields.DateTimeField', [], {}),
|
||||
'description': ('ckeditor.fields.RichTextField', [], {'null': 'True'}),
|
||||
'details': ('ckeditor.fields.RichTextField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'edition': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'campaigns'", 'null': 'True', 'to': "orm['core.Edition']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'left': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'license': ('django.db.models.fields.CharField', [], {'default': "'CC BY-NC-ND'", 'max_length': '255'}),
|
||||
'managers': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'campaigns'", 'symmetrical': 'False', 'to': "orm['auth.User']"}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '500', 'null': 'True'}),
|
||||
'paypal_receiver': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
|
||||
'status': ('django.db.models.fields.CharField', [], {'default': "'INITIALIZED'", 'max_length': '15', 'null': 'True'}),
|
||||
'target': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'campaigns'", 'to': "orm['core.Work']"})
|
||||
},
|
||||
'core.edition': {
|
||||
'Meta': {'object_name': 'Edition'},
|
||||
'cover_image': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'public_domain': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'publication_date': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
|
||||
'publisher': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'title': ('django.db.models.fields.CharField', [], {'max_length': '1000'}),
|
||||
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'editions'", 'null': 'True', 'to': "orm['core.Work']"})
|
||||
},
|
||||
'core.premium': {
|
||||
'Meta': {'object_name': 'Premium'},
|
||||
'amount': ('django.db.models.fields.DecimalField', [], {'max_digits': '10', 'decimal_places': '0'}),
|
||||
'campaign': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'premiums'", 'null': 'True', 'to': "orm['core.Campaign']"}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'description': ('django.db.models.fields.TextField', [], {'null': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'limit': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'type': ('django.db.models.fields.CharField', [], {'max_length': '2'})
|
||||
},
|
||||
'core.work': {
|
||||
'Meta': {'ordering': "['title']", 'object_name': 'Work'},
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'language': ('django.db.models.fields.CharField', [], {'default': "'en'", 'max_length': '2'}),
|
||||
'num_wishes': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'openlibrary_lookup': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
|
||||
'title': ('django.db.models.fields.CharField', [], {'max_length': '1000'})
|
||||
},
|
||||
'payment.credit': {
|
||||
'Meta': {'object_name': 'Credit'},
|
||||
'balance': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'last_activity': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
|
||||
'pledged': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'credit'", 'unique': 'True', 'to': "orm['auth.User']"})
|
||||
},
|
||||
'payment.creditlog': {
|
||||
'Meta': {'object_name': 'CreditLog'},
|
||||
'action': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
|
||||
'amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'})
|
||||
},
|
||||
'payment.paymentresponse': {
|
||||
'Meta': {'object_name': 'PaymentResponse'},
|
||||
'api': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
|
||||
'correlation_id': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'info': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True'}),
|
||||
'status': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
|
||||
'timestamp': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
|
||||
'transaction': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['payment.Transaction']"})
|
||||
},
|
||||
'payment.receiver': {
|
||||
'Meta': {'object_name': 'Receiver'},
|
||||
'amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'currency': ('django.db.models.fields.CharField', [], {'max_length': '10'}),
|
||||
'email': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'local_status': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
|
||||
'primary': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'reason': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
|
||||
'status': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
|
||||
'transaction': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['payment.Transaction']"}),
|
||||
'txn_id': ('django.db.models.fields.CharField', [], {'max_length': '64'})
|
||||
},
|
||||
'payment.transaction': {
|
||||
'Meta': {'object_name': 'Transaction'},
|
||||
'ack_dedication': ('django.db.models.fields.CharField', [], {'max_length': '140', 'null': 'True'}),
|
||||
'ack_name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
|
||||
'amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'approved': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'campaign': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Campaign']", 'null': 'True'}),
|
||||
'currency': ('django.db.models.fields.CharField', [], {'default': "'USD'", 'max_length': '10', 'null': 'True'}),
|
||||
'date_authorized': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
|
||||
'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'date_executed': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
|
||||
'date_expired': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
|
||||
'date_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
|
||||
'date_payment': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
|
||||
'error': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True'}),
|
||||
'execution': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'host': ('django.db.models.fields.CharField', [], {'default': "'none'", 'max_length': '32'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'local_status': ('django.db.models.fields.CharField', [], {'default': "'NONE'", 'max_length': '32', 'null': 'True'}),
|
||||
'max_amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'pay_key': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
|
||||
'preapproval_key': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
|
||||
'premium': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Premium']", 'null': 'True'}),
|
||||
'reason': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
|
||||
'receipt': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True'}),
|
||||
'secret': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
|
||||
'status': ('django.db.models.fields.CharField', [], {'default': "'None'", 'max_length': '32'}),
|
||||
'type': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['payment']
|
|
@ -8,21 +8,26 @@ class Migration(SchemaMigration):
|
|||
|
||||
def forwards(self, orm):
|
||||
|
||||
# Adding model 'Credit'
|
||||
db.create_table('payment_credit', (
|
||||
# Adding model 'Sent'
|
||||
db.create_table('payment_sent', (
|
||||
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
('user', self.gf('django.db.models.fields.related.OneToOneField')(related_name='credit', unique=True, to=orm['auth.User'])),
|
||||
('balance', self.gf('django.db.models.fields.IntegerField')(default=0)),
|
||||
('pledged', self.gf('django.db.models.fields.IntegerField')(default=0)),
|
||||
('last_activity', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)),
|
||||
('user', self.gf('django.db.models.fields.CharField')(max_length=32, null=True)),
|
||||
('amount', self.gf('django.db.models.fields.DecimalField')(default='0.00', max_digits=14, decimal_places=2)),
|
||||
('timestamp', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)),
|
||||
))
|
||||
db.send_create_signal('payment', ['Credit'])
|
||||
db.send_create_signal('payment', ['Sent'])
|
||||
|
||||
# Adding field 'CreditLog.sent'
|
||||
db.add_column('payment_creditlog', 'sent', self.gf('django.db.models.fields.IntegerField')(null=True), keep_default=False)
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
|
||||
# Deleting model 'Credit'
|
||||
db.delete_table('payment_credit')
|
||||
# Deleting model 'Sent'
|
||||
db.delete_table('payment_sent')
|
||||
|
||||
# Deleting field 'CreditLog.sent'
|
||||
db.delete_column('payment_creditlog', 'sent')
|
||||
|
||||
|
||||
models = {
|
||||
|
@ -41,7 +46,7 @@ class Migration(SchemaMigration):
|
|||
},
|
||||
'auth.user': {
|
||||
'Meta': {'object_name': 'User'},
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 9, 5, 23, 7, 10, 823074)'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
|
@ -49,7 +54,7 @@ class Migration(SchemaMigration):
|
|||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2012, 9, 5, 23, 7, 10, 822938)'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
|
@ -102,21 +107,6 @@ class Migration(SchemaMigration):
|
|||
'limit': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'type': ('django.db.models.fields.CharField', [], {'max_length': '2'})
|
||||
},
|
||||
'core.wishes': {
|
||||
'Meta': {'object_name': 'Wishes', 'db_table': "'core_wishlist_works'"},
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'source': ('django.db.models.fields.CharField', [], {'max_length': '15', 'blank': 'True'}),
|
||||
'wishlist': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Wishlist']"}),
|
||||
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'wishes'", 'to': "orm['core.Work']"})
|
||||
},
|
||||
'core.wishlist': {
|
||||
'Meta': {'object_name': 'Wishlist'},
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'wishlist'", 'unique': 'True', 'to': "orm['auth.User']"}),
|
||||
'works': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'wishlists'", 'symmetrical': 'False', 'through': "orm['core.Wishes']", 'to': "orm['core.Work']"})
|
||||
},
|
||||
'core.work': {
|
||||
'Meta': {'ordering': "['title']", 'object_name': 'Work'},
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
|
@ -129,12 +119,21 @@ class Migration(SchemaMigration):
|
|||
},
|
||||
'payment.credit': {
|
||||
'Meta': {'object_name': 'Credit'},
|
||||
'balance': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'balance': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'last_activity': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
|
||||
'pledged': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'pledged': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'credit'", 'unique': 'True', 'to': "orm['auth.User']"})
|
||||
},
|
||||
'payment.creditlog': {
|
||||
'Meta': {'object_name': 'CreditLog'},
|
||||
'action': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
|
||||
'amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'sent': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
|
||||
'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'})
|
||||
},
|
||||
'payment.paymentresponse': {
|
||||
'Meta': {'object_name': 'PaymentResponse'},
|
||||
'api': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
|
||||
|
@ -158,8 +157,17 @@ class Migration(SchemaMigration):
|
|||
'transaction': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['payment.Transaction']"}),
|
||||
'txn_id': ('django.db.models.fields.CharField', [], {'max_length': '64'})
|
||||
},
|
||||
'payment.sent': {
|
||||
'Meta': {'object_name': 'Sent'},
|
||||
'amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
|
||||
'user': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'})
|
||||
},
|
||||
'payment.transaction': {
|
||||
'Meta': {'object_name': 'Transaction'},
|
||||
'ack_dedication': ('django.db.models.fields.CharField', [], {'max_length': '140', 'null': 'True'}),
|
||||
'ack_name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
|
||||
'amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'approved': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
|
||||
|
@ -173,9 +181,8 @@ class Migration(SchemaMigration):
|
|||
'date_payment': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
|
||||
'error': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True'}),
|
||||
'execution': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'host': ('django.db.models.fields.CharField', [], {'default': "'amazon'", 'max_length': '32'}),
|
||||
'host': ('django.db.models.fields.CharField', [], {'default': "'none'", 'max_length': '32'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'list': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Wishlist']", 'null': 'True'}),
|
||||
'local_status': ('django.db.models.fields.CharField', [], {'default': "'NONE'", 'max_length': '32', 'null': 'True'}),
|
||||
'max_amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'pay_key': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
|
||||
|
@ -185,7 +192,6 @@ class Migration(SchemaMigration):
|
|||
'receipt': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True'}),
|
||||
'secret': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
|
||||
'status': ('django.db.models.fields.CharField', [], {'default': "'None'", 'max_length': '32'}),
|
||||
'target': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'type': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'})
|
||||
}
|
|
@ -8,31 +8,19 @@ from django.db import models
|
|||
class Migration(SchemaMigration):
|
||||
|
||||
def forwards(self, orm):
|
||||
# Adding field 'Transaction.ack_name'
|
||||
db.add_column('payment_transaction', 'ack_name',
|
||||
self.gf('django.db.models.fields.CharField')(max_length=64, null=True),
|
||||
keep_default=False)
|
||||
|
||||
# Adding field 'Transaction.ack_link'
|
||||
db.add_column('payment_transaction', 'ack_link',
|
||||
self.gf('django.db.models.fields.URLField')(max_length=200, null=True),
|
||||
keep_default=False)
|
||||
|
||||
# Adding field 'Transaction.ack_dedication'
|
||||
db.add_column('payment_transaction', 'ack_dedication',
|
||||
self.gf('django.db.models.fields.CharField')(max_length=140, null=True),
|
||||
keep_default=False)
|
||||
# Adding model 'Account'
|
||||
db.create_table('payment_account', (
|
||||
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
('host', self.gf('django.db.models.fields.CharField')(default='none', max_length=32)),
|
||||
('account_id', self.gf('django.db.models.fields.CharField')(max_length=128, null=True)),
|
||||
('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True)),
|
||||
))
|
||||
db.send_create_signal('payment', ['Account'])
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
# Deleting field 'Transaction.ack_name'
|
||||
db.delete_column('payment_transaction', 'ack_name')
|
||||
|
||||
# Deleting field 'Transaction.ack_link'
|
||||
db.delete_column('payment_transaction', 'ack_link')
|
||||
|
||||
# Deleting field 'Transaction.ack_dedication'
|
||||
db.delete_column('payment_transaction', 'ack_dedication')
|
||||
# Deleting model 'Account'
|
||||
db.delete_table('payment_account')
|
||||
|
||||
|
||||
models = {
|
||||
|
@ -78,8 +66,8 @@ class Migration(SchemaMigration):
|
|||
'amazon_receiver': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'deadline': ('django.db.models.fields.DateTimeField', [], {}),
|
||||
'description': ('django.db.models.fields.TextField', [], {'null': 'True'}),
|
||||
'details': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'description': ('ckeditor.fields.RichTextField', [], {'null': 'True'}),
|
||||
'details': ('ckeditor.fields.RichTextField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'edition': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'campaigns'", 'null': 'True', 'to': "orm['core.Edition']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'left': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '14', 'decimal_places': '2'}),
|
||||
|
@ -100,6 +88,7 @@ class Migration(SchemaMigration):
|
|||
'publication_date': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
|
||||
'publisher': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'title': ('django.db.models.fields.CharField', [], {'max_length': '1000'}),
|
||||
'unglued': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'editions'", 'null': 'True', 'to': "orm['core.Work']"})
|
||||
},
|
||||
'core.premium': {
|
||||
|
@ -112,21 +101,6 @@ class Migration(SchemaMigration):
|
|||
'limit': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'type': ('django.db.models.fields.CharField', [], {'max_length': '2'})
|
||||
},
|
||||
'core.wishes': {
|
||||
'Meta': {'object_name': 'Wishes', 'db_table': "'core_wishlist_works'"},
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'source': ('django.db.models.fields.CharField', [], {'max_length': '15', 'blank': 'True'}),
|
||||
'wishlist': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Wishlist']"}),
|
||||
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'wishes'", 'to': "orm['core.Work']"})
|
||||
},
|
||||
'core.wishlist': {
|
||||
'Meta': {'object_name': 'Wishlist'},
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'wishlist'", 'unique': 'True', 'to': "orm['auth.User']"}),
|
||||
'works': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'wishlists'", 'symmetrical': 'False', 'through': "orm['core.Wishes']", 'to': "orm['core.Work']"})
|
||||
},
|
||||
'core.work': {
|
||||
'Meta': {'ordering': "['title']", 'object_name': 'Work'},
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
|
@ -137,6 +111,30 @@ class Migration(SchemaMigration):
|
|||
'openlibrary_lookup': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
|
||||
'title': ('django.db.models.fields.CharField', [], {'max_length': '1000'})
|
||||
},
|
||||
'payment.account': {
|
||||
'Meta': {'object_name': 'Account'},
|
||||
'account_id': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
|
||||
'host': ('django.db.models.fields.CharField', [], {'default': "'none'", 'max_length': '32'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'})
|
||||
},
|
||||
'payment.credit': {
|
||||
'Meta': {'object_name': 'Credit'},
|
||||
'balance': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'last_activity': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
|
||||
'pledged': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'credit'", 'unique': 'True', 'to': "orm['auth.User']"})
|
||||
},
|
||||
'payment.creditlog': {
|
||||
'Meta': {'object_name': 'CreditLog'},
|
||||
'action': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
|
||||
'amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'sent': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
|
||||
'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'})
|
||||
},
|
||||
'payment.paymentresponse': {
|
||||
'Meta': {'object_name': 'PaymentResponse'},
|
||||
'api': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
|
||||
|
@ -160,10 +158,16 @@ class Migration(SchemaMigration):
|
|||
'transaction': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['payment.Transaction']"}),
|
||||
'txn_id': ('django.db.models.fields.CharField', [], {'max_length': '64'})
|
||||
},
|
||||
'payment.sent': {
|
||||
'Meta': {'object_name': 'Sent'},
|
||||
'amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
|
||||
'user': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'})
|
||||
},
|
||||
'payment.transaction': {
|
||||
'Meta': {'object_name': 'Transaction'},
|
||||
'ack_dedication': ('django.db.models.fields.CharField', [], {'max_length': '140', 'null': 'True'}),
|
||||
'ack_link': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True'}),
|
||||
'ack_name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
|
||||
'amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
|
@ -178,9 +182,8 @@ class Migration(SchemaMigration):
|
|||
'date_payment': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
|
||||
'error': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True'}),
|
||||
'execution': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'host': ('django.db.models.fields.CharField', [], {'default': "'amazon'", 'max_length': '32'}),
|
||||
'host': ('django.db.models.fields.CharField', [], {'default': "'none'", 'max_length': '32'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'list': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Wishlist']", 'null': 'True'}),
|
||||
'local_status': ('django.db.models.fields.CharField', [], {'default': "'NONE'", 'max_length': '32', 'null': 'True'}),
|
||||
'max_amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'pay_key': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
|
||||
|
@ -190,7 +193,6 @@ class Migration(SchemaMigration):
|
|||
'receipt': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True'}),
|
||||
'secret': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
|
||||
'status': ('django.db.models.fields.CharField', [], {'default': "'None'", 'max_length': '32'}),
|
||||
'target': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'type': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'})
|
||||
}
|
|
@ -0,0 +1,270 @@
|
|||
# -*- coding: 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 field 'Account.card_last4'
|
||||
db.add_column('payment_account', 'card_last4',
|
||||
self.gf('django.db.models.fields.CharField')(max_length=4, null=True),
|
||||
keep_default=False)
|
||||
|
||||
# Adding field 'Account.card_type'
|
||||
db.add_column('payment_account', 'card_type',
|
||||
self.gf('django.db.models.fields.CharField')(max_length=32, null=True),
|
||||
keep_default=False)
|
||||
|
||||
# Adding field 'Account.card_exp_month'
|
||||
db.add_column('payment_account', 'card_exp_month',
|
||||
self.gf('django.db.models.fields.IntegerField')(null=True),
|
||||
keep_default=False)
|
||||
|
||||
# Adding field 'Account.card_exp_year'
|
||||
db.add_column('payment_account', 'card_exp_year',
|
||||
self.gf('django.db.models.fields.IntegerField')(null=True),
|
||||
keep_default=False)
|
||||
|
||||
# Adding field 'Account.card_fingerprint'
|
||||
db.add_column('payment_account', 'card_fingerprint',
|
||||
self.gf('django.db.models.fields.CharField')(max_length=32, null=True),
|
||||
keep_default=False)
|
||||
|
||||
# Adding field 'Account.card_country'
|
||||
db.add_column('payment_account', 'card_country',
|
||||
self.gf('django.db.models.fields.CharField')(max_length=2, null=True),
|
||||
keep_default=False)
|
||||
|
||||
# Adding field 'Account.date_created'
|
||||
db.add_column('payment_account', 'date_created',
|
||||
self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, default=datetime.datetime(2012, 9, 17, 0, 0), blank=True),
|
||||
keep_default=False)
|
||||
|
||||
# Adding field 'Account.date_modified'
|
||||
db.add_column('payment_account', 'date_modified',
|
||||
self.gf('django.db.models.fields.DateTimeField')(auto_now=True, default=datetime.datetime(2012, 9, 17, 0, 0), blank=True),
|
||||
keep_default=False)
|
||||
|
||||
# Adding field 'Account.date_deactivated'
|
||||
db.add_column('payment_account', 'date_deactivated',
|
||||
self.gf('django.db.models.fields.DateTimeField')(null=True),
|
||||
keep_default=False)
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
# Deleting field 'Account.card_last4'
|
||||
db.delete_column('payment_account', 'card_last4')
|
||||
|
||||
# Deleting field 'Account.card_type'
|
||||
db.delete_column('payment_account', 'card_type')
|
||||
|
||||
# Deleting field 'Account.card_exp_month'
|
||||
db.delete_column('payment_account', 'card_exp_month')
|
||||
|
||||
# Deleting field 'Account.card_exp_year'
|
||||
db.delete_column('payment_account', 'card_exp_year')
|
||||
|
||||
# Deleting field 'Account.card_fingerprint'
|
||||
db.delete_column('payment_account', 'card_fingerprint')
|
||||
|
||||
# Deleting field 'Account.card_country'
|
||||
db.delete_column('payment_account', 'card_country')
|
||||
|
||||
# Deleting field 'Account.date_created'
|
||||
db.delete_column('payment_account', 'date_created')
|
||||
|
||||
# Deleting field 'Account.date_modified'
|
||||
db.delete_column('payment_account', 'date_modified')
|
||||
|
||||
# Deleting field 'Account.date_deactivated'
|
||||
db.delete_column('payment_account', 'date_deactivated')
|
||||
|
||||
|
||||
models = {
|
||||
'auth.group': {
|
||||
'Meta': {'object_name': 'Group'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
|
||||
'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
|
||||
},
|
||||
'auth.permission': {
|
||||
'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
|
||||
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||
},
|
||||
'auth.user': {
|
||||
'Meta': {'object_name': 'User'},
|
||||
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
|
||||
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
|
||||
'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
|
||||
'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
|
||||
'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
|
||||
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
|
||||
},
|
||||
'contenttypes.contenttype': {
|
||||
'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
|
||||
'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
|
||||
},
|
||||
'core.campaign': {
|
||||
'Meta': {'object_name': 'Campaign'},
|
||||
'activated': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
|
||||
'amazon_receiver': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'deadline': ('django.db.models.fields.DateTimeField', [], {}),
|
||||
'description': ('ckeditor.fields.RichTextField', [], {'null': 'True'}),
|
||||
'details': ('ckeditor.fields.RichTextField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'edition': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'campaigns'", 'null': 'True', 'to': "orm['core.Edition']"}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'left': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'license': ('django.db.models.fields.CharField', [], {'default': "'CC BY-NC-ND'", 'max_length': '255'}),
|
||||
'managers': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'campaigns'", 'symmetrical': 'False', 'to': "orm['auth.User']"}),
|
||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '500', 'null': 'True'}),
|
||||
'paypal_receiver': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
|
||||
'status': ('django.db.models.fields.CharField', [], {'default': "'INITIALIZED'", 'max_length': '15', 'null': 'True'}),
|
||||
'target': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'campaigns'", 'to': "orm['core.Work']"})
|
||||
},
|
||||
'core.edition': {
|
||||
'Meta': {'object_name': 'Edition'},
|
||||
'cover_image': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'public_domain': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'publication_date': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
|
||||
'publisher': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
|
||||
'title': ('django.db.models.fields.CharField', [], {'max_length': '1000'}),
|
||||
'unglued': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'editions'", 'null': 'True', 'to': "orm['core.Work']"})
|
||||
},
|
||||
'core.premium': {
|
||||
'Meta': {'object_name': 'Premium'},
|
||||
'amount': ('django.db.models.fields.DecimalField', [], {'max_digits': '10', 'decimal_places': '0'}),
|
||||
'campaign': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'premiums'", 'null': 'True', 'to': "orm['core.Campaign']"}),
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'description': ('django.db.models.fields.TextField', [], {'null': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'limit': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'type': ('django.db.models.fields.CharField', [], {'max_length': '2'})
|
||||
},
|
||||
'core.work': {
|
||||
'Meta': {'ordering': "['title']", 'object_name': 'Work'},
|
||||
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'language': ('django.db.models.fields.CharField', [], {'default': "'en'", 'max_length': '2'}),
|
||||
'num_wishes': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'openlibrary_lookup': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
|
||||
'title': ('django.db.models.fields.CharField', [], {'max_length': '1000'})
|
||||
},
|
||||
'payment.account': {
|
||||
'Meta': {'object_name': 'Account'},
|
||||
'account_id': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
|
||||
'card_country': ('django.db.models.fields.CharField', [], {'max_length': '2', 'null': 'True'}),
|
||||
'card_exp_month': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
|
||||
'card_exp_year': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
|
||||
'card_fingerprint': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
|
||||
'card_last4': ('django.db.models.fields.CharField', [], {'max_length': '4', 'null': 'True'}),
|
||||
'card_type': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
|
||||
'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
|
||||
'date_deactivated': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
|
||||
'date_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
|
||||
'host': ('django.db.models.fields.CharField', [], {'default': "'none'", 'max_length': '32'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'})
|
||||
},
|
||||
'payment.credit': {
|
||||
'Meta': {'object_name': 'Credit'},
|
||||
'balance': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'last_activity': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
|
||||
'pledged': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'credit'", 'unique': 'True', 'to': "orm['auth.User']"})
|
||||
},
|
||||
'payment.creditlog': {
|
||||
'Meta': {'object_name': 'CreditLog'},
|
||||
'action': ('django.db.models.fields.CharField', [], {'max_length': '16'}),
|
||||
'amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'sent': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
|
||||
'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'})
|
||||
},
|
||||
'payment.paymentresponse': {
|
||||
'Meta': {'object_name': 'PaymentResponse'},
|
||||
'api': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
|
||||
'correlation_id': ('django.db.models.fields.CharField', [], {'max_length': '512', 'null': 'True'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'info': ('django.db.models.fields.CharField', [], {'max_length': '1024', 'null': 'True'}),
|
||||
'status': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
|
||||
'timestamp': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
|
||||
'transaction': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['payment.Transaction']"})
|
||||
},
|
||||
'payment.receiver': {
|
||||
'Meta': {'object_name': 'Receiver'},
|
||||
'amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'currency': ('django.db.models.fields.CharField', [], {'max_length': '10'}),
|
||||
'email': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'local_status': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
|
||||
'primary': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'reason': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
|
||||
'status': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
|
||||
'transaction': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['payment.Transaction']"}),
|
||||
'txn_id': ('django.db.models.fields.CharField', [], {'max_length': '64'})
|
||||
},
|
||||
'payment.sent': {
|
||||
'Meta': {'object_name': 'Sent'},
|
||||
'amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'timestamp': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
|
||||
'user': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'})
|
||||
},
|
||||
'payment.transaction': {
|
||||
'Meta': {'object_name': 'Transaction'},
|
||||
'ack_dedication': ('django.db.models.fields.CharField', [], {'max_length': '140', 'null': 'True'}),
|
||||
'ack_name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
|
||||
'amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'anonymous': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
|
||||
'approved': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
|
||||
'campaign': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Campaign']", 'null': 'True'}),
|
||||
'currency': ('django.db.models.fields.CharField', [], {'default': "'USD'", 'max_length': '10', 'null': 'True'}),
|
||||
'date_authorized': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
|
||||
'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
|
||||
'date_executed': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
|
||||
'date_expired': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
|
||||
'date_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
|
||||
'date_payment': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
|
||||
'error': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True'}),
|
||||
'execution': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'host': ('django.db.models.fields.CharField', [], {'default': "'none'", 'max_length': '32'}),
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'local_status': ('django.db.models.fields.CharField', [], {'default': "'NONE'", 'max_length': '32', 'null': 'True'}),
|
||||
'max_amount': ('django.db.models.fields.DecimalField', [], {'default': "'0.00'", 'max_digits': '14', 'decimal_places': '2'}),
|
||||
'pay_key': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
|
||||
'preapproval_key': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
|
||||
'premium': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['core.Premium']", 'null': 'True'}),
|
||||
'reason': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
|
||||
'receipt': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True'}),
|
||||
'secret': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
|
||||
'status': ('django.db.models.fields.CharField', [], {'default': "'None'", 'max_length': '32'}),
|
||||
'type': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
|
||||
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'})
|
||||
}
|
||||
}
|
||||
|
||||
complete_apps = ['payment']
|
|
@ -1,12 +1,23 @@
|
|||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
from django.conf import settings
|
||||
from regluit.core.models import Campaign, Wishlist, Premium
|
||||
from regluit.core.models import Campaign, Wishlist, Premium, PledgeExtra
|
||||
from regluit.payment.parameters import *
|
||||
from regluit.payment.signals import credit_balance_added
|
||||
from regluit.payment.signals import credit_balance_added, pledge_created
|
||||
from regluit.utils.localdatetime import now
|
||||
from decimal import Decimal
|
||||
from datetime import timedelta
|
||||
import uuid
|
||||
import urllib
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# in fitting stripe -- here are possible fields to fit in with Transaction
|
||||
# c.id, c.amount, c.amount_refunded, c.currency, c.description, datetime.fromtimestamp(c.created, tz=utc), c.paid,
|
||||
# c.fee, c.disputed, c.amount_refunded, c.failure_message,
|
||||
# c.card.fingerprint, c.card.type, c.card.last4, c.card.exp_month, c.card.exp_year
|
||||
|
||||
# promising fields
|
||||
|
||||
class Transaction(models.Model):
|
||||
|
||||
|
@ -14,16 +25,13 @@ class Transaction(models.Model):
|
|||
type = models.IntegerField(default=PAYMENT_TYPE_NONE, null=False)
|
||||
|
||||
# host: the payment processor. Named after the payment module that hosts the payment processing functions
|
||||
host = models.CharField(default=settings.PAYMENT_PROCESSOR, max_length=32, null=False)
|
||||
|
||||
# target: e.g, TARGET_TYPE_CAMPAIGN, TARGET_TYPE_LIST -- defined in parameters.py
|
||||
target = models.IntegerField(default=TARGET_TYPE_NONE, null=False)
|
||||
host = models.CharField(default=PAYMENT_HOST_NONE, max_length=32, null=False)
|
||||
|
||||
#execution: e.g. EXECUTE_TYPE_CHAINED_INSTANT, EXECUTE_TYPE_CHAINED_DELAYED, EXECUTE_TYPE_PARALLEL
|
||||
execution = models.IntegerField(default=EXECUTE_TYPE_NONE, null=False)
|
||||
|
||||
# status: general status constants defined in parameters.py
|
||||
status = models.CharField(max_length=32, default='None', null=False)
|
||||
status = models.CharField(max_length=32, default=TRANSACTION_STATUS_NONE, null=False)
|
||||
|
||||
# local_status: status code specific to the payment processor
|
||||
local_status = models.CharField(max_length=32, default='NONE', null=True)
|
||||
|
@ -75,14 +83,14 @@ class Transaction(models.Model):
|
|||
|
||||
# how to acknowledge the user on the supporter page of the campaign ebook
|
||||
ack_name = models.CharField(max_length=64, null=True)
|
||||
ack_link = models.URLField(null=True)
|
||||
ack_dedication = models.CharField(max_length=140, null=True)
|
||||
|
||||
# whether the user wants to be not listed publicly
|
||||
anonymous = models.BooleanField(null=False)
|
||||
|
||||
# list: makes allowance for pledging against a Wishlist: not currently in use
|
||||
list = models.ForeignKey(Wishlist, null=True)
|
||||
@property
|
||||
def ack_link(self):
|
||||
return 'https://unglue.it/supporter/%s'%urllib.urlencode(self.user.username) if not self.anonymous else ''
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.secret:
|
||||
|
@ -109,6 +117,50 @@ class Transaction(models.Model):
|
|||
mod = __import__("regluit.payment." + self.host, fromlist=[str(self.host)])
|
||||
return mod
|
||||
|
||||
def set_credit_approved(self, amount):
|
||||
self.amount=amount
|
||||
self.host = PAYMENT_HOST_CREDIT
|
||||
self.type = PAYMENT_TYPE_AUTHORIZATION
|
||||
self.status=TRANSACTION_STATUS_ACTIVE
|
||||
self.approved=True
|
||||
now_val = now()
|
||||
self.date_authorized = now_val
|
||||
self.date_expired = now_val + timedelta( days=settings.PREAPPROVAL_PERIOD )
|
||||
self.save()
|
||||
pledge_created.send(sender=self, transaction=self)
|
||||
|
||||
def set_pledge_extra(self, pledge_extra):
|
||||
if pledge_extra:
|
||||
self.anonymous = pledge_extra.anonymous
|
||||
self.premium = pledge_extra.premium
|
||||
self.ack_name = pledge_extra.ack_name
|
||||
self.ack_dedication = pledge_extra.ack_dedication
|
||||
|
||||
def get_pledge_extra(self, pledge_extra):
|
||||
return PledgeExtra(anonymous=self.anonymous,
|
||||
premium=self.premium,
|
||||
ack_name=self.ack_name,
|
||||
ack_dedication=self.ack_dedication)
|
||||
|
||||
@classmethod
|
||||
def create(cls,amount=0.00, host=PAYMENT_HOST_NONE, max_amount=0.00, currency='USD',
|
||||
status=TRANSACTION_STATUS_NONE,campaign=None, user=None, pledge_extra=None):
|
||||
if pledge_extra:
|
||||
return cls.objects.create(amount=amount,
|
||||
host=host,
|
||||
max_amount=max_amount,
|
||||
currency=currency,
|
||||
status=status,
|
||||
campaign=campaign,
|
||||
user=user,
|
||||
premium=pledge_extra.premium,
|
||||
anonymous=pledge_extra.anonymous,
|
||||
ack_name=pledge_extra.ack_name,
|
||||
ack_dedication=pledge_extra.ack_dedication
|
||||
)
|
||||
else:
|
||||
return cls.objects.create(amount=amount, host=host, max_amount=max_amount, currency=currency,status=status,
|
||||
campaign=campaign, user=user)
|
||||
|
||||
class PaymentResponse(models.Model):
|
||||
# The API used
|
||||
|
@ -149,10 +201,19 @@ class Receiver(models.Model):
|
|||
def __unicode__(self):
|
||||
return u"Receiver -- email: {0} status: {1} transaction: {2}".format(self.email, self.status, unicode(self.transaction))
|
||||
|
||||
class CreditLog(models.Model):
|
||||
# a write only record of Donation Credit Transactions
|
||||
user = models.ForeignKey(User, null=True)
|
||||
amount = models.DecimalField(default=Decimal('0.00'), max_digits=14, decimal_places=2) # max 999,999,999,999.99
|
||||
timestamp = models.DateTimeField(auto_now=True)
|
||||
action = models.CharField(max_length=16)
|
||||
# used to record the sent id when action = 'deposit'
|
||||
sent=models.IntegerField(null=True)
|
||||
|
||||
class Credit(models.Model):
|
||||
user = models.OneToOneField(User, related_name='credit')
|
||||
balance = models.IntegerField(default=0)
|
||||
pledged = models.IntegerField(default=0)
|
||||
balance = models.DecimalField(default=Decimal('0.00'), max_digits=14, decimal_places=2) # max 999,999,999,999.99
|
||||
pledged = models.DecimalField(default=Decimal('0.00'), max_digits=14, decimal_places=2) # max 999,999,999,999.99
|
||||
last_activity = models.DateTimeField(auto_now=True)
|
||||
|
||||
@property
|
||||
|
@ -165,17 +226,29 @@ class Credit(models.Model):
|
|||
else:
|
||||
self.balance = self.balance + num_credits
|
||||
self.save()
|
||||
credit_balance_added.send(sender=self, amount=num_credits)
|
||||
try: # bad things can happen here if you don't return True
|
||||
CreditLog(user = self.user, amount = num_credits, action="add_to_balance").save()
|
||||
except:
|
||||
logger.exception("failed to log add_to_balance of %s", num_credits)
|
||||
try:
|
||||
credit_balance_added.send(sender=self, amount=num_credits)
|
||||
except:
|
||||
logger.exception("credit_balance_added failed of %s", num_credits)
|
||||
return True
|
||||
|
||||
def add_to_pledged(self, num_credits):
|
||||
if not isinstance( num_credits, int):
|
||||
num_credits=Decimal(num_credits)
|
||||
if num_credits is Decimal('NaN'):
|
||||
return False
|
||||
if self.balance - self.pledged < num_credits :
|
||||
return False
|
||||
else:
|
||||
self.pledged=self.pledged + num_credits
|
||||
self.save()
|
||||
try: # bad things can happen here if you don't return True
|
||||
CreditLog(user = self.user, amount = num_credits, action="add_to_pledged").save()
|
||||
except:
|
||||
logger.exception("failed to log add_to_pledged of %s", num_credits)
|
||||
return True
|
||||
|
||||
def use_pledge(self, num_credits):
|
||||
|
@ -187,6 +260,10 @@ class Credit(models.Model):
|
|||
self.pledged=self.pledged - num_credits
|
||||
self.balance = self.balance - num_credits
|
||||
self.save()
|
||||
try:
|
||||
CreditLog(user = self.user, amount = - num_credits, action="use_pledge").save()
|
||||
except:
|
||||
logger.exception("failed to log use_pledge of %s", num_credits)
|
||||
return True
|
||||
|
||||
def transfer_to(self, receiver, num_credits):
|
||||
|
@ -202,6 +279,42 @@ class Credit(models.Model):
|
|||
else:
|
||||
return False
|
||||
|
||||
class Sent(models.Model):
|
||||
'''used by donation view to record donations it has sent'''
|
||||
user = models.CharField(max_length=32, null=True)
|
||||
amount = models.DecimalField(default=Decimal('0.00'), max_digits=14, decimal_places=2) # max 999,999,999,999.99
|
||||
timestamp = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Account(models.Model):
|
||||
"""holds references to accounts at third party payment gateways, especially for representing credit cards"""
|
||||
|
||||
# the following fields from stripe Customer might be relevant to Account -- we need to pick good selection
|
||||
# c.id, c.description, c.email, datetime.fromtimestamp(c.created, tz=utc), c.account_balance, c.delinquent,
|
||||
# c.active_card.fingerprint, c.active_card.type, c.active_card.last4, c.active_card.exp_month, c.active_card.exp_year,
|
||||
# c.active_card.country
|
||||
|
||||
# host: the payment processor. Named after the payment module that hosts the payment processing functions
|
||||
host = models.CharField(default=PAYMENT_HOST_NONE, max_length=32, null=False)
|
||||
account_id = models.CharField(max_length=128, null=True)
|
||||
|
||||
# card related info
|
||||
card_last4 = models.CharField(max_length=4, null=True)
|
||||
|
||||
# Visa, American Express, MasterCard, Discover, JCB, Diners Club, or Unknown
|
||||
card_type = models.CharField(max_length=32, null=True)
|
||||
card_exp_month = models.IntegerField(null=True)
|
||||
card_exp_year = models.IntegerField(null=True)
|
||||
card_fingerprint = models.CharField(max_length=32, null=True)
|
||||
card_country = models.CharField(max_length=2, null=True)
|
||||
|
||||
# creation and last modified timestamps
|
||||
date_created = models.DateTimeField(auto_now_add=True)
|
||||
date_modified = models.DateTimeField(auto_now=True)
|
||||
date_deactivated = models.DateTimeField(null=True)
|
||||
|
||||
# associated User
|
||||
user = models.ForeignKey(User, null=True)
|
||||
|
||||
from django.db.models.signals import post_save, post_delete
|
||||
import regluit.payment.manager
|
||||
|
||||
|
|
|
@ -5,29 +5,30 @@ PAYMENT_TYPE_AUTHORIZATION = 2
|
|||
PAYMENT_HOST_NONE = "none"
|
||||
PAYMENT_HOST_PAYPAL = "paypal"
|
||||
PAYMENT_HOST_AMAZON = "amazon"
|
||||
PAYMENT_HOST_STRIPE = "stripe"
|
||||
|
||||
PAYMENT_HOST_TEST = "test"
|
||||
PAYMENT_HOST_CREDIT = "credit"
|
||||
|
||||
EXECUTE_TYPE_NONE = 0
|
||||
EXECUTE_TYPE_CHAINED_INSTANT = 1
|
||||
EXECUTE_TYPE_CHAINED_DELAYED = 2
|
||||
EXECUTE_TYPE_PARALLEL = 3
|
||||
|
||||
TARGET_TYPE_NONE = 0
|
||||
TARGET_TYPE_CAMPAIGN = 1
|
||||
TARGET_TYPE_LIST = 2
|
||||
TARGET_TYPE_DONATION = 3
|
||||
|
||||
# The default status for a transaction that is newly created
|
||||
TRANSACTION_STATUS_NONE = 'None'
|
||||
|
||||
# Indicates a transaction has been sent to the co-branded API
|
||||
TRANSACTION_STATUS_CREATED = 'Created'
|
||||
|
||||
# A general complete code to indicate payment is comlete to all receivers
|
||||
# A general complete code to indicate payment is complete to all receivers
|
||||
TRANSACTION_STATUS_COMPLETE = 'Complete'
|
||||
|
||||
# A general pending code that means in process
|
||||
TRANSACTION_STATUS_PENDING = 'Pending'
|
||||
|
||||
# This means that the max amount has increased but the increase hasn't been executed
|
||||
TRANSACTION_STATUS_MODIFIED = 'Modified'
|
||||
|
||||
# Indicates a preapproval is active
|
||||
TRANSACTION_STATUS_ACTIVE = 'Active'
|
||||
|
@ -47,6 +48,3 @@ TRANSACTION_STATUS_REFUNDED = 'Refunded'
|
|||
# The transaction was refused/denied
|
||||
TRANSACTION_STATUS_FAILED = 'Failed'
|
||||
|
||||
# these two following parameters are probably extraneous since I think we will compute dynamically where to return each time.
|
||||
COMPLETE_URL = '/paymentcomplete'
|
||||
NEVERMIND_URL = '/paymentnevermind'
|
||||
|
|
1023
payment/paypal.py
|
@ -0,0 +1,274 @@
|
|||
# https://github.com/stripe/stripe-python
|
||||
# https://stripe.com/docs/api?lang=python#top
|
||||
|
||||
from datetime import datetime
|
||||
from pytz import utc
|
||||
|
||||
import stripe
|
||||
|
||||
try:
|
||||
import unittest
|
||||
from unittest import TestCase
|
||||
except:
|
||||
from django.test import TestCase
|
||||
from django.utils import unittest
|
||||
|
||||
# if customer.id doesn't exist, create one and then charge the customer
|
||||
# we probably should ask our users whether they are ok with our creating a customer id account -- or ask for credit
|
||||
# card info each time....
|
||||
|
||||
# should load the keys for Stripe from db -- but for now just hardcode here
|
||||
# moving towards not having the stripe api key for the non profit partner in the unglue.it code -- but in a logically
|
||||
# distinct application
|
||||
|
||||
try:
|
||||
from regluit.core.models import Key
|
||||
STRIPE_PK = Key.objects.get(name="STRIPE_PK").value
|
||||
STRIPE_SK = Key.objects.get(name="STRIPE_SK").value
|
||||
STRIPE_PARTNER_PK = Key.objects.get(name="STRIPE_PARTNER_PK").value
|
||||
STRIPE_PARTNER_SK = Key.objects.get(name="STRIPE_PARTNER_SK").value
|
||||
logger.info('Successful loading of STRIPE_*_KEYs')
|
||||
except Exception, e:
|
||||
# currently test keys for Gluejar and for raymond.yee@gmail.com as standin for non-profit
|
||||
STRIPE_PK = 'pk_0EajXPn195ZdF7Gt7pCxsqRhNN5BF'
|
||||
STRIPE_SK = 'sk_0EajIO4Dnh646KPIgLWGcO10f9qnH'
|
||||
STRIPE_PARTNER_PK ='pk_0AnIkNu4WRiJYzxMKgruiUwxzXP2T'
|
||||
STRIPE_PARTNER_SK = 'sk_0AnIvBrnrJoFpfD3YmQBVZuTUAbjs'
|
||||
|
||||
# set default stripe api_key to that of unglue.it
|
||||
|
||||
stripe.api_key = STRIPE_SK
|
||||
|
||||
# https://stripe.com/docs/testing
|
||||
|
||||
TEST_CARDS = (
|
||||
('4242424242424242', 'Visa'),
|
||||
('4012888888881881', 'Visa'),
|
||||
('5555555555554444', 'MasterCard'),
|
||||
('5105105105105100', 'MasterCard'),
|
||||
('378282246310005', 'American Express'),
|
||||
('371449635398431', 'American Express'),
|
||||
('6011111111111117', 'Discover'),
|
||||
('6011000990139424', 'Discover'),
|
||||
('30569309025904', "Diner's Club"),
|
||||
('38520000023237', "Diner's Club"),
|
||||
('3530111333300000', 'JCB'),
|
||||
('3566002020360505','JCB')
|
||||
)
|
||||
|
||||
ERROR_TESTING = dict((
|
||||
('ADDRESS1_ZIP_FAIL', ('4000000000000010', 'address_line1_check and address_zip_check will both fail')),
|
||||
('ADDRESS1_FAIL', ('4000000000000028', 'address_line1_check will fail.')),
|
||||
('ADDRESS_ZIP_FAIL', ('4000000000000036', 'address_zip_check will fail.')),
|
||||
('CVC_CHECK_FAIL', ('4000000000000101', 'cvc_check will fail.')),
|
||||
('BAD_ATTACHED_CARD', ('4000000000000341', 'Attaching this card to a Customer object will succeed, but attempts to charge the customer will fail.')),
|
||||
('CHARGE_DECLINE', ('4000000000000002', 'Charges with this card will always be declined.'))
|
||||
))
|
||||
|
||||
# types of errors / when they can be handled
|
||||
|
||||
#card_declined: Use this special card number - 4000000000000002.
|
||||
#incorrect_number: Use a number that fails the Luhn check, e.g. 4242424242424241.
|
||||
#invalid_expiry_month: Use an invalid month e.g. 13.
|
||||
#invalid_expiry_year: Use a year in the past e.g. 1970.
|
||||
#invalid_cvc: Use a two digit number e.g. 99.
|
||||
|
||||
|
||||
def filter_none(d):
|
||||
return dict([(k,v) for (k,v) in d.items() if v is not None])
|
||||
|
||||
# if you create a Customer object, then you'll be able to charge multiple times. You can create a customer with a token.
|
||||
|
||||
# https://stripe.com/docs/tutorials/charges
|
||||
|
||||
def card (number=TEST_CARDS[0][0], exp_month='01', exp_year='2020', cvc=None, name=None,
|
||||
address_line1=None, address_line2=None, address_zip=None, address_state=None, address_country=None):
|
||||
|
||||
card = {
|
||||
"number": number,
|
||||
"exp_month": str(exp_month),
|
||||
"exp_year": str(exp_year),
|
||||
"cvc": str(cvc) if cvc is not None else None,
|
||||
"name": name,
|
||||
"address_line1": address_line1,
|
||||
"address_line2": address_line2,
|
||||
"address_zip": address_zip,
|
||||
"address_state": address_state,
|
||||
"address_country": address_country
|
||||
}
|
||||
|
||||
return filter_none(card)
|
||||
|
||||
|
||||
class StripeClient(object):
|
||||
def __init__(self, api_key=STRIPE_SK):
|
||||
self.api_key = api_key
|
||||
|
||||
# key entities: Charge, Customer, Token, Event
|
||||
|
||||
@property
|
||||
def charge(self):
|
||||
return stripe.Charge(api_key=self.api_key)
|
||||
|
||||
@property
|
||||
def customer(self):
|
||||
return stripe.Customer(api_key=self.api_key)
|
||||
|
||||
@property
|
||||
def token(self):
|
||||
return stripe.Token(api_key=self.api_key)
|
||||
|
||||
@property
|
||||
def transfer(self):
|
||||
return stripe.Transfer(api_key=self.api_key)
|
||||
|
||||
@property
|
||||
def event(self):
|
||||
return stripe.Event(api_key=self.api_key)
|
||||
|
||||
|
||||
def create_token(self, card):
|
||||
return stripe.Token(api_key=self.api_key).create(card=card)
|
||||
|
||||
def create_customer (self, card=None, description=None, email=None, account_balance=None, plan=None, trial_end=None):
|
||||
"""card is a dictionary or a token"""
|
||||
# https://stripe.com/docs/api?lang=python#create_customer
|
||||
|
||||
customer = stripe.Customer(api_key=self.api_key).create(
|
||||
card=card,
|
||||
description=description,
|
||||
email=email,
|
||||
account_balance=account_balance,
|
||||
plan=plan,
|
||||
trial_end=trial_end
|
||||
)
|
||||
|
||||
# customer.id is useful to save in db
|
||||
return customer
|
||||
|
||||
|
||||
def create_charge(self, amount, currency="usd", customer=None, card=None, description=None ):
|
||||
# https://stripe.com/docs/api?lang=python#create_charge
|
||||
# customer or card required but not both
|
||||
# charge the Customer instead of the card
|
||||
# amount in cents
|
||||
|
||||
charge = stripe.Charge(api_key=self.api_key).create(
|
||||
amount=int(100*amount), # in cents
|
||||
currency=currency,
|
||||
customer=customer.id if customer is not None else None,
|
||||
card=card,
|
||||
description=description
|
||||
)
|
||||
|
||||
return charge
|
||||
|
||||
def refund_charge(self, charge_id):
|
||||
# https://stripe.com/docs/api?lang=python#refund_charge
|
||||
ch = stripe.Charge(api_key=self.api_key).retrieve(charge_id)
|
||||
ch.refund()
|
||||
return ch
|
||||
|
||||
def list_all_charges(self, count=None, offset=None, customer=None):
|
||||
# https://stripe.com/docs/api?lang=python#list_charges
|
||||
return stripe.Charge(api_key=self.api_key).all(count=count, offset=offset, customer=customer)
|
||||
|
||||
# what to work through?
|
||||
|
||||
# can't test Transfer in test mode: "There are no transfers in test mode."
|
||||
|
||||
#pledge scenario
|
||||
# bad card -- what types of erros to handle?
|
||||
# https://stripe.com/docs/api#errors
|
||||
|
||||
# what errors are handled in the python library and how?
|
||||
#
|
||||
|
||||
# Account?
|
||||
|
||||
# https://stripe.com/docs/api#event_types
|
||||
# events of interest -- especially ones that do not directly arise immediately (synchronously) from something we do -- I think
|
||||
# especially: charge.disputed
|
||||
# I think following (charge.succeeded, charge.failed, charge.refunded) pretty much sychronous to our actions
|
||||
# customer.created, customer.updated, customer.deleted
|
||||
|
||||
# transfer
|
||||
# I expect the ones related to transfers all happen asynchronously: transfer.created, transfer.updated, transfer.failed
|
||||
|
||||
# When will the money I charge with Stripe end up in my bank account?
|
||||
# Every day, we transfer the money that you charged seven days previously?that is, you receive the money for your March 1st charges on March 8th.
|
||||
|
||||
# pending payments?
|
||||
# how to tell whether money transferred to bank account yet
|
||||
# best practices for calling Events -- not too often.
|
||||
|
||||
class PledgeScenarioTest(TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls._sc = StripeClient(api_key=STRIPE_SK)
|
||||
|
||||
# valid card
|
||||
card0 = card()
|
||||
cls._good_cust = cls._sc.create_customer(card=card0, description="test good customer", email="raymond.yee@gmail.com")
|
||||
|
||||
# bad card
|
||||
test_card_num_to_get_BAD_ATTACHED_CARD = ERROR_TESTING['BAD_ATTACHED_CARD'][0]
|
||||
card1 = card(number=test_card_num_to_get_BAD_ATTACHED_CARD)
|
||||
cls._cust_bad_card = cls._sc.create_customer(card=card1, description="test bad customer", email="rdhyee@gluejar.com")
|
||||
|
||||
def test_charge_good_cust(self):
|
||||
charge = self._sc.create_charge(10, customer=self._good_cust, description="$10 for good cust")
|
||||
self.assertEqual(type(charge.id), str)
|
||||
|
||||
# print out all the pieces of Customer and Charge objects
|
||||
print dir(charge)
|
||||
print dir(self._good_cust)
|
||||
|
||||
def test_error_creating_customer_with_declined_card(self):
|
||||
# should get a CardError upon attempt to create Customer with this card
|
||||
_card = card(number=card(ERROR_TESTING['CHARGE_DECLINE'][0]))
|
||||
self.assertRaises(stripe.CardError, self._sc.create_customer, card=_card)
|
||||
|
||||
def test_charge_bad_cust(self):
|
||||
# expect the card to be declined -- and for us to get CardError
|
||||
self.assertRaises(stripe.CardError, self._sc.create_charge, 10,
|
||||
customer = self._cust_bad_card, description="$10 for bad cust")
|
||||
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
# clean up stuff we create in test -- right now list current objects
|
||||
|
||||
#cls._good_cust.delete()
|
||||
|
||||
print "list of customers"
|
||||
print [(i, c.id, c.description, c.email, datetime.fromtimestamp(c.created, tz=utc), c.account_balance, c.delinquent, c.active_card.fingerprint, c.active_card.type, c.active_card.last4, c.active_card.exp_month, c.active_card.exp_year, c.active_card.country) for(i, c) in enumerate(cls._sc.customer.all()["data"])]
|
||||
|
||||
print "list of charges"
|
||||
print [(i, c.id, c.amount, c.amount_refunded, c.currency, c.description, datetime.fromtimestamp(c.created, tz=utc), c.paid, c.fee, c.disputed, c.amount_refunded, c.failure_message, c.card.fingerprint, c.card.type, c.card.last4, c.card.exp_month, c.card.exp_year) for (i, c) in enumerate(cls._sc.charge.all()['data'])]
|
||||
|
||||
# can retrieve events since a certain time?
|
||||
print "list of events", cls._sc.event.all()
|
||||
print [(i, e.id, e.type, e.created, e.pending_webhooks, e.data) for (i,e) in enumerate(cls._sc.event.all()['data'])]
|
||||
|
||||
def suite():
|
||||
|
||||
testcases = [PledgeScenarioTest]
|
||||
#testcases = []
|
||||
suites = unittest.TestSuite([unittest.TestLoader().loadTestsFromTestCase(testcase) for testcase in testcases])
|
||||
#suites.addTest(LibraryThingTest('test_cache'))
|
||||
#suites.addTest(SettingsTest('test_dev_me_alignment')) # give option to test this alignment
|
||||
return suites
|
||||
|
||||
|
||||
# IPNs/webhooks: https://stripe.com/docs/webhooks
|
||||
# how to use pending_webhooks ?
|
||||
|
||||
# all events
|
||||
# https://stripe.com/docs/api?lang=python#list_events
|
||||
|
||||
if __name__ == '__main__':
|
||||
#unittest.main()
|
||||
suites = suite()
|
||||
#suites = unittest.defaultTestLoader.loadTestsFromModule(__import__('__main__'))
|
||||
unittest.TextTestRunner().run(suites)
|
|
@ -0,0 +1,38 @@
|
|||
{% extends "basepledge.html" %}
|
||||
{% load humanize %}
|
||||
|
||||
{% block title %}Stripe{% endblock %}
|
||||
|
||||
{% block extra_extra_head %}
|
||||
<link type="text/css" rel="stylesheet" href="/static/css/campaign.css" />
|
||||
<link type="text/css" rel="stylesheet" href="/static/css/pledge.css" />
|
||||
|
||||
<link href="/static/stripe/tag.css" rel="stylesheet" type="text/css">
|
||||
<script type="text/javascript" src="/static/stripe/tag.js"></script>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block doccontent %}
|
||||
Stripe Test!:
|
||||
|
||||
<span class="payment-errors"></span>
|
||||
<form action="" method="post" id="payment-form">
|
||||
{% csrf_token %}
|
||||
<payment key="{{STRIPE_PK}}"></payment>
|
||||
<input type="submit" value="Submit">
|
||||
</form>
|
||||
|
||||
<script type="application/x-javascript">
|
||||
|
||||
var $j = jQuery.noConflict();
|
||||
console.debug('setting up handlers in stripe.html');
|
||||
|
||||
$j('payment').bind('success.payment', function () {
|
||||
console.debug('success.payment ev');
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
|
|
@ -190,7 +190,7 @@ class PledgeTest(TestCase):
|
|||
|
||||
# Note, set this to 1-5 different receivers with absolute amounts for each
|
||||
receiver_list = [{'email':settings.PAYPAL_GLUEJAR_EMAIL, 'amount':20.00}]
|
||||
t, url = p.pledge('USD', TARGET_TYPE_NONE, receiver_list, campaign=None, list=None, user=None)
|
||||
t, url = p.pledge('USD', receiver_list, campaign=None, list=None, user=None)
|
||||
|
||||
self.validateRedirect(t, url, 1)
|
||||
|
||||
|
@ -220,7 +220,7 @@ class PledgeTest(TestCase):
|
|||
receiver_list = [{'email':settings.PAYPAL_GLUEJAR_EMAIL, 'amount':20.00},
|
||||
{'email':settings.PAYPAL_TEST_RH_EMAIL, 'amount':10.00}]
|
||||
|
||||
t, url = p.pledge('USD', TARGET_TYPE_NONE, receiver_list, campaign=None, list=None, user=None)
|
||||
t, url = p.pledge('USD', receiver_list, campaign=None, list=None, user=None)
|
||||
|
||||
self.validateRedirect(t, url, 2)
|
||||
|
||||
|
@ -244,7 +244,7 @@ class PledgeTest(TestCase):
|
|||
|
||||
# Note, set this to 1-5 different receivers with absolute amounts for each
|
||||
receiver_list = [{'email':settings.PAYPAL_GLUEJAR_EMAIL, 'amount':50000.00}]
|
||||
t, url = p.pledge('USD', TARGET_TYPE_NONE, receiver_list, campaign=None, list=None, user=None)
|
||||
t, url = p.pledge('USD', receiver_list, campaign=None, list=None, user=None)
|
||||
|
||||
self.validateRedirect(t, url, 1)
|
||||
|
||||
|
@ -284,7 +284,7 @@ class AuthorizeTest(TestCase):
|
|||
|
||||
# Note, set this to 1-5 different receivers with absolute amounts for each
|
||||
|
||||
t, url = p.authorize('USD', TARGET_TYPE_NONE, 100.0, campaign=None, list=None, user=None)
|
||||
t, url = p.authorize(t)
|
||||
|
||||
self.validateRedirect(t, url)
|
||||
|
||||
|
@ -388,7 +388,7 @@ class BasicGuiTest(TestCase):
|
|||
def suite():
|
||||
|
||||
#testcases = [PledgeTest, AuthorizeTest, TransactionTest]
|
||||
testcases = [TransactionTest]
|
||||
testcases = [TransactionTest, CreditTest]
|
||||
suites = unittest.TestSuite([unittest.TestLoader().loadTestsFromTestCase(testcase) for testcase in testcases])
|
||||
return suites
|
||||
|
||||
|
|
|
@ -1,16 +1,12 @@
|
|||
from django.conf.urls.defaults import *
|
||||
from django.conf import settings
|
||||
from regluit.payment.views import StripeView
|
||||
|
||||
urlpatterns = patterns(
|
||||
"regluit.payment.views",
|
||||
url(r"^handleipn/(?P<module>\w+)$", "handleIPN", name="HandleIPN"),
|
||||
)
|
||||
|
||||
# Amazon payment URLs
|
||||
urlpatterns += patterns(
|
||||
"regluit.payment.amazon",
|
||||
url(r"^amazonpaymentreturn", "amazonPaymentReturn", name="AmazonPaymentReturn"),
|
||||
)
|
||||
|
||||
# this should be on only if DEBUG is on
|
||||
|
||||
|
@ -22,12 +18,11 @@ if settings.DEBUG:
|
|||
url(r"^testexecute", "testExecute"),
|
||||
url(r"^testcancel", "testCancel"),
|
||||
url(r"^querycampaign", "queryCampaign"),
|
||||
url(r"^runtests", "runTests"),
|
||||
url(r"^paymentcomplete","paymentcomplete"),
|
||||
url(r"^checkstatus", "checkStatus"),
|
||||
url(r"^testfinish", "testFinish"),
|
||||
url(r"^testrefund", "testRefund"),
|
||||
url(r"^testmodify", "testModify"),
|
||||
url(r"^stripe/test", StripeView.as_view())
|
||||
)
|
||||
|
||||
|
|
@ -1,7 +1,11 @@
|
|||
from regluit.payment.manager import PaymentManager
|
||||
from regluit.payment.paypal import IPN
|
||||
from regluit.payment.models import Transaction
|
||||
from regluit.core.models import Campaign, Wishlist
|
||||
|
||||
from regluit.payment.stripelib import STRIPE_PK
|
||||
|
||||
from regluit.payment.forms import StripePledgeForm
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.shortcuts import render_to_response
|
||||
|
@ -13,8 +17,12 @@ from django.views.decorators.csrf import csrf_exempt
|
|||
from django.test.utils import setup_test_environment
|
||||
from django.template import RequestContext
|
||||
|
||||
from django.views.generic.edit import FormView
|
||||
from django.views.generic.base import TemplateView
|
||||
|
||||
from unittest import TestResult
|
||||
from regluit.payment.tests import PledgeTest, AuthorizeTest
|
||||
|
||||
|
||||
import uuid
|
||||
from decimal import Decimal as D
|
||||
|
||||
|
@ -113,12 +121,8 @@ def testAuthorize(request):
|
|||
receiver_list = [{'email': TEST_RECEIVERS[0], 'amount':20.00},
|
||||
{'email': TEST_RECEIVERS[1], 'amount':10.00}]
|
||||
|
||||
if campaign_id:
|
||||
campaign = Campaign.objects.get(id=int(campaign_id))
|
||||
t, url = p.authorize('USD', TARGET_TYPE_CAMPAIGN, amount, campaign=campaign, return_url=None, list=None, user=None)
|
||||
|
||||
else:
|
||||
t, url = p.authorize('USD', TARGET_TYPE_NONE, amount, campaign=None, return_url=None, list=None, user=None)
|
||||
campaign = Campaign.objects.get(id=int(campaign_id))
|
||||
t, url = p.authorize(Transaction.objects.create(currency='USD', max_amount=amount, campaign=campaign, user=None), return_url=None)
|
||||
|
||||
if url:
|
||||
logger.info("testAuthorize: " + url)
|
||||
|
@ -249,12 +253,9 @@ def testPledge(request):
|
|||
else:
|
||||
receiver_list = [{'email':TEST_RECEIVERS[0], 'amount':78.90}, {'email':TEST_RECEIVERS[1], 'amount':34.56}]
|
||||
|
||||
if campaign_id:
|
||||
campaign = Campaign.objects.get(id=int(campaign_id))
|
||||
t, url = p.pledge('USD', TARGET_TYPE_CAMPAIGN, receiver_list, campaign=campaign, list=None, user=user, return_url=None)
|
||||
campaign = Campaign.objects.get(id=int(campaign_id))
|
||||
t, url = p.pledge('USD', receiver_list, campaign=campaign, list=None, user=user, return_url=None)
|
||||
|
||||
else:
|
||||
t, url = p.pledge('USD', TARGET_TYPE_NONE, receiver_list, campaign=None, list=None, user=user, return_url=None)
|
||||
|
||||
if url:
|
||||
logger.info("testPledge: " + url)
|
||||
|
@ -265,33 +266,6 @@ def testPledge(request):
|
|||
logger.info("testPledge: Error " + str(t.error))
|
||||
return HttpResponse(response)
|
||||
|
||||
def runTests(request):
|
||||
|
||||
try:
|
||||
# Setup the test environement. We need to run these tests on a live server
|
||||
# so our code can receive IPN notifications from paypal
|
||||
setup_test_environment()
|
||||
result = TestResult()
|
||||
|
||||
# Run the authorize test
|
||||
test = AuthorizeTest('test_authorize')
|
||||
test.run(result)
|
||||
|
||||
# Run the pledge test
|
||||
test = PledgeTest('test_pledge_single_receiver')
|
||||
test.run(result)
|
||||
|
||||
# Run the pledge failure test
|
||||
test = PledgeTest('test_pledge_too_much')
|
||||
test.run(result)
|
||||
|
||||
output = "Tests Run: " + str(result.testsRun) + str(result.errors) + str(result.failures)
|
||||
logger.info(output)
|
||||
|
||||
return HttpResponse(output)
|
||||
|
||||
except:
|
||||
traceback.print_exc()
|
||||
|
||||
@csrf_exempt
|
||||
def handleIPN(request, module):
|
||||
|
@ -304,11 +278,6 @@ def handleIPN(request, module):
|
|||
return HttpResponse("ipn")
|
||||
|
||||
|
||||
def paymentcomplete(request):
|
||||
# pick up all get and post parameters and display
|
||||
output = "payment complete"
|
||||
output += request.method + "\n" + str(request.REQUEST.items())
|
||||
return HttpResponse(output)
|
||||
|
||||
def checkStatus(request):
|
||||
# Check the status of all PAY transactions and flag any errors
|
||||
|
@ -322,5 +291,25 @@ def checkStatus(request):
|
|||
def _render(request, template, template_vars={}):
|
||||
return render_to_response(template, template_vars, RequestContext(request))
|
||||
|
||||
class StripeView(FormView):
|
||||
template_name="stripe.html"
|
||||
form_class = StripePledgeForm
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
||||
context = super(StripeView, self).get_context_data(**kwargs)
|
||||
|
||||
context.update({
|
||||
'STRIPE_PK':STRIPE_PK
|
||||
})
|
||||
return context
|
||||
|
||||
def form_valid(self, form):
|
||||
stripe_token = form.cleaned_data["stripe_token"]
|
||||
# e.g., tok_0C0k4jG5B2Oxox
|
||||
#
|
||||
return HttpResponse("stripe_token: {0}".format(stripe_token))
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
Django==1.3.1
|
||||
Django==1.4.1
|
||||
MySQL-python==1.2.3
|
||||
south
|
||||
django-extensions
|
||||
|
@ -19,8 +19,8 @@ freebase
|
|||
django-endless-pagination
|
||||
django-selectable
|
||||
pytz
|
||||
django-notification
|
||||
git+ssh://git@github.com/Gluejar/boto.git@2.3.0
|
||||
https://github.com/aladagemre/django-notification/tarball/master
|
||||
fabric
|
||||
paramiko
|
||||
pyasn1
|
||||
|
@ -28,3 +28,4 @@ pycrypto
|
|||
django-maintenancemode
|
||||
django-smtp-ssl
|
||||
django-ckeditor
|
||||
stripe
|
||||
|
|
|
@ -53,4 +53,5 @@ requests==0.9.1
|
|||
selenium==2.24.0
|
||||
South==0.7.3
|
||||
ssh==1.7.13
|
||||
stripe==1.7.4
|
||||
wsgiref==0.1.2
|
||||
|
|
|
@ -49,7 +49,7 @@ STATIC_URL = '/static/'
|
|||
# URL prefix for admin static files -- CSS, JavaScript and images.
|
||||
# Make sure to use a trailing slash.
|
||||
# Examples: "http://foo.com/static/admin/", "/static/admin/".
|
||||
ADMIN_MEDIA_PREFIX = '/static/admin/'
|
||||
# ADMIN_MEDIA_PREFIX = '/static/admin/'
|
||||
|
||||
# Additional locations of static files
|
||||
STATICFILES_DIRS = (
|
||||
|
@ -271,12 +271,8 @@ EBOOK_NOTIFICATIONS_JOB = {
|
|||
|
||||
# by default, in common, we don't turn any of the celerybeat jobs on -- turn them on in the local settings file
|
||||
|
||||
# set -- sandbox or production Amazon FPS?
|
||||
AMAZON_FPS_HOST = "fps.sandbox.amazonaws.com"
|
||||
#AMAZON_FPS_HOST = "fps.amazonaws.com"
|
||||
|
||||
# amazon or paypal for now.
|
||||
PAYMENT_PROCESSOR = 'amazon'
|
||||
PAYMENT_PROCESSOR = 'test'
|
||||
|
||||
# a SECRET_KEY to be used for encrypting values in core.models.Key -- you should store in settings/local.py
|
||||
SECRET_KEY = ''
|
||||
|
@ -286,3 +282,7 @@ SECRET_KEY = ''
|
|||
MAINTENANCE_MODE = False
|
||||
# Sequence of URL path regexes to exclude from the maintenance mode.
|
||||
MAINTENANCE_IGNORE_URLS = {}
|
||||
|
||||
class NONPROFIT:
|
||||
name = 'Library Renewal'
|
||||
link = 'http://127.0.0.1:8000/donate_to_campaign/'
|
|
@ -10,7 +10,9 @@ IS_PREVIEW = False
|
|||
SITE_ID = 3
|
||||
|
||||
ADMINS = (
|
||||
('Ed Summers', 'ehs@pobox.com'),
|
||||
('Raymond Yee', 'rdhyee+ungluebugs@gluejar.com'),
|
||||
('Eric Hellman', 'eric@gluejar.com'),
|
||||
('Andromeda Yelton', 'andromeda@gluejar.com'),
|
||||
)
|
||||
|
||||
MANAGERS = ADMINS
|
||||
|
|
|
@ -4,7 +4,9 @@ DEBUG = True
|
|||
TEMPLATE_DEBUG = DEBUG
|
||||
|
||||
ADMINS = (
|
||||
('Ed Summers', 'ehs@pobox.com'),
|
||||
('Raymond Yee', 'rdhyee+ungluebugs@gluejar.com'),
|
||||
('Eric Hellman', 'eric@gluejar.com'),
|
||||
('Andromeda Yelton', 'andromeda@gluejar.com'),
|
||||
)
|
||||
|
||||
MANAGERS = ADMINS
|
||||
|
|
|
@ -6,7 +6,6 @@ TEMPLATE_DEBUG = DEBUG
|
|||
SITE_ID = 5
|
||||
|
||||
ADMINS = (
|
||||
('Ed Summers', 'ed.summers@gmail.com'),
|
||||
('Raymond Yee', 'rdhyee+ungluebugs@gluejar.com'),
|
||||
('Eric Hellman', 'eric@gluejar.com'),
|
||||
('Andromeda Yelton', 'andromeda@gluejar.com'),
|
||||
|
|
|
@ -6,7 +6,6 @@ TEMPLATE_DEBUG = DEBUG
|
|||
SITE_ID = 2
|
||||
|
||||
ADMINS = (
|
||||
('Ed Summers', 'ed.summers@gmail.com'),
|
||||
('Raymond Yee', 'rdhyee+ungluebugs@gluejar.com'),
|
||||
('Eric Hellman', 'eric@gluejar.com'),
|
||||
('Andromeda Yelton', 'andromeda@gluejar.com'),
|
||||
|
|
|
@ -8,7 +8,6 @@ IS_PREVIEW = False
|
|||
SITE_ID = 1
|
||||
|
||||
ADMINS = (
|
||||
('Ed Summers', 'ed.summers@gmail.com'),
|
||||
('Raymond Yee', 'rdhyee+ungluebugs@gluejar.com'),
|
||||
('Eric Hellman', 'eric@gluejar.com'),
|
||||
('Andromeda Yelton', 'andromeda@gluejar.com'),
|
||||
|
|
|
@ -220,6 +220,7 @@ ul.tabs li.active a {
|
|||
}
|
||||
.book-detail-info > div.layout div.btn_support {
|
||||
float: right;
|
||||
margin-top: 9px;
|
||||
}
|
||||
.book-detail-info > div.layout div.btn_support input {
|
||||
font-size: 15px;
|
||||
|
@ -429,6 +430,10 @@ div#content-block-content #tabs-3 {
|
|||
.tabs-content form {
|
||||
margin-left: -5px;
|
||||
}
|
||||
.tabs-content .clearfix {
|
||||
margin-bottom: 10px;
|
||||
border-bottom: 2px solid #d6dde0;
|
||||
}
|
||||
.work_supporter {
|
||||
height: auto;
|
||||
min-height: 50px;
|
||||
|
@ -461,12 +466,10 @@ div#content-block-content #tabs-3 {
|
|||
padding: 3px;
|
||||
margin-left: -5px;
|
||||
}
|
||||
.editions {
|
||||
clear: both;
|
||||
}
|
||||
.editions div {
|
||||
float: left;
|
||||
padding-bottom: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.editions .image {
|
||||
width: 60px;
|
||||
|
|
|
@ -0,0 +1,173 @@
|
|||
/* variables and mixins used in multiple less files go here */
|
||||
.header-text {
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
font-size: 13px;
|
||||
letter-spacing: -0.05em;
|
||||
}
|
||||
.panelborders {
|
||||
border-width: 1px 0px;
|
||||
border-style: solid none;
|
||||
border-color: #FFFFFF;
|
||||
}
|
||||
.roundedspan {
|
||||
border: 1px solid #d4d4d4;
|
||||
-moz-border-radius: 7px;
|
||||
-webkit-border-radius: 7px;
|
||||
border-radius: 7px;
|
||||
padding: 1px;
|
||||
color: #fff;
|
||||
margin: 0 8px 0 0;
|
||||
display: inline-block;
|
||||
}
|
||||
.roundedspan > span {
|
||||
padding: 7px 7px;
|
||||
min-width: 15px;
|
||||
-moz-border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
}
|
||||
.roundedspan > span .hovertext {
|
||||
display: none;
|
||||
}
|
||||
.roundedspan > span:hover .hovertext {
|
||||
display: inline;
|
||||
}
|
||||
.mediaborder {
|
||||
padding: 5px;
|
||||
border: solid 5px #EDF3F4;
|
||||
}
|
||||
.google_signup_div {
|
||||
padding: 14px 0;
|
||||
}
|
||||
.google_signup_div div {
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
float: left;
|
||||
padding-left: 5px;
|
||||
}
|
||||
.google_signup_div img {
|
||||
float: left;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
}
|
||||
.actionbuttons {
|
||||
width: auto;
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
background: #8dc63f;
|
||||
-moz-border-radius: 32px;
|
||||
-webkit-border-radius: 32px;
|
||||
border-radius: 32px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
padding: 0 15px;
|
||||
border: none;
|
||||
margin: 5px 0;
|
||||
}
|
||||
.errors {
|
||||
-moz-border-radius: 16px 16px 0 0;
|
||||
-webkit-border-radius: 16px 16px 0 0;
|
||||
border-radius: 16px 16px 0 0;
|
||||
border: solid #e35351 3px;
|
||||
clear: both;
|
||||
width: 90%;
|
||||
height: auto;
|
||||
line-height: 16px;
|
||||
padding: 7px 0;
|
||||
font-weight: bold;
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
}
|
||||
.errors li {
|
||||
list-style: none;
|
||||
border: none;
|
||||
}
|
||||
.download_container {
|
||||
width: 50%;
|
||||
margin: auto;
|
||||
}
|
||||
#lightbox_content a {
|
||||
color: #6994a3;
|
||||
}
|
||||
#lightbox_content .signuptoday a {
|
||||
color: white;
|
||||
}
|
||||
#lightbox_content h2,
|
||||
#lightbox_content h3,
|
||||
#lightbox_content h4 {
|
||||
margin-top: 15px;
|
||||
}
|
||||
#lightbox_content h2 a {
|
||||
font-size: 18.75px;
|
||||
}
|
||||
#lightbox_content .ebook_download a {
|
||||
margin: auto 5px auto 0;
|
||||
font-size: 15px;
|
||||
}
|
||||
#lightbox_content .ebook_download img {
|
||||
vertical-align: middle;
|
||||
}
|
||||
#lightbox_content .logo {
|
||||
font-size: 15px;
|
||||
}
|
||||
#lightbox_content .logo img {
|
||||
-moz-border-radius: 7px;
|
||||
-webkit-border-radius: 7px;
|
||||
border-radius: 7px;
|
||||
height: 50px;
|
||||
width: 50px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
#lightbox_content .unglued,
|
||||
#lightbox_content .not_unglued {
|
||||
-moz-border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
margin-left: -2px;
|
||||
padding: 5px;
|
||||
padding-bottom: 15px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
#lightbox_content .unglued h3,
|
||||
#lightbox_content .not_unglued h3 {
|
||||
margin-top: 5px;
|
||||
}
|
||||
#lightbox_content .unglued {
|
||||
border: solid 2px #8dc63f;
|
||||
}
|
||||
#lightbox_content .not_unglued {
|
||||
border: solid 2px #d6dde0;
|
||||
}
|
||||
#lightbox_content a.add-wishlist .on-wishlist,
|
||||
#lightbox_content a.success,
|
||||
a.success:hover {
|
||||
text-decoration: none;
|
||||
color: #3d4e53;
|
||||
}
|
||||
#lightbox_content a.success,
|
||||
a.success:hover {
|
||||
cursor: default;
|
||||
}
|
||||
#lightbox_content ul {
|
||||
padding-left: 50px;
|
||||
}
|
||||
#lightbox_content ul li {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.border {
|
||||
-moz-border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
border: solid 2px #d6dde0;
|
||||
margin: 5px auto;
|
||||
padding-right: 5px;
|
||||
padding-left: 5px;
|
||||
}
|
|
@ -220,6 +220,7 @@ ul.tabs li.active a {
|
|||
}
|
||||
.book-detail-info > div.layout div.btn_support {
|
||||
float: right;
|
||||
margin-top: 9px;
|
||||
}
|
||||
.book-detail-info > div.layout div.btn_support input {
|
||||
font-size: 15px;
|
||||
|
|
|
@ -19,6 +19,9 @@
|
|||
-moz-border-radius: 7px;
|
||||
-webkit-border-radius: 7px;
|
||||
border-radius: 7px;
|
||||
-moz-border-radius: 7px;
|
||||
-webkit-border-radius: 7px;
|
||||
border-radius: 7px;
|
||||
padding: 1px;
|
||||
color: #fff;
|
||||
margin: 0 8px 0 0;
|
||||
|
@ -30,6 +33,9 @@
|
|||
-moz-border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
-moz-border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
}
|
||||
|
@ -47,6 +53,8 @@
|
|||
padding: 14px 0;
|
||||
}
|
||||
.google_signup_div div {
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
float: left;
|
||||
|
@ -61,10 +69,15 @@
|
|||
width: auto;
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
background: #8dc63f;
|
||||
-moz-border-radius: 32px;
|
||||
-webkit-border-radius: 32px;
|
||||
border-radius: 32px;
|
||||
-moz-border-radius: 32px;
|
||||
-webkit-border-radius: 32px;
|
||||
border-radius: 32px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
|
@ -74,6 +87,9 @@
|
|||
margin: 5px 0;
|
||||
}
|
||||
.errors {
|
||||
-moz-border-radius: 16px 16px 0 0;
|
||||
-webkit-border-radius: 16px 16px 0 0;
|
||||
border-radius: 16px 16px 0 0;
|
||||
-moz-border-radius: 16px 16px 0 0;
|
||||
-webkit-border-radius: 16px 16px 0 0;
|
||||
border-radius: 16px 16px 0 0;
|
||||
|
@ -91,16 +107,217 @@
|
|||
list-style: none;
|
||||
border: none;
|
||||
}
|
||||
/* variables and mixins used in multiple less files go here */
|
||||
.header-text {
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
font-size: 13px;
|
||||
letter-spacing: -0.05em;
|
||||
}
|
||||
.panelborders {
|
||||
border-width: 1px 0px;
|
||||
border-style: solid none;
|
||||
border-color: #FFFFFF;
|
||||
}
|
||||
.roundedspan {
|
||||
border: 1px solid #d4d4d4;
|
||||
-moz-border-radius: 7px;
|
||||
-webkit-border-radius: 7px;
|
||||
border-radius: 7px;
|
||||
-moz-border-radius: 7px;
|
||||
-webkit-border-radius: 7px;
|
||||
border-radius: 7px;
|
||||
padding: 1px;
|
||||
color: #fff;
|
||||
margin: 0 8px 0 0;
|
||||
display: inline-block;
|
||||
}
|
||||
.roundedspan > span {
|
||||
padding: 7px 7px;
|
||||
min-width: 15px;
|
||||
-moz-border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
-moz-border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
}
|
||||
.roundedspan > span .hovertext {
|
||||
display: none;
|
||||
}
|
||||
.roundedspan > span:hover .hovertext {
|
||||
display: inline;
|
||||
}
|
||||
.mediaborder {
|
||||
padding: 5px;
|
||||
border: solid 5px #EDF3F4;
|
||||
}
|
||||
.google_signup_div {
|
||||
padding: 14px 0;
|
||||
}
|
||||
.google_signup_div div {
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
float: left;
|
||||
padding-left: 5px;
|
||||
}
|
||||
.google_signup_div img {
|
||||
float: left;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
}
|
||||
.actionbuttons {
|
||||
width: auto;
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
background: #8dc63f;
|
||||
-moz-border-radius: 32px;
|
||||
-webkit-border-radius: 32px;
|
||||
border-radius: 32px;
|
||||
-moz-border-radius: 32px;
|
||||
-webkit-border-radius: 32px;
|
||||
border-radius: 32px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
padding: 0 15px;
|
||||
border: none;
|
||||
margin: 5px 0;
|
||||
}
|
||||
.errors {
|
||||
-moz-border-radius: 16px 16px 0 0;
|
||||
-webkit-border-radius: 16px 16px 0 0;
|
||||
border-radius: 16px 16px 0 0;
|
||||
-moz-border-radius: 16px 16px 0 0;
|
||||
-webkit-border-radius: 16px 16px 0 0;
|
||||
border-radius: 16px 16px 0 0;
|
||||
border: solid #e35351 3px;
|
||||
clear: both;
|
||||
width: 90%;
|
||||
height: auto;
|
||||
line-height: 16px;
|
||||
padding: 7px 0;
|
||||
font-weight: bold;
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
}
|
||||
.errors li {
|
||||
list-style: none;
|
||||
border: none;
|
||||
}
|
||||
.download_container {
|
||||
width: 50%;
|
||||
margin: auto;
|
||||
}
|
||||
#lightbox_content a {
|
||||
color: #6994a3;
|
||||
}
|
||||
#lightbox_content .signuptoday a {
|
||||
color: white;
|
||||
}
|
||||
#lightbox_content h2,
|
||||
#lightbox_content h3,
|
||||
#lightbox_content h4 {
|
||||
margin-top: 15px;
|
||||
}
|
||||
#lightbox_content h2 a {
|
||||
font-size: 18.75px;
|
||||
}
|
||||
#lightbox_content .ebook_download a {
|
||||
margin: auto 5px auto 0;
|
||||
font-size: 15px;
|
||||
}
|
||||
#lightbox_content .ebook_download img {
|
||||
vertical-align: middle;
|
||||
}
|
||||
#lightbox_content .logo {
|
||||
font-size: 15px;
|
||||
}
|
||||
#lightbox_content .logo img {
|
||||
-moz-border-radius: 7px;
|
||||
-webkit-border-radius: 7px;
|
||||
border-radius: 7px;
|
||||
-moz-border-radius: 7px;
|
||||
-webkit-border-radius: 7px;
|
||||
border-radius: 7px;
|
||||
height: 50px;
|
||||
width: 50px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
#lightbox_content .unglued,
|
||||
#lightbox_content .not_unglued {
|
||||
-moz-border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
-moz-border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
margin-left: -2px;
|
||||
padding: 5px;
|
||||
padding-bottom: 15px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
#lightbox_content .unglued h3,
|
||||
#lightbox_content .not_unglued h3 {
|
||||
margin-top: 5px;
|
||||
}
|
||||
#lightbox_content .unglued {
|
||||
border: solid 2px #8dc63f;
|
||||
}
|
||||
#lightbox_content .not_unglued {
|
||||
border: solid 2px #d6dde0;
|
||||
}
|
||||
#lightbox_content a.add-wishlist .on-wishlist,
|
||||
#lightbox_content a.success,
|
||||
a.success:hover {
|
||||
text-decoration: none;
|
||||
color: #3d4e53;
|
||||
}
|
||||
#lightbox_content a.success,
|
||||
a.success:hover {
|
||||
cursor: default;
|
||||
}
|
||||
#lightbox_content ul {
|
||||
padding-left: 50px;
|
||||
}
|
||||
#lightbox_content ul li {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.border {
|
||||
-moz-border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
-moz-border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
border: solid 2px #d6dde0;
|
||||
margin: 5px auto;
|
||||
padding-right: 5px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
/* remove before beta */
|
||||
.preview {
|
||||
border: solid 3px #e35351;
|
||||
-moz-border-radius: 7px;
|
||||
-webkit-border-radius: 7px;
|
||||
border-radius: 7px;
|
||||
-moz-border-radius: 7px;
|
||||
-webkit-border-radius: 7px;
|
||||
border-radius: 7px;
|
||||
clear: both;
|
||||
padding: 5px 10px;
|
||||
font-size: 13px;
|
||||
line-height: 17px;
|
||||
width: 90%;
|
||||
}
|
||||
.preview a {
|
||||
color: #8dc63f;
|
||||
|
@ -110,12 +327,14 @@
|
|||
-moz-border-radius: 7px;
|
||||
-webkit-border-radius: 7px;
|
||||
border-radius: 7px;
|
||||
-moz-border-radius: 7px;
|
||||
-webkit-border-radius: 7px;
|
||||
border-radius: 7px;
|
||||
clear: both;
|
||||
padding: 5px 10px;
|
||||
font-size: 13px;
|
||||
line-height: 17px;
|
||||
border-color: #8dc63f;
|
||||
width: 90%;
|
||||
border-color: #8dc63f;
|
||||
margin: 10px auto 0 auto;
|
||||
font-size: 120%;
|
||||
}
|
||||
|
@ -130,10 +349,13 @@
|
|||
-moz-border-radius: 7px;
|
||||
-webkit-border-radius: 7px;
|
||||
border-radius: 7px;
|
||||
-moz-border-radius: 7px;
|
||||
-webkit-border-radius: 7px;
|
||||
border-radius: 7px;
|
||||
clear: both;
|
||||
padding: 5px 10px;
|
||||
font-size: 13px;
|
||||
line-height: 17px;
|
||||
width: 90%;
|
||||
width: 80%;
|
||||
margin: 10px auto;
|
||||
}
|
||||
|
@ -187,6 +409,9 @@ body {
|
|||
-moz-border-radius: 0 0 10px 10px;
|
||||
-webkit-border-radius: 0 0 10px 10px;
|
||||
border-radius: 0 0 10px 10px;
|
||||
-moz-border-radius: 0 0 10px 10px;
|
||||
-webkit-border-radius: 0 0 10px 10px;
|
||||
border-radius: 0 0 10px 10px;
|
||||
background: #8dc63f;
|
||||
margin-bottom: 0;
|
||||
text-align: center;
|
||||
|
@ -211,25 +436,48 @@ a {
|
|||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
h1 {
|
||||
font-size: 22.5px;
|
||||
}
|
||||
h2 {
|
||||
font-size: 18.75px;
|
||||
}
|
||||
h3 {
|
||||
font-size: 17.549999999999997px;
|
||||
}
|
||||
h4 {
|
||||
font-size: 15px;
|
||||
}
|
||||
img {
|
||||
border: none;
|
||||
}
|
||||
input,
|
||||
textarea {
|
||||
textarea,
|
||||
a.fakeinput {
|
||||
border: 2px solid #d6dde0;
|
||||
-moz-border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
-moz-border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
input:focus,
|
||||
textarea:focus {
|
||||
textarea:focus,
|
||||
a.fakeinput:focus {
|
||||
border: 2px solid #8dc63f;
|
||||
outline: none;
|
||||
}
|
||||
a.fakeinput:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
.js-search input {
|
||||
-moz-border-radius: 0;
|
||||
-webkit-border-radius: 0;
|
||||
border-radius: 0;
|
||||
-moz-border-radius: 0;
|
||||
-webkit-border-radius: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
h2.content-heading {
|
||||
padding: 15px;
|
||||
|
@ -244,6 +492,9 @@ h2.content-heading span {
|
|||
font-style: italic;
|
||||
}
|
||||
h3.jsmod-title {
|
||||
-moz-border-radius: 8px 8px 0 0;
|
||||
-webkit-border-radius: 8px 8px 0 0;
|
||||
border-radius: 8px 8px 0 0;
|
||||
-moz-border-radius: 8px 8px 0 0;
|
||||
-webkit-border-radius: 8px 8px 0 0;
|
||||
border-radius: 8px 8px 0 0;
|
||||
|
@ -259,7 +510,8 @@ h3.jsmod-title span {
|
|||
padding: 26px 40px 27px 20px;
|
||||
display: block;
|
||||
}
|
||||
input[type="submit"] {
|
||||
input[type="submit"],
|
||||
a.fakeinput {
|
||||
background: #8dc63f;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
|
@ -282,6 +534,9 @@ ul.menu {
|
|||
margin: 0;
|
||||
}
|
||||
.errorlist {
|
||||
-moz-border-radius: 16px 16px 0 0;
|
||||
-webkit-border-radius: 16px 16px 0 0;
|
||||
border-radius: 16px 16px 0 0;
|
||||
-moz-border-radius: 16px 16px 0 0;
|
||||
-webkit-border-radius: 16px 16px 0 0;
|
||||
border-radius: 16px 16px 0 0;
|
||||
|
@ -294,6 +549,25 @@ ul.menu {
|
|||
font-weight: bold;
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
-moz-border-radius: 16px 16px 0 0;
|
||||
-webkit-border-radius: 16px 16px 0 0;
|
||||
border-radius: 16px 16px 0 0;
|
||||
-moz-border-radius: 16px 16px 0 0;
|
||||
-webkit-border-radius: 16px 16px 0 0;
|
||||
border-radius: 16px 16px 0 0;
|
||||
border: solid #e35351 3px;
|
||||
clear: both;
|
||||
width: 90%;
|
||||
height: auto;
|
||||
line-height: 16px;
|
||||
padding: 7px 0;
|
||||
font-weight: bold;
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
}
|
||||
.errorlist li {
|
||||
list-style: none;
|
||||
border: none;
|
||||
}
|
||||
.errorlist li {
|
||||
list-style: none;
|
||||
|
@ -349,6 +623,13 @@ ul.menu {
|
|||
font-weight: bold;
|
||||
font-size: 13px;
|
||||
letter-spacing: -0.05em;
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
font-size: 13px;
|
||||
letter-spacing: -0.05em;
|
||||
}
|
||||
.js-topmenu ul li span#welcome {
|
||||
color: #8dc63f;
|
||||
|
@ -359,6 +640,13 @@ ul.menu {
|
|||
font-weight: bold;
|
||||
font-size: 13px;
|
||||
letter-spacing: -0.05em;
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
font-size: 13px;
|
||||
letter-spacing: -0.05em;
|
||||
overflow: auto;
|
||||
max-width: 240px;
|
||||
}
|
||||
|
@ -367,6 +655,9 @@ ul.menu {
|
|||
-moz-border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
-moz-border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
padding: 3px;
|
||||
line-height: 16px;
|
||||
width: 16px;
|
||||
|
@ -388,6 +679,9 @@ ul.menu {
|
|||
background: url("/static/images/bg.png") right top no-repeat;
|
||||
}
|
||||
.js-topmenu ul li.last a span {
|
||||
-moz-border-radius: 32px 0 0 32px;
|
||||
-webkit-border-radius: 32px 0 0 32px;
|
||||
border-radius: 32px 0 0 32px;
|
||||
-moz-border-radius: 32px 0 0 32px;
|
||||
-webkit-border-radius: 32px 0 0 32px;
|
||||
border-radius: 32px 0 0 32px;
|
||||
|
@ -415,9 +709,14 @@ ul.menu {
|
|||
-moz-border-radius: 50px 0 0 50px;
|
||||
-webkit-border-radius: 50px 0 0 50px;
|
||||
border-radius: 50px 0 0 50px;
|
||||
-moz-border-radius: 50px 0 0 50px;
|
||||
-webkit-border-radius: 50px 0 0 50px;
|
||||
border-radius: 50px 0 0 50px;
|
||||
outline: none;
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
width: 156px;
|
||||
float: left;
|
||||
color: #6994a3;
|
||||
|
@ -464,6 +763,9 @@ a#readon.down {
|
|||
background: url("/static/images/learnmore-uparrow.png") right center no-repeat;
|
||||
}
|
||||
a#readon span {
|
||||
-moz-border-radius: 32px 0 0 32px;
|
||||
-webkit-border-radius: 32px 0 0 32px;
|
||||
border-radius: 32px 0 0 32px;
|
||||
-moz-border-radius: 32px 0 0 32px;
|
||||
-webkit-border-radius: 32px 0 0 32px;
|
||||
border-radius: 32px 0 0 32px;
|
||||
|
@ -472,6 +774,8 @@ a#readon span {
|
|||
padding: 0 5px 0 20px;
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
display: block;
|
||||
}
|
||||
.spread_the_word {
|
||||
|
@ -497,6 +801,9 @@ a#readon span {
|
|||
-moz-border-radius: 0 0 10px 10px;
|
||||
-webkit-border-radius: 0 0 10px 10px;
|
||||
border-radius: 0 0 10px 10px;
|
||||
-moz-border-radius: 0 0 10px 10px;
|
||||
-webkit-border-radius: 0 0 10px 10px;
|
||||
border-radius: 0 0 10px 10px;
|
||||
}
|
||||
#js-leftcol ul.level1 > li > a,
|
||||
#js-leftcol ul.level1 > li > span {
|
||||
|
@ -520,10 +827,15 @@ a#readon span {
|
|||
-moz-border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
-moz-border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
#js-leftcol ul.level2 li .ungluer-name {
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
}
|
||||
/* Main content area: top */
|
||||
#js-topsection {
|
||||
|
@ -647,6 +959,9 @@ a.nounderline {
|
|||
-moz-border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
-moz-border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
border: solid 5px #d6dde0;
|
||||
background: white;
|
||||
z-index: 500;
|
||||
|
@ -657,44 +972,61 @@ a.nounderline {
|
|||
margin-left: 0;
|
||||
padding: 9px;
|
||||
}
|
||||
#about_expandable p {
|
||||
#about_expandable .collapser_x {
|
||||
margin-top: -27px;
|
||||
margin-right: -27px;
|
||||
}
|
||||
#lightbox_content p {
|
||||
padding: 9px 0;
|
||||
font-size: 15px;
|
||||
line-height: 20px;
|
||||
}
|
||||
#about_expandable p a {
|
||||
#lightbox_content p a {
|
||||
font-size: 15px;
|
||||
line-height: 20px;
|
||||
}
|
||||
#about_expandable p b {
|
||||
#lightbox_content p b {
|
||||
color: #8dc63f;
|
||||
}
|
||||
#about_expandable p.last {
|
||||
#lightbox_content p.last {
|
||||
border-bottom: solid 2px #d6dde0;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
#about_expandable .right_border {
|
||||
#lightbox_content .right_border {
|
||||
border-right: solid 1px #d6dde0;
|
||||
float: left;
|
||||
padding: 9px;
|
||||
}
|
||||
#about_expandable .signuptoday {
|
||||
#lightbox_content .signuptoday {
|
||||
float: right;
|
||||
margin-top: 0;
|
||||
clear: none;
|
||||
}
|
||||
#about_expandable .collapser_x {
|
||||
margin-top: -27px;
|
||||
margin-right: -27px;
|
||||
.nonlightbox .about_page {
|
||||
-moz-border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
-moz-border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
border: solid 5px #d6dde0;
|
||||
width: 75%;
|
||||
margin: 10px auto auto auto;
|
||||
padding: 9px;
|
||||
}
|
||||
.collapser_x {
|
||||
float: right;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
width: 24px;
|
||||
-moz-border-radius: 24px;
|
||||
-webkit-border-radius: 24px;
|
||||
border-radius: 24px;
|
||||
-moz-border-radius: 24px;
|
||||
-webkit-border-radius: 24px;
|
||||
border-radius: 24px;
|
||||
-moz-box-shadow: -1px 1px #3d4e53;
|
||||
-webkit-box-shadow: -1px 1px #3d4e53;
|
||||
box-shadow: -1px 1px #3d4e53;
|
||||
|
@ -708,6 +1040,9 @@ a.nounderline {
|
|||
margin-right: -22px;
|
||||
}
|
||||
.signuptoday {
|
||||
-moz-border-radius: 32px;
|
||||
-webkit-border-radius: 32px;
|
||||
border-radius: 32px;
|
||||
-moz-border-radius: 32px;
|
||||
-webkit-border-radius: 32px;
|
||||
border-radius: 32px;
|
||||
|
@ -715,6 +1050,8 @@ a.nounderline {
|
|||
padding: 0 15px;
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
float: left;
|
||||
clear: both;
|
||||
margin: 10px auto;
|
||||
|
|
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 6.4 KiB |
After Width: | Height: | Size: 12 KiB |
|
@ -1,45 +0,0 @@
|
|||
var $j = jQuery.noConflict();
|
||||
|
||||
$j(document).ready(function(){
|
||||
$j('.about_expander').click(function(){
|
||||
// decide which about content to show
|
||||
var whichbox = $j(this).attr('id');
|
||||
|
||||
// if we're on a supporter page, personalize our about box
|
||||
// by writing the supporter's name in
|
||||
if ($j(location).attr('pathname').slice(0,11) == '/supporter/') {
|
||||
var ungluer = $j(location).attr('pathname').slice(11, -1);
|
||||
}
|
||||
|
||||
if (ungluer != null) {
|
||||
$j('#lightbox').load('/static/html/'+whichbox+'.html');
|
||||
|
||||
// span.ungluer doesn't exist until the ajax call so we
|
||||
// can't bind to the DOM on document ready; need to use
|
||||
// the ajaxComplete event
|
||||
$j('#lightbox').ajaxComplete(function() {
|
||||
$j('#lightbox span.ungluer').replaceWith(ungluer);
|
||||
});
|
||||
} else {
|
||||
$j('#lightbox').load('/static/html/'+whichbox+'.html');
|
||||
}
|
||||
|
||||
// fade-out (fade-in) rest of page elements on expand (collapse)
|
||||
$j('#js-topsection').css({"opacity": "0.07"});
|
||||
$j('.launch_top').css({"opacity": "0.07"});
|
||||
$j('#main-container').css({"opacity": "0.07"});
|
||||
$j('#js-rightcol').css({"opacity": "0.07"});
|
||||
$j('#js-header').css({"opacity": "0.07"});
|
||||
$j('#js-header a').css({"cursor": "default"});
|
||||
$j('#about_expandable').fadeTo("slow", 1);
|
||||
});
|
||||
$j('#about_collapser').click(function(){
|
||||
$j('#js-topsection').fadeTo("slow", 1);
|
||||
$j('.launch_top').fadeTo("slow", 1);
|
||||
$j('#main-container').fadeTo("slow", 1);
|
||||
$j('#js-rightcol').fadeTo("slow", 1);
|
||||
$j('#js-header').fadeTo("slow", 1);
|
||||
$j('#js-header a').css({"cursor": "pointer"});
|
||||
$j('#about_expandable').css({"display": "none"});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,53 @@
|
|||
// hijack a link with class "hijax" to show its content in a lightbox instead
|
||||
// allows for ajaxy presentation of things like download links in a way that
|
||||
// degrades gracefully for non-js users
|
||||
var $j = jQuery.noConflict();
|
||||
|
||||
$j(document).ready(function() {
|
||||
$j("a.hijax").click(function(event) {
|
||||
event.preventDefault();
|
||||
$j("#lightbox").load( $j(this).attr("href") + " #lightbox_content");
|
||||
|
||||
// fade-out rest of page elements on expand
|
||||
$j('#js-topsection').css({"opacity": "0.07"});
|
||||
$j('.launch_top').css({"opacity": "0.07"});
|
||||
$j('.preview').css({"opacity": "0.07"});
|
||||
$j('#main-container').css({"opacity": "0.07"});
|
||||
$j('#js-rightcol').css({"opacity": "0.07"});
|
||||
$j('#js-header').css({"opacity": "0.07"});
|
||||
$j('#js-header a').css({"cursor": "default"});
|
||||
$j('#js-page-wrap').css({"opacity": "0.07"});
|
||||
$j('#footer').css({"opacity": "0.07"});
|
||||
$j('#about_expandable').fadeTo("slow", 1);
|
||||
|
||||
// if we're on a supporter page, personalize our about box
|
||||
// by writing the supporter's name in
|
||||
if ($j(location).attr('pathname').slice(0,11) == '/supporter/') {
|
||||
var ungluer = $j(location).attr('pathname').slice(11, -1);
|
||||
|
||||
if (ungluer != null) {
|
||||
// span.ungluer doesn't exist until the ajax call so we
|
||||
// can't bind to the DOM on document ready; need to use
|
||||
// the ajaxComplete event
|
||||
$j('#lightbox').ajaxComplete(function() {
|
||||
$j('#lightbox span.ungluer').replaceWith(ungluer);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// fade-in normal page elements on collapse
|
||||
$j('#about_collapser').click(function(){
|
||||
$j('#js-topsection').fadeTo("slow", 1);
|
||||
$j('.launch_top').fadeTo("slow", 1);
|
||||
$j('.preview').fadeTo("slow", 1);
|
||||
$j('#main-container').fadeTo("slow", 1);
|
||||
$j('#js-rightcol').fadeTo("slow", 1);
|
||||
$j('#js-header').fadeTo("slow", 1);
|
||||
$j('#js-header a').css({"cursor": "pointer"});
|
||||
$j('#js-page-wrap').fadeTo("slow", 1);
|
||||
$j('#footer').fadeTo("slow", 1);
|
||||
$j('#about_expandable').css({"display": "none"});
|
||||
});
|
||||
|
||||
});
|
|
@ -4,7 +4,7 @@ $j().ready(function() {
|
|||
// only do the lookup once, then cache it
|
||||
var contentblock = $j('#content-block');
|
||||
|
||||
contentblock.on("click", "div.add-wishlist", function () {
|
||||
contentblock.on("click", ".add-wishlist", function () {
|
||||
var span = $j(this).find("span");
|
||||
var id_val = span.attr('id').substring(1);
|
||||
var id_type = span.attr('class');
|
||||
|
@ -25,6 +25,11 @@ $j().ready(function() {
|
|||
else {
|
||||
span.html('a type error occurred');
|
||||
}
|
||||
|
||||
// prevent perversities on download page
|
||||
if ($j(this).is("a")) {
|
||||
$j(this).removeClass("add-wishlist").addClass("success");
|
||||
}
|
||||
});
|
||||
|
||||
contentblock.on("click", "div.remove-wishlist", function() {
|
||||
|
@ -50,7 +55,7 @@ $j().ready(function() {
|
|||
// we're going to have to tell /wishlist/ that we're feeding it a different identifier
|
||||
contentblock.on("click", "div.remove-wishlist-workpage", function () {
|
||||
var span = $j(this).find("span");
|
||||
var work_id = span.attr('id').substring(1)
|
||||
var work_id = span.attr('id').substring(1);
|
||||
|
||||
// provide feedback
|
||||
span.html('Removing...');
|
||||
|
@ -65,3 +70,19 @@ $j().ready(function() {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
var $k = jQuery.noConflict();
|
||||
// allows user to re-add on work page after erroneously removing, without page reload
|
||||
// can't bind this to document ready because the .add-wishlist-workpage div doesn't exist until remove-wishlist is executed
|
||||
$k(document).on("click", ".add-wishlist-workpage span", function() {
|
||||
var span = $k(this);
|
||||
var work_id = span.attr("class");
|
||||
if (!work_id) return;
|
||||
jQuery.post('/wishlist/', {'add_work_id': work_id}, function(data) {
|
||||
span.fadeOut();
|
||||
var newSpan = $k('<span class="on-wishlist">On Wishlist!</span>').hide();
|
||||
span.replaceWith(newSpan);
|
||||
newSpan.fadeIn('slow');
|
||||
newSpan.removeAttr("id");
|
||||
});
|
||||
});
|
|
@ -62,6 +62,7 @@
|
|||
|
||||
div.btn_support {
|
||||
float: right;
|
||||
margin-top: 9px;
|
||||
|
||||
input {
|
||||
font-size: @font-size-larger;
|
||||
|
|
|
@ -184,6 +184,11 @@ div#content-block-content {
|
|||
form {
|
||||
margin-left: -5px;
|
||||
}
|
||||
|
||||
.clearfix {
|
||||
margin-bottom: 10px;
|
||||
border-bottom: 2px solid @blue-grey;
|
||||
}
|
||||
}
|
||||
|
||||
.work_supporter {
|
||||
|
@ -222,11 +227,10 @@ div#content-block-content {
|
|||
}
|
||||
|
||||
.editions {
|
||||
clear: both;
|
||||
|
||||
div {
|
||||
float:left;
|
||||
padding-bottom: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.image {
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
@import "variables.less";
|
||||
|
||||
.download_container {
|
||||
width: 50%;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
#lightbox_content a {
|
||||
color: @medium-blue;
|
||||
|
||||
}
|
||||
|
||||
#lightbox_content .signuptoday a {
|
||||
color: white;
|
||||
}
|
||||
|
||||
#lightbox_content h2, #lightbox_content h3, #lightbox_content h4 {
|
||||
margin-top: 15px;
|
||||
|
||||
}
|
||||
|
||||
#lightbox_content h2 a {
|
||||
font-size: @font-size-larger*1.25;
|
||||
}
|
||||
|
||||
#lightbox_content .ebook_download {
|
||||
a {
|
||||
margin: auto 5px auto 0;
|
||||
font-size: @font-size-larger;
|
||||
}
|
||||
|
||||
img {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
#lightbox_content .logo {
|
||||
img {
|
||||
.one-border-radius(7px);
|
||||
height: 50px;
|
||||
width: 50px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
font-size: @font-size-larger;
|
||||
}
|
||||
|
||||
#lightbox_content .unglued, #lightbox_content .not_unglued {
|
||||
.one-border-radius(5px);
|
||||
margin-left: -2px;
|
||||
padding: 5px;
|
||||
padding-bottom: 15px;
|
||||
margin-bottom: 5px;
|
||||
|
||||
h3 {
|
||||
margin-top: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
#lightbox_content .unglued {
|
||||
border: solid 2px @call-to-action;
|
||||
}
|
||||
|
||||
#lightbox_content .not_unglued {
|
||||
border: solid 2px @blue-grey;
|
||||
}
|
||||
|
||||
#lightbox_content a.add-wishlist .on-wishlist, #lightbox_content a.success, a.success:hover {
|
||||
text-decoration: none;
|
||||
color: @text-blue;
|
||||
}
|
||||
|
||||
#lightbox_content a.success, a.success:hover {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
#lightbox_content ul {
|
||||
padding-left: 50px;
|
||||
|
||||
li {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.border {
|
||||
.one-border-radius(5px);
|
||||
border: solid 2px @blue-grey;
|
||||
margin: 5px auto;
|
||||
padding-right: 5px;
|
||||
padding-left: 5px;
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
/* For sitewide elements of unglue.it. */
|
||||
|
||||
@import "variables.less";
|
||||
@import "download.less";
|
||||
|
||||
/* remove before beta */
|
||||
.preview {
|
||||
|
@ -9,8 +10,7 @@
|
|||
clear:both;
|
||||
padding: 5px 10px;
|
||||
font-size: @font-size-default;
|
||||
|
||||
line-height: 17px;
|
||||
width: 90%;
|
||||
|
||||
a {
|
||||
color: @call-to-action;
|
||||
|
@ -20,7 +20,6 @@
|
|||
.launch_top {
|
||||
.preview;
|
||||
border-color: @green;
|
||||
width: 90%;
|
||||
margin:10px auto 0 auto;
|
||||
font-size: 120%;
|
||||
|
||||
|
@ -118,11 +117,27 @@ a {
|
|||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: @font-size-larger*1.5;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: @font-size-larger*1.25;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: @font-size-larger*1.17;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: @font-size-larger;
|
||||
}
|
||||
|
||||
img {
|
||||
border:none;
|
||||
}
|
||||
|
||||
input, textarea {
|
||||
input, textarea, a.fakeinput {
|
||||
border: 2px solid @blue-grey;
|
||||
.one-border-radius(5px);
|
||||
|
||||
|
@ -132,6 +147,10 @@ input, textarea {
|
|||
}
|
||||
}
|
||||
|
||||
a.fakeinput:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.js-search input {
|
||||
.one-border-radius(0);
|
||||
}
|
||||
|
@ -170,7 +189,7 @@ h3 {
|
|||
}
|
||||
}
|
||||
|
||||
input[type="submit"] {
|
||||
input[type="submit"], a.fakeinput {
|
||||
background: @call-to-action;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
|
@ -585,6 +604,13 @@ a.nounderline {
|
|||
margin-left: 0;
|
||||
padding: 9px;
|
||||
|
||||
.collapser_x {
|
||||
margin-top: -27px;
|
||||
margin-right: -27px;
|
||||
}
|
||||
}
|
||||
|
||||
#lightbox_content {
|
||||
p {
|
||||
padding: 9px 0;
|
||||
font-size: @font-size-larger;
|
||||
|
@ -617,10 +643,15 @@ a.nounderline {
|
|||
clear: none;
|
||||
}
|
||||
|
||||
.collapser_x {
|
||||
margin-top: -27px;
|
||||
margin-right: -27px;
|
||||
}
|
||||
}
|
||||
|
||||
// need to style /about/X pages such that they will not conflict with lightboxed About from header
|
||||
.nonlightbox .about_page {
|
||||
.one-border-radius(5px);
|
||||
border: solid 5px @blue-grey;
|
||||
width: 75%;
|
||||
margin: 10px auto auto auto;
|
||||
padding: 9px;
|
||||
}
|
||||
|
||||
.collapser_x {
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
payment, .payment {
|
||||
position: relative;
|
||||
display: block;
|
||||
|
||||
padding: 15px 20px;
|
||||
max-width: 300px;
|
||||
overflow: hidden;
|
||||
|
||||
box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
-ms-sizing: border-box;
|
||||
}
|
||||
|
||||
payment label, .payment label {
|
||||
display: block;
|
||||
padding: 5px 0;
|
||||
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
payment input, .payment input {
|
||||
padding: 5px 5px;
|
||||
|
||||
box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
-ms-sizing: border-box;
|
||||
}
|
||||
|
||||
payment .number input, .payment .number input {
|
||||
padding: 7px 40px 7px 7px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
payment .expiry input, payment .cvc input {
|
||||
width: 45px;
|
||||
}
|
||||
|
||||
payment .expiry em {
|
||||
display: none;
|
||||
}
|
||||
|
||||
payment .cvc,
|
||||
.payment .cvc {
|
||||
float: right;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
payment .expiry,
|
||||
.payment .expiry {
|
||||
float: left;
|
||||
}
|
||||
|
||||
payment .message,
|
||||
.payment .message {
|
||||
display: block;
|
||||
}
|
|
@ -0,0 +1,405 @@
|
|||
(function() {
|
||||
var $, global, script,
|
||||
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
|
||||
__slice = [].slice;
|
||||
|
||||
$ = this.jQuery || this.Zepto;
|
||||
|
||||
if (!$) {
|
||||
throw 'jQuery/Zepto required';
|
||||
}
|
||||
|
||||
this.PaymentTag = (function() {
|
||||
|
||||
PaymentTag.replaceTags = function(element) {
|
||||
var _this = this;
|
||||
if (element == null) {
|
||||
element = document.body;
|
||||
}
|
||||
return $('payment, .payment-tag', element).each(function(i, tag) {
|
||||
return new _this({
|
||||
el: tag
|
||||
}).render();
|
||||
});
|
||||
};
|
||||
|
||||
PaymentTag.prototype.defaults = {
|
||||
tokenName: 'stripe_token',
|
||||
token: true,
|
||||
cvc: true
|
||||
};
|
||||
|
||||
function PaymentTag(options) {
|
||||
var _ref, _ref1;
|
||||
if (options == null) {
|
||||
options = {};
|
||||
}
|
||||
this.changeCardType = __bind(this.changeCardType, this);
|
||||
|
||||
this.restrictNumeric = __bind(this.restrictNumeric, this);
|
||||
|
||||
this.formatNumber = __bind(this.formatNumber, this);
|
||||
|
||||
this.handleToken = __bind(this.handleToken, this);
|
||||
|
||||
this.submit = __bind(this.submit, this);
|
||||
|
||||
this.$el = options.el || '<payment />';
|
||||
this.$el = $(this.$el);
|
||||
options.key || (options.key = this.$el.attr('key') || this.$el.attr('data-key'));
|
||||
if ((_ref = options.cvc) == null) {
|
||||
options.cvc = !((this.$el.attr('nocvc') != null) || (this.$el.attr('data-nocvc') != null));
|
||||
}
|
||||
if ((_ref1 = options.token) == null) {
|
||||
options.token = !((this.$el.attr('notoken') != null) || (this.$el.attr('data-notoken') != null));
|
||||
}
|
||||
options.form || (options.form = this.$el.parents('form'));
|
||||
this.options = $.extend({}, this.defaults, options);
|
||||
if (this.options.key) {
|
||||
this.setKey(this.options.key);
|
||||
}
|
||||
this.setForm(this.options.form);
|
||||
this.$el.delegate('.number input', 'keydown', this.formatNumber);
|
||||
this.$el.delegate('.number input', 'keyup', this.changeCardType);
|
||||
this.$el.delegate('input[type=tel]', 'keypress', this.restrictNumeric);
|
||||
}
|
||||
|
||||
PaymentTag.prototype.render = function() {
|
||||
this.$el.html(this.constructor.view(this));
|
||||
this.$number = this.$('.number input');
|
||||
this.$cvc = this.$('.cvc input');
|
||||
this.$expiryMonth = this.$('.expiry input.expiryMonth');
|
||||
this.$expiryYear = this.$('.expiry input.expiryYear');
|
||||
this.$message = this.$('.message');
|
||||
return this;
|
||||
};
|
||||
|
||||
PaymentTag.prototype.renderToken = function(token) {
|
||||
this.$token = $('<input type="hidden">');
|
||||
this.$token.attr('name', this.options.tokenName);
|
||||
this.$token.val(token);
|
||||
return this.$el.html(this.$token);
|
||||
};
|
||||
|
||||
PaymentTag.prototype.setForm = function($form) {
|
||||
this.$form = $($form);
|
||||
return this.$form.bind('submit.payment', this.submit);
|
||||
};
|
||||
|
||||
PaymentTag.prototype.setKey = function(key) {
|
||||
this.key = key;
|
||||
return Stripe.setPublishableKey(this.key);
|
||||
};
|
||||
|
||||
PaymentTag.prototype.validate = function() {
|
||||
var expiry, valid;
|
||||
valid = true;
|
||||
this.$('div').removeClass('invalid');
|
||||
this.$message.empty();
|
||||
if (!Stripe.validateCardNumber(this.$number.val())) {
|
||||
valid = false;
|
||||
this.handleError({
|
||||
code: 'invalid_number'
|
||||
});
|
||||
}
|
||||
expiry = this.expiryVal();
|
||||
if (!Stripe.validateExpiry(expiry.month, expiry.year)) {
|
||||
valid = false;
|
||||
this.handleError({
|
||||
code: 'expired_card'
|
||||
});
|
||||
}
|
||||
if (this.options.cvc && !Stripe.validateCVC(this.$cvc.val())) {
|
||||
valid = false;
|
||||
this.handleError({
|
||||
code: 'invalid_cvc'
|
||||
});
|
||||
}
|
||||
if (!valid) {
|
||||
this.$('.invalid input:first').select();
|
||||
}
|
||||
return valid;
|
||||
};
|
||||
|
||||
PaymentTag.prototype.createToken = function(callback) {
|
||||
var complete, expiry,
|
||||
_this = this;
|
||||
complete = function(status, response) {
|
||||
if (response.error) {
|
||||
return callback(response.error);
|
||||
} else {
|
||||
return callback(null, response);
|
||||
}
|
||||
};
|
||||
expiry = this.expiryVal();
|
||||
return Stripe.createToken({
|
||||
number: this.$number.val(),
|
||||
cvc: this.$cvc.val() || null,
|
||||
exp_month: expiry.month,
|
||||
exp_year: expiry.year
|
||||
}, complete);
|
||||
};
|
||||
|
||||
PaymentTag.prototype.submit = function(e) {
|
||||
if (e != null) {
|
||||
e.preventDefault();
|
||||
}
|
||||
if (e != null) {
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
if (!this.validate()) {
|
||||
return;
|
||||
}
|
||||
if (this.pending) {
|
||||
return;
|
||||
}
|
||||
this.pending = true;
|
||||
this.disableInputs();
|
||||
this.trigger('pending');
|
||||
this.$el.addClass('pending');
|
||||
return this.createToken(this.handleToken);
|
||||
};
|
||||
|
||||
PaymentTag.prototype.handleToken = function(err, response) {
|
||||
this.enableInputs();
|
||||
this.trigger('complete');
|
||||
this.$el.removeClass('pending');
|
||||
this.pending = false;
|
||||
if (err) {
|
||||
return this.handleError(err);
|
||||
} else {
|
||||
this.trigger('success', response);
|
||||
this.$el.addClass('success');
|
||||
if (this.options.token) {
|
||||
this.renderToken(response.id);
|
||||
}
|
||||
this.$form.unbind('submit.payment', this.submit);
|
||||
return this.$form.submit();
|
||||
}
|
||||
};
|
||||
|
||||
PaymentTag.prototype.formatNumber = function(e) {
|
||||
var digit, lastDigits, value;
|
||||
digit = String.fromCharCode(e.which);
|
||||
if (!/^\d+$/.test(digit)) {
|
||||
return;
|
||||
}
|
||||
value = this.$number.val();
|
||||
if (Stripe.cardType(value) === 'American Express') {
|
||||
lastDigits = value.match(/^(\d{4}|\d{4}\s\d{6})$/);
|
||||
} else {
|
||||
lastDigits = value.match(/(?:^|\s)(\d{4})$/);
|
||||
}
|
||||
if (lastDigits) {
|
||||
return this.$number.val(value + ' ');
|
||||
}
|
||||
};
|
||||
|
||||
PaymentTag.prototype.restrictNumeric = function(e) {
|
||||
var char;
|
||||
if (e.shiftKey || e.metaKey) {
|
||||
return true;
|
||||
}
|
||||
if (e.which === 0) {
|
||||
return true;
|
||||
}
|
||||
char = String.fromCharCode(e.which);
|
||||
return !/[A-Za-z]/.test(char);
|
||||
};
|
||||
|
||||
PaymentTag.prototype.cardTypes = {
|
||||
'Visa': 'visa',
|
||||
'American Express': 'amex',
|
||||
'MasterCard': 'mastercard',
|
||||
'Discover': 'discover',
|
||||
'Unknown': 'unknown'
|
||||
};
|
||||
|
||||
PaymentTag.prototype.changeCardType = function(e) {
|
||||
var map, name, type, _ref;
|
||||
type = Stripe.cardType(this.$number.val());
|
||||
if (!this.$number.hasClass(type)) {
|
||||
_ref = this.cardTypes;
|
||||
for (name in _ref) {
|
||||
map = _ref[name];
|
||||
this.$number.removeClass(map);
|
||||
}
|
||||
return this.$number.addClass(this.cardTypes[type]);
|
||||
}
|
||||
};
|
||||
|
||||
PaymentTag.prototype.handleError = function(err) {
|
||||
if (err.message) {
|
||||
this.$message.text(err.message);
|
||||
}
|
||||
switch (err.code) {
|
||||
case 'card_declined':
|
||||
this.invalidInput(this.$number);
|
||||
break;
|
||||
case 'invalid_number':
|
||||
case 'incorrect_number':
|
||||
this.invalidInput(this.$number);
|
||||
break;
|
||||
case 'invalid_expiry_month':
|
||||
this.invalidInput(this.$expiryMonth);
|
||||
break;
|
||||
case 'invalid_expiry_year':
|
||||
case 'expired_card':
|
||||
this.invalidInput(this.$expiryYear);
|
||||
break;
|
||||
case 'invalid_cvc':
|
||||
this.invalidInput(this.$cvc);
|
||||
}
|
||||
this.$('label.invalid:first input').select();
|
||||
this.trigger('error', err);
|
||||
return typeof console !== "undefined" && console !== null ? console.error('Stripe error:', err) : void 0;
|
||||
};
|
||||
|
||||
PaymentTag.prototype.invalidInput = function(input) {
|
||||
input.parent().addClass('invalid');
|
||||
return this.trigger('invalid', [input.attr('name'), input]);
|
||||
};
|
||||
|
||||
PaymentTag.prototype.expiryVal = function() {
|
||||
var month, prefix, trim, year;
|
||||
trim = function(s) {
|
||||
return s.replace(/^\s+|\s+$/g, '');
|
||||
};
|
||||
month = trim(this.$expiryMonth.val());
|
||||
year = trim(this.$expiryYear.val());
|
||||
if (year.length === 2) {
|
||||
prefix = (new Date).getFullYear();
|
||||
prefix = prefix.toString().slice(0, 2);
|
||||
year = prefix + year;
|
||||
}
|
||||
return {
|
||||
month: month,
|
||||
year: year
|
||||
};
|
||||
};
|
||||
|
||||
PaymentTag.prototype.enableInputs = function() {
|
||||
var $elements;
|
||||
$elements = this.$el.add(this.$form).find(':input');
|
||||
return $elements.each(function() {
|
||||
var $item, _ref;
|
||||
$item = $(this);
|
||||
return $elements.attr('disabled', (_ref = $item.data('olddisabled')) != null ? _ref : false);
|
||||
});
|
||||
};
|
||||
|
||||
PaymentTag.prototype.disableInputs = function() {
|
||||
var $elements;
|
||||
$elements = this.$el.add(this.$form).find(':input');
|
||||
return $elements.each(function() {
|
||||
var $item;
|
||||
$item = $(this);
|
||||
$item.data('olddisabled', $item.attr('disabled'));
|
||||
return $item.attr('disabled', true);
|
||||
});
|
||||
};
|
||||
|
||||
PaymentTag.prototype.trigger = function() {
|
||||
var data, event, _ref;
|
||||
event = arguments[0], data = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
|
||||
return (_ref = this.$el).trigger.apply(_ref, ["" + event + ".payment"].concat(__slice.call(data)));
|
||||
};
|
||||
|
||||
PaymentTag.prototype.$ = function(sel) {
|
||||
return $(sel, this.$el);
|
||||
};
|
||||
|
||||
return PaymentTag;
|
||||
|
||||
})();
|
||||
|
||||
document.createElement('payment');
|
||||
|
||||
if (typeof module !== "undefined" && module !== null) {
|
||||
module.exports = PaymentTag;
|
||||
}
|
||||
|
||||
global = this;
|
||||
|
||||
if (global.Stripe) {
|
||||
$(function() {
|
||||
return typeof PaymentTag.replaceTags === "function" ? PaymentTag.replaceTags() : void 0;
|
||||
});
|
||||
} else {
|
||||
script = document.createElement('script');
|
||||
script.onload = script.onreadystatechange = function() {
|
||||
if (!global.Stripe) {
|
||||
return;
|
||||
}
|
||||
if (script.done) {
|
||||
return;
|
||||
}
|
||||
script.done = true;
|
||||
return typeof PaymentTag.replaceTags === "function" ? PaymentTag.replaceTags() : void 0;
|
||||
};
|
||||
script.src = 'https://js.stripe.com/v1/';
|
||||
$(function() {
|
||||
var sibling;
|
||||
sibling = document.getElementsByTagName('script')[0];
|
||||
return sibling != null ? sibling.parentNode.insertBefore(script, sibling) : void 0;
|
||||
});
|
||||
}
|
||||
|
||||
}).call(this);
|
||||
(function() {
|
||||
this.PaymentTag || (this.PaymentTag = {});
|
||||
this.PaymentTag["view"] = function(__obj) {
|
||||
if (!__obj) __obj = {};
|
||||
var __out = [], __capture = function(callback) {
|
||||
var out = __out, result;
|
||||
__out = [];
|
||||
callback.call(this);
|
||||
result = __out.join('');
|
||||
__out = out;
|
||||
return __safe(result);
|
||||
}, __sanitize = function(value) {
|
||||
if (value && value.ecoSafe) {
|
||||
return value;
|
||||
} else if (typeof value !== 'undefined' && value != null) {
|
||||
return __escape(value);
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}, __safe, __objSafe = __obj.safe, __escape = __obj.escape;
|
||||
__safe = __obj.safe = function(value) {
|
||||
if (value && value.ecoSafe) {
|
||||
return value;
|
||||
} else {
|
||||
if (!(typeof value !== 'undefined' && value != null)) value = '';
|
||||
var result = new String(value);
|
||||
result.ecoSafe = true;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
if (!__escape) {
|
||||
__escape = __obj.escape = function(value) {
|
||||
return ('' + value)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
};
|
||||
}
|
||||
(function() {
|
||||
(function() {
|
||||
|
||||
__out.push('<span class="message"></span>\n\n<div class="number">\n <label for="paymentNumber">Card number</label>\n\n <input type="tel" id="paymentNumber" placeholder="4242 4242 4242 4242" autofocus required>\n</div>\n\n<div class="expiry">\n <label for="paymentExpiryMonth">Expiry date <em>(mm/yy)</em></label>\n\n <input class="expiryMonth" type="tel" id="paymentExpiryMonth" placeholder="mm" required>\n <input class="expiryYear" type="tel" id="paymentExpiryYear" placeholder="yy" required>\n</div>\n\n');
|
||||
|
||||
if (this.options.cvc) {
|
||||
__out.push('\n <div class="cvc">\n <label for="paymentCVC">Security code</label>\n <input type="tel" id="paymentCVC" placeholder="123" maxlength="4" required>\n </div>\n');
|
||||
}
|
||||
|
||||
__out.push('\n');
|
||||
|
||||
}).call(this);
|
||||
|
||||
}).call(__obj);
|
||||
__obj.safe = __objSafe, __obj.escape = __escape;
|
||||
return __out.join('');
|
||||
};
|
||||
}).call(this);
|
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 5.3 KiB |
|
@ -0,0 +1,152 @@
|
|||
payment, .payment {
|
||||
position: relative;
|
||||
display: block;
|
||||
|
||||
border-radius: 5px;
|
||||
padding: 15px 20px;
|
||||
max-width: 300px;
|
||||
overflow: hidden;
|
||||
|
||||
box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
-ms-sizing: border-box;
|
||||
|
||||
font-size: 12px;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-family: 'Helvetica Neue', Helvetica, Arial Geneva, sans-serif;
|
||||
|
||||
background: #FFF;
|
||||
background-image: -o-linear-gradient(#FFF, #F9FAFA);
|
||||
background-image: -ms-linear-gradient(#FFF, #F9FAFA);
|
||||
background-image: -moz-linear-gradient(#FFF, #F9FAFA);
|
||||
background-image: -webkit-linear-gradient(#FEFEFE, #F9FAFA);
|
||||
background-image: linear-gradient(#FFF, #F9FAFA);
|
||||
|
||||
-moz-box-shadow: 0 0 2px rgba(80,84,92,0.3), 0 1px 1px rgba(80,84,92,0.5);
|
||||
-webkit-box-shadow: 0 0 2px rgba(80, 84, 92, 0.3), 0 1px 1px rgba(80, 84, 92, 0.5);
|
||||
-ms-box-shadow: 0 0 2px rgba(80, 84, 92, 0.3), 0 1px 1px rgba(80, 84, 92, 0.5);
|
||||
box-shadow: 0 0 2px rgba(80, 84, 92, 0.3), 0 1px 1px rgba(80, 84, 92, 0.5);
|
||||
}
|
||||
|
||||
payment ::-webkit-input-placeholder,
|
||||
.payment ::-webkit-input-placeholder {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
payment label, .payment label {
|
||||
display: block;
|
||||
color: #999;
|
||||
font-size: 13px;
|
||||
padding: 5px 0;
|
||||
text-transform: uppercase;
|
||||
text-shadow: 0 1px 0 #FFF;
|
||||
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
payment input, .payment input {
|
||||
font-size: 13px;
|
||||
padding: 5px 5px;
|
||||
border: 1px solid #BBB;
|
||||
border-top-color: #999;
|
||||
box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.1);
|
||||
border-radius: 3px;
|
||||
|
||||
-webkit-transition: -webkit-box-shadow 0.1s ease-in-out;
|
||||
-moz-transition: -moz-box-shadow 0.1s ease-in-out;
|
||||
transition: -moz-box-shadow 0.1s ease-in-out;
|
||||
|
||||
box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
-ms-sizing: border-box;
|
||||
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-family: 'Helvetica Neue', Helvetica, Arial Geneva, sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
payment input:focus, .payment input:focus {
|
||||
border: 1px solid #5695DB;
|
||||
outline: none;
|
||||
-webkit-box-shadow: inset 0 1px 2px #DDD, 0px 0 5px #5695DB;
|
||||
-moz-box-shadow: 0 0 5px #5695db;
|
||||
box-shadow: inset 0 1px 2px #DDD, 0px 0 5px #5695DB;
|
||||
}
|
||||
|
||||
payment .invalid input, .payment .invalid input {
|
||||
outline: none;
|
||||
border-color: rgba(255, 0, 0, 0.5);
|
||||
-moz-box-shadow: inset 0 1px 2px rgba(0,0,0,0.20), 0 1px 5px 0 rgba(255, 0, 0, 0.4);
|
||||
-webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.20), 0 1px 5px 0 rgba(255, 0, 0, 0.4);
|
||||
-ms-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.20), 0 1px 5px 0 rgba(255, 0, 0, 0.4);
|
||||
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.20), 0 1px 5px 0 rgba(255, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
payment input:disabled, .payment input:disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
payment .number,
|
||||
.payment .number {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
payment .number input, .payment .number input {
|
||||
padding: 7px 40px 7px 7px;
|
||||
background: #FFF url(generic.png) 98.5% 20% no-repeat;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
payment .number input.visa, .payment .number input.visa {
|
||||
background-image: url(visa.png);
|
||||
}
|
||||
|
||||
payment .number input.mastercard, .payment .number input.mastercard {
|
||||
background-image: url(mastercard.png);
|
||||
}
|
||||
|
||||
payment .number input.discover, .payment .number input.discover {
|
||||
background-image: url(discover.png);
|
||||
}
|
||||
|
||||
payment .number input.amex, .payment .number input.amex {
|
||||
background-image: url(amex.png);
|
||||
}
|
||||
|
||||
payment .expiry input, payment .cvc input {
|
||||
width: 45px;
|
||||
}
|
||||
|
||||
payment .expiry em {
|
||||
font-size: 10px;
|
||||
font-style: normal;
|
||||
display: none;
|
||||
}
|
||||
|
||||
payment .cvc,
|
||||
.payment .cvc {
|
||||
float: right;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
payment .expiry,
|
||||
.payment .expiry {
|
||||
float: left;
|
||||
}
|
||||
|
||||
payment .message,
|
||||
.payment .message {
|
||||
display: block;
|
||||
}
|
||||
|
||||
payment.pending,
|
||||
.payment.pending,
|
||||
payment.success,
|
||||
.payment.success {
|
||||
background: #FFF url(spinner.gif) center center no-repeat;
|
||||
min-height: 130px;
|
||||
}
|
After Width: | Height: | Size: 3.7 KiB |