Merge pull request #202 from Gluejar/b2u_fulfillment

B2u file upload, testing and watermarking
pull/1/head
Raymond Yee 2013-08-30 11:08:29 -07:00
commit c5048c15b5
20 changed files with 685 additions and 28 deletions

74
booxtream/__init__.py Normal file
View File

@ -0,0 +1,74 @@
import requests
from django.conf import settings
from urllib import quote
from functools import partial
from xml.etree import ElementTree
from . exceptions import BooXtreamError
from . models import Boox
class BooXtream(object):
""" ``apikey``
The API key for your BooXtream account, obtained from BooXtream. Defaults to using
settings.BOOXTREAM_API_KEY
``apiuser``
The username key for your BooXtream account, obtained from BooXtream. Defaults to using
settings.BOOXTREAM_API_USER
``timeout``
passed to requests
"""
def __init__(self,
apikey='', apiuser='',
timeout=None,
**params):
if not apikey:
apikey = settings.BOOXTREAM_API_KEY
if not apiuser:
apiuser = settings.BOOXTREAM_API_USER
self.endpoint = 'http://service.booxtream.com/'
self.postrequest = partial(requests.post, timeout=timeout, auth=(apiuser,apikey))
def platform(self, epubfile=None, epub=True, kf8mobi=False, **kwargs):
""" Make an API request to BooXtream
``self.apikey``, ``epubfile`` and the supplied ``kwargs``.
Attempts to deserialize the XML response and return the download link.
Will raise ``BooXtreamError`` if BooXtream returns an exception
code.
"""
url = self.endpoint + 'booxtream.xml'
kwargs['epub'] = '1' if epub else '0'
kwargs['kf8mobi'] = '1' if kf8mobi else '0'
files= {'epubfile': epubfile} if epubfile else {}
resp = self.postrequest(url, data=kwargs, files=files)
doc = ElementTree.fromstring(resp.content)
# it turns out an Error can have an Error in it
errors = doc.findall('.//Response/Error')
if len(errors) > 0:
raise BooXtreamError(errors)
download_link_epub = doc.find('.//DownloadLink[@type="epub"]')
if download_link_epub is not None:
download_link_epub = download_link_epub.text
download_link_mobi = doc.find('.//DownloadLink[@type="mobi"]')
if download_link_mobi is not None:
download_link_mobi = download_link_mobi.text
boox = Boox.objects.create(
download_link_epub=download_link_epub,
download_link_mobi=download_link_mobi,
referenceid= kwargs.get('referenceid'),
downloads_remaining= kwargs.get('downloadlimit'),
expirydays=kwargs.get('expirydays'),
)
return boox

13
booxtream/exceptions.py Normal file
View File

@ -0,0 +1,13 @@
class BooXtreamError(Exception):
""" list of errors returned in xml
"""
def __init__(self, errors):
self.errors = errors
def __str__(self):
errormsg='BooXtream errors:'
for error in self.errors:
errormsg += 'Error %s: %s\n'% (error.find('Code').text,error.find('Msg').text)
return errormsg

View File

