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

pull/1/head
Jason 2012-05-11 07:19:38 -04:00
commit 17019e6f2c
59 changed files with 3214 additions and 686 deletions

View File

@ -5,7 +5,7 @@
{% block doccontent %}
<h2>API Help</h2>
<p>Some of the data from Unglue It is avaiable via a JSON API. You will need a key and username to be able to use the API.
<p>Some of the data from Unglue.it is avaiable via a JSON API. You will need a key and username to be able to use the API.
</p>
{% if user.is_authenticated %}
<p> Welcome {{user.username}}. Your API key is <span style="font-weight:bold">{{api_key}}</span>.</p>

View File

@ -189,7 +189,6 @@ def update_edition(edition):
# update the edition
edition.title = title
# edition.description = d.get('description')
edition.publisher = d.get('publisher')
edition.publication_date = d.get('publishedDate', '')
edition.save()
@ -331,7 +330,6 @@ def add_by_googlebooks_id(googlebooks_id, work=None, results=None, isbn=None):
# because this is a new google id, we have to create a new edition
e = models.Edition(work=work)
e.title = title
#e.description = d.get('description')
e.publisher = d.get('publisher')
e.publication_date = d.get('publishedDate', '')
e.save()
@ -463,6 +461,7 @@ def add_openlibrary(work):
# get the 1st openlibrary match by isbn that has an associated work
url = "http://openlibrary.org/api/books"
params = {"format": "json", "jscmd": "details"}
subjects = []
for edition in work.editions.all():
isbn_key = "ISBN:%s" % edition.isbn_13
params['bibkeys'] = isbn_key
@ -471,34 +470,47 @@ def add_openlibrary(work):
except LookupFailure:
logger.exception("OL lookup failed for %s", isbn_key)
e = {}
if e.has_key(isbn_key) and e[isbn_key]['details'].has_key('works'):
work_key = e[isbn_key]['details']['works'].pop(0)['key']
logger.info("got openlibrary work %s for isbn %s", work_key, isbn_key)
try:
w = _get_json("http://openlibrary.org" + work_key,type='ol')
if w.has_key('subjects'):
found = True
break
except LookupFailure:
logger.exception("OL lookup failed for %s", work_key)
if not found:
if e.has_key(isbn_key):
if e[isbn_key].has_key('details'):
if e[isbn_key]['details'].has_key('oclc_numbers'):
for oclcnum in e[isbn_key]['details']['oclc_numbers']:
models.Identifier.get_or_add(type='oclc',value=oclcnum,work=work, edition=edition)
if e[isbn_key]['details'].has_key('identifiers'):
ids = e[isbn_key]['details']['identifiers']
if ids.has_key('goodreads'):
models.Identifier.get_or_add(type='gdrd',value=ids['goodreads'][0],work=work,edition=edition)
if ids.has_key('librarything'):
models.Identifier.get_or_add(type='ltwk',value=ids['librarything'][0],work=work)
if ids.has_key('google'):
models.Identifier.get_or_add(type='goog',value=ids['google'][0],work=work)
if ids.has_key('project_gutenberg'):
models.Identifier.get_or_add(type='gute',value=ids['project_gutenberg'][0],work=work)
if e[isbn_key]['details'].has_key('works'):
work_key = e[isbn_key]['details']['works'].pop(0)['key']
logger.info("got openlibrary work %s for isbn %s", work_key, isbn_key)
models.Identifier.get_or_add(type='olwk',value=work_key,work=work)
try:
w = _get_json("http://openlibrary.org" + work_key,type='ol')
if w.has_key('description'):
if not work.description or len(w['description']) > len(work.description):
work.description = w['description']
work.save()
if w.has_key('subjects') and len(w['subjects']) > len(subjects):
subjects = w['subjects']
except LookupFailure:
logger.exception("OL lookup failed for %s", work_key)
if not subjects:
logger.warn("unable to find work %s at openlibrary", work.id)
return
# add the subjects to the Work
for s in w.get('subjects', []):
for s in subjects:
logger.info("adding subject %s to work %s", s, work.id)
subject, created = models.Subject.objects.get_or_create(name=s)
work.subjects.add(subject)
work.save()
models.Identifier.get_or_add(type='olwk',value=w['key'],work=work)
if e[isbn_key]['details'].has_key('identifiers'):
ids = e[isbn_key]['details']['identifiers']
if ids.has_key('goodreads'):
models.Identifier.get_or_add(type='gdrd',value=ids['goodreads'][0],work=work,edition=edition)
if ids.has_key('librarything'):
models.Identifier.get_or_add(type='ltwk',value=ids['librarything'][0],work=work)
# TODO: add authors here once they are moved from Edition to Work

View File

@ -27,10 +27,18 @@
"pk": 4,
"model": "sites.site",
"fields": {
"domain": "ry-dev.dyndns.org",
"domain": "ry-dev.unglueit.com",
"name": "ry-dev development"
}
},
},
{
"pk": 5,
"model": "sites.site",
"fields": {
"domain": "just.unglueit.com",
"name": "unglue.it staging"
}
},
{
"pk": 1,
"model": "core.premium",

View File

@ -0,0 +1,219 @@
# 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 field 'Work.description'
db.add_column('core_work', 'description', self.gf('django.db.models.fields.TextField')(default='', null=True), keep_default=False)
def backwards(self, orm):
# Deleting field 'Work.description'
db.delete_column('core_work', 'description')
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': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'details': ('django.db.models.fields.TextField', [], {'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, 5, 6, 22, 35, 23, 793331)', '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'},
'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'}),
'public_domain': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
'publication_date': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True'}),
'publisher': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': '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.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.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

@ -0,0 +1,227 @@
# encoding: utf-8
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
no_dry_run = True
def longest_description(self,work):
"""get the longest description from an edition of this work
"""
description = ""
for edition in work.editions.all():
if edition.description and len(edition.description) > len(description):
description = edition.description
return description
def forwards(self, orm):
for work in orm.Work.objects.all():
work.description = self.longest_description(work)
work.save()
def backwards(self, orm):
pass
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': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'details': ('django.db.models.fields.TextField', [], {'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, 5, 6, 22, 38, 0, 501641)', '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'},
'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'}),
'public_domain': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
'publication_date': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True'}),
'publisher': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': '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.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.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

@ -0,0 +1,218 @@
# 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 'Edition.description'
db.delete_column('core_edition', 'description')
def backwards(self, orm):
# Adding field 'Edition.description'
db.add_column('core_edition', 'description', self.gf('django.db.models.fields.TextField')(default='', null=True), keep_default=False)
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': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'details': ('django.db.models.fields.TextField', [], {'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, 5, 7, 0, 36, 13, 555983)', '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'},
'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'}),
'publisher': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': '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.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.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

@ -0,0 +1,229 @@
# 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 'Key'
db.create_table('core_key', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('name', self.gf('django.db.models.fields.CharField')(max_length=255)),
('encrypted_value', self.gf('django.db.models.fields.TextField')(null=True, blank=True)),
))
db.send_create_signal('core', ['Key'])
def backwards(self, orm):
# Deleting model 'Key'
db.delete_table('core_key')
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': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'details': ('django.db.models.fields.TextField', [], {'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, 5, 8, 18, 28, 57, 631763)', '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'},
'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'}),
'publisher': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': '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.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', [], {'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

@ -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 unique constraint on 'Key', fields ['name']
db.create_unique('core_key', ['name'])
def backwards(self, orm):
# Removing unique constraint on 'Key', fields ['name']
db.delete_unique('core_key', ['name'])
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': ('django.db.models.fields.TextField', [], {'null': 'True'}),
'details': ('django.db.models.fields.TextField', [], {'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, 5, 8, 19, 46, 40, 240837)', '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'},
'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'}),
'publisher': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': '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.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

@ -1,6 +1,7 @@
import re
import random
from regluit.utils.localdatetime import now, date_today
from regluit.utils import crypto
from datetime import timedelta
from decimal import Decimal
from notification import models as notification
@ -13,10 +14,27 @@ from django.utils.translation import ugettext_lazy as _
import regluit
import regluit.core.isbn
import binascii
class UnglueitError(RuntimeError):
pass
class Key(models.Model):
"""an encrypted key store"""
name = models.CharField(max_length=255, unique=True)
encrypted_value = models.TextField(null=True, blank=True)
def _get_value(self):
return crypto.decrypt_string(binascii.a2b_hex(self.encrypted_value), settings.SECRET_KEY)
def _set_value(self, value):
self.encrypted_value = binascii.b2a_hex(crypto.encrypt_string(value, settings.SECRET_KEY))
value = property(_get_value, _set_value)
def __unicode__(self):
return "Key with name {0}".format(self.name)
class CeleryTask(models.Model):
created = models.DateTimeField(auto_now_add=True, default=now())
task_id = models.CharField(max_length=255)
@ -245,7 +263,7 @@ class Campaign(models.Model):
return Premium.objects.filter(campaign=self).filter(type='CU')
class Identifier(models.Model):
# olib, ltwk, goog, gdrd, thng, isbn, oclc, olwk, olib
# olib, ltwk, goog, gdrd, thng, isbn, oclc, olwk, olib, gute, glue
type = models.CharField(max_length=4, null=False)
value = models.CharField(max_length=31, null=False)
work = models.ForeignKey("Work", related_name="identifiers", null=False)
@ -272,6 +290,7 @@ class Work(models.Model):
language = models.CharField(max_length=2, default="en", null=False)
openlibrary_lookup = models.DateTimeField(null=True)
num_wishes = models.IntegerField(default=0)
description = models.TextField(default='', null=True)
class Meta:
ordering = ['title']
@ -450,15 +469,6 @@ class Work(models.Model):
self.num_wishes = Wishes.objects.filter(work=self).count()
self.save()
def longest_description(self):
"""get the longest description from an edition of this work
"""
description = ""
for edition in self.editions.all():
if len(edition.description) > len(description):
description = edition.description
return description
def first_isbn_13(self):
try:
return self.identifiers.filter(type='isbn')[0].value
@ -500,7 +510,6 @@ class Subject(models.Model):
class Edition(models.Model):
created = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=1000)
description = models.TextField(default='', null=True)
publisher = models.CharField(max_length=255, null=True)
publication_date = models.CharField(max_length=50, null=True)
public_domain = models.NullBooleanField(null=True)

View File

@ -13,7 +13,7 @@ from django.contrib.contenttypes.models import ContentType
from django.contrib.sites.models import Site
from regluit.payment.models import Transaction
from regluit.core.models import Campaign, Work, UnglueitError, Edition, RightsHolder, Claim
from regluit.core.models import Campaign, Work, UnglueitError, Edition, RightsHolder, Claim, Key
from regluit.core import bookloader, models, search, goodreads, librarything
from regluit.core import isbn
from regluit.payment.parameters import PAYMENT_TYPE_AUTHORIZATION
@ -102,6 +102,9 @@ class BookLoaderTests(TestCase):
self.assertTrue(edition.work.publication_date)
edition.publication_date = None
self.assertTrue(edition.work.publication_date)
self.assertTrue(len(edition.work.description) > 20)
self.assertTrue(edition.work.identifiers.filter(type='oclc')[0])
def test_merge_works_mechanics(self):
"""Make sure then merge_works is still okay when we try to merge works with themselves and with deleted works"""
@ -278,9 +281,10 @@ class BookLoaderTests(TestCase):
subjects = [s.name for s in work.subjects.all()]
self.assertTrue(len(subjects) > 10)
self.assertTrue('Science fiction' in subjects)
self.assertEqual(work.openlibrary_id, '/works/OL27258W')
self.assertEqual(work.goodreads_id, '14770')
self.assertEqual(work.librarything_id, '609')
self.assertTrue('/works/OL27258W' in work.identifiers.filter(type='olwk').values_list('value',flat=True) )
self.assertTrue('14770' in work.identifiers.filter(type='gdrd').values_list('value',flat=True))
self.assertTrue('609' in work.identifiers.filter(type='ltwk').values_list('value',flat=True))
def test_load_gutenberg_edition(self):
"""Let's try this out for Moby Dick"""
@ -532,3 +536,14 @@ class ISBNTest(TestCase):
self.assertEqual(len(set([str(isbn.ISBN(milosz_10)), str(isbn.ISBN(milosz_13))])),2)
self.assertEqual(len(set([isbn.ISBN(milosz_10).to_string(), isbn.ISBN(milosz_13).to_string()])),1)
class EncryptedKeyTest(TestCase):
def test_create_read_key(self):
name = "the great answer"
value = "42"
key = Key.objects.create(name=name, value=value)
key.save()
# do we get back the value?
self.assertEqual(Key.objects.filter(name=name)[0].value, value)
# just checking that the encrypted value is not the same as the value
self.assertNotEqual(key.encrypted_value, value) # is this always true?

23
deploy/crontab_just.txt Normal file
View File

@ -0,0 +1,23 @@
# Edit this file to introduce tasks to be run by cron.
#
# Each task to run has to be defined through a single line
# indicating with different fields when the task will be run
# and what command to run for the task
#
# To define the time you can provide concrete values for
# minute (m), hour (h), day of month (dom), month (mon),
# and day of week (dow) or use '*' in these fields (for 'any').#
# Notice that tasks will be started based on the cron's system
# daemon's notion of time and timezones.
#
# Output of the crontab jobs (including errors) is sent through
# email to the user the crontab file belongs to (unless redirected).
#
# For example, you can run a backup of all your user accounts
# at 5 a.m every week with:
# 0 5 * * 1 tar -zcf /var/backups/home.tgz /home/
#
# For more information see the manual pages of crontab(5) and cron(8)
#
# m h dom mon dow command
* * * * * cd /opt/regluit; source /opt/regluit/ENV/bin/activate; /opt/regluit/ENV/bin/django-admin.py emit_notices --settings=regluit.settings.please > /opt/regluit/deploy/emit_notices.log 2>&1 ; touch /opt/regluit/deploy/last-cron

56
deploy/just.conf Normal file
View File

@ -0,0 +1,56 @@
WSGIPythonHome /opt/regluit/ENV
WSGISocketPrefix /opt/regluit
<VirtualHost *:80>
ServerName just.unglueit.com
ServerAdmin info@gluejar.com
RewriteEngine On
RewriteRule ^/$ https://just.unglueit.com/ [R=301]
RewriteRule /admin(.*) https://just.unglueit.com/admin$1 [R=301]
RewriteRule /accounts(.*) https://just.unglueit.com/accounts$1 [R=301]
WSGIDaemonProcess regluit processes=4 threads=4 python-eggs=/tmp/regluit-python-eggs
WSGIScriptAlias / /opt/regluit/deploy/just.wsgi
<Directory /opt/regluit/static>
Options Indexes FollowSymLinks
AllowOverride None
Order allow,deny
Allow from all
</Directory>
Alias /static /var/www/static
</VirtualHost>
<VirtualHost _default_:443>
SSLEngine on
SSLCertificateFile /etc/ssl/certs/server.crt
SSLCertificateKeyFile /etc/ssl/private/server.key
#SSLCertificateChainFile /etc/ssl/certs/gd_bundle.crt
WSGIDaemonProcess regluit-ssl processes=4 threads=4 python-eggs=/tmp/regluit-python-eggs
WSGIScriptAlias / /opt/regluit/deploy/just.wsgi
<Directory /opt/regluit/static>
Options Indexes FollowSymLinks
AllowOverride None
Order allow,deny
Allow from all
</Directory>
Alias /static /var/www/static
BrowserMatch "MSIE [2-6]" \
nokeepalive ssl-unclean-shutdown \
downgrade-1.0 force-response-1.0
# MSIE 7 and newer should be able to use keepalive
BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown
</VirtualHost>

9
deploy/just.wsgi Normal file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env python
import os
import django.core.handlers.wsgi
os.environ['CELERY_LOADER'] = 'django'
os.environ['DJANGO_SETTINGS_MODULE'] = 'regluit.settings.just'
application = django.core.handlers.wsgi.WSGIHandler()

View File

@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA41Tg+92k7xzXm27beIAHh4ekTRVoXBK8rhYa2SkxJ+yHcxhxLw78NckuCmNW0x/DJN/nZDAcgJfXRma2zVuZAPMdWpAFppksj4gLIAcrbHAnDw2Ef15476Oc+QhdZXQyEEBuvjrAK3tCZJ7iMHEykoR+whOR5PzCrPLgWAvL4560c0AkG9+EDwRPJA8VJrwyZUGE7a2j+eNVtAwZYvb+0rYvKwKWgwCgx585rHd5ooqwD3UfY91pjeFp2msEcp4h+4pJk99RdSo7Pjf4+olkrl1SHmEX6eWYLsaY0IkYKXPglNOiWcD2zxevdP4jlz5CFKO63o/K5LUndeF6Vc+/4Q== ehs@pobox.com

View File

@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA7ozGYIwEEXj10hAICsy+qdBHYbV95cs7rSR8ZG+6te2aPL1Pg0e5lUJkyMao0T//LfFNBRwg0wqWsb7b6yUvXmJAD3r5hjTCWlKJ/54APxInT/YMi08GVwSnwgVnib8zmjmfJ+6zFj5MsPTNPsdL/DWxheyZ0oHDxL0rpcL192sdgu4//5j0oyk+w/rrm+jUMNpuOOGPINMZxEd6+OMXr239ZLGSkWAavtMW1FKOXc/qrLHn03rNdz4cHZ1Gsx2++d8lk4jf8BzC0t3XXVuRZNu8lhlzCMW6sUEG6uACnXYlnmQg597fcVUFXmdQ4I79PGndjy35FwdbmiLLe+bnrw== raymond.yee@gmail.com

19
deploy/update-just Executable file
View File

@ -0,0 +1,19 @@
#!/bin/bash
# this script is used by jenkins to remotely update a regluit instance
# for it to work the jenkins user's public ssh key needs to be in the
# authorized key for the machine running the regluit instance
# you can then put something like this in a post build configuration
# ssh ubuntu@please.unglueit.com "/opt/regluit/deploy/update-regluit"
cd /opt/regluit
sudo -u ubuntu /usr/bin/git pull
source ENV/bin/activate
#pip install -r requirements.pip
django-admin.py syncdb --migrate --settings regluit.settings.just
django-admin.py collectstatic --noinput --settings regluit.settings.just
sudo /etc/init.d/apache2 restart
sudo /etc/init.d/celeryd restart
sudo /etc/init.d/celerybeat restart
crontab deploy/crontab_just.txt
touch /opt/regluit/deploy/last-update

