Merge branch 'relaunch' of github.com:Gluejar/regluit into relaunch

Conflicts:
	core/signals.py
pull/1/head
Andromeda Yelton 2012-09-19 12:48:36 -04:00
commit a7040f47ce
93 changed files with 4472 additions and 3021 deletions

View File

@ -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']

View File

@ -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': {

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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(self):
cleaned_data = self.cleaned_data
# check on whether the preapproval amount is < amount for premium tier. If so, put an error message
def clean_premium_id(self):
premium_id = self.cleaned_data['premium_id']
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:
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:
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):
# check on whether the preapproval amount is < amount for premium tier. If so, put an error message
try:
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." % (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=(

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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> wants to give 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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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 %}

View File

@ -0,0 +1,125 @@
{% extends "base.html" %}
{% with work.title as title %}
{% block title %}
&mdash; 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 %}

View File

@ -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 %}

View File

@ -1,10 +1,13 @@
{% 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 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 ebook %}
{% if ebooks %}
{% for ebook in ebooks %}
<div>
<I>{{ work.title }}</i><br />
{% if authors|length = 1 %}
{{ authors.0 }}
@ -23,6 +26,8 @@
<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 %}

View File

@ -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' %}

View File

@ -10,16 +10,22 @@
{% 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>
<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>
@ -29,15 +35,32 @@
<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>
{% 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>
@ -47,9 +70,14 @@
<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 />
<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>

View File

@ -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 %}

View File

@ -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 &amp; 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&amp;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&amp;url={{ nonprofit.link|urlencode }}&amp;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 }}&amp;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 &copy; 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>

View File

@ -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).

View File

@ -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>

View File

@ -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">&lt;iframe src="https://{{request.META.HTTP_HOST}}/api/widget/{{work.first_isbn_13}}/" width="152" height="325" frameborder="0"&gt;&lt;/iframe&gt;</textarea></div>

View File

@ -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 &amp; 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 &amp; 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 />
<a id="releases"></a><h2>Press Releases</h2>
<div class="pressarticles">
<div>
{% comment %}
should have logos at a variety of resolutions and also in greyscale
{% endcomment %}
<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>
</div>
<br /><br /><br /><br /><br /><br />
{% endblock %}

View File

@ -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 %} &#8212; {{ 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">

View File

@ -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 %}
{% endif %}
</div>
</div>
{% else %}
{% 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 class="btn_support">
<a href="{% url download work_id %}" class="fakeinput hijax">Download</a>
</div>
{% endif %}
{% endif %}
</div>
</div>
<div class="find-book">
<label>Learn more at...</label>
<div class="find-link">
@ -345,7 +317,13 @@ $j(document).ready(function(){
{% 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="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 %}
<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 %}
@ -363,7 +341,9 @@ $j(document).ready(function(){
</div>
</div>
{% if edition.ebook_form %}{% ifnotequal status 'ACTIVE' %}
{% if edition.hide_details %}<div class="show_more_edition" >more...</div>{% endif %}
{% 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>
@ -382,7 +362,7 @@ $j(document).ready(function(){
</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>

View File

@ -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"),
)

View File

@ -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,142 +594,110 @@ 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
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)
if self.transaction:
if preapproval_amount:
preapproval_amount = preapproval_amount if preapproval_amount>self.transaction.amount else self.transaction.amount
else:
form = form_class()
preapproval_amount = self.transaction.amount
return preapproval_amount
def get_form_kwargs(self):
assert self.request.user.is_authenticated()
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:
pubdate = work.publication_date[:4]
except IndexError:
pubdate = 'unknown'
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 self.campaign.status == 'ACTIVE'
except Exception, e:
raise e
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:
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
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}
def get_context_data(self, **kwargs):
"""set up the pledge page"""
context = super(PledgeView, self).get_context_data(**kwargs)
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,
'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,
})
# 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()
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))
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
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:
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)
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)
@ -727,138 +705,202 @@ class PledgeView(FormView):
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
"""
class FundPledgeView(FormView):
template_name="fund_the_pledge.html"
form_class = CCForm
transaction = None
template_name="pledge.html"
form_class = CampaignPledgeForm
embedded = False
def get_form_kwargs(self):
kwargs = super(FundPledgeView, self).get_form_kwargs()
assert self.request.user.is_authenticated()
if self.transaction is None:
self.transaction = get_object_or_404(Transaction, id=self.kwargs["t_id"])
if kwargs.has_key('data'):
data = kwargs['data'].copy()
else:
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(PledgeModifyView, self).get_context_data(**kwargs)
# the following should be true since PledgeModifyView.as_view is wrapped in login_required
assert self.request.user.is_authenticated()
user = self.request.user
work = get_object_or_404(models.Work, id=self.kwargs["work_id"])
try:
campaign = work.last_campaign()
premiums = campaign.custom_premiums() | models.Premium.objects.filter(id=150)
# which combination of campaign and transaction status required?
# 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
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
else:
premium_id = None
premium_description = None
# is there a Transaction for an ACTIVE campaign for this
# should make sure Transaction is modifiable.
preapproval_amount = transaction.amount
data = {'preapproval_amount':preapproval_amount, 'premium_id':premium_id}
# 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.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,
})
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 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 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"""
# 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.
# first pass -- we have a token -- also do more direct coupling to stripelib -- then move to
# abstraction of payment.manager / payment.baseprocessor
work_id = self.kwargs["work_id"]
# 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"]
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"]
retain_cc_info = form.cleaned_data["retain_cc_info"]
assert self.request.user.is_authenticated()
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
)
# 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'
account.save()
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
charge = sc.create_charge(preapproval_amount, customer=customer, description="${0} for test / retain cc".format(preapproval_amount))
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))
else:
return HttpResponse("No modification made")
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)