@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding model 'Boox'
db.create_table('booxtream_boox', (
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('download_link_epub', self.gf('django.db.models.fields.URLField')(max_length=200, null=True)),
('download_link_mobi', self.gf('django.db.models.fields.URLField')(max_length=200, null=True)),
('referenceid', self.gf('django.db.models.fields.CharField')(max_length=32)),
('downloads_remaining', self.gf('django.db.models.fields.PositiveSmallIntegerField')(default=0)),
('expirydays', self.gf('django.db.models.fields.PositiveSmallIntegerField')()),
('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
))
db.send_create_signal('booxtream', ['Boox'])
def backwards(self, orm):
# Deleting model 'Boox'
db.delete_table('booxtream_boox')
models = {
'booxtream.boox': {
'Meta': {'object_name': 'Boox'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'download_link_epub': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True'}),
'download_link_mobi': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True'}),
'downloads_remaining': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '0'}),
'expirydays': ('django.db.models.fields.PositiveSmallIntegerField', [], {}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'referenceid': ('django.db.models.fields.CharField', [], {'max_length': '32'})
}
}
complete_apps = ['booxtream']

View File

19
booxtream/models.py Normal file
View File

@ -0,0 +1,19 @@
from datetime import timedelta, datetime
from django.db import models
class Boox(models.Model):
"""
keeps a record of a file that's been watermarked
"""
download_link_epub = models.URLField(null=True)
download_link_mobi = models.URLField(null=True)
referenceid = models.CharField(max_length=32)
downloads_remaining = models.PositiveSmallIntegerField(default = 0)
expirydays = models.PositiveSmallIntegerField()
created = models.DateTimeField(auto_now_add=True)
@property
def expired(self):
return self.created+timedelta(days=self.expirydays) < datetime.now()

15
booxtream/readme.rst Normal file
View File

@ -0,0 +1,15 @@
This is a thin Python (2.6+) wrapper for BooXtream's API for watermarking epub files. It's configured to look for parameters in Django settings files:
BOOXTREAM_API_KEY = ''
BOOXTREAM_API_USER = ''
BOOXTREAM_TEST_EPUB = ''
http://www.booxtream.com/
version 2.52.
Currently, only the platform method is implemented.
See Tests for usage
Eric Hellman August 2013
Apache license

38
booxtream/tests.py Normal file
View File

@ -0,0 +1,38 @@
import unittest
import time
# uses settings.BOOXTREAM_TEST_EPUB
from . import settings
class TestBooXtream(unittest.TestCase):
def _makeOne(self):
from . import BooXtream
manager = BooXtream()
return manager
def test_booxtream_errors(self):
from .exceptions import BooXtreamError
inst = self._makeOne()
with self.assertRaises(BooXtreamError) as cm:
inst.platform()
self.assertIn( 'expirydays not set',str(cm.exception))
def test_booxtream_good(self):
inst = self._makeOne()
params={
'customeremailaddress':'jane@example.com',
'customername': 'Jane Test',
'languagecode':'1043',
'expirydays': 1,
'downloadlimit': 3,
'exlibris':1,
'chapterfooter':1,
'disclaimer':1,
}
params['referenceid']= 'order'+str(time.time())
epubfile= open(settings.BOOXTREAM_TEST_EPUB)
boox=inst.platform(epubfile=epubfile, **params)
self.assertRegexpMatches(boox.download_link_epub,'download.booxtream.com/')
self.assertFalse(boox.expired)
self.assertEqual(boox.downloads_remaining,3)

View File

@ -0,0 +1,319 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding field 'Acq.watermarked'
db.add_column('core_acq', 'watermarked',
self.gf('django.db.models.fields.related.ForeignKey')(to=orm['booxtream.Boox'], null=True),
keep_default=False)
def backwards(self, orm):
# Deleting field 'Acq.watermarked'
db.delete_column('core_acq', 'watermarked_id')
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'})
},
'booxtream.boox': {
'Meta': {'object_name': 'Boox'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'download_link_epub': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True'}),
'download_link_mobi': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True'}),
'downloads_remaining': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '0'}),
'expirydays': ('django.db.models.fields.PositiveSmallIntegerField', [], {}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'referenceid': ('django.db.models.fields.CharField', [], {'max_length': '32'})
},
'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.acq': {
'Meta': {'object_name': 'Acq'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'license': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '1'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'acqs'", 'to': "orm['auth.User']"}),
'watermarked': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['booxtream.Boox']", 'null': 'True'}),
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'acqs'", 'to': "orm['core.Work']"})
},
'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.badge': {
'Meta': {'object_name': 'Badge'},
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '72', 'blank': 'True'})
},
'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'}),
'cc_date_initial': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'deadline': ('django.db.models.fields.DateTimeField', [], {'db_index': 'True'}),
'description': ('ckeditor.fields.RichTextField', [], {'null': 'True'}),
'details': ('ckeditor.fields.RichTextField', [], {'null': 'True', 'blank': 'True'}),
'dollar_per_day': ('django.db.models.fields.FloatField', [], {'null': 'True'}),
'edition': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'campaigns'", 'null': 'True', 'to': "orm['core.Edition']"}),
'email': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}),
'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'}),
'publisher': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'campaigns'", 'null': 'True', 'to': "orm['core.Publisher']"}),
'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'}),
'type': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '1'}),
'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(2013, 8, 26, 0, 0)', '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'}),
'download_count': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
'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', 'db_index': 'True'}),
'url': ('django.db.models.fields.URLField', [], {'max_length': '1024'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True'})
},
'core.ebookfile': {
'Meta': {'object_name': 'EbookFile'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'edition': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'ebook_files'", 'to': "orm['core.Edition']"}),
'file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
'format': ('django.db.models.fields.CharField', [], {'max_length': '25'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
},
'core.edition': {
'Meta': {'object_name': 'Edition'},
'cover_image': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'public_domain': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
'publication_date': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}),
'publisher_name': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'editions'", 'null': 'True', 'to': "orm['core.PublisherName']"}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '1000'}),
'unglued': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'editions'", 'null': 'True', 'to': "orm['core.Work']"})
},
'core.identifier': {
'Meta': {'unique_together': "(('type', 'value'),)", 'object_name': 'Identifier'},
'edition': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'identifiers'", 'null': 'True', 'to': "orm['core.Edition']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'type': ('django.db.models.fields.CharField', [], {'max_length': '4'}),
'value': ('django.db.models.fields.CharField', [], {'max_length': '31'}),
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'identifiers'", 'to': "orm['core.Work']"})
},
'core.key': {
'Meta': {'object_name': 'Key'},
'encrypted_value': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'})
},
'core.libpref': {
'Meta': {'object_name': 'Libpref'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'marc_link_target': ('django.db.models.fields.CharField', [], {'default': "'UNGLUE'", 'max_length': '6'}),
'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'libpref'", 'unique': 'True', 'to': "orm['auth.User']"})
},
'core.marcrecord': {
'Meta': {'object_name': 'MARCRecord'},
'edition': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'MARCrecords'", 'null': 'True', 'to': "orm['core.Edition']"}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'link_target': ('django.db.models.fields.CharField', [], {'default': "'DIRECT'", 'max_length': '6'}),
'mrc_record': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'}),
'xml_record': ('django.db.models.fields.URLField', [], {'max_length': '200', 'blank': 'True'})
},
'core.offer': {
'Meta': {'object_name': 'Offer'},
'active': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'license': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '1'}),
'price': ('django.db.models.fields.DecimalField', [], {'null': 'True', 'max_digits': '6', 'decimal_places': '2'}),
'work': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'offers'", '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.press': {
'Meta': {'object_name': 'Press'},
'date': ('django.db.models.fields.DateField', [], {}),
'highlight': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'language': ('django.db.models.fields.CharField', [], {'max_length': '20', 'blank': 'True'}),
'note': ('django.db.models.fields.CharField', [], {'max_length': '140', 'blank': 'True'}),
'source': ('django.db.models.fields.CharField', [], {'max_length': '140'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '140'}),
'url': ('django.db.models.fields.URLField', [], {'max_length': '200'})
},
'core.publisher': {
'Meta': {'object_name': 'Publisher'},
'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
'description': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'logo_url': ('django.db.models.fields.URLField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'}),
'name': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'key_publisher'", 'to': "orm['core.PublisherName']"}),
'url': ('django.db.models.fields.URLField', [], {'max_length': '1024', 'null': 'True', 'blank': 'True'})
},
'core.publishername': {
'Meta': {'object_name': 'PublisherName'},
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'publisher': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'alternate_names'", 'null': 'True', 'to': "orm['core.Publisher']"})
},
'core.rightsholder': {
'Meta': {'object_name': 'RightsHolder'},
'can_sell': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
'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'},
'avatar_source': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '1', 'null': 'True'}),
'badges': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "'holders'", 'symmetrical': 'False', 'to': "orm['core.Badge']"}),
'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'}),
'kindle_email': ('django.db.models.fields.EmailField', [], {'max_length': '254', 'blank': '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', 'blank': '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', 'db_index': 'True'}),
'openlibrary_lookup': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
'title': ('django.db.models.fields.CharField', [], {'max_length': '1000'})
}
}
complete_apps = ['core']