10
fabfile.py vendored
View File

@ -4,6 +4,8 @@ from regluit.sysadmin import aws
# allow us to use our ssh config files (e.g., ~/.ssh/config)
env.use_ssh_config = True
DATA_BACKUP_ACCOUNT = 'b235656@hanjin.dreamhost.com'
def rydev():
"""An example of using a function to define a host and use that definition in a command
to run:
@ -19,8 +21,12 @@ def update_prod():
with cd("/opt/regluit"):
run("./deploy/update-prod")
def backup_db(name='unglue.it'):
run("""TS=`date +"%Y-%m-%dT%H:%M:%S"`; /home/ubuntu/dump.sh | gzip > {0}.${{TS}}.sql.gz; scp ./{0}.${{TS}}.sql.gz b235656@hanjin.dreamhost.com: ; rm -f {0}.${{TS}}.sql.gz""".format(name))
def backup_db(name='unglue.it', server=DATA_BACKUP_ACCOUNT):
"""backup database on unglue.it or please with name to server"""
run("""TS=`date +"%Y-%m-%dT%H:%M:%S"`; /home/ubuntu/dump.sh | gzip > {0}.${{TS}}.sql.gz; scp ./{0}.${{TS}}.sql.gz {1}: ; rm -f {0}.${{TS}}.sql.gz""".format(name, server))
def list_backups(name='unglue.it', server=DATA_BACKUP_ACCOUNT):
local("""echo "ls -lt {0}.*" | sftp {1}""".format(name, server))
def get_dump():
"""Dump the current db on remote server and scp it over to local machine.

View File

@ -69,7 +69,7 @@ class RightsHolderForm(forms.ModelForm):
label='Owner',
widget=AutoCompleteSelectWidget(OwnerLookup),
required=True,
error_messages={'required': 'Please ensure the owner is a valid Unglue.It account.'},
error_messages={'required': 'Please ensure the owner is a valid Unglue.it account.'},
)
email = forms.EmailField(
label=_("notification email address for rights holder"),
@ -177,15 +177,17 @@ def getManageCampaignForm ( instance, data=None, *args, **kwargs ):
max_length=100,
error_messages={'required': 'You must enter the email associated with your Paypal account.'},
)
target = forms.DecimalField( min_value= D('0.00'), error_messages={'required': 'Please specify a target price.'} )
target = forms.DecimalField( min_value= D(settings.UNGLUEIT_MINIMUM_TARGET), error_messages={'required': 'Please specify a target price.'} )
edition = forms.ModelChoiceField(get_queryset(), widget=RadioSelect(),empty_label='no edition selected')
minimum_target = settings.UNGLUEIT_MINIMUM_TARGET
latest_ending = (timedelta(days=int(settings.UNGLUEIT_LONGEST_DEADLINE)) + now()).date
class Meta:
model = Campaign
fields = 'description', 'details', 'license', 'target', 'deadline', 'paypal_receiver', 'edition'
widgets = {
'description': forms.Textarea(attrs={'cols': 80, 'rows': 20}),
'details': forms.Textarea(attrs={'cols': 80, 'rows': 20}),
'details': forms.Textarea(attrs={'cols': 80, 'rows': 5}),
'deadline': SelectDateWidget,
}

View File

@ -2,7 +2,7 @@
{% block doccontent %}
<h2>About</h2>
<p><a href="http://unglue.it">Unglue.It</a> is a service provided by <a href="http://gluejar.com">Gluejar, Inc.</a> It's a place for individuals and institutions to join together to liberate specific ebooks and other types of digital content by paying rights holders to relicense their works under <a href="http://creativecommons.org">Creative Commons</a> licenses.</p>
<p><a href="http://unglue.it">Unglue.it</a> is a service provided by <a href="http://gluejar.com">Gluejar, Inc.</a> It's a place for individuals and institutions to join together to liberate specific ebooks and other types of digital content by paying rights holders to relicense their works under <a href="http://creativecommons.org">Creative Commons</a> licenses.</p>
<p>What does this mean?</p>
<ul>

View File

@ -79,7 +79,7 @@
{% if is_preview %}
<div class="preview_top">
Welcome to the alpha version of Unglue.It. This site is a preview of our full functionality; some things (including pledging) aren't turned on yet. If something seems broken or confusing -- or if you find something you love! -- please give us <a href="/feedback">feedback</a>. Thank you for your interest, and have fun.
Welcome to the alpha version of Unglue.it. This site is a preview of our full functionality; some things (including pledging) aren't turned on yet. If something seems broken or confusing -- or if you find something you love! -- please give us <a href="/feedback">feedback</a>. Thank you for your interest, and have fun.
</div>
{% endif %}
{% block topsection %}{% endblock %}
@ -88,7 +88,7 @@ Welcome to the alpha version of Unglue.It. This site is a preview of our full f
<div id="footer">
<div class="js-main">
<div class="column">
<span>About unglue.it</span>
<span>About Unglue.it</span>
<ul>
<li><a href="{{ abouturl }}">About</a></li>
<li><a href="http://blog.unglue.it">Blog</a></li>

View File

@ -9,6 +9,7 @@
<div class="greenpanel_top">
<div class="unglued_white">
{% if not is_preview %}
{% comment %}top section: campaign info + optional action button. Varies by campaign status.{% endcomment %}
{% if first_ebook %}
<b>AVAILABLE!</b>
@ -51,6 +52,12 @@
</div>
{% endif %}{% endif %}{% endif %}{% endif %}{% endif %}{% endif %}{% endif %}
{% else %}
<p>No campaign yet.</p><br /><p>But if lots of ungluers wishlist this book, maybe there will be!</p>
</div>
{% endif %}
</div>
{% comment %}link to work page{% endcomment %}
@ -127,13 +134,15 @@
</div>
{% endif %}{% endif %}{% endifequal %}{% endif %}
<div class="listview panelfront side1 booklist-status">
{% ifequal status "ACTIVE" %}
<span class="booklist-status-text" style="width: 190px"><b>${{ work.last_campaign.current_total }}</b> raised of <b>${{ work.last_campaign.target }}</b> goal</span>
{% else %}{% ifequal status "INITIALIZED" %}
<span class="booklist-status-label">Status:&nbsp;</span><span class="booklist-status-text">Coming soon!</span>
{% else %}
<span class="booklist-status-label">Status:&nbsp;</span><span class="booklist-status-text">{{ status }}</span>
{% endifequal %}{% endifequal %}
{% if not is_preview %}
{% ifequal status "ACTIVE" %}
<span class="booklist-status-text" style="width: 190px"><b>${{ work.last_campaign.current_total }}</b> raised of <b>${{ work.last_campaign.target }}</b> goal</span>
{% else %}{% if status == "INITIALIZED" %}
<span class="booklist-status-label">Status:&nbsp;</span><span class="booklist-status-text">Coming soon!</span>
{% else %}
<span class="booklist-status-label">Status:&nbsp;</span><span class="booklist-status-text">{{ status }}</span>
{% endif %}{% endifequal %}
{% endif %}
</div>
<div class="listview panelfront side1 icons">
{% if status == 'No campaign yet' or status == 'INITIALIZED' %}

View File

@ -9,21 +9,19 @@
<ul class="menu level2">
{% if not request.user.is_anonymous %}
<li class="first"><a href="/"><span>My Wishlist</span></a></li>
{% endif %}
<li><a href="{% url comment %}"><span>Latest Comments</span></a></li>
<li><a href="{% url work_list 'recommended' %}"><span>Recommended</span></a></li>
{% if is_preview %}
<li><a href="{% url work_list 'popular' %}"><span>Popular</span></a></li>
<li><a href="{% url unglued_list 'recent' %}"><span>Recently unglued</span></a></li>
<li class="last"><a href="{% url work_list 'new' %}"><span>Newly Wished</span></a></li>
<li>
{% else %}
<li><a href="{% url work_list 'popular' %}"><span>Popular</span></a></li>
<li><a href="{% url campaign_list 'pledges' %}" class="comingsoon"><span>Most pledges</span></a></li>
<li><a href="{% url campaign_list 'pledged' %}" class="comingsoon"><span>Biggest campaigns</span></a></li>
<li><a href="{% url unglued_list 'recent' %}" class="comingsoon"><span>Recently unglued</span></a></li>
<li><a href="{% url campaign_list 'ending' %}" class="comingsoon"><span>Ending Soon</span></a></li>
<li class="last"><a href="{% url campaign_list 'newest' %}" class="comingsoon"><span>Just Listed</span></a></li>
<li class="first">
{% endif %}
{% if not is_preview %}
<a href="{% url campaign_list 'ending' %}"><span>Active Campaigns</span></a></li>
<li><a href="{% url work_list 'popular' %}"><span>Most Wished</span></a></li>
{% else %}
<a href="{% url work_list 'popular' %}"><span>Most Wishes</span></a></li>
{% endif %}
<li><a href="{% url work_list 'new' %}"><span>Newly Wished</span></a></li>
<li><a href="{% url work_list 'recommended' %}"><span>Staff Picks</span></a></li>
<li class="last"><a href="{% url unglued_list 'recent' %}"><span>Recently unglued</span></a></li>
</ul>
</li>
<li class="parent">

View File

@ -475,7 +475,7 @@ Need more ideas? We're happy to work with rights holders personally to craft a
{% if sublocation == 'funding' or sublocation == 'all' %}
<h4>Funding</h4>
<dt>Is a PayPal account required to launch a campaign?</dt>
<dd>Unglue.it prefers that rights holders have PayPal accounts as it will simplify the process of paying you when a campaign succeeds. If you do not have a PayPal account, please address this with us before signing the Platform Services Agreement and we'll find a way to make sure you can be paid. There may be an additional cost to receive funds other than through Paypal.</dd>
<dd>At this time, Unglue.it requires that rights holders have PayPal accounts as it will simplify the process of paying you when a campaign succeeds. </dd>
<dt>Are a funding goal and deadline required?</dt>
<dd>Yes.</dd>

View File