View File

@ -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()

182
payment/baseprocessor.py Normal file
View File

@ -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

55
payment/credit.py Normal file
View File

@ -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

7
payment/forms.py Normal file
View File

@ -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())

View File

@ -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.objects.create(amount=amount,
t = Transaction.create(amount=amount,
max_amount=amount,
type=PAYMENT_TYPE_INSTANT,
execution=EXECUTE_TYPE_CHAINED_INSTANT,
target=target,
currency=currency,
status='NONE',
status=TRANSACTION_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
pledge_extra=pledge_extra
)
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,10 +911,6 @@ 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()
logger.info("Pledge Success: " + url)

View File

@ -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']

View File

@ -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'})
}

View File

@ -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'})
}

View File

@ -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']

View File

@ -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()
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

View File

@ -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'

File diff suppressed because it is too large Load Diff

274
payment/stripelib.py Normal file
View File

@ -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)

View File

@ -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 %}

View File

@ -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

View File

@ -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())
)

View File

@ -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)
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)
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))

View File

@ -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

View File

@ -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

View File

@ -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/'

View File

@ -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

View File

@ -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

View File

@ -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'),

View File

@ -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'),

View File

@ -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'),

View File

@ -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;

173
static/css/download.css Normal file
View File

@ -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;
}

View File

@ -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;

View File

@ -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;

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -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"});
});
});

53
static/js/hijax.js Normal file
View File

@ -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"});
});
});

View File

@ -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");
});
});

View File

@ -62,6 +62,7 @@
div.btn_support {
float: right;
margin-top: 9px;
input {
font-size: @font-size-larger;

View File

@ -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 {

91
static/less/download.less Normal file
View File

@ -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;
}

View File

@ -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 {

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

58
static/stripe/tag.css Executable file
View File

@ -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;
}

405
static/stripe/tag.dev.js Executable file
View File

@ -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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
};
}
(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);

1
static/stripe/tag.js Executable file

File diff suppressed because one or more lines are too long

BIN
static/stripe/themes/amex.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
static/stripe/themes/discover.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

BIN
static/stripe/themes/generic.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

BIN
static/stripe/themes/spinner.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

152
static/stripe/themes/stripe.css Executable file
View File

@ -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;
}

BIN
static/stripe/themes/visa.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

0
sysadmin/__init__.py Normal file
View File