View File

@ -47,7 +47,19 @@ from regluit.payment.parameters import (
TRANSACTION_STATUS_FAILED,
TRANSACTION_STATUS_INCOMPLETE
)
from regluit.core.parameters import *
from regluit.core.parameters import (
REWARDS,
BUY2UNGLUE,
INDIVIDUAL,
LIBRARY,
BORROWED,
TESTING
)
from regluit.booxtream import BooXtream
watermarker = BooXtream()
pm = PostMonkey(settings.MAILCHIMP_API_KEY)
@ -226,7 +238,6 @@ class CCLicense():
return ''
(INDIVIDUAL, LIBRARY, BORROWED) = (1, 2, 3)
class Offer(models.Model):
CHOICES = ((INDIVIDUAL,'Individual license'),(LIBRARY,'Library License'))
work = models.ForeignKey("Work", related_name="offers", null=False)
@ -244,13 +255,33 @@ class Acq(models.Model):
"""
Short for Acquisition, this is a made-up word to describe the thing you acquire when you buy or borrow an ebook
"""
CHOICES = ((INDIVIDUAL,'Individual license'),(LIBRARY,'Library License'),(BORROWED,'Borrowed from Library'))
CHOICES = ((INDIVIDUAL,'Individual license'),(LIBRARY,'Library License'),(BORROWED,'Borrowed from Library'), (TESTING,'Just for Testing'))
created = models.DateTimeField(auto_now_add=True)
expires = models.DateTimeField(null=True)
work = models.ForeignKey("Work", related_name='acqs', null=False)
user = models.ForeignKey(User, related_name='acqs')
license = models.PositiveSmallIntegerField(null = False, default = INDIVIDUAL,
choices=CHOICES)
watermarked = models.ForeignKey("booxtream.Boox", null=True)
def get_epub_url(self):
if self.watermarked == None or self.watermarked.expired:
params={
'customeremailaddress': self.user.email,
'customername': self.user.username,
'languagecode':'1043',
'expirydays': 1,
'downloadlimit': 7,
'exlibris':1,
'chapterfooter':1,
'disclaimer':1,
'referenceid': '%s:%s:%s' % (self.work.id, self.user.id, self.id),
'kf8mobi': True,
'epub': True,
}
self.watermarked = watermarker.platform(epubfile= self.work.ebookfiles()[0].file, **params)
self.save()
return self.watermarked.download_link_epub
class Campaign(models.Model):
LICENSE_CHOICES = settings.CCCHOICES
@ -870,7 +901,7 @@ class Work(models.Model):
return Ebook.objects.filter(edition__work=self).order_by('-created')
def ebookfiles(self):
return EbookFile.objects.filter(edition__work=self).order_by('format')
return EbookFile.objects.filter(edition__work=self).order_by('-created')
@property
def download_count(self):
@ -986,7 +1017,7 @@ class Work(models.Model):
def purchased_by(self,user):
if user==None or not user.is_authenticated():
return False
acqs= Acq.objects.filter(user=user,work=self)
acqs= self.acqs.filter(user=user)
if acqs.count()==0:
return False
for acq in acqs:

View File

@ -1 +1,3 @@
(REWARDS, BUY2UNGLUE) = (1, 2)
(INDIVIDUAL, LIBRARY, BORROWED) = (1, 2, 3)
TESTING = 0

View File

@ -184,9 +184,11 @@ def handle_transaction_charged(sender,transaction=None, **kwargs):
else:
# provision the book
Acq = get_model('core', 'Acq')
Acq.objects.create(user=transaction.user,work=transaction.campaign.work,license= transaction.offer.license)
new_acq = Acq.objects.create(user=transaction.user,work=transaction.campaign.work,license= transaction.offer.license)
transaction.campaign.update_left()
notification.send([transaction.user], "purchase_complete", {'transaction':transaction}, True)
from regluit.core.tasks import watermark_acq
watermark_acq(new_acq).delay()
from regluit.core.tasks import emit_notifications
emit_notifications.delay()

View File

@ -121,5 +121,7 @@ def notify_ending_soon():
"""
deadline_impending.send(sender=None, campaign=c)
@task
def watermark_acq(acq):
acq.get_epub_url()

View File

@ -6,8 +6,11 @@ from decimal import Decimal as D
from math import factorial
from time import sleep, mktime
from urlparse import parse_qs, urlparse
from tempfile import NamedTemporaryFile
from celery.task import chord
from celery.task.sets import TaskSet
import requests
import os
"""
django imports
@ -17,6 +20,7 @@ from django.contrib.auth.models import User
from django.contrib.comments.models import Comment
from django.contrib.contenttypes.models import ContentType
from django.contrib.sites.models import Site
from django.core.files import File as DjangoFile
from django.db import IntegrityError
from django.http import Http404
from django.test import TestCase
@ -49,7 +53,11 @@ from regluit.core.models import (
Subject,
Publisher,
Offer,
EbookFile,
Acq,
)
from regluit.core.parameters import TESTING
from regluit.frontend.views import safe_get_work
from regluit.payment.models import Transaction
from regluit.payment.parameters import PAYMENT_TYPE_AUTHORIZATION
@ -812,4 +820,40 @@ class MailingListTests(TestCase):
self.user = User.objects.create_user('chimp_test', 'eric@gluejar.com', 'chimp_test')
self.assertTrue(self.user.profile.on_ml)
class EbookFileTests(TestCase):
def test_ebookfile(self):
"""
Read the test epub file
"""
w = Work.objects.create(title="Work 1")
e = Edition.objects.create(title=w.title,work=w)
u = User.objects.create_user('test', 'test@example.org', 'testpass')
# download the test epub into a temp file
temp = NamedTemporaryFile(delete=False)
test_file_content = requests.get(settings.BOOXTREAM_TEST_EPUB_URL).content
temp.write(test_file_content)
temp.close()
try:
# now we can try putting the test epub file into Django storage
temp_file = open(temp.name)
dj_file = DjangoFile(temp_file)
ebf = EbookFile( format='epub', edition=e, file=dj_file)
ebf.save()
temp_file.close()
finally:
# make sure we get rid of temp file
os.remove(temp.name)
acq=Acq.objects.create(user=u,work=w,license=TESTING)
url= acq.get_epub_url()
self.assertRegexpMatches(url,'download.booxtream.com/')