@ -8,6 +8,9 @@ textarea {
width: 90%;
}
</style>
<link type="text/css" rel="stylesheet" href="/static/css/manage_campaign.css" />
<script type="text/javascript" src="/static/js/tabs.js"></script>
<script type="text/javascript">
var $j = jQuery.noConflict();
@ -21,12 +24,26 @@ $j(document).ready(function(){
);
});
</script>
<script>
var $j = jQuery.noConflict();
$j(document).ready(function(){
$j('#launchme').click(function() {
$j('#campaign_launcher').click();
});
});
</script>
{% endblock %}
{% block doccontent %}
{% block topsection %}
{% endblock %}
{% block doccontent %}
{% with campaign.status as campaign_status %}
{% if campaign.not_manager %}
<h2>You're not a manager for campaign: {{ campaign.name }}</h2>
{% else %}
<div id="locationhash">{{ activetab }}</div>
{% for alert in alerts %}
<h1 class="alert">{{ alert }}</h1>
{% empty %}
@ -41,116 +58,207 @@ Please fix the following before launching your campaign:
{% empty %}
{% endfor %}
Or, <a href="{% url rightsholders %}">go back</a> to rights holder tools page.<br />
<h2>The work</h2>
(Or, <a href="{% url rightsholders %}">go back</a> to rights holder tools page.)<br />
<div style="height:15px;"></div>
<div class="book-detail">
<div id="book-detail-img">
<a href="#"><img src="{{ work.cover_image_thumbnail }}" alt="{{ work.title }}" title="{{ work.title }}" width="131" height="192" /></a>
</div>
<div class="book-detail-info">
<h2 class="book-name">Title: <a href="{% url work campaign.work.id %}">{{ campaign.work.title }}</a></h2>
<h3 class="book-author">Authors: {{ campaign.work.author }}</h3>
<h3 class="book-year">Published: {{ campaign.work.publication_date }}</h3>
<h3 class="book-author">Language: {{ campaign.work.language }}</h3>
<p>Target Price: ${{ campaign.target }}</p>
<p>End Date: {{ campaign.deadline|date:"M d, Y" }}</p>
<p>Campaign status: {{ campaign.status }}</p>
<div class="layout">
<h2 class="book-name">{{ work.title }}</h2>
<div>
<div class="pubinfo">
<h3 class="book-author">{{ work.author }}</h3>
<h3 class="book-year">{{ pubdate }}</h3>
</div>
</div>
</div>
<div class="pledged-info">
<div class="pledged-group">
{{ work.last_campaign.supporters.count }} Ungluers have pledged ${{ work.last_campaign.current_total }}
</div>
<div class="status">
<img src="/static/images/images/icon-book-37by25-{{ work.percent_unglued }}.png" title="book list status" alt="book list status" />
</div>
</div>
</div>
<div>
<a href="{% url work_preview campaign.work.id %}" class="manage">Preview This Campaign</a>
</div>
<h3>Description of the work offered for ungluing</h3>
<form action="#" method="POST">
{% csrf_token %}
<p> Please choose the edition that most closely matches the edition to be unglued:
{{ form.edition.errors }}{{ form.edition }}
<p>This will be displayed in the Campaign tab for your work. It's your main pitch to supporters. It should include:</p>
<ul class="terms">
<li>A synopsis of the work.</li>
<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>
<p>&lt;a href="http://your_link_here"&gt;your link title here&lt;/a&gt;</p>
<p>For example:</p>
<p>&lt;a href="http://www.archive.org"&gt;the Internet Archive&lt;/a&gt;</p>
<p>will display as <a href="http://www.archive.org">the Internet Archive</a>.</p>
<p>Copy-paste the code above and substitute your own values. You will find the link address in the location bar of your browser.</p>
</div></li>
<li>Anything especially appealing about the work or author: awards, embedded video (445px max), etc. <span class="rh_help" id="helpEmbed">(How do I embed video?)</span>
<div id="helpEmbed2" class="rh_answer">
<p>To embed a video from YouTube:</p>
<p>Go to the page where you watch the video. Under the video, click "Share".</p>
<p>A new set of options will open up. Click the "Embed" button.</p>
<p>This will generate some code for you to copy/paste into the box below. It will look something like this:</p>
<p>&lt;iframe width="444" height="301" src="<span style="color:red">http:</span>//www.youtube-nocookie.com/embed/adeDb0BRMZY?rel=0" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;</p>
<p>Copy/paste that text into the box below. Delete the part that says "http:" (shown in red in the example). You're done! We'll figure out the details.</p>
<p>Of course, if you want to include other text in addition to video, you can do that.</p>
<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>
</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>
{{ form.description.errors }}{{ form.description }}
<h3>Offer details</h3>
<p>This will be displayed on the Details 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>
{{ form.details.errors }}{{ form.details }}
<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.</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>
{{ 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.</p>
{{ form.deadline.errors }}{{ form.deadline }}
<div class="preview_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>
{% endifequal %}
</div>
<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>{{ form.paypal_receiver.errors }}{{ form.paypal_receiver }}</p>
<p>We recommend that you save and then preview your campaign before launch. If it doesn't look exactly the way you like, we're happy to help; please email unglue.it support (<a href="mailto:support@gluejar.com">support@gluejar.com</a>).</p>
{% ifequal campaign.status 'ACTIVE' %}
<input type="submit" name="save" value="Modify Campaign" />
{% else %}
<input type="submit" name="save" value="Save Campaign" />
{% endifequal %}
{% ifequal campaign.status 'INITIALIZED' %}
<input type="submit" name="launch" value="Launch Campaign" />
{% endifequal %}
</form>
<h3>Premiums</h3>
<div class="jsmod-content">
<form action="#" method="POST">
{% csrf_token %}
<ul class="support menu">
{% for premium in premiums %}
<li class="{% if forloop.first %}first{% else %}{% if forloop.last %}last{% endif %}{% endif %}">
<a href="{% url pledge work_id=campaign.work.id %}?premium_id={{premium.id}}">
<span class="menu-item-price">${{ premium.amount }}</span>
<span class="menu-item-desc">{{ premium.description }}</span>
</a>
{% if premium.type %}<span class="custom-premium"> <br />Type: {{ premium.get_type_display }}</span>{% endif %}
{% ifnotequal premium.limit 0 %}<br />Limit: {{ premium.limit }}{% endifnotequal %}
{% ifequal premium.type 'CU' %}<br />Deactivate? <input type="checkbox" name="premium_id" value="{{ premium.id }}" />{% endifequal %}
</li>
{% endfor %}
</ul>
{% if campaign.custom_premiums.count %}
<input type="submit" name="inactivate" value="Inactivate Checked Premiums" />
{% endif %}
</form>
</div>
<h4>Add a custom premium for this campaign</h4>
<form action="#" method="POST">
{% csrf_token %}
Pledge Amount: {{ premium_form.amount.errors }}{{ premium_form.amount }}<br />
Premium Description: {{ premium_form.description.errors }}{{ premium_form.description }}<br />
Number Available (0 if no limit): {{ premium_form.limit.errors }}{{ premium_form.limit }}<br />
{{ premium_form.campaign }}
{{ premium_form.type.errors }}{{ premium_form.type }}
<br />
<input type="submit" name="add_premium" value="Add Premium" />
</form>
<div class="content-block-heading" id="tabs">
<ul class="tabs">
<li class="tabs1 {% if activetab == '1' %}active{% endif %}"><a href="#">Description</a></li>
<li class="tabs2 {% if activetab == '2' %}active{% endif %}"><a href="#">Premiums</a></li>
<li class="tabs3 {% if activetab == '3' %}active{% endif %}"><a href="#">Launch</a></li>
</ul>
</div>
<div class="clearfix"></div>
<div class="tabs-1">
<form action="#" method="POST">
{% csrf_token %}
<h3>Select the edition</h3>
<p> Please choose the edition that most closely matches the edition to be unglued:
{{ form.edition.errors }}{{ form.edition }}
<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>
<ul class="terms">
<li>A synopsis of the work.</li>
<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>
<p>&lt;a href="http://your_link_here"&gt;your link title here&lt;/a&gt;</p>
<p>For example:</p>
<p>&lt;a href="http://www.archive.org"&gt;the Internet Archive&lt;/a&gt;</p>
<p>will display as <a href="http://www.archive.org">the Internet Archive</a>.</p>
<p>Copy-paste the code above and substitute your own values. You will find the link address in the location bar of your browser.</p>
</div></li>
<li>Anything especially appealing about the work or author: awards, embedded video (445px max), etc. <span class="rh_help" id="helpEmbed">(How do I embed video?)</span>
<div id="helpEmbed2" class="rh_answer">
<p>To embed a video from YouTube:</p>
<p>Go to the page where you watch the video. Under the video, click "Share".</p>
<p>A new set of options will open up. Click the "Embed" button.</p>
<p>This will generate some code for you to copy/paste into the box below. It will look something like this:</p>
<p>&lt;iframe width="444" height="301" src="<span style="color:red">http:</span>//www.youtube-nocookie.com/embed/adeDb0BRMZY?rel=0" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;</p>
<p>Copy/paste that text into the box below. Delete the part that says "http:" (shown in red in the example). You're done! We'll figure out the details.</p>
<p>Of course, if you want to include other text in addition to video, you can do that.</p>
<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>
</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>
{{ 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>
{{ 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}} .</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>
{{ 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>
{{ form.deadline.errors }}{{ form.deadline }}
{% else %}
<h3>Target Price</h3>
<p>The current target price for your campaign is <b>${{ campaign.target }}</b>. Since your campaign is active, you may lower, but not raise, this target.</p>
${{ form.target.errors }}{{ form.target }}
<h3>License being offered</h3>
<p>If your campaign succeeds, you will be offering your ebook under a <b>{{ campaign.license }}</b> license.</p>
<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>
{% endifnotequal %}
{% comment %}
<!-- not enforcing this while using Amazon payments -->
<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>{{ form.paypal_receiver.errors }}{{ form.paypal_receiver }}</p>
{% endcomment %}
{% ifequal campaign_status 'ACTIVE' %}
<div class="yikes">When you click this button, your changes will be visible to supporters immediately. Make sure to proofread!</div><br />
<input type="submit" name="save" value="Modify Campaign" />
{% else %}
<br /><br /><input type="submit" name="save" value="Save Campaign" />
{% endifequal %}
{% if campaign_status == 'INITIALIZED' %}
<input id="campaign_launcher" type="submit" name="launch" value="Launch Campaign" />
{% endif %}
</form>
</div>
<div class="tabs-2">
<h3>Premiums</h3>
<div class="jsmod-content">
<form action="#" method="POST">
{% csrf_token %}
<ul class="support menu">
{% for premium in premiums %}
<li class="{% if forloop.first %}first{% else %}{% if forloop.last %}last{% endif %}{% endif %}">
<a href="{% url pledge work_id=campaign.work.id %}?premium_id={{premium.id}}">
<span class="menu-item-price">${{ premium.amount }}</span>
<span class="menu-item-desc">{{ premium.description }}</span>
</a>
{% if premium.type %}<span class="custom-premium"> <br />Type: {{ premium.get_type_display }}</span>{% endif %}
{% ifnotequal premium.limit 0 %}<br />Limit: {{ premium.limit }}{% endifnotequal %}
{% ifequal premium.type 'CU' %}<br />Deactivate? <input type="checkbox" name="premium_id" value="{{ premium.id }}" />{% endifequal %}
</li>
{% endfor %}
</ul>
{% if campaign.custom_premiums.count %}
<input type="submit" name="inactivate" value="Inactivate Checked Premiums" />
{% endif %}
</form>
</div>
<h4>Add a custom premium for this campaign</h4>
<form action="#" method="POST">
{% csrf_token %}
Pledge Amount: {{ premium_form.amount.errors }}{{ premium_form.amount }}<br />
Premium Description: {{ premium_form.description.errors }}{{ premium_form.description }}<br />
Number Available (0 if no limit): {{ premium_form.limit.errors }}{{ premium_form.limit }}<br />
{{ premium_form.campaign }}
{{ premium_form.type.errors }}{{ premium_form.type }}
<br />
<input type="submit" name="add_premium" value="Add Premium" />
</form>
</div>
{% ifequal campaign_status 'INITIALIZED' %}
<div class="tabs-3">
{% if campaign.description and campaign.target and campaign.deadline %}
<p>Before you hit launch:</p>
<ul>
<li>Have you proofread your campaign? (Make sure to spellcheck!)</li>
<li>Have you <a href="{% url work_preview campaign.work.id %}">previewed your campaign</a>? Does it look how you want it to?</li>
</ul>
<p>If it doesn't look exactly the way you like, or you're having any trouble with your description or premiums, we're happy to help; please email unglue.it support (<a href="mailto:support@gluejar.com">support@gluejar.com</a>).</p>
<p>If you're happy with your campaign, here's your moment of truth!</p>
<div id="launchme"><a href="#" class="manage">Launch Campaign</a></div>
{% else %}
<p>Please make sure you've entered your campaign's description, target, deadline, and premiums, and previewed your campaign, before launching.</p>
{% endif %}
</div>
{% endifequal %}
{% ifequal campaign_status 'ACTIVE' %}
<div class="tabs-3">
<h2 class="thank-you">Your campaign is now active! Hooray!</h2>
<h3>What to do next</h3>
<ul>
<li>Tell your friends, relatives, media contacts, professional organizations, social media networks -- everyone!</li>
<li>Check in with your campaign frequently. Use comments, description updates, and maybe new custom premiums to spark additional interest, keep supporters engaged, and keep the momentum going.</li>
<li>Watch media and social networks for mentions of your campaign, and engage in those conversations.</li>
<li>Need help doing any of this? Talk to us.</li>
</ul>
</div>
{% endifequal %}
{% endif %}
{% endwith %}
{% endblock %}

View File

@ -1,4 +1,4 @@
Congratulations, you wished for it, and now there is an active Campaign for {{ campaign.work.title }} to be Unglued. If ungluers like you pledge {{ campaign.target }} by {{ campaign.deadline }}, this book will be released under a Creative Commons license for everyone to enjoy.
Congratulations, you wished for it, and now there is an active Campaign for {{ campaign.work.title }} to be unglued. If ungluers like you pledge {{ campaign.target }} by {{ campaign.deadline }}, this book will be released under a Creative Commons license for everyone to enjoy.
You can help!
@ -10,4 +10,4 @@ Join the discussion: share why you love {{ campaign.work.title }} and the world
Thank you!
{{ active_claim.rights_holder.rights_holder_name }} (rights holder for {{ campaign.work.title }}) and the Unglue.It Team
{{ active_claim.rights_holder.rights_holder_name }} (rights holder for {{ campaign.work.title }}) and the Unglue.it Team

View File

@ -1 +1 @@
Campaign for {{ campaign.work }} now active at Unglue.It!
Campaign for {{ campaign.work }} now active at Unglue.it!

View File

@ -23,7 +23,7 @@
</div>
<div class="press_spacer"></div>
<div class="pressemail">
Thanks for your interest! As of January 2011 Unglue.It is in alpha release. Things are mostly working but they're rough around the edges and may change without notice. Please do kick the tires and forgive us any mess.
Thanks for your interest! As of January 2011 Unglue.it is in alpha release. Things are mostly working but they're rough around the edges and may change without notice. Please do kick the tires and forgive us any mess.
</div>
</div>
@ -34,7 +34,7 @@
PubWest Endsheet - Spring 2012
</div>
<div>
<a href="http://americanlibrariesmagazine.org/solutions-and-services/unglueit">Solutions and Services: Unglue.It</a><br />
<a href="http://americanlibrariesmagazine.org/solutions-and-services/unglueit">Solutions and Services: Unglue.it</a><br />
American Libraries - February 14, 2012
</div>
<div>
@ -46,24 +46,24 @@
<a id="overview"></a><h2>Overview</h2>
<dl>
<dt>What?</dt>
<dd>Unglue.It offers a win-win solution to readers, who want to read and share their favorite books conveniently, and rights holders, who want to be rewarded for their work.<br /><br />
<dd>Unglue.it offers a win-win solution to readers, who want to read and share their favorite books conveniently, and rights holders, who want to be rewarded for their work.<br /><br />
We will run <a href="http://en.wikipedia.org/wiki/Crowdfunding">crowdfunding</a> campaigns to raise money for specific, already-published books. When we reach goals set by the rights holders, we'll pay them to unglue their work. They'll issue an electronic edition with a <a href="http://creativecommons.org">Creative Commons</a> <a href="http://creativecommons.org/licenses/by-nc-nd/3.0/">BY-NC-ND</a> license. This license will make the edition free and legal for everyone to read, copy, and share, noncommercially, worldwide.<br /><br />
At present, in our alpha phase, we're not running live campaigns (though you may see some fake campaign data for testing purposes). However, most of the other features of the site -- such as searching for books, adding them to your wishlist, and personalizing your user profile -- work. We invite you to try them out and give us feedback.<br /><br />
Once we've fully tested our payment processes and user experience, we'll have a beta launch. At this point we'll announce our founding rights holders, run live campaigns, and invite everyone to join the site.</dd>
<dt>Why?</dt>
<dd>As ereaders proliferate, more and more people are enjoying the ereading experience. However, their favorite books may not be available as ebooks. Their ebooks may come with DRM which makes them unreadable on certain devices, and difficult or impossible to lend to friends. Or they may not be able to tell if they have the legal right to use the book as they'd like. The situation is even more challenging for libraries, which may not be able to acquire ebooks at all, or can only acquire them under legal terms and DRM restrictions which run counter to library lending.<br /><br />
When books have a clear, established legal license which promotes use, they can be read more widely, leading to enjoyment, scholarship, and innovation. By raising money to compensate authors and publishers up front, Unglue.It encourages the benefits of openness while ensuring sustainability for creators.<br /><br />
For more background, read our president Eric Hellman's thoughts on <a href="http://go-to-hellman.blogspot.com/2011/04/public-broadcasting-model-for-ebooks.html">a public broadcasting model for ebooks</a> (on why the numbers work) and <a href="http://go-to-hellman.blogspot.com/search/label/Unglue.it">the development of Unglue.It</a>.</dd>
When books have a clear, established legal license which promotes use, they can be read more widely, leading to enjoyment, scholarship, and innovation. By raising money to compensate authors and publishers up front, Unglue.it encourages the benefits of openness while ensuring sustainability for creators.<br /><br />
For more background, read our president Eric Hellman's thoughts on <a href="http://go-to-hellman.blogspot.com/2011/04/public-broadcasting-model-for-ebooks.html">a public broadcasting model for ebooks</a> (on why the numbers work) and <a href="http://go-to-hellman.blogspot.com/search/label/Unglue.it">the development of Unglue.it</a>.</dd>
<dt>Who?</dt>
<dd>Unglue.It is a service of Gluejar, Inc. We are <a href="{% url supporter "eric" %}">Eric Hellman</a>, <a href="{% url supporter "AmandaM" %}">Amanda Mecke</a>, <a href="{% url supporter "RaymondYee" %}">Raymond Yee</a>, and <a href="{% url supporter "andromeda" %}">Andromeda Yelton</a>, with help from designer Stefan Fabry and software developers Jason Kace and <a href="{% url supporter "edsu" %}">Ed Summers</a>. We come from the worlds of entrepreneurship, linked data, physics, publishing, education, and library science, to name a few. You can learn more about us at our Unglue.It pages (linked above) or <a href="http://gluejar.com/team">the team page</a> of our corporate site.</dd>
<dd>Unglue.it is a service of Gluejar, Inc. We are <a href="{% url supporter "eric" %}">Eric Hellman</a>, <a href="{% url supporter "AmandaM" %}">Amanda Mecke</a>, <a href="{% url supporter "RaymondYee" %}">Raymond Yee</a>, and <a href="{% url supporter "andromeda" %}">Andromeda Yelton</a>, with help from designer Stefan Fabry and software developers Jason Kace and <a href="{% url supporter "edsu" %}">Ed Summers</a>. We come from the worlds of entrepreneurship, linked data, physics, publishing, education, and library science, to name a few. You can learn more about us at our Unglue.it pages (linked above) or <a href="http://gluejar.com/team">the team page</a> of our corporate site.</dd>
<dt>When?</dt>
<dd>Unglue.It is in alpha -- a limited release for testing purposes -- as of January 2011. If you <a href="http://eepurl.com/fKLfI">sign up for our newsletter</a>, we'll tell you the moment we're in beta. At that point we'll have active campaigns and we will open account signups to everyone.</dd>
<dd>Unglue.it is in alpha -- a limited release for testing purposes -- as of January 2011. If you <a href="http://eepurl.com/fKLfI">sign up for our newsletter</a>, we'll tell you the moment we're in beta. At that point we'll have active campaigns and we will open account signups to everyone.</dd>
<dt>Where?</dt>
<dd>Gluejar is a New Jersey corporation, but its employees and contractors live and work across North America. The best way to contact us is by email, <a href="mailto:press@gluejar.com">press@gluejar.com</a>.</dd>
<dt>What does it cost?</dt>
<dd>Unglue.It is free to join and explore. Supporters pay only if they choose to support campaigns, and the amount is up to them. Unglue.It takes a small percentage from successful campaigns, with the remainder going to the rights holders.</dd>
<dd>Unglue.it is free to join and explore. Supporters pay only if they choose to support campaigns, and the amount is up to them. Unglue.it takes a small percentage from successful campaigns, with the remainder going to the rights holders.</dd>
<dt>What's your technology?</dt>
<dd>Unglue.It is built using <a href="http://python.org/">Python</a> and the <a href="https://www.djangoproject.com/">Django framework</a>. We use data from the <a href="http://code.google.com/apis/books/docs/v1/getting_started.html">Google Books</a>, <a href="http://openlibrary.org/developers/api">Open Library</a>, <a href="http://www.librarything.com/api">LibraryThing</a>, and <a href="http://www.goodreads.com/api">GoodReads</a> APIs; we appreciate that they've made these APIs available, and we're returning the favor with <a href="/api/help">our own API</a>. You're welcome to use it. We use <a href="http://lesscss.org/">Less</a> to organize our CSS. We collaborate on our code at <a href="https://github.com/">GitHub</a> and deploy to the cloud with <a href="http://aws.amazon.com/ec2/">Amazon EC2</a>.</dd>
<dd>Unglue.it is built using <a href="http://python.org/">Python</a> and the <a href="https://www.djangoproject.com/">Django framework</a>. We use data from the <a href="http://code.google.com/apis/books/docs/v1/getting_started.html">Google Books</a>, <a href="http://openlibrary.org/developers/api">Open Library</a>, <a href="http://www.librarything.com/api">LibraryThing</a>, and <a href="http://www.goodreads.com/api">GoodReads</a> APIs; we appreciate that they've made these APIs available, and we're returning the favor with <a href="/api/help">our own API</a>. You're welcome to use it. We use <a href="http://lesscss.org/">Less</a> to organize our CSS. We collaborate on our code at <a href="https://github.com/">GitHub</a> and deploy to the cloud with <a href="http://aws.amazon.com/ec2/">Amazon EC2</a>.</dd>
<dt>I have more questions...</dt>
<dd>Please consult our <a href="/faq/">FAQ</a> (sidebar at left); join the site and explore its features for yourself; or email us, <a href="press@gluejar.com">press@gluejar.com</a>.</dd>
</dl>
@ -74,7 +74,7 @@ For more background, read our president Eric Hellman's thoughts on <a href="http
PubWest Endsheet - Spring 2012
</div>
<div>
<a href="http://americanlibrariesmagazine.org/solutions-and-services/unglueit">Solutions and Services: Unglue.It</a><br />
<a href="http://americanlibrariesmagazine.org/solutions-and-services/unglueit">Solutions and Services: Unglue.it</a><br />
American Libraries - February 14, 2012
</div>
<div>
@ -138,7 +138,7 @@ For more background, read our president Eric Hellman's thoughts on <a href="http
Apoge Online - February 6, 2012
</div>
<div>
<a href="http://www.ereaders.nl/article.php?article_id=100623">Unglue.It Maakt Ebooks Gratis Door Crowdfunding</a> <I>(Dutch)</I><br />
<a href="http://www.ereaders.nl/article.php?article_id=100623">Unglue.it Maakt Ebooks Gratis Door Crowdfunding</a> <I>(Dutch)</I><br />
eReaders - February 3, 2012
</div>
<div>
@ -165,7 +165,8 @@ For more background, read our president Eric Hellman's thoughts on <a href="http
<a id="video"></a><h2>Video</h2>
<div class="pressvideos">
<div>
<object width="480" height="270"><param name="allowfullscreen" value="true" /><param name="allowscriptaccess" value="always" /><param name="movie" value="https://secure.vimeo.com/moogaloop.swf?clip_id=39352026&amp;server=secure.vimeo.com&amp;show_title=1&amp;show_byline=0&amp;show_portrait=0&amp;color=00adef&amp;fullscreen=1&amp;autoplay=0&amp;loop=0" /><embed src="https://secure.vimeo.com/moogaloop.swf?clip_id=39352026&amp;server=secure.vimeo.com&amp;show_title=1&amp;show_byline=0&amp;show_portrait=0&amp;color=00adef&amp;fullscreen=1&amp;autoplay=0&amp;loop=0" type="application/x-shockwave-flash" allowfullscreen="true" allowscriptaccess="always" width="480" height="270"></embed></object><br />
<div class="mediaborder">
<object width="480" height="270"><param name="allowfullscreen" value="true" /><param name="allowscriptaccess" value="always" /><param name="movie" value="https://secure.vimeo.com/moogaloop.swf?clip_id=39352026&amp;server=secure.vimeo.com&amp;show_title=1&amp;show_byline=0&amp;show_portrait=0&amp;color=00adef&amp;fullscreen=1&amp;autoplay=0&amp;loop=0" /><embed src="https://secure.vimeo.com/moogaloop.swf?clip_id=39352026&amp;server=secure.vimeo.com&amp;show_title=1&amp;show_byline=0&amp;show_portrait=0&amp;color=00adef&amp;fullscreen=1&amp;autoplay=0&amp;loop=0" type="application/x-shockwave-flash" allowfullscreen="true" allowscriptaccess="always" width="480" height="270"></embed></object></div><br />
<I>March 2012</I><br />
Andromeda Yelton; "The Future of Ebooks" panel at <a href="http://www.infotoday.com/cil2012/">Computers in Libraries</a>.
</div>
@ -206,7 +207,7 @@ For more background, read our president Eric Hellman's thoughts on <a href="http
</div>
<div class="outer">
<div><a href="/static/images/workpage.png"><img src="/static/images/workpage_thumb.png" class="screenshot" alt="screenshot" /></a></div>
<div class="text"><p>300 ppi screenshot of a book page on Unglue.It. Features include links to the book, where available, at Google Books, Open Library, GoodReads, and LibraryThing; social sharing options; tabs with user comments and more information; and an explore bar linking to other books and users. The user list is dynamically generated, reflecting others interested in the same book.</p></div>
<div class="text"><p>300 ppi screenshot of a book page on Unglue.it. Features include links to the book, where available, at Google Books, Open Library, GoodReads, and LibraryThing; social sharing options; tabs with user comments and more information; and an explore bar linking to other books and users. The user list is dynamically generated, reflecting others interested in the same book.</p></div>
</div>
<div class="outer">
<div><a href="/static/images/supporter_listview.png"><img src="/static/images/supporter_listview_thumb.png" class="screenshot" alt="screenshot" /></a></div>

View File

@ -3,7 +3,7 @@
{% block title %}Register for an account{% endblock %}
{% block doccontent %}
<h3>Sign up for a Unglue It account:</h3>
<h3>Sign up for a Unglue.it account:</h3>
<form method='post' action=''>{% csrf_token %}
{{ form }}

View File

@ -8,6 +8,10 @@
<script type="text/javascript" src="/static/js/jquery.dj.selectable.js"></script>
{% endblock %}
{% block topsection %}
{% endblock %}
{% block doccontent %}
<h1>unglue.it Tools for Rightsholders</h1>
@ -95,7 +99,7 @@ Any questions not covered here? Please email us at <a href="mailto:rights@gluej
{% endfor %}
</dl>
{% else %}
<I>If you were a registered rights holder with Unglue.It, you'd be able to see and manage your campaigns here. If you hold electronic rights to one or more works and you'd like to be a registered rights holder, please contact us at <a href="mailto:rights@gluejar.com">rights@gluejar.com</a>.</I>
<I>If you were a registered rights holder with Unglue.it, you'd be able to see and manage your campaigns here. If you hold electronic rights to one or more works and you'd like to be a registered rights holder, please contact us at <a href="mailto:rights@gluejar.com">rights@gluejar.com</a>.</I>
{% endif %}
<h2>How to launch an Unglue.it campaign</h2>

View File

@ -37,11 +37,14 @@
<a id="acceptance"><h3>Acceptance of Terms</h3></a>
<p>Welcome to Unglue.it, the website and online service of Gluejar, Inc. (the “Company”, "Unglue.it" "we," or "us"). This terms of use agreement (the "Agreement") governs your use of the web site and the service owned and operated by Gluejar (collectively with the site, the “Service”). This Agreement also incorporates the Privacy Policy available at Unglue.it/privacy, and all other operating rules, policies and procedures that may be published from time to time on the Site by Company, each of which is incorporated by reference and each of which may be updated by Company from time to time without notice to you. In addition, some services offered through the Service may be subject to additional terms promulgated by Company from time to time; your use of such services is subject to those additional terms, which are incorporated into these Terms of Use by this reference. By using this site in any manner, you agree to be bound by this Agreement, whether or not you are a registered user of our Service.</p>
<p>Welcome to Unglue.it, the website and online service of Gluejar, Inc. (the “Company”, "Unglue.it" "we," or "us"). Unglue.it is a crowd funding platform that facilitates the distribution and licensing of copyrighted literary works. The holder(s) of rights to a copyrighted literary work (the “Rights Holder”) can use the Unglue.it Service to accept and collect contributions in exchange for the release of the work, in digital form, under a Creative Commons License. Persons who wish to contribute (“Supporters”) towards the fundraising goal set by the Rights Holder can do so using Unglue.it. The mechanism by which Rights Holders collect contributions from Supporters using the Service is known as an “Unglue.it Campaign.”
This terms of use agreement (the "Agreement") governs your use of the web site and the service owned and operated by Gluejar (collectively with the site, the “Service”). This Agreement also incorporates the Privacy Policy available at <a href="https://Unglue.it/privacy">Unglue.it/privacy</a>, and all other operating rules, policies and procedures that may be published from time to time on the Site by Company, each of which is incorporated by reference and each of which may be updated by Company from time to time without notice to you. In addition, some services offered through the Service may be subject to additional terms promulgated by Company from time to time; your use of such services is subject to those additional terms, which are incorporated into these Terms of Use by this reference. By using this site in any manner, you agree to be bound by this Agreement, whether or not you are a registered user of our Service.</p>
<a id="registration"><h3>Registration</h3></a>
<p>You do not have to register an account in order to visit Unglue.it. To access certain features of the Service, though, including wishlisting and pledging, you will need to register with Unglue.it. You shall not (i) select or use as a User ID a name of another person with the intent to impersonate that person; (ii) use as a User ID a name subject to any rights of a person other than you without appropriate authorization; or (iii) use as a User ID a name that is otherwise offensive, vulgar or obscene. Company reserves the right to refuse registration of, or cancel a User ID and domain in its sole discretion. You are solely responsible for activity that occurs on your account and shall be responsible for maintaining the confidentiality of your Company password. You shall never use another users account without such other users express permission. You will immediately notify Company in writing of any unauthorized use of your account, or other account related security breach of which you are aware.</p>
<p>You do not have to register an account in order to visit Unglue.it. To access certain features of the Service, though, including wishlisting and pledging, you will need to register with Unglue.it. You shall not (i) select or use as a Username a name of another person with the intent to impersonate that person; (ii) use as a Username a name subject to any rights of a person other than you without appropriate authorization; or (iii) use as a Username a name that is otherwise offensive, vulgar or obscene. Company reserves the right to refuse registration of, or cancel a Username and domain in its sole discretion. You are solely responsible for activity that occurs on your account and shall be responsible for maintaining the confidentiality of your Company password. You shall never use another users account without such other users express permission. You will immediately notify Company in writing of any unauthorized use of your account, or other account related security breach of which you are aware.</p>
<a id="use"><h3>Use of the Service</h3></a>
@ -61,8 +64,8 @@
<p>Some areas of the Service may allow you to post feedback, comments, questions, and other information. Any such postings, together with Campaigns, constitute "User Content." You are solely responsible for your User Content that you upload, publish, display, or otherwise make available (hereinafter, "post") on the Service, and you agree that we are only acting as a passive conduit for your online distribution and publication of your User Content. Unglue.it does not endorse, control, or have ownership rights to User Content. By posting User Content, you:</p>
<ul class="terms">
<li>acknowledge that you may be identified publicly by your User ID in association with any such User Content;</li>
<li>grant Company a worldwide, non-exclusive, perpetual, irrevocable, royalty-free, fully paid, sublicensable and transferable license to use, edit, modify, reproduce, distribute, prepare derivative works of, display, perform, and otherwise fully exploit the User Content in connection with the Service and Companys (and its successors and assigns) business, including without limitation for promoting and redistributing part or all of the Site (and derivative works thereof) or the Service in any media formats and through any media channels (including, without limitation, third party websites). You also hereby do and shall grant each user of the Service a non-exclusive license to access your User Content through the Service, and to use, edit, modify, reproduce, distribute, prepare derivative works of, display and perform such User Content solely for personal, non-commercial use. For clarity, the foregoing license grant to Company does not affect your other ownership or license rights in your User Content, including the right to grant additional licenses to the material in your User Content, unless otherwise agreed in writing;</li>
<li>acknowledge that you may be identified publicly by your Username in association with any such User Content;</li>
<li>grant the Company a worldwide, non-exclusive, perpetual, irrevocable, royalty-free, fully paid, sublicensable and transferable license to use, edit, modify, reproduce, distribute, prepare derivative works of, display, perform, and otherwise fully exploit the User Content in connection with the Service and Companys (and its successors' and assigns') business, including without limitation for promoting and redistributing part or all of the Site (and derivative works thereof) or the Service in any media formats and through any media channels (including, without limitation, third party websites). You also hereby do and shall grant each user of the Service a non-exclusive license to access your User Content through the Service, and to use, edit, modify, reproduce, distribute, prepare derivative works of, display and perform such User Content solely for personal, non-commercial use. For clarity, the foregoing license grant to Company does not affect your other ownership or license rights in your User Content, including the right to grant additional licenses to the material in your User Content, unless otherwise agreed in writing;</li>
<li>represent and warrant, and can demonstrate to Companys full satisfaction upon request that you (i) own or otherwise control all rights to all content in your User Content, or that the content in such User Content is in the public domain or subject to an appropriate license (e.g. Creative Commons), (ii) you have full authority to act on behalf of any and all owners of any right, title or interest in and to any content in your User Content to use such content as contemplated by these Terms of Use and to grant the license rights set forth above, (iii) you have the permission to use the name and likeness of each identifiable individual person and to use such individuals identifying or personal information as contemplated by these Terms of Use; and (iv) you are authorized to grant all of the aforementioned rights to the User Submissions to Company and all users of the Service;</li>
<li>agree to pay all royalties and other amounts owed to any person or entity due to your posting of any User Content to the Service;</li>
<li>warrant that the use or other exploitation of such User Content by Company and use or other exploitation by users of the Site and Service as contemplated by this Agreement will not infringe or violate the rights of any third party, including without limitation any privacy rights, publicity rights, copyrights, contract rights, or any other intellectual property or proprietary rights; and</li>
@ -77,27 +80,55 @@
<a id="fundraising"><h3>Campaigns: Fund-Raising and Commerce</h3></a>
<p>Unglue.it is a venue for fund-raising and commerce. Unglue.it allows certain users ("Rights Holders") to list campaigns and raise funds from other users ("Supporters"). All funds are collected for Rights Holders by Paypal. </p>
<p>Unglue.it is a venue for fund-raising and commerce. Unglue.it allows certain users ("Rights Holders") to list campaigns and raise funds from other users ("Supporters"). All funds are collected for Rights Holders by third party payment processors such as Amazon Payments or Paypal. </p>
<p>Unglue.it shall not be liable for your interactions with any organizations and/or individuals found on or through the Service. This includes, but is not limited to, delivery of goods and services, and any other terms, conditions, warranties or representations associated with campaigns on Unglue.it. Unglue.it is not responsible for any damage or loss incurred as a result of any such dealings. All dealings are solely between you and such organizations and/or individuals. Unglue.it is under no obligation to become involved in disputes between Supporters and Rights Holders, or between site members and any third party. In the event of a dispute, you release Unglue.it, its officers, employees, agents and successors in rights from claims, damages and demands of every kind, known or unknown, suspected or unsuspected, disclosed or undisclosed, arising out of or in any way related to such disputes and our Service.</p>
<a id="supporting"><h3>Supporting a Campaign</h3></a>
<p>Joining Unglue.it is free. However, Unglue.it may provide you the opportunity to make Donations or Pledges (collectively, Contributions) to Campaigns on the Service. You may contribute to any active Campaign in any amount you choose, subject to limitation imposed by payment processors such as PayPal. You may contribute to as many Campaigns as you like.</p>
<p>Joining Unglue.it is free. However, Unglue.it may provide you the opportunity to make Donations or Pledges (collectively, Contributions) to Campaigns on the Service. You may contribute to any active Campaign in any amount you choose, subject to limitation imposed by payment processors such as PayPal or Amazon Payments. You may contribute to as many Campaigns as you like.</p>
<p>It is solely your choice to contribute to a Campaign. You understand that making a Contribution to a Project does not give you any rights in or to that Campaign or its associated work(s), including without limitation any ownership, control, or distribution rights. You understand that the Rights Holder shall be free to solicit other funding for the Campaign, enter into contracts for the Campaign, and otherwise direct the Campaign in its sole discretion. You further understand that nothing in this Agreement or otherwise limits Unglue.it's right to enter into agreements or business relationships relating to Campaigns. Unglue.it does not guarantee that any Campaigns goal will be met. Any Rewards offered to you are between you and the Rights Holder only, and Unglue.it does not guarantee that Rewards will be delivered or satisfactory to you. You understand that your pledge may be declined at Unglue.it's sole discretion. Unglue.it does not warrant the use of any Campaign funding or the outcome of any Campaign.</p>
<p>Donations to Campaigns are nonrefundable. Under certain circumstances Unglue.it may, but is under no obligation to, seek the refund of Campaign Funding if a Rights Holder misrepresents the Campaign or misuses the funds. In the event of a suspended or withdrawn campaign, Pledges will be allowed to expire according to their original time limits. If a suspended campaign is resolved and reactivated within a Pledges time limit, that Pledge will remain active.</p>
<p>You acknowledge and agree that all your Contributions are between you, the Rights Holder, and the Processor only, and that Unglue.it is not responsible for Contribution transactions, including without limitation any personal or payment information you provide to the Processor.</p>
<p>You acknowledge and agree that all your Contributions are between you, the Rights Holder, and the Processor only, and that Unglue.it is not responsible for Contribution transactions, including without limitation any personal or payment information you provide to the Processor. </p>
<p>Unglue.it makes no representations regarding the deductibility of any Contribution for tax purposes. Please consult your tax advisor for more information.</p>
<p>You acknowledge and understand that the Company uses third party payment processing services to collect the Contributions from you and/or for the distribution of Contributions to the Rights Holder or otherwise pursuant to the terms of this Agreement (a “Payment Processor.”) and that your Pledge may be subject to terms and conditions imposed on you by the Payment Processor. The Company reserves the right, in its sole discretion, to select Payment Processors for this purpose. </p>
<p>You understand that funds pledged are not debited from the your account until the Unglue.it Campaign to which the Supporter has contributed has concluded and the Campaign Goal has been met. You further appreciate that Rights Holders act in reliance on your pledge in determining whether their Campaign Goal has been met. You agree to maintain a sufficient balance in the account from which your Pledge is to be paid to cover the full amount of the pledged Contribution at the conclusion of a successful Unglue.it Campaign.</p>
<p>Rights Holders may offer perks, rewards or other signs of gratitude (“Premiums”) to its Supporters. You understand that the Service is simply a platform for the conduct of Unglue.it Campaigns. When you make a pledge, and/or request a Premium using the Service, the Rights Holder is solely responsible for delivery of any and all goods and services, and any other terms, conditions, warranties or representations made to the Supporter in connection with an Unglue.it Campaign (“Fulfillment.”) You hereby release the Company and its officers, employees, agents, licensees and assignees from any and all loss, damage or other liability that may arise out of the Supporters participation in an Unglue.it Campaign based on insufficient Fulfillment or otherwise.</p>
<p>You understand and agree that delivery of a Premium, and/or distribution of an Creative Commons licensed work, may not occur until a period of time after the conclusion of a successful Unglue.it Campaign. Such a delay may occur, for example, when production or conversion of the Premium or digital work is not completed before the conclusion of the campaign.</p>
<p>You authorize the Company to hold funds for up to ninety (90) days following the conclusion of an Unglue.it Campaign, pending proof of fulfillment by the Rights Holder.</p>
<a id="rightsholders"><h3>Campaigns: Additional Terms for Rights Holders</h3></a>
<p>
The Company's mission is to make copyrighted literary works ("Works") freely and readily available to all who wish to read them in digital form, while ensuring that authors, publishers and other copyright owners are compensated for their efforts and investment in creating and developing the Works. </p>
<p>Joining Unglue.it as a Rights Holder is free. However, we do charge a Sales Commission on the completion of a successful campaign, as well as require Rights Holders to supply a Standard Ebook File at their own expense, as described in a Platform Services Agreement. Pursuant to the Platform Services Agreement, Gluejar will collect a Sales Commission of 6% on the Gross Licensing Price of successful campaigns. </p>
<p>The Company invites and encourages Rights Holders to propose Works for Creative Commons licensing via Unglue.it Campaigns. Rights Holders have to apply to the Company to initiate campaigns and, upon acceptance, the Rights Holder must enter into a Platform Services Agreement with the Company prior to starting an Unglue.it Campaign.</p>
<p>The Standard Ebook File referenced in the Platform Service Agreement shall meet the following set of criteria:</p>
<p>Rights Holders who have executed a Platform Services Agreement may claim works and may initiate and conduct campaigns, as described in the Rights Holder Tools page at ( <a href="http://unglue.it/rightsholders/">http://unglue.it/rightsholders/</a>.</p>
<p>There is no charge for Rights Holders to participate in Unglue.it. However, the company will withhold a Sales Commission of 6% of gross aggregate Contributions upon the completion of a successful campaign. The Rights Holder is responsible for providing a Standard Ebook File at their own expense, as described in a Platform Services Agreement. </p>
<p>The Rights Holder hereby authorizes the Company to use the Service to display and market the Subject Work, to collect Contributions or cause Contributions to be collected from Supporters on behalf of the Rights Holder, and to retain, reserve and/or distribute the Contributions to the Rights Holder, to the Company, to Designated Vendors (as defined below) or otherwise pursuant to the terms of this Agreement. </p>
<p>The Rights Holder acknowledges and understands that the Company uses one or more third party payment processing services to collect Contributions and/or for the distribution of Contributions to the Rights Holder or otherwise pursuant to the terms of this Agreement (the “Payment Processor.”) The Company reserves the right, in its sole discretion, to select Payment Processors for this purpose and to change Payment Processors at any time, with or without notice to the Rights Holder. The Rights Holder hereby consents to any and all modifications of any of the terms and conditions of this Agreement as may be required or requested by the Payment Processor from time to time as a condition of continuing service to the Company or otherwise. The Rights Holder understands and agrees that the Company may hold funds for up to 90 days following the conclusion of an Unglue.it Campaign, pending proof of fulfillment by the Rights Holder, and that Payment Processors may withhold funds for various reasons according to their own terms of service.</p>
<p>The Payment Processors used at this time are Paypal and Amazon Payments. Rights Holders must have a Paypal account and agree to Paypal's terms and conditions. </p>
<p>The duration of any single Unglue.it Campaign shall be determined by the Rights Holder, provided that no single Unglue.it Campaign shall be longer than one hundred eighty (180) days.</p>
<p>No Premium may be offered on the Website constitutes an illegal, prohibited or restricted item in the United States. Items that are prohibited or restricted by the Company include, but are not limited to, alcoholic beverages, firearms, knives or other weapons, drugs, drug-like substances and paraphernalia, counterfeit currency and stamps, credit cards, electronic surveillance equipment, hazardous materials, medical devices, or any other item that is illegal. The Company reserves the right to remove Premium items from the Website in its sole discretion and without notice to the Rights Holder.</p>
<p>The Rights Holder shall not, whether as a Premium or otherwise, use the Service to offer any financial incentive to potential Supporters. The prohibition against financial incentives expressly includes, but is not limited to, a share of profits or ownership interest in any entity, interest payable on any Contributions, or any Premium that would require registration under the securities laws of any country or state, including but not limited to the state or federal securities laws and regulations of the United States.</p>
<p>The Standard Ebook File as referenced in the Platform Service Agreement shall meet the following set of criteria:</p>
<ul class="terms">
<li>Unless otherwise permitted in writing by Unglue.it, the file shall use the EPUB standard format format according to best pactice at time of submission. Exceptions will be made for content requiring more page layout than available in prevailing EPUB implementations.</li>
<li>The file shall be compliant where ever reasonable with the QED inspection checklist specified at <a href="http://www.publishinginnovationawards.com/featured/qed">http://www.publishinginnovationawards.com/featured/qed </a> </li>
@ -105,27 +136,45 @@
<li>The file shall contain front matter which includes the following:
<ul class="terms">
<li>a list of ISBNs of other editions of the work</li>
<li>the Creative Commons license applied to the work, formatted in accordance with best practices at http://wiki.creativecommons.org/Marking/Creators</li>
<li>the Creative Commons license selected for the campaign, formatted in accordance with best practices at http://wiki.creativecommons.org/Marking/Creators and a statement that for the purposes of the license, "Non-Commercial" use shall include, but not be limited to, the distribution of the Work by a commercial entity without charge.</li>
<li>an acknowledgement of supporters of the work, formatted in accordance with the premium descriptions on the Campaign page.</li>
<li>The Unglue.it logo</li>
<li>the text “CC edition release enabled by Unglue.it users” (including the hyperlink to the site)</li>
<li>The Rights Holder must offer the schedule of Standard Premiums as described at at ( <a href="http://unglue.it/rightsholders/">http://unglue.it/rightsholders/</a> in effect at the time of launching an Unglue.it Campaign.</li>
</ul></li>
<li>The cover graphic shall match any description given in the Campaign.</li>
<li>Any graphics which must be excluded from the released ebook shall be specified in the description given in the campaign.
</ul>
<p>Rights Holders who have executed a Platform Services Agreement may initiate and conduct campaigns, as described in the Rights Holder Tools page at ( <a href="http://unglue.it/rightsholders/">http://unglue.it/rightsholders/</a>.</p>
<p>To provide Confirmation of Fulfillment and become eligible to receive funds raised in a successful campaign, the Rights Holder shall make either make available to the Company a Standard Ebook File for the work, or the Rights Holder shall designate a third party vendor from a list of vendors approved by the Company (the “Designated Vendor”) to create the Converted eBook at a rate that has been established between the Designated Vendor and the Rights Holder (the “Conversion Cost.”) The Rights Holder shall provide the Company with written notice of the name and contact information for the Designated Vendor, the Conversion Cost, and any supporting documentation or other verification as may be reasonably requested by the Company.</p>
<p>Unglue.it may release Supporter information to Rights Holders, or invite Supporters to share this information, as needed to fulfill the terms of campaigns. Rights Holders, agree to treat this information in a manner consistent with the Unglue.it <a href="http://unglue.it/privacy/">Privacy Policy</a>.</p>
<p>In the event that the Company releases Supporter information to the Rights Holder, or invites Supporters to share such information with the Rights Holder, Rights Holders agree to treat such information in a manner consistent with the Companys <a href="http://unglue.it/privacy/">Privacy Policy</a>.</p>
<p>Once an Unglue.it Campaign has been launched, neither the Subject Work nor any of the Premiums may be revoked or modified, except that either may be supplemented provided that the Digital License Fee is not increased. The Rights Holder understands that any Pledge may be declined for any reason or for no reason at the sole discretion of the Company.</p>
<p>Contributions shall not be collected by the Company from Supporters until the Campaign is successfully completed and the Campaign Goal met or exceeded. In the event that any of the promised Contributions cannot be collected by the Company or its Payment Processor after the date of the successful conclusion of the Campaign, the Rights Holder understands that neither the Company nor the Payment Processor shall have any obligation whatsoever in connection with the collection of such monies. The Rights Holder understands and knowingly accepts the risk that the actual Contributions collected or capable of collection may be less than the total Contributions promised by the Supporters individually or in the aggregate. The Rights Holder understands that it is obligated to release the Converted eBook and to ship the promised Premiums even if the actual Contributions collected are less than the Campaign Goal.</a>
<p>Immediately upon the date of the conclusion of a successful Unglue.it Campaign, the Rights Holder agrees to and hereby does grant to the Company the right (but not the obligation) to place the Subject Work in the Internet Archive [archive.org] or such other open digital libraries as the Company may select in its sole discretion, and to distribute the Subject Work on the Website, whether directly or indirectly, provided that the copyright notice and mark of the applicable CC License is at all times clearly displayed in association with the Subject Work.</p>
<p>Rights Holders are responsible for paying all fees and applicable taxes associated with their use of the site. In the event a listing is removed from the Service for violating the Terms of Use, all fees paid will be non-refundable, unless in its sole discretion Unglue.it determines that a refund is appropriate.</p>
<p>Rights Holders may initiate refunds at their own discretion. Unglue.it is not responsible for issuing refunds for funds that have been collected by Rights Holders.</p>
<p>Donations and Pledges to campaigns are collected on behalf of Unglue.it by a payment processor such as PayPal. Unglue.it makes no guarantees regarding the performance or fairness of Paypal. Additionally, because of occasional failures of some credit cards, Unglue.it cannot guarantee the full receipt of pledged or donated amounts.</p>
<p>No later than seven (7) days from the receipt by the Company of the Rights Holders Confirmation of Fulfillment or, where applicable, satisfactory Fulfillment Verification, the Company shall transfer into an approved account designated by the Rights Holder for this purpose, all monies actually received from the Supporters in connection with the Unglue.it Campaign for the Subject Work (the “Contributions,”) minus the following:</p>
<ol>
<li> any transaction fees deducted or otherwise charged to the Company by the Payment Processor;</li>
<li> any transaction fees deducted or other charges imposed by the Rights Holders respective banks, credit card companies or other financial institutions in connection with the Contributions;</li>
<li> the Companys sales commission as set at the start of the Campaign</li>
<li> reimbursement of the Companys actual costs for production of a Standard Ebook File for the Subject Work, in the event that no Confirmation of Fulfillment is timely delivered to the Company;</li>
<li> the Conversion Costs, to be paid by the Company directly to the Designated Vendor upon delivery of the Converted eBook; in the event that the Rights Holder has designated a conversion vendor </li>
</ol>
<p>Though Unglue.it cannot be held liable for the actions of a Rights Holder, Rights Holders are nevertheless wholly responsible for fulfilling obligations both implied and stated in any campaign listing they create. Unglue.it reserves the right to cancel a campaign, refund all associated members' payments, and cancel all associated members pledges at any time for any reason. Unglue.it reserves the right to cancel, interrupt, suspend, or remove a campaign listing at any time for any reason.</p>
<p>Notwithstanding anything to the contrary set forth in or implied by this Agreement, all payment obligations undertaken by the Company in connection with any Campaign are expressly subject to the Platform Services Agreement entered into between the Rights Holder and the Company. </p>
<p> The Company may suspend or terminate the account of any Rights Holder at any time in the event that the Company determines, in its sole discretion, that (i) the Rights Holders use of the Service is in violation of this Agreement; (ii) the Rights Holder is in breach of any of its representations, warrantees or obligations under this Agreement; (iii) the Company receives a complaint from a third party alleging that the Rights Holders use of the Service does or will result in copyright infringement or breach of contract.</p>
<a id="termination"><h3>Termination</h3></a>
<p>The Company may immediately terminate this Agreement at its sole discretion at any time upon written notice to the email address you have provided if you have registered, or without notice otherwise. Upon termination, you agree that the Company may immediately deactivate your account and bar you from accessing any portions of the Service requiring an account. If you wish to terminate your account, you may do so by following the instructions on the Site. Any fees paid hereunder are non-refundable. All provisions of the Terms of Use which by their nature should survive termination shall survive termination, including, without limitation, ownership provisions, warranty disclaimers, indemnity and limitations of liability.</p>
@ -146,7 +195,7 @@
<a id="governinglaw"><h3>Governing Law</h3></a>
<p>This agreement is governed by and shall be interpreted and construed according to the laws of the United States with respect to copyright law and the laws of New Jersey with regard to all other matters, without regard to conflict of laws rules to the contrary. At Unglue.its option, any controversy or claim arising out of or related to this Agreement or breach thereof, shall be settled by arbitration administered by the American Arbitration Association in accordance with its Commercial Arbitration Rules, and judgment on the award rendered by the arbitrator(s) may be entered in any court having jurisdiction thereof. If Unglue.it elects to settle any controversy or claim in court rather than through arbitration, then the dispute shall be subject to the exclusive jurisdiction of the applicable federal and state courts located in the state of New Jersey, and the Parties hereby consent to such jurisdiction.</p>
<p>This Agreement is governed by and shall be interpreted and construed according to the laws of the United States with respect to copyright law and the laws of New York with regard to all other matters, without regard to conflict of laws rules to the contrary. At Gluejars option, any controversy or claim arising out of or related to this Agreement or breach thereof, shall be settled by arbitration administered by the American Arbitration Association in accordance with its Commercial Arbitration Rules, and judgment on the award rendered by the arbitrator(s) may be entered in any court having jurisdiction thereof. If Gluejar elects to settle any controversy or claim in court rather than through arbitration, then the dispute shall be subject to the exclusive jurisdiction of the applicable federal and state courts located in New York state, and the Parties hereby consent to such jurisdiction.</p>
<a id="giraffes"><h3>Indemnification</h3></a>

View File

@ -4,7 +4,7 @@
{% block doccontent %}
<p>Thanks for helping us make Unglue.It better!</p>
<p>Thanks for helping us make Unglue.it better!</p>
<p>Would you like to <a href="{{ page }}">go back to the page you were on</a>?

View File

@ -59,7 +59,7 @@ $j(document).ready(function(){
<span>
{% if work.first_ebook %}
AVAILABLE!
{% else %}{% if work.last_campaign %}
{% else %}{% if work.last_campaign and not is_preview %}
{% if status == 'ACTIVE' %}
Unglue it! <br />
${{ work.last_campaign.current_total }}/${{ work.last_campaign.target }} <br />
@ -103,9 +103,15 @@ $j(document).ready(function(){
<div class="js-maincol-inner">
<div id="content-block">
<div class="book-detail">
{% if work.googlebooks_id %}
<div id="book-detail-img"><a href="{{ work.googlebooks_url }}">
<img src="{{ work.cover_image_thumbnail }}" alt="Find {{ work.title }} at Google Books" title="Find {{ work.title }} at Google Books" width="131" height="192" /></a>
</div>
{% else %}
<div id="book-detail-img">
<img src="/static/images/generic_cover_larger.png" alt="{{ work.title }}" title="{{ work.title }}" width="131" height="192" />
</div>
{% endif %}
<div class="book-detail-info">
<div class="layout">
<h2 class="book-name">{{ work.title }}</h2>
@ -223,18 +229,10 @@ $j(document).ready(function(){
<div id="tabs-1" class="tabs {% if activetab == '1' %}active{% endif %}">
<div class="tabs-content">
{% if status == 'ACTIVE' %}
<h3 class="tabcontent-title">A campaign is running to unglue <i>{{work.title}}</i>!</h3>
<p>The rights holder, {% for claim in work.claim.all %}
{% if claim.status == 'active' %}
{{ claim.rights_holder.rights_holder_name }}
{% endif %}
{% endfor %}
, has agreed to release <i>{{work.title}}</i> to the world as a Creative Commons licensed ebook ({{ work.last_campaign.license }}) if ungluers can join together to raise ${{ work.last_campaign.target }} by {{ work.last_campaign.deadline }}.
You can help!</p>
{{ work.last_campaign.description|safe }}
{% else %}
<h3 class="tabcontent-title">{{work.title}}</h3>
<p>{{ work.longest_description|safe }}
<p>{{ work.description|safe }}
{% endif %}
</p>
</div>
@ -271,6 +269,15 @@ $j(document).ready(function(){
<div id="tabs-4" class="tabs {% if activetab == '4' %}active{% endif %}">
<div class="tabs-content">
{% if status == 'ACTIVE' %}
<h3 class="tabcontent-title">A campaign is running to unglue <i>{{work.title}}</i>!</h3>
<p>The rights holder, {% for claim in work.claim.all %}
{% if claim.status == 'active' %}
{{ claim.rights_holder.rights_holder_name }}
{% endif %}
{% endfor %}
, has agreed to release <i>{{work.title}}</i> to the world as a Creative Commons licensed ebook ({{ work.last_campaign.license }}) if ungluers can join together to raise ${{ work.last_campaign.target }} by {{ work.last_campaign.deadline }}.
You can help!</p>
<h4>Last campaign details</h4>
{{ work.last_campaign.details|safe }}
{% endif %}
@ -314,18 +321,20 @@ $j(document).ready(function(){
<h4>Editions</h4>
{% if alert %}<div class="alert"><b>Ebook Contribution:</b><br />{{ alert }}</div>{% endif %}
{% for edition in editions %}
<div class="editions"><div class="image"><img src="{{ edition.cover_image_small }}" title="edition cover" alt="edition cover" /></div>
<div class="metadata" id="edition_{{edition.id}}">Publisher: {{edition.publisher}}<br />
Published: {{edition.publication_date}}<br />
{% with edition.isbn_13 as isbn %}
{% if isbn %}
ISBN: {{ isbn }}<br />
{% else %}
No ISBN available<br />
<div class="editions">{% if edition.googlebooks_id %}<div class="image"><img src="{{ edition.cover_image_small }}" title="edition cover" alt="edition cover" /></div>{% endif %}
<div class="metadata" id="edition_{{edition.id}}">{% if edition.publisher %}Publisher: {{edition.publisher}}<br />{% endif %}
{% if edition.publication_date %}Published: {{edition.publication_date}}<br />{% endif %}
{% if edition.isbn_13 %}
ISBN: {{ edition.isbn_13 }}<br />
{% endif %}
{% endwith %}
See <a href="https://encrypted.google.com/books?id={{ edition.googlebooks_id }}">this edition on Google Books</a></div>
</div>{{work.last_campaign_status}}
{% if edition.oclc %}
OCLC: <a href="http://www.worldcat.org/oclc/{{ edition.oclc }}">{{ edition.oclc }}</a><br />
{% endif %}
{% if edition.googlebooks_id %}
See <a href="https://encrypted.google.com/books?id={{ edition.googlebooks_id }}">this edition on Google Books</a>
{% endif %}
</div>
</div>
{% if edition.ebook_form %}{% ifnotequal status 'ACTIVE' %}
{% if edition.hide_details %}<div class="show_more_edition" >more...</div>{% endif %}
<div {% if edition.hide_details %} class="more_edition" {% endif %}>

View File

@ -27,12 +27,24 @@
<div class="js-topnews3">
<div class="user-block">
<div id="user-block1">
<div id="block-intro-text"><span class="special-user-name">{{ facet|capfirst }}</span></div>
</div>
<div class="user-block2"><span class="user-short-info">With your help we're raising money to buy the rights to give these books to the world.</span>
</div>
<div class="user-block3">
</div>
{% if facet == 'recommended' %}
<div id="block-intro-text"><span class="special-user-name">Staff Picks</span></div>
</div>
<div class="user-block2"><span class="user-short-info">Here are the books {% for staffer in unglue_staff %}{% if not forloop.last %}<a href="{% url supporter '{{ staffer }}' %}">{{ staffer }}</a>, {% else %} and <a href="{% url supporter '{{ staffer }}' %}">{{ staffer }}</a>{% endif %}{% endfor %} are loving lately.</span>
</div>
<div class="user-block3 recommended">
{% for staffer in unglue_staff %}
<img class="user-avatar" src="{{ staffer.profile.pic_url }}" height="50" width="50" alt="Picture of {{ supporter }}" title="{{ supporter }}" />
{% endfor %}
</div>
{% else %}
<div id="block-intro-text"><span class="special-user-name">{{ facet|capfirst }}</span></div>
</div>
<div class="user-block2"><span class="user-short-info">With your help we're raising money to buy the rights to give these books to the world.</span>
</div>
<div class="user-block3">
</div>
{% endif %}
</div>
</div>
</div>
@ -82,7 +94,11 @@
</div>
<div id="content-block-content">
{% ifequal work_list.count 0 %}
{% if facet == 'recommended' %}
Check back soon to see what we're recommending.
{% else %}
There aren't any works in this list yet. Why not add your favorite books to your wishlist, so we can feature them here?
{% endif %}
{% else %}
{% lazy_paginate 20 works_unglued using "works_unglued" %}
{% for work in works_unglued %}

View File

@ -1,97 +0,0 @@
{% extends "base.html" %}
{% block title %}
&#8212; {{ title }}
{% endblock %}
{% block extra_head %}
<link type="text/css" rel="stylesheet" href="/static/css/campaign.css" />
<script type="text/javascript" src="/static/js/wishlist.js"></script>
{% endblock %}
{% block content %}
<div id="main-container">
<div class="js-main">
<div id="js-leftcol">
<div class="jsmodule rounded">
<div class="jsmod-content">
0 Ungluers are WISHING<br />&nbsp;
</div>
</div>
{% include "explore.html" %}
</div>
<div id="js-maincol">
<div class="js-maincol-inner">
<div id="content-block">
<div class="book-detail">
<div class="book-detail-img">
<a href="#"><img src="http://{{ imagebase }}/{{ image }}" alt="{{ title }}" title="{{ title }}" width="131" height="192" /></a>
</div>
<div class="book-detail-info">
<h2 class="book-name">{{ title }}</h2>
<h3 class="book-author">{{ author }}</h3>
<div class="find-book">
<label>Find it here</label>
<!-- todo: these should be a real thing -->
<div class="find-link">
<a class="find-google" href="#"><img src="/static/images/icons/google.png" title="" alt="link to google" /></a>
<a class="find-group" href="#"><img src="/static/images/icons/group.png" title="" alt="link to group" /></a>
</div>
</div>
<div class="pledged-info"><div class="pledged-group">0 Ungluers have pledged $0</div><div class="status"><img src="/static/images/images/icon-book-37by25-0.png" title="book list status" alt="book list status" /></div></div>
</div>
</div>
<div id="tabs" class="content-block-heading">
</div>
<div id="content-block-content">
<div id="tabs-1" class="tabs">
<div class="tabs-content">
{% if request.user.is_anonymous %}
<div class="create-account"><p>No one has wishlisted this yet.&nbsp;&nbsp;<b><span>Be the first.</span></b></p>
</div>
{% else %}
<div class="add-wishlist"><p>No one has wishlisted this yet.&nbsp;&nbsp;<b><span id="{{ googlebooks_id }}">Be the first.</span></b></p>
</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
</div>
<div id="js-rightcol">
<div class="js-rightcol-pad rounded">
<div class="jsmodule">
<h3 class="jsmod-title"><span>Share</span></h3>
<div class="jsmod-content">
<ul class="social menu">
<li class="facebook first"><a href="#"><span>Facebook</span></a></li>
<li class="twitter"><a href="#"><span>Twitter</span></a></li>
<li class="lasts email"><a href="#"><span>Email</span></a></li>
</ul>
</div>
</div>
{% if work.last_campaign %}
<div class="jsmodule">
<h3 class="jsmod-title"><span>Support</span></h3>
<div class="jsmod-content">
<ul class="support menu">
{% for premium in premiums %}
<li class="{% if forloop.first %}first{% else %}{% if forloop.last %}last{% endif %}{% endif %}">
<a href="{% url pledge work_id=work.id %}?premium_id={{premium.id}}">
<span class="menu-item-price">${{ premium.amount }}</span>
<span class="menu-item-desc">{{ premium.description }}</span>
{% ifequal premium.type 'CU' %}<span class="custom-premium">exclusive!</span>{% endifequal %}
</a></li>
{% endfor %}
</ul>
</div>
</div>
{% endif %}
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -225,7 +225,9 @@ def manage_campaign(request, id):
if (not request.user.is_authenticated) or (not request.user in campaign.managers.all()):
campaign.not_manager=True
return render(request, 'manage_campaign.html', {'campaign': campaign})
alerts = []
alerts = []
activetab = '#1'
if request.method == 'POST' :
if request.POST.has_key('add_premium') :
postcopy=request.POST.copy()
@ -238,23 +240,29 @@ def manage_campaign(request, id):
else:
alerts.append(_('New premium has not been added'))
form = getManageCampaignForm(instance=campaign)
elif request.POST.has_key('save') or request.POST.has_key('launch') :
activetab = '#2'
elif request.POST.has_key('save') or request.POST.has_key('launch') :
form= getManageCampaignForm(instance=campaign, data=request.POST)
if form.is_valid():
form.save()
alerts.append(_('Campaign data has been saved'))
activetab = '#2'
else:
alerts.append(_('Campaign data has NOT been saved'))
if 'launch' in request.POST.keys():
if campaign.launchable and form.is_valid() :
activetab = '#3'
# only staff should be allowed to launch a campaign before site launch
# this line can be removed after site launch
if (campaign.launchable and form.is_valid()) and (not settings.IS_PREVIEW or request.user.is_staff):
campaign.activate()
alerts.append(_('Campaign has been launched'))
else:
alerts.append(_('Campaign has NOT been launched'))
new_premium_form = CustomPremiumForm(data={'campaign': campaign})
elif request.POST.has_key('inactivate') :
activetab = '#2'
if request.POST.has_key('premium_id'):
premiums_to_stop = request.POST['premium_id']
premiums_to_stop = request.POST.getlist('premium_id')
for premium_to_stop in premiums_to_stop:
selected_premium = models.Premium.objects.get(id=premium_to_stop)
if selected_premium.type == 'CU':
@ -267,6 +275,13 @@ def manage_campaign(request, id):
form = getManageCampaignForm(instance=campaign)
new_premium_form = CustomPremiumForm(data={'campaign': campaign})
work = campaign.work
try:
pubdate = work.publication_date[:4]
except IndexError:
pubdate = 'unknown'
return render(request, 'manage_campaign.html', {
'campaign': campaign,
'form':form,
@ -274,6 +289,9 @@ def manage_campaign(request, id):
'alerts': alerts,
'premiums' : campaign.effective_premiums(),
'premium_form' : new_premium_form,
'pubdate': pubdate,
'work': work,
'activetab': activetab,
})
def googlebooks(request, googlebooks_id):
@ -327,7 +345,7 @@ class WorkListView(ListView):
context = super(WorkListView, self).get_context_data(**kwargs)
qs=self.get_queryset()
context['ungluers'] = userlists.work_list_users(qs,5)
context['facet'] =self.kwargs['facet']
context['facet'] = self.kwargs['facet']
context['works_unglued'] = qs.filter(editions__ebooks__isnull=False).distinct()
context['works_active'] = qs.exclude(editions__ebooks__isnull=False).filter(Q(campaigns__status='ACTIVE') | Q(campaigns__status='SUCCESSFUL')).distinct()
context['works_wished'] = qs.exclude(editions__ebooks__isnull=False).exclude(campaigns__status='ACTIVE').exclude(campaigns__status='SUCCESSFUL').distinct()
@ -339,6 +357,11 @@ class WorkListView(ListView):
counts['unglueing'] = context['works_active'].count()
counts['wished'] = context['works_wished'].count()
context['counts'] = counts
if (self.kwargs['facet'] == 'recommended'):
unglue_staff = models.User.objects.filter(is_staff=True)
context['unglue_staff'] = unglue_staff
return context
class UngluedListView(ListView):
@ -861,7 +884,7 @@ def rh_tools(request):
claim.campaign_form.save_m2m()
claim.can_open_new=False
else:
claim.campaign_form = OpenCampaignForm(data={'work': claim.work, 'name': claim.work.title, 'userid': request.user.id})
claim.campaign_form = OpenCampaignForm(data={'work': claim.work, 'name': claim.work.title, 'userid': request.user.id, 'managers_1': request.user.id})
else:
claim.can_open_new=False
return render(request, "rh_tools.html", {'claims': claims ,})
@ -1658,7 +1681,7 @@ def emailshare(request):
work_id = next.split('=')[1]
book = models.Work.objects.get(pk=int(work_id))
title = book.title
message = "I just pledged to unglue one of my favorite books, "+title+", on Unglue.It: http://unglue.it/work/"+work_id+". If enough of us pledge to unglue this book, the creator will be paid and the ebook will become free to everyone on earth. Will you join me?"
message = "I just pledged to unglue one of my favorite books, "+title+", on Unglue.it: http://unglue.it/work/"+work_id+". If enough of us pledge to unglue this book, the creator will be paid and the ebook will become free to everyone on earth. Will you join me?"
subject = "Help me unglue "+title
else:
work_id = next.split('/')[-2]
@ -1677,15 +1700,15 @@ def emailshare(request):
# customize the call to action depending on campaign status
if status == 'ACTIVE':
message = 'Help me unglue one of my favorite books'+title+'on Unglue.It: http://unglue.it/'+next+'. If enough of us pledge to unglue this book, the creator will be paid and the ebook will become free to everyone on earth.'
message = 'Help me unglue one of my favorite books'+title+'on Unglue.it: http://unglue.it/'+next+'. If enough of us pledge to unglue this book, the creator will be paid and the ebook will become free to everyone on earth.'
else:
message = 'Help me unglue one of my favorite books'+title+'on Unglue.It: http://unglue.it'+next+'. If enough of us wishlist this book, Unglue.It may start a campaign to pay the creator and make the ebook free to everyone on earth.'
subject = 'Come see one of my favorite books on Unglue.It'
message = 'Help me unglue one of my favorite books'+title+'on Unglue.it: http://unglue.it'+next+'. If enough of us wishlist this book, Unglue.it may start a campaign to pay the creator and make the ebook free to everyone on earth.'
subject = 'Come see one of my favorite books on Unglue.it'
form = EmailShareForm(initial={'sender': sender, 'next':next, 'subject': subject, 'message': message})
except:
next = ''
form = EmailShareForm(initial={'sender': sender, 'next':next, 'subject': 'Come join me on Unglue.It', 'message':"I'm ungluing books on Unglue.It. Together we're paying creators and making ebooks free to everyone on earth. Join me! http://unglue.it"})
form = EmailShareForm(initial={'sender': sender, 'next':next, 'subject': 'Come join me on Unglue.it', 'message':"I'm ungluing books on Unglue.it. Together we're paying creators and making ebooks free to everyone on earth. Join me! http://unglue.it"})
return render(request, "emailshare.html", {'form':form})

View File

@ -25,3 +25,4 @@ fabric
git+git://github.com/agiliq/merchant.git#egg=django-merchant
paramiko
pyasn1
pycrypto

View File

@ -256,13 +256,6 @@ 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
# Amazon credentials (for fps)
AWS_ACCESS_KEY = ''
AWS_SECRET_ACCESS_KEY = ''
# Amazon FPS Credentials
FPS_ACCESS_KEY = ''
FPS_SECRET_KEY = ''
# set -- sandbox or production Amazon FPS?
AMAZON_FPS_HOST = "fps.sandbox.amazonaws.com"
@ -270,3 +263,6 @@ AMAZON_FPS_HOST = "fps.sandbox.amazonaws.com"
# amazon or paypal for now.
PAYMENT_PROCESSOR = 'amazon'
# a SECRET_KEY to be used for encrypting values in core.models.Key -- you should store in settings/local.py
SECRET_KEY = ''

View File

@ -144,4 +144,9 @@ UNGLUEIT_TEST_PASSWORD = None
# decide which of the period tasks to add to the schedule
#CELERYBEAT_SCHEDULE['send_test_email'] = SEND_TEST_EMAIL_JOB
#CELERYBEAT_SCHEDULE['emit_notifications'] = EMIT_NOTIFICATIONS_JOB
CELERYBEAT_SCHEDULE['report_new_ebooks'] = EBOOK_NOTIFICATIONS_JOB
CELERYBEAT_SCHEDULE['report_new_ebooks'] = EBOOK_NOTIFICATIONS_JOB
try:
from regluit.settings.local import *
except ImportError:
pass

136
settings/just.py Normal file
View File

@ -0,0 +1,136 @@
from regluit.settings.common import *
DEBUG = False
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'),
)
MANAGERS = ADMINS
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'unglueit',
'USER': 'root',
'PASSWORD': 'forgetn0t',
'HOST': 'justdb.cboagmr25pjs.us-east-1.rds.amazonaws.com',
'PORT': '',
}
}
TIME_ZONE = 'America/New_York'
SECRET_KEY = '_^_off!8zsj4+)%qq623m&$7_m-q$iau5le0w!mw&n5tgt#x=t'
# settings for outbout email
# if you have a gmail account you can use your email address and password
EMAIL_USE_TLS = True
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_HOST_USER = 'accounts@gluejar.com'
EMAIL_HOST_PASSWORD = '7k3sWyzHpI'
EMAIL_PORT = 587
DEFAULT_FROM_EMAIL = 'accounts@gluejar.com'
# googlebooks
GOOGLE_BOOKS_API_KEY = 'AIzaSyBE36z7o6NUafIWcLEB8yk2I47-8_5y1_0'
# twitter auth
TWITTER_CONSUMER_KEY = 'sd9StEg1N1qB8gGb2GRX4A'
TWITTER_CONSUMER_SECRET = 'YSKHn8Du6EWqpcWZ6sp5tqDPvcOBXK0WJWVGWyB0'
# facebook auth
FACEBOOK_APP_ID = '242881179080779'
FACEBOOK_API_SECRET = '5eae483a0e92113d884c427b578ef23a'
# google auth
GOOGLE_OAUTH2_CLIENT_ID = '989608723367.apps.googleusercontent.com'
GOOGLE_OAUTH2_CLIENT_SECRET = '3UqalKyNynnaaarumUIWh8vS'
GOOGLE_DISPLAY_NAME = 'unglue it!'
# credentials from a sandbox account that Raymond set up.
PAYPAL_USERNAME = 'glueja_1317336101_biz_api1.gluejar.com'
PAYPAL_PASSWORD = '1317336137'
PAYPAL_SIGNATURE = 'AHVb0D1mzGD6zdX4XtKZbH.Kd6OhALVyiJVbNReOZEfyz79AoEnQJWTR'
PAYPAL_APPID = 'APP-80W284485P519543T' # sandbox app id -- will have to replace with production id
PAYPAL_ENDPOINT = 'svcs.sandbox.paypal.com' # sandbox
PAYPAL_PAYMENT_HOST = 'https://www.sandbox.paypal.com' # sandbox
PAYPAL_SANDBOX_LOGIN = ''
PAYPAL_SANDBOX_PASSWORD = ''
PAYPAL_BUYER_LOGIN =''
PAYPAL_BUYER_PASSWORD = ''
# in live system, replace with the real Gluejar paypal email and that for our non-profit partner
PAYPAL_GLUEJAR_EMAIL = "glueja_1317336101_biz@gluejar.com"
PAYPAL_NONPROFIT_PARTNER_EMAIL = "nppart_1318957063_per@gluejar.com"
# for test purposes have a single RH paypal email
PAYPAL_TEST_RH_EMAIL = "rh1_1317336251_biz@gluejar.com"
# Goodreads API
GOODREADS_API_KEY = "vfqIO6QAhBVvlxt6hAzZJg"
GOODREADS_API_SECRET = "57tq4MpyJ15Hgm2ToZQQFWJ7vraZzOAqHLckWRXQ"
# Freebase credentials
FREEBASE_USERNAME = ''
FREEBASE_PASSWORD = ''
# send celery log to Python logging
CELERYD_HIJACK_ROOT_LOGGER = False
# BASE_URL is a hard-coding of the domain name for site and used for PayPal IPN
# Next step to try https
BASE_URL = 'http://just.unglueit.com'
# use redis for production queue
BROKER_TRANSPORT = "redis"
BROKER_HOST = "localhost"
BROKER_PORT = 6379
BROKER_VHOST = "0"
LOGGING = {
'version': 1,
'disable_existing_loggers': True,
'handlers': {
'mail_admins': {
'level': 'ERROR',
'class': 'django.utils.log.AdminEmailHandler'
},
},
'loggers': {
'django.request': {
'handlers': ['mail_admins'],
'level': 'ERROR',
'propagate': True,
},
}
}
STATIC_ROOT = '/var/www/static'
IS_PREVIEW = False
# decide which of the period tasks to add to the schedule
#CELERYBEAT_SCHEDULE['send_test_email'] = SEND_TEST_EMAIL_JOB
CELERYBEAT_SCHEDULE['report_new_ebooks'] = EBOOK_NOTIFICATIONS_JOB
CELERYBEAT_SCHEDULE['emit_notifications'] = EMIT_NOTIFICATIONS_JOB
# Amazon credentials (for fps)
AWS_ACCESS_KEY = ''
AWS_SECRET_ACCESS_KEY = ''
# if settings/local.py exists, import those settings -- allows for dynamic generation of parameters such as DATABASES
try:
from regluit.settings.local import *
except ImportError:
pass

View File

@ -128,3 +128,9 @@ CELERYBEAT_SCHEDULE['emit_notifications'] = EMIT_NOTIFICATIONS_JOB
# Amazon credentials (for fps)
AWS_ACCESS_KEY = ''
AWS_SECRET_ACCESS_KEY = ''
# if settings/local.py exists, import those settings -- allows for dynamic generation of parameters such as DATABASES
try:
from regluit.settings.local import *
except ImportError:
pass

View File

@ -123,3 +123,9 @@ STATIC_ROOT = '/var/www/static'
#CELERYBEAT_SCHEDULE['send_test_email'] = SEND_TEST_EMAIL_JOB
#CELERYBEAT_SCHEDULE['emit_notifications'] = EMIT_NOTIFICATIONS_JOB
CELERYBEAT_SCHEDULE['report_new_ebooks'] = EBOOK_NOTIFICATIONS_JOB
# if settings/local.py exists, import those settings -- allows for dynamic generation of parameters such as DATABASES
try:
from regluit.settings.local import *
except ImportError:
pass

View File

@ -89,50 +89,73 @@
list-style: none;
border: none;
}
#js-page-wrap {
overflow: hidden;
}
#main-container {
margin-top: 20px;
}
#js-leftcol .jsmodule, .pledge.jsmodule {
margin-bottom: 10px;
}
#js-leftcol .jsmodule.rounded .jsmod-content, .pledge.jsmodule.rounded .jsmod-content {
-moz-border-radius: 20px;
-webkit-border-radius: 20px;
border-radius: 20px;
background: #edf3f4;
color: #3d4e53;
padding: 10px 20px;
font-weight: bold;
border: none;
margin: 0;
}
#js-leftcol .jsmodule.rounded .jsmod-content.ACTIVE, .pledge.jsmodule.rounded .jsmod-content.ACTIVE {
background: #8dc63f;
color: white;
font-size: 18px;
font-weight: normal;
}
#js-leftcol .jsmodule.rounded .jsmod-content.No.campaign.yet, .pledge.jsmodule.rounded .jsmod-content.No.campaign.yet {
background: #e18551;
color: white;
}
#js-leftcol .jsmodule.rounded .jsmod-content span, .pledge.jsmodule.rounded .jsmod-content span {
display: inline-block;
vertical-align: middle;
}
#js-leftcol .jsmodule.rounded .jsmod-content span.spacer, .pledge.jsmodule.rounded .jsmod-content span.spacer {
visibility: none;
}
#js-leftcol .jsmodule.rounded .jsmod-content span.findtheungluers, .pledge.jsmodule.rounded .jsmod-content span.findtheungluers {
cursor: pointer;
}
.jsmodule.pledge {
/* Campaign and manage_campaign use same tab styling, so it's factored out here */
#tabs {
border-bottom: 4px solid #6994a3;
clear: both;
float: left;
margin-left: 10px;
margin-top: 10px;
width: 100%;
}
#tabs ul.book-list-view {
margin-bottom: 4px !important;
}
#tabs-1,
#tabs-2,
#tabs-3,
#tabs-4 {
display: none;
}
#tabs-1.active,
#tabs-2.active,
#tabs-3.active,
#tabs-4.active {
display: inherit;
}
#tabs-2 textarea {
width: 95%;
}
ul.tabs {
float: left;
padding: 0;
margin: 0;
list-style: none;
width: 100%;
}
ul.tabs li {
float: left;
height: 46px;
line-height: 46px;
padding-right: 2px;
width: 116px;
background: none;
margin: 0;
padding: 0 2px 0 0;
}
ul.tabs li.tabs4 {
padding-right: 0px;
}
ul.tabs li a {
height: 46px;
line-height: 46px;
display: block;
text-align: center;
padding: 0 10px;
min-width: 80px;
-moz-border-radius: 7px 7px 0 0;
-webkit-border-radius: 7px 7px 0 0;
border-radius: 7px 7px 0 0;
background: #d6dde0;
color: #3d4e53;
}
ul.tabs li a:hover {
text-decoration: none;
}
ul.tabs li a:hover, ul.tabs li.active a {
background: #6994a3;
color: #fff;
}
/* needed for campaign, pledge, and manage_campaign */
.book-detail {
float: left;
width: 100%;
@ -239,67 +262,51 @@
height: 25px;
margin-top: -12px;
}
#tabs {
border-bottom: 4px solid #6994a3;
clear: both;
float: left;
margin-top: 10px;
width: 100%;
#js-page-wrap {
overflow: hidden;
}
#tabs ul.book-list-view {
margin-bottom: 4px !important;
#main-container {
margin-top: 20px;
}
#tabs-1,
#tabs-2,
#tabs-3,
#tabs-4 {
display: none;
#js-leftcol .jsmodule, .pledge.jsmodule {
margin-bottom: 10px;
}
#tabs-1.active,
#tabs-2.active,
#tabs-3.active,
#tabs-4.active {
display: inherit;
}
#tabs-2 textarea {
width: 95%;
}
ul.tabs {
float: left;
padding: 0;
margin: 0;
list-style: none;
width: 100%;
}
ul.tabs li {
float: left;
height: 46px;
line-height: 46px;
padding-right: 2px;
width: 116px;
}
ul.tabs li.tabs4 {
padding-right: 0px;
}
ul.tabs li a {
height: 46px;
line-height: 46px;
display: block;
text-align: center;
padding: 0 10px;
min-width: 80px;
-moz-border-radius: 7px 7px 0 0;
-webkit-border-radius: 7px 7px 0 0;
border-radius: 7px 7px 0 0;
background: #d6dde0;
#js-leftcol .jsmodule.rounded .jsmod-content, .pledge.jsmodule.rounded .jsmod-content {
-moz-border-radius: 20px;
-webkit-border-radius: 20px;
border-radius: 20px;
background: #edf3f4;
color: #3d4e53;
padding: 10px 20px;
font-weight: bold;
border: none;
margin: 0;
line-height: 16px;
}
ul.tabs li a:hover {
text-decoration: none;
#js-leftcol .jsmodule.rounded .jsmod-content.ACTIVE, .pledge.jsmodule.rounded .jsmod-content.ACTIVE {
background: #8dc63f;
color: white;
font-size: 18px;
font-weight: normal;
line-height: 20px;
}
ul.tabs li a:hover, ul.tabs li.active a {
background: #6994a3;
color: #fff;
#js-leftcol .jsmodule.rounded .jsmod-content.No.campaign.yet, .pledge.jsmodule.rounded .jsmod-content.No.campaign.yet {
background: #e18551;
color: white;
}
#js-leftcol .jsmodule.rounded .jsmod-content span, .pledge.jsmodule.rounded .jsmod-content span {
display: inline-block;
vertical-align: middle;
}
#js-leftcol .jsmodule.rounded .jsmod-content span.spacer, .pledge.jsmodule.rounded .jsmod-content span.spacer {
visibility: none;
}
#js-leftcol .jsmodule.rounded .jsmod-content span.findtheungluers, .pledge.jsmodule.rounded .jsmod-content span.findtheungluers {
cursor: pointer;
}
.jsmodule.pledge {
float: left;
margin-left: 10px;
}
#js-rightcol, #pledge-rightcol {
float: right;

View File

@ -0,0 +1,157 @@
/* Campaign and manage_campaign use same tab styling, so it's factored out here */
/* 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;
text-align: center;
}
.errors li {
list-style: none;
border: none;
}
#tabs {
border-bottom: 4px solid #6994a3;
clear: both;
float: left;
margin-top: 10px;
width: 100%;
}
#tabs ul.book-list-view {
margin-bottom: 4px !important;
}
#tabs-1,
#tabs-2,
#tabs-3,
#tabs-4 {
display: none;
}
#tabs-1.active,
#tabs-2.active,
#tabs-3.active,
#tabs-4.active {
display: inherit;
}
#tabs-2 textarea {
width: 95%;
}
ul.tabs {
float: left;
padding: 0;
margin: 0;
list-style: none;
width: 100%;
}
ul.tabs li {
float: left;
height: 46px;
line-height: 46px;
padding-right: 2px;
width: 116px;
background: none;
margin: 0;
padding: 0 2px 0 0;
}
ul.tabs li.tabs4 {
padding-right: 0px;
}
ul.tabs li a {
height: 46px;
line-height: 46px;
display: block;
text-align: center;
padding: 0 10px;
min-width: 80px;
-moz-border-radius: 7px 7px 0 0;
-webkit-border-radius: 7px 7px 0 0;
border-radius: 7px 7px 0 0;
background: #d6dde0;
color: #3d4e53;
}
ul.tabs li a:hover {
text-decoration: none;
}
ul.tabs li a:hover, ul.tabs li.active a {
background: #6994a3;
color: #fff;
}

View File

@ -485,7 +485,7 @@ dd {
border-bottom: solid 1px #3d4e53;
float: left;
}
.pressvideos iframe {
.pressvideos iframe, .pressvideos div.mediaborder {
padding: 5px;
border: solid 5px #EDF3F4;
padding: 5px;
@ -569,6 +569,7 @@ a.manage:hover {
h2.thank-you {
font-size: 34px;
color: #8dc63f;
line-height: 40px;
}
.pledge_complete, .pledge_complete a {
font-size: 14px;
@ -591,3 +592,7 @@ ul.social.pledge li {
div.pledge-container {
width: 100%;
}
.yikes {
color: #e35351;
font-weight: bold;
}

View File

@ -0,0 +1,274 @@
/* 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;
text-align: center;
}
.errors li {
list-style: none;
border: none;
}
/* Campaign and manage_campaign use same tab styling, so it's factored out here */
#tabs {
border-bottom: 4px solid #6994a3;
clear: both;
float: left;
margin-top: 10px;
width: 100%;
}
#tabs ul.book-list-view {
margin-bottom: 4px !important;
}
#tabs-1,
#tabs-2,
#tabs-3,
#tabs-4 {
display: none;
}
#tabs-1.active,
#tabs-2.active,
#tabs-3.active,
#tabs-4.active {
display: inherit;
}
#tabs-2 textarea {
width: 95%;
}
ul.tabs {
float: left;
padding: 0;
margin: 0;
list-style: none;
width: 100%;
}
ul.tabs li {
float: left;
height: 46px;
line-height: 46px;
padding-right: 2px;
width: 116px;
background: none;
margin: 0;
padding: 0 2px 0 0;
}
ul.tabs li.tabs4 {
padding-right: 0px;
}
ul.tabs li a {
height: 46px;
line-height: 46px;
display: block;
text-align: center;
padding: 0 10px;
min-width: 80px;
-moz-border-radius: 7px 7px 0 0;
-webkit-border-radius: 7px 7px 0 0;
border-radius: 7px 7px 0 0;
background: #d6dde0;
color: #3d4e53;
}
ul.tabs li a:hover {
text-decoration: none;
}
ul.tabs li a:hover, ul.tabs li.active a {
background: #6994a3;
color: #fff;
}
/* needed for campaign, pledge, and manage_campaign */
.book-detail {
float: left;
width: 100%;
clear: both;
display: block;
}
#book-detail-img {
float: left;
margin-right: 10px;
width: 151px;
}
#book-detail-img img {
padding: 5px;
border: solid 5px #EDF3F4;
}
.book-detail-info {
float: left;
/* if we want to nix the explore bar, width should be 544ish */
width: 309px;
}
.book-detail-info h2.book-name, .book-detail-info h3.book-author, .book-detail-info h3.book-year {
padding: 0;
margin: 0;
line-height: normal;
}
.book-detail-info h2.book-name {
font-size: 18px;
text-transform: capitalize;
font-weight: bold;
color: #3d4e53;
}
.book-detail-info h3.book-author, .book-detail-info h3.book-year {
font-size: 12px;
font-weight: normal;
color: #6994a3;
}
.book-detail-info > div {
width: 100%;
clear: both;
display: block;
overflow: hidden;
border-top: 1px solid #edf3f4;
padding: 10px 0;
}
.book-detail-info > div.layout {
border: none;
padding: 0;
}
.book-detail-info > div.layout div.pubinfo {
float: left;
width: auto;
padding-bottom: 7px;
}
.book-detail-info > div.layout div.btn_support {
float: right;
}
.book-detail-info > div.layout div.btn_support input {
background: url("/static/images/btn_bg.png") 0 0 no-repeat;
width: 104px;
height: 41px;
display: block;
color: #fff;
font-weight: bold;
text-align: center;
border: none;
padding: 0;
cursor: pointer;
}
.book-detail-info > div.layout div.btn_support.modify input {
background: url("/static/images/btn_bg_grey.png") 0 0 no-repeat;
}
.book-detail-info .btn_wishlist span {
text-align: right;
}
.book-detail-info .find-book {
margin-top: 15px;
}
.book-detail-info .find-book label {
float: left;
line-height: 31px;
}
.book-detail-info .find-link {
float: right;
}
.book-detail-info .find-link img {
padding: 2px;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
border-radius: 5px;
}
.book-detail-info .pledged-info {
padding: 0;
position: relative;
}
.book-detail-info .pledged-group {
padding: 10px 40px 10px 0;
float: left;
}
.book-detail-info .status {
position: absolute;
top: 50%;
right: 0%;
height: 25px;
margin-top: -12px;
}
.preview_campaign {
float: right;
margin-right: 10px;
}
input[name="launch"] {
display: none;
}
#launchme {
margin: 15px auto;
}