View File

@ -55,6 +55,7 @@ from regluit.core.lookups import (
EditionLookup
)
from regluit.utils.localdatetime import now
from regluit.utils.fields import EpubFileField
logger = logging.getLogger(__name__)
@ -135,10 +136,15 @@ class EditionForm(forms.ModelForm):
'add_subject': forms.TextInput(attrs={'size': 30}),
'unglued': forms.CheckboxInput(),
}
class EbookFileForm(forms.ModelForm):
file = EpubFileField(max_length=16777216)
def clean_format(self):
return 'epub'
class Meta:
model = EbookFile
widgets = { 'edition': forms.HiddenInput, }
widgets = { 'edition': forms.HiddenInput, 'format': forms.HiddenInput }
exclude = { 'created', }
class EbookForm(forms.ModelForm):

View File

@ -20,15 +20,31 @@
</div>
{% if edition.ebook_files.all %}
<h2> Ebook Files for this Edition</h2>
<ul>
{% for ebook_file in edition.ebook_files.all %}
<li>{{ebook_file.file}} created {{ebook_file.created}} </li>
{% endfor %}
</ul>
<h2> Ebook Files for this Edition</h2>
<ul>
{% for ebook_file in edition.ebook_files.all %}
<li>{{ebook_file.file}} created {{ebook_file.created}} </li>
{% endfor %}
</ul>
{% endif %}
{% if uploaded %}
<h2> Your file was successfully loaded. </h2>
{% if watermarked %}
<p> Reference id: <b>{{watermarked.referenceid}}</b></p>
<ul>
<li><a href="{{watermarked.download_link_epub}}">Watermarked epub for testing</a></li>
<li><a href="{{watermarked.download_link_mobi}}">Watermarked mobi for testing</a></li>
</ul>
{% else %}
<p>
<span class="yikes">Unfortunately, your file failed testing.</span>
The error(s) were: <pre>
{{ upload_error }}</pre>
{% endif %}
{% endif %}
<h2>Upload Ebook files</h2>
<p>At this time, we accept only EPUB files for "Buy to Unglue" campaigns. Use the <a href=https://code.google.com/p/epubcheck/">epubcheck</a> tool to make sure everything will work properly.</p>
<form method="POST" action="#" enctype="multipart/form-data">
{% csrf_token %}
{{form.as_p}}

View File

@ -54,10 +54,11 @@
</div>
</div>
<div class="find-book">
<label>Available formats...</label>
{% for ebookfile in work.ebookfiles %}
<span class="format_display"><img src="/static/images/{{ ebookfile.format }}32.png" height="32" alt="{{ ebookfile.get_format_display }}" title="{{ ebookfile.get_format_display }}" /></span>
{% endfor %}
<h4>Available formats...</h4>
<ul>
<li><span class="format_display"><img src="/static/images/mobi32.png" height="32" alt="mobi" title="mobi" /> (for Kindle) </span></li>
<li><span class="format_display"><img src="/static/images/epub32.png" height="32" alt="epub" title="epub" /> (for iBooks, Readmill, Nook, Kobo) </span></li>
</ul>
</div>
</div>
</div>

View File