View File

@ -485,14 +485,6 @@ a#readon span {
#js-leftcol a:hover {
text-decoration: underline;
}
#js-leftcol a.comingsoon:hover {
text-decoration: none;
cursor: default;
}
#js-leftcol a.comingsoon:hover:after {
content: " Coming soon!";
color: #8dc63f;
}
#js-leftcol .jsmod-content {
border: solid 1px #edf3f4;
-moz-border-radius: 0 0 10px 10px;

View File

@ -163,6 +163,9 @@
float: left;
width: 25%;
}
.user-block3.recommended, .user-block4.recommended {
margin-top: auto;
}
.user-block3 {
margin-top: 8px;
}

View File

@ -0,0 +1,125 @@
/* needed for campaign, pledge, and manage_campaign */
.book-detail {
float:left;
width:100%;
clear:both;
display:block;
}
#book-detail-img {
float: left;
margin-right:10px;
width:151px;
img {
.mediaborder;
}
}
.book-detail-info {
float:left;
/* if we want to nix the explore bar, width should be 544ish */
width:309px;
h2.book-name, h3.book-author, h3.book-year {
padding:0;
margin:0;
line-height:normal
}
h2.book-name {
font-size:18px;
text-transform:capitalize;
font-weight:bold;
color:@text-blue;
}
h3.book-author, h3.book-year {
font-size:12px;
font-weight:normal;
color:@medium-blue;
}
> div {
width:100%;
clear:both;
display:block;
overflow:hidden;
border-top:1px solid @pale-blue;
padding:10px 0;
}
> div.layout {
border: none;
padding: 0;
div.pubinfo {
float: left;
width: auto;
padding-bottom: 7px;
}
div.btn_support {
float: right;
input {
background:url("@{image-base}btn_bg.png") 0 0 no-repeat;
width:104px;
height:41px;
display:block;
color:#fff;
font-weight:bold;
text-align:center;
border:none;
padding:0;
cursor:pointer;
}
&.modify input {
background:url("@{image-base}btn_bg_grey.png") 0 0 no-repeat;
}
}
}
.btn_wishlist span {
text-align: right;
}
.find-book {
margin-top:15px;
label {
float:left;
line-height:31px;
}
}
.find-link {
float:right;
img {
padding: 2px;
.one-border-radius(5px);
}
}
.pledged-info {
padding:0;
position: relative;
}
.pledged-group {
padding:10px 40px 10px 0;
float:left;
}
.status {
position: absolute;
top:50%;
right:0%;
height: 25px;
margin-top: -12px;
}
}

View File

@ -1,4 +1,6 @@
@import "variables.less";
@import "campaign_tabs.less";
@import "book_detail.less";
.shareclass(@sharewhere) {
background:url("@{image-base}icons/@{sharewhere}.png") 10px center no-repeat;
@ -36,12 +38,14 @@
font-weight:bold;
border:none;
margin:0;
line-height: 16px;
&.ACTIVE {
background: @green;
color: white;
font-size: 18px;
font-weight: normal;
line-height: 20px;
}
&.No.campaign.yet {
@ -70,195 +74,6 @@
margin-left: 10px;
}
.book-detail {
float:left;
width:100%;
clear:both;
display:block;
}
#book-detail-img {
float: left;
margin-right:10px;
width:151px;
img {
.mediaborder;
}
}
.book-detail-info {
float:left;
/* if we want to nix the explore bar, width should be 544ish */
width:309px;
h2.book-name, h3.book-author, h3.book-year {
padding:0;
margin:0;
line-height:normal
}
h2.book-name {
font-size:18px;
text-transform:capitalize;
font-weight:bold;
color:@text-blue;
}
h3.book-author, h3.book-year {
font-size:12px;
font-weight:normal;
color:@medium-blue;
}
> div {
width:100%;
clear:both;
display:block;
overflow:hidden;
border-top:1px solid @pale-blue;
padding:10px 0;
}
> div.layout {
border: none;
padding: 0;
div.pubinfo {
float: left;
width: auto;
padding-bottom: 7px;
}
div.btn_support {
float: right;
input {
background:url("@{image-base}btn_bg.png") 0 0 no-repeat;
width:104px;
height:41px;
display:block;
color:#fff;
font-weight:bold;
text-align:center;
border:none;
padding:0;
cursor:pointer;
}
&.modify input {
background:url("@{image-base}btn_bg_grey.png") 0 0 no-repeat;
}
}
}
.btn_wishlist span {
text-align: right;
}
.find-book {
margin-top:15px;
label {
float:left;
line-height:31px;
}
}
.find-link {
float:right;
img {
padding: 2px;
.one-border-radius(5px);
}
}
.pledged-info {
padding:0;
position: relative;
}
.pledged-group {
padding:10px 40px 10px 0;
float:left;
}
.status {
position: absolute;
top:50%;
right:0%;
height: 25px;
margin-top: -12px;
}
}
#tabs{
border-bottom: 4px solid @medium-blue;
clear: both;
float: left;
margin-top: 10px;
width: 100%;
ul.book-list-view {
margin-bottom:4px !important;
}
}
#tabs-1, #tabs-2, #tabs-3, #tabs-4 {
display:none;
}
#tabs-1.active, #tabs-2.active, #tabs-3.active, #tabs-4.active {
display: inherit;
}
#tabs-2 textarea {
width: 95%;
}
ul.tabs {
float:left;
padding:0;
margin:0;
list-style:none;
width: 100%;
li {
float: left;
.height(46px);
padding-right:2px;
width: 116px;
&.tabs4 {
padding-right:0px;
}
a {
.height(46px);
display:block;
text-align:center;
padding:0 10px;
min-width:80px;
.border-radius(7px, 7px, 0, 0);
background:@blue-grey;
color:@text-blue;
&:hover {
text-decoration: none;
}
}
a:hover, &.active a {
background:@medium-blue;
color:#fff;
}
}
}
#js-rightcol, #pledge-rightcol {
float:right;
width:235px;