@ -134,6 +134,7 @@ from regluit.payment.parameters import (
)
from regluit.utils.localdatetime import now, date_today
from regluit.booxtream.exceptions import BooXtreamError
logger = logging.getLogger(__name__)
@ -424,6 +425,7 @@ def work(request, work_id, action='display'):
})
def edition_uploads(request, edition_id):
context = {}
if not request.user.is_authenticated() :
return render(request, "admins_only.html")
try:
@ -437,13 +439,22 @@ def edition_uploads(request, edition_id):
form = EbookFileForm(request.POST,request.FILES)
if form.is_valid() :
form.save()
context['uploaded']=True
# campaign mangager gets a copy
test_acq = models.Acq.objects.create(user=request.user,work=edition.work,license= TESTING)
try:
test_acq.get_epub_url()
context['watermarked']= test_acq.watermarked
except (BooXtreamError, ET.ParseError) as e:
context['upload_error']= e
form.instance.delete()
else:
form = EbookFileForm(initial={'edition':edition})
return render(request, 'edition_uploads.html', {
form = EbookFileForm(initial={'edition':edition,'format':'epub'})
context.update({
'form': form, 'edition': edition,
'ebook_files': models.EbookFile.objects.filter(edition = edition)
})
return render(request, 'edition_uploads.html', context )
def new_edition(request, work_id, edition_id, by=None):
@ -1091,6 +1102,7 @@ class PurchaseView(PledgeView):
template_name="purchase.html"
form_class = CampaignPurchaseForm
action = "purchase"
offer_id = None
def get_context_data(self, **kwargs):
context = super(PledgeView, self).get_context_data(**kwargs)
@ -1100,7 +1112,8 @@ class PurchaseView(PledgeView):
'faqmenu': 'purchase' ,
'transaction': self.transaction,
'tid': self.transaction.id if self.transaction else None,
'cover_width': cover_width(self.work)
'cover_width': cover_width(self.work),
'offer_id':self.offer_id,
})
return context
@ -1117,10 +1130,9 @@ class PurchaseView(PledgeView):
except Exception, e:
# this used to raise an exception, but that seemed pointless. This now has the effect of preventing any pledges.
return {}
self.data = {
'preapproval_amount':self.get_preapproval_amount(),
'anonymous':self.request.user.profile.anon_pref
'anonymous':self.request.user.profile.anon_pref,
}
if self.request.method == 'POST':
self.data.update(self.request.POST.dict())
@ -1131,11 +1143,13 @@ class PurchaseView(PledgeView):
return {'initial':self.data}
def get_preapproval_amount(self):
offer_id = self.request.REQUEST.get('offer_id', None)
self.offer_id = self.request.REQUEST.get('offer_id', None)
if not self.offer_id:
self.offer_id = self.work.last_campaign().active_offers()[0].id
preapproval_amount = None
if offer_id != None:
if self.offer_id != None:
try:
preapproval_amount = D(models.Offer.objects.get(id=offer_id).price)
preapproval_amount = D(models.Offer.objects.get(id=self.offer_id).price)
except:
preapproval_amount = None
return preapproval_amount

View File

@ -148,6 +148,7 @@ INSTALLED_APPS = (
# this must appear *after* django.frontend or else it overrides the
# registration templates in frontend/templates/registration
'django.contrib.admin',
'booxtream',
)
@ -407,3 +408,7 @@ MARC_CHOICES = (
('UNGLUE', 'Unglue.it link'),
)
BOOXTREAM_API_KEY = '7ynRCsx4q21zEY67it7yk8u5rc6EXY'
BOOXTREAM_API_USER = 'ungluetest'
BOOXTREAM_TEST_EPUB_URL = 'https://github.com/Gluejar/open_access_ebooks_ebook/raw/master/download/open_access_ebooks.epub'
FILE_UPLOAD_MAX_MEMORY_SIZE = 20971520 #20MB

BIN
static/test/134221.0.epub Executable file

Binary file not shown.

14
utils/fields.py Normal file
View File

@ -0,0 +1,14 @@
import zipfile
from django import forms
from django.utils.translation import ugettext_lazy as _
from django.template.defaultfilters import filesizeformat
class EpubFileField(forms.FileField):
"""
does some epub checking; currently only checks its a zip:
"""
def clean(self, data, initial=None):
data = super(EpubFileField, self).clean(data, initial)
if data.name and not zipfile.is_zipfile(data.file):
raise forms.ValidationError(_('%s is not a valid EPUB file' % data.name) )
return data