View File

@ -0,0 +1,68 @@
/* Campaign and manage_campaign use same tab styling, so it's factored out here */
#tabs{
border-bottom: 4px solid @medium-blue;
clear: both;
float: left;
margin-top: 10px;
width: 100%;
ul.book-list-view {
margin-bottom:4px !important;
}
}
#tabs-1, #tabs-2, #tabs-3, #tabs-4 {
display:none;
}
#tabs-1.active, #tabs-2.active, #tabs-3.active, #tabs-4.active {
display: inherit;
}
#tabs-2 textarea {
width: 95%;
}
ul.tabs {
float:left;
padding:0;
margin:0;
list-style:none;
width: 100%;
li {
float: left;
.height(46px);
padding-right:2px;
width: 116px;
background: none;
margin: 0;
padding: 0 2px 0 0;
&.tabs4 {
padding-right:0px;
}
a {
.height(46px);
display:block;
text-align:center;
padding:0 10px;
min-width:80px;
.border-radius(7px, 7px, 0, 0);
background:@blue-grey;
color:@text-blue;
&:hover {
text-decoration: none;
}
}
a:hover, &.active a {
background:@medium-blue;
color:#fff;
}
}
}

View File

@ -209,7 +209,7 @@ dd {
float: left;
}
iframe {
iframe, div.mediaborder {
.mediaborder;
}
}
@ -292,6 +292,7 @@ a.manage {
h2.thank-you {
font-size: 34px;
color: @call-to-action;
line-height: 40px;
}
.pledge_complete, .pledge_complete a {
@ -319,4 +320,9 @@ ul.social.pledge {
div.pledge-container {
width: 100%;
}
.yikes {
color: @alert;
font-weight: bold;
}

View File

@ -0,0 +1,16 @@
@import "variables.less";
@import "campaign_tabs.less";
@import "book_detail.less";
.preview_campaign {
float: right;
margin-right: 10px;
}
input[name="launch"] {
display: none;
}
#launchme {
margin: 15px auto;
}

View File

@ -401,17 +401,7 @@ a#readon {
&:hover{
text-decoration:underline;
}
&.comingsoon:hover {
text-decoration: none;
cursor: default;
&:after {
content: " Coming soon!";
color: @call-to-action;
}
}
}
}
.jsmod-content {

View File

@ -91,6 +91,10 @@
.user-block4 {
float:left;
width:25%;
&.recommended {
margin-top: auto;
}
}
.user-block3 {

View File

@ -55,10 +55,18 @@ def all_snapshots(owner=GLUEJAR_ACCOUNT_ID):
return ec2.get_all_snapshots(owner=owner)
def instance(tag_name):
"""return instance tagged with Name=tag_name"""
try:
return ec2.get_all_instances(filters={'tag:Name' : tag_name})[0].instances[0]
except Exception, e:
return None
def db(instance_id):
"""return RDS instance with instance_id if it exists; None otherwise"""
try:
return rds.get_all_dbinstances(instance_id=instance_id)[0]
except Exception, e:
return None
def all_images(owners=(GLUEJAR_ACCOUNT_ID, )):
return ec2.get_all_images(owners=owners)
@ -258,7 +266,82 @@ def launch_instance(ami='ami-a29943cb',
ssh_pwd=ssh_pwd)
return (instance, cmd)
def create_dbinstance(id, allocated_storage, instance_class, master_username, master_password,
port=3306, engine='MySQL5.1', db_name=None,
param_group=None, security_groups=None, availability_zone='us-east-1c', preferred_maintenance_window=None, backup_retention_period=None, preferred_backup_window=None, multi_az=False, engine_version=None, auto_minor_version_upgrade=True):
"""
create rds instance
"""
# rds-create-db-instance
return rds.create_dbinstance(id, allocated_storage, instance_class, master_username, master_password, port=port, engine=engine, db_name=db_name, param_group=param_group, security_groups=security_groups, availability_zone=availability_zone, preferred_maintenance_window=preferred_maintenance_window, backup_retention_period=backup_retention_period, preferred_backup_window=preferred_backup_window, multi_az=multi_az, engine_version=engine_version, auto_minor_version_upgrade=auto_minor_version_upgrade)
def instance_info(e):
return(
{
'id': e.id,
'ip_address': e.ip_address,
'tags': e.tags,
'name_tag': e.tags.get('Name', None)
}
)
def db_info(db, db_name='unglueit', master_password=None):
"""given an rds instance db and master_password, return basic info"""
try:
django_setting = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': db_name,
'USER': db.master_username,
'PASSWORD': master_password,
'HOST': db.endpoint[0],
'PORT': db.endpoint[1]
}
}
return({'id': db.id,
'allocated_storage': db.allocated_storage,
'availability_zone':db.availability_zone,
'instance_class': db.instance_class,
'multi_az': db.multi_az,
'master_username': db.master_username,
'engine': db.engine,
'preferred_backup_window': db.preferred_backup_window,
'preferred_maintenance_window': db.preferred_maintenance_window,
'backup_retention_period':db.backup_retention_period,
'parameter_group': db.parameter_group,
'security_group': db.security_group,
'endpoint':db.endpoint,
'status':db.status,
'create_time': db.create_time,
'latest_restorable_time':db.latest_restorable_time,
'django_setting': django_setting})
except Exception, e:
return None
def test_ec2_user_data(ssh_pwd=None):
script = """#!/bin/sh
echo "Hello World. The time is now $(date -R)!" | tee /root/output.txt
"""
return launch_instance(user_data=script, ssh_pwd=ssh_pwd)
def create_image(instance_id, name, description=None, no_reboot=False):
"""e.g., img = aws.ec2.create_image(aws.instance('rdhyee-dev').id, name="rdhyee-dev_2012_05_05", description="snapshot of rdhyee-dev", no_reboot=False)"""
# It's now good to delete the old AMI...have to deregister AMI and then delete the corresponding snapshot.
image = ec2.create_image(instance_id, name, description, no_reboot)
# So I still need to add a way to name the snapshot corresponding to AMI to make the panel more human readable.
return image
def extraneous_snapshot_ids():
"""Find snapshots that don't correspond to any registered images"""
return set([sn.id for sn in all_snapshots()]) - set([img.block_device_mapping.values()[0].snapshot_id for img in all_images()])
def destroy_image(image_id):
# first deregister image
# delete snapshot corresponding to the image
pass
if __name__ == '__main__':
pprint (stats_for_instances(all_instances()))
web1 = instance('web1')

View File

@ -0,0 +1,56 @@
#!/bin/bash
# Cert4Host.sh - Generate SSL Certificates for a host name.
SERVER_KEY_PATH="/etc/ssl/private/server.key"
SERVER_CRT_PATH="/etc/ssl/certs/server.crt"
HOSTNAME="$1";
if [ -z "${HOSTNAME}" ]; then
echo "Usage : Cert4Host.sh HOSTNAME";
exit;
fi
if [ ! -e $SERVER_KEY_PATH ]; then
openssl genrsa -out server.key 2048
else
echo "Key already exists ... skipping ..."
umask 77; cp $SERVER_KEY_PATH server.key
fi
umask 77; openssl rsa -in server.key -out $HOSTNAME.key
# Country Name (2 letter code) [GB]:.
# State or Province Name (full name) [Berkshire]:.
# Locality Name (eg, city) [Newbury]:.
# Organization Name (eg, company) [My Company Ltd]:.
# Organizational Unit Name (eg, section) []:.
# Common Name (eg, your name or your server's hostname) []:.
# Email Address []:.
# A challenge password []:
# An optional company name []:
COUNTRY="US";
STATE="NJ";
LOCALITY="Montclair";
ORGNAME="Gluejar, Inc.";
ORGUNIT="";
CNAME=$HOSTNAME;
EMAIL="eric@gluejar.com";
PASSWORD="";
OPTION_COMPANY_NAME="";
echo "$COUNTRY
$STATE
$LOCALITY
$ORGNAME
$ORGUNIT
$CNAME
$EMAIL
$PASSWORD
$OPTIONAL_COMPANY_NAME" | openssl req -new -key $HOSTNAME.key -out $HOSTNAME.csr
openssl x509 -req -days 999 -in $HOSTNAME.csr -signkey $HOSTNAME.key -out $HOSTNAME.crt
cp $HOSTNAME.key $SERVER_KEY_PATH
cp $HOSTNAME.crt $SERVER_CRT_PATH

File diff suppressed because one or more lines are too long

View File

@ -202,7 +202,7 @@ def support_campaign(unglue_it_url = settings.LIVE_SERVER_TEST_URL, do_local=Tru
sel.find_element_by_css_selector("input[value*='sign in']").click()
# click on biggest campaign list
biggest_campaign_link = WebDriverWait(sel,20).until(lambda d: d.find_element_by_css_selector("a[href*='/campaigns/pledged']"))
biggest_campaign_link = WebDriverWait(sel,20).until(lambda d: d.find_element_by_css_selector("a[href*='/campaigns/ending']"))
biggest_campaign_link.click()
time.sleep(1)

354
utils/crypto.py Normal file
View File

@ -0,0 +1,354 @@
# https://raw.github.com/asimihsan/crypto_example/master/src/utilities/crypto.py
# (https://github.com/asimihsan/crypto_example/blob/655694e0bea974813d2252a54e69478e272b1d1e/src/utilities/crypto.py)
# ---------------------------------------------------------------------------
# Copyright (c) 2012 Asim Ihsan (asim dot ihsan at gmail dot com)
# Distributed under the MIT/X11 software license, see the accompanying
# file license.txt or http://www.opensource.org/licenses/mit-license.php.
# ---------------------------------------------------------------------------
import os
import sys
import struct
import cStringIO as StringIO
import bz2
from Crypto.Cipher import AES
from Crypto.Hash import SHA256, HMAC
from Crypto.Protocol.KDF import PBKDF2
# ----------------------------------------------------------------------------
# Constants.
# ----------------------------------------------------------------------------
# Length of salts in bytes.
salt_length_in_bytes = 16
# Hash function to use in general.
hash_function = SHA256
# PBKDF pseudo-random function. Used to mix a password and a salt.
# See Crypto\Protocol\KDF.py
pbkdf2_prf = lambda p, s: HMAC.new(p, s, hash_function).digest()
# PBKDF count, number of iterations.
pbkdf2_count = 1000
# PBKDF derived key length.
pbkdf2_dk_len = 32
# ----------------------------------------------------------------------------
class HMACIsNotValidException(Exception):
pass
class InvalidFormatException(Exception):
def __init__(self, reason):
self.reason = reason
def __str__(self):
return repr(self.reason)
class CTRCounter:
""" Callable class that returns an iterating counter for PyCrypto
AES in CTR mode."""
def __init__(self, nonce):
""" Initialize the counter object.
@nonce An 8-byte binary string.
"""
assert(len(nonce)==8)
self.nonce = nonce
self.cnt = 0
def __call__(self):
""" Return the next 16 byte counter, as a binary string. """
right_half = struct.pack('>Q', self.cnt)
self.cnt += 1
return self.nonce + right_half
def encrypt_string(plaintext, key, compress=False):
plaintext_obj = StringIO.StringIO(plaintext)
ciphertext_obj = StringIO.StringIO()
encrypt_file(plaintext_obj, key, ciphertext_obj, compress=compress)
return ciphertext_obj.getvalue()
def decrypt_string(ciphertext, key):
plaintext_obj = StringIO.StringIO()
ciphertext_obj = StringIO.StringIO(ciphertext)
decrypt_file(ciphertext_obj, key, plaintext_obj)
return plaintext_obj.getvalue()
def decrypt_file(ciphertext_file_obj,
key,
plaintext_file_obj,
chunk_size=4096):
# ------------------------------------------------------------------------
# Unpack the header values from the ciphertext.
# ------------------------------------------------------------------------
header_format = ">HHHHHQ?H"
header_size = struct.calcsize(header_format)
header_string = ciphertext_file_obj.read(header_size)
try:
header = struct.unpack(header_format, header_string)
except struct.error:
raise InvalidFormatException("Header is invalid.")
pbkdf2_count = header[0]
pbkdf2_dk_len = header[1]
password_salt_size = header[2]
nonce_size = header[3]
hmac_salt_size = header[4]
ciphertext_size = header[5]
compress = header[6]
hmac_size = header[7]
# ------------------------------------------------------------------------
# Unpack everything except the ciphertext and HMAC, which are the
# last two strings in the ciphertext file.
# ------------------------------------------------------------------------
encrypted_string_format = ''.join([">",
"%ss" % (password_salt_size, ) ,
"%ss" % (nonce_size, ),
"%ss" % (hmac_salt_size, )])
encrypted_string_size = struct.calcsize(encrypted_string_format)
body_string = ciphertext_file_obj.read(encrypted_string_size)
try:
body = struct.unpack(encrypted_string_format, body_string)
except struct.error:
raise InvalidFormatException("Start of body is invalid.")
password_salt = body[0]
nonce = body[1]
hmac_salt = body[2]
# ------------------------------------------------------------------------
# ------------------------------------------------------------------------
# Prepare the HMAC with everything except the ciphertext.
#
# Notice we do not HMAC the ciphertext_size, just like the encrypt
# stage.
# ------------------------------------------------------------------------
hmac_password_derived = PBKDF2(password = key,
salt = hmac_salt,
dkLen = pbkdf2_dk_len,
count = pbkdf2_count,
prf = pbkdf2_prf)
elems_to_hmac = [str(pbkdf2_count),
str(pbkdf2_dk_len),
str(len(password_salt)),
password_salt,
str(len(nonce)),
nonce,
str(len(hmac_salt)),
hmac_salt]
hmac_object = HMAC.new(key = hmac_password_derived,
msg = ''.join(elems_to_hmac),
digestmod = hash_function)
# ------------------------------------------------------------------------
# ------------------------------------------------------------------------
# First pass: stream in the ciphertext object into the HMAC object
# and verify that the HMAC is correct.
#
# Notice we don't need to decompress anything here even if compression
# is in use. We're using Encrypt-Then-MAC.
# ------------------------------------------------------------------------
ciphertext_file_pos = ciphertext_file_obj.tell()
ciphertext_bytes_read = 0
while True:
bytes_remaining = ciphertext_size - ciphertext_bytes_read
current_chunk_size = min(bytes_remaining, chunk_size)
ciphertext_chunk = ciphertext_file_obj.read(current_chunk_size)
if ciphertext_chunk == '':
break
ciphertext_bytes_read += len(ciphertext_chunk)
hmac_object.update(ciphertext_chunk)
if ciphertext_bytes_read != ciphertext_size:
raise InvalidFormatException("first pass ciphertext_bytes_read %s != ciphertext_size %s" % (ciphertext_bytes_read, ciphertext_size))
# the rest of the file is the HMAC.
hmac = ciphertext_file_obj.read()
if len(hmac) != hmac_size:
raise InvalidFormatException("len(hmac) %s != hmac_size %s" % (len(hmac), hmac_size))
hmac_calculated = hmac_object.digest()
if hmac != hmac_calculated:
raise HMACIsNotValidException
# ------------------------------------------------------------------------
# ------------------------------------------------------------------------
# Second pass: stream in the ciphertext object and decrypt it into the
# plaintext object.
# ------------------------------------------------------------------------
cipher_password_derived = PBKDF2(password = key,
salt = password_salt,
dkLen = pbkdf2_dk_len,
count = pbkdf2_count,
prf = pbkdf2_prf)
cipher_ctr = AES.new(key = cipher_password_derived,
mode = AES.MODE_CTR,
counter = CTRCounter(nonce))
ciphertext_file_obj.seek(ciphertext_file_pos, os.SEEK_SET)
ciphertext_bytes_read = 0
if compress:
decompressor = bz2.BZ2Decompressor()
while True:
bytes_remaining = ciphertext_size - ciphertext_bytes_read
current_chunk_size = min(bytes_remaining, chunk_size)
ciphertext_chunk = ciphertext_file_obj.read(current_chunk_size)
end_of_file = ciphertext_chunk == ''
ciphertext_bytes_read += len(ciphertext_chunk)
plaintext_chunk = cipher_ctr.decrypt(ciphertext_chunk)
if compress:
try:
decompressed = decompressor.decompress(plaintext_chunk)
except EOFError:
decompressed = ""
plaintext_chunk = decompressed
plaintext_file_obj.write(plaintext_chunk)
if end_of_file:
break
if ciphertext_bytes_read != ciphertext_size:
raise InvalidFormatException("second pass ciphertext_bytes_read %s != ciphertext_size %s" % (ciphertext_bytes_read, ciphertext_size))
# ------------------------------------------------------------------------
def encrypt_file(plaintext_file_obj,
key,
ciphertext_file_obj,
chunk_size = 4096,
compress = False):
# ------------------------------------------------------------------------
# Prepare input key.
# ------------------------------------------------------------------------
password_salt = os.urandom(salt_length_in_bytes)
cipher_password_derived = PBKDF2(password = key,
salt = password_salt,
dkLen = pbkdf2_dk_len,
count = pbkdf2_count,
prf = pbkdf2_prf)
# ------------------------------------------------------------------------
# ------------------------------------------------------------------------
# Prepare cipher object.
# ------------------------------------------------------------------------
nonce_size = 8
nonce = os.urandom(nonce_size)
cipher_ctr = AES.new(key = cipher_password_derived,
mode = AES.MODE_CTR,
counter = CTRCounter(nonce))
# ------------------------------------------------------------------------
# ------------------------------------------------------------------------
# Prepare HMAC object, and hash what we have so far.
#
# Notice that we do not HMAC the size of the ciphertext. We don't
# know how big it'll be until we compress it, if we do, and we can't
# compress it without reading it into memory. So let the HMAC of
# the ciphertext itself do.
# ------------------------------------------------------------------------
hmac_salt = os.urandom(salt_length_in_bytes)
hmac_password_derived = PBKDF2(password = key,
salt = hmac_salt,
dkLen = pbkdf2_dk_len,
count = pbkdf2_count,
prf = pbkdf2_prf)
elems_to_hmac = [str(pbkdf2_count),
str(pbkdf2_dk_len),
str(len(password_salt)),
password_salt,
str(len(nonce)),
nonce,
str(len(hmac_salt)),
hmac_salt]
hmac_object = HMAC.new(key = hmac_password_derived,
msg = ''.join(elems_to_hmac),
digestmod = hash_function)
# ------------------------------------------------------------------------
# ------------------------------------------------------------------------
# Write in what we have so far into the output, ciphertext file.
#
# Given that the plaintext may be compressed we don't know what
# it's final length will be without compressing it, and we can't
# do this without reading it all into memory. Hence let's
# put 0 as the ciphertext length for now and fill it in after.
# ------------------------------------------------------------------------
header_format = ''.join([">",
"H", # PBKDF2 count
"H", # PBKDF2 derived key length
"H", # Length of password salt
"H", # Length of CTR nonce
"H", # Length of HMAC salt
"Q", # Length of ciphertext
"?", # Is compression used?
"H", # Length of HMAC
"%ss" % (len(password_salt), ) , # Password salt
"%ss" % (nonce_size, ), # CTR nonce
"%ss" % (len(hmac_salt), )]) # HMAC salt
header = struct.pack(header_format,
pbkdf2_count,
pbkdf2_dk_len,
len(password_salt),
len(nonce),
len(hmac_salt),
0, # This is the ciphertext size, wrong for now.
compress,
hmac_object.digest_size,
password_salt,
nonce,
hmac_salt)
ciphertext_file_obj.write(header)
# ------------------------------------------------------------------------
# ------------------------------------------------------------------------
# Stream in the input file and stream out ciphertext into the
# ciphertext file.
# ------------------------------------------------------------------------
ciphertext_size = 0
if compress:
compressor = bz2.BZ2Compressor()
while True:
plaintext_chunk = plaintext_file_obj.read(chunk_size)
end_of_file = plaintext_chunk == ''
if compress:
if end_of_file:
compressed = compressor.flush()
else:
compressed = compressor.compress(plaintext_chunk)
plaintext_chunk = compressed
ciphertext_chunk = cipher_ctr.encrypt(plaintext_chunk)
ciphertext_size += len(ciphertext_chunk)
ciphertext_file_obj.write(ciphertext_chunk)
hmac_object.update(ciphertext_chunk)
if end_of_file:
break
# ------------------------------------------------------------------------
# ------------------------------------------------------------------------
# Write the HMAC to the ciphertext file.
# ------------------------------------------------------------------------
hmac = hmac_object.digest()
ciphertext_file_obj.write(hmac)
# ------------------------------------------------------------------------
# ------------------------------------------------------------------------
# Go back to the header and update the ciphertext size.
#
# Notice that we capture the header such that the last unpacked
# element of the struct is the unsigned long long of the ciphertext
# length.
# ------------------------------------------------------------------------
# Read in.
ciphertext_file_obj.seek(0, os.SEEK_SET)
header_format = ">HHHHHQ"
header_size = struct.calcsize(header_format)
header = ciphertext_file_obj.read(header_size)
# Modify.
header_elems = list(struct.unpack(header_format, header))
header_elems[-1] = ciphertext_size
# Write out.
header = struct.pack(header_format, *header_elems)
ciphertext_file_obj.seek(0, os.SEEK_SET)
ciphertext_file_obj.write(header)
# ------------------------------------------------------------------------