2013-06-03 16:31:39 +00:00
'''
external library imports
'''
import binascii
import logging
import hashlib
2011-12-03 04:07:55 +00:00
import re
2011-10-10 16:57:10 +00:00
import random
2013-03-14 13:58:21 +00:00
import urllib
2013-11-15 04:16:55 +00:00
import urllib2
2015-01-15 02:29:17 +00:00
from urlparse import urlparse
2015-03-03 22:39:23 +00:00
import unicodedata
2013-03-14 13:58:21 +00:00
2013-06-03 16:31:39 +00:00
from ckeditor . fields import RichTextField
2013-08-08 23:56:26 +00:00
from datetime import timedelta , datetime
2011-10-10 16:57:10 +00:00
from decimal import Decimal
2012-04-03 14:45:12 +00:00
from notification import models as notification
2013-03-04 22:01:33 +00:00
from postmonkey import PostMonkey , MailChimpException
2014-08-28 19:29:41 +00:00
from tempfile import SpooledTemporaryFile
2011-12-03 04:07:55 +00:00
2013-06-03 16:31:39 +00:00
'''
django imports
'''
from django . conf import settings
2011-09-02 04:10:54 +00:00
from django . contrib . auth . models import User
2012-05-10 21:13:09 +00:00
from django . contrib . sites . models import Site
2013-03-09 22:37:33 +00:00
from django . core . urlresolvers import reverse
2013-11-15 04:16:55 +00:00
from django . core . files . base import ContentFile
2013-06-03 16:31:39 +00:00
from django . db import models
from django . db . models import F , Q , get_model
2014-12-13 17:37:35 +00:00
from django . db . models . signals import post_save , pre_delete
2011-11-24 02:41:06 +00:00
from django . utils . translation import ugettext_lazy as _
2011-10-10 16:57:10 +00:00
2013-06-03 16:31:39 +00:00
'''
regluit imports
'''
2011-11-10 17:33:22 +00:00
import regluit
2011-12-20 04:26:55 +00:00
import regluit . core . isbn
2014-05-08 14:21:50 +00:00
import regluit . core . cc as cc
2014-09-05 20:35:57 +00:00
from regluit . core . epub import personalize , ungluify , test_epub , ask_epub
2014-08-28 19:29:41 +00:00
from regluit . core . pdf import ask_pdf , pdf_append
2014-10-27 15:55:46 +00:00
from regluit . marc . models import MARCRecord as NewMARC
2013-06-03 16:31:39 +00:00
from regluit . core . signals import (
successful_campaign ,
unsuccessful_campaign ,
wishlist_added
)
2014-05-07 22:37:19 +00:00
2013-06-03 16:31:39 +00:00
from regluit . utils import crypto
from regluit . utils . localdatetime import now , date_today
2012-07-12 02:51:36 +00:00
2013-06-03 16:31:39 +00:00
from regluit . payment . parameters import (
TRANSACTION_STATUS_ACTIVE ,
TRANSACTION_STATUS_COMPLETE ,
TRANSACTION_STATUS_CANCELED ,
TRANSACTION_STATUS_ERROR ,
TRANSACTION_STATUS_FAILED ,
TRANSACTION_STATUS_INCOMPLETE
)
2013-08-29 00:13:35 +00:00
from regluit . core . parameters import (
REWARDS ,
BUY2UNGLUE ,
2014-01-03 19:15:26 +00:00
THANKS ,
2013-08-29 00:13:35 +00:00
INDIVIDUAL ,
LIBRARY ,
BORROWED ,
2013-10-17 02:48:29 +00:00
TESTING ,
RESERVE ,
2014-02-20 03:18:23 +00:00
THANKED ,
2013-08-29 00:13:35 +00:00
)
2012-07-12 02:51:36 +00:00
2013-08-27 03:56:01 +00:00
from regluit . booxtream import BooXtream
watermarker = BooXtream ( )
2013-10-10 06:41:50 +00:00
from regluit . libraryauth . models import Library
2013-03-04 22:01:33 +00:00
pm = PostMonkey ( settings . MAILCHIMP_API_KEY )
logger = logging . getLogger ( __name__ )
2012-05-16 15:18:04 +00:00
2011-10-08 03:11:57 +00:00
class UnglueitError ( RuntimeError ) :
pass
2011-09-06 03:50:38 +00:00
2012-05-08 23:08:36 +00:00
class Key ( models . Model ) :
""" an encrypted key store """
2012-05-08 23:47:07 +00:00
name = models . CharField ( max_length = 255 , unique = True )
2012-05-08 23:08:36 +00:00
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 )
2011-11-10 01:31:31 +00:00
class CeleryTask ( models . Model ) :
2012-03-07 19:42:16 +00:00
created = models . DateTimeField ( auto_now_add = True , default = now ( ) )
2011-11-19 16:55:35 +00:00
task_id = models . CharField ( max_length = 255 )
2011-11-10 17:33:22 +00:00
user = models . ForeignKey ( User , related_name = " tasks " , null = True )
description = models . CharField ( max_length = 2048 , null = True ) # a description of what the task is
function_name = models . CharField ( max_length = 1024 ) # used to reconstitute the AsyncTask with which to get status
2011-11-15 21:35:50 +00:00
function_args = models . IntegerField ( null = True ) # not full generalized here -- takes only a single arg for now.
2011-11-10 17:33:22 +00:00
active = models . NullBooleanField ( default = True )
2011-11-10 01:31:31 +00:00
def __unicode__ ( self ) :
2012-03-30 19:27:35 +00:00
return " Task %s arg: %s ID# %s %s : State %s " % ( self . function_name , self . function_args , self . task_id , self . description , self . state )
2011-11-10 17:33:22 +00:00
@property
def AsyncResult ( self ) :
f = getattr ( regluit . core . tasks , self . function_name )
return f . AsyncResult ( self . task_id )
@property
def state ( self ) :
f = getattr ( regluit . core . tasks , self . function_name )
return f . AsyncResult ( self . task_id ) . state
@property
def result ( self ) :
f = getattr ( regluit . core . tasks , self . function_name )
return f . AsyncResult ( self . task_id ) . result
2011-11-10 01:31:31 +00:00
@property
2011-11-10 17:33:22 +00:00
def info ( self ) :
f = getattr ( regluit . core . tasks , self . function_name )
return f . AsyncResult ( self . task_id ) . info
2011-11-07 20:41:51 +00:00
class Claim ( models . Model ) :
2011-11-16 19:45:37 +00:00
STATUSES = ( (
2012-04-05 15:58:26 +00:00
u ' active ' , u ' Claim has been accepted. ' ) ,
( u ' pending ' , u ' Claim is pending acceptance. ' ) ,
2012-03-28 18:12:10 +00:00
( u ' release ' , u ' Claim has not been accepted. ' ) ,
2011-11-16 19:45:37 +00:00
)
2011-11-19 16:55:35 +00:00
created = models . DateTimeField ( auto_now_add = True )
2011-11-07 20:41:51 +00:00
rights_holder = models . ForeignKey ( " RightsHolder " , related_name = " claim " , null = False )
work = models . ForeignKey ( " Work " , related_name = " claim " , null = False )
2011-11-16 17:20:19 +00:00
user = models . ForeignKey ( User , related_name = " claim " , null = False )
2014-06-20 18:26:28 +00:00
status = models . CharField ( max_length = 7 , choices = STATUSES , default = ' active ' )
2011-11-20 02:12:18 +00:00
2012-10-01 13:48:57 +00:00
@property
2012-09-28 21:44:08 +00:00
def can_open_new ( self ) :
# whether a campaign can be opened for this claim
#must be an active claim
if self . status != ' active ' :
return False
#can't already be a campaign
for campaign in self . campaigns :
if campaign . status in [ ' ACTIVE ' , ' INITIALIZED ' , ' SUCCESSFUL ' ] :
return False
return True
2014-02-08 20:29:27 +00:00
2013-05-03 22:05:43 +00:00
def __unicode__ ( self ) :
return self . work . title
2014-02-08 20:29:27 +00:00
@property
def campaign ( self ) :
return self . work . last_campaign ( )
@property
def campaigns ( self ) :
return self . work . campaigns . all ( )
2013-11-06 19:58:50 +00:00
def notify_claim ( sender , created , instance , * * kwargs ) :
2013-11-10 15:28:22 +00:00
if ' example.org ' in instance . user . email or hasattr ( instance , ' dont_notify ' ) :
2013-11-06 20:43:54 +00:00
return
2013-11-06 19:58:50 +00:00
try :
( rights , new_rights ) = User . objects . get_or_create ( email = ' rights@gluejar.com ' , defaults = { ' username ' : ' RightsatUnglueit ' } )
except :
rights = None
if instance . user == instance . rights_holder . owner :
ul = ( instance . user , rights )
else :
ul = ( instance . user , instance . rights_holder . owner , rights )
notification . send ( ul , " rights_holder_claim " , { ' claim ' : instance , } )
post_save . connect ( notify_claim , sender = Claim )
2013-05-03 22:05:43 +00:00
2011-11-07 20:41:51 +00:00
class RightsHolder ( models . Model ) :
2011-11-19 16:55:35 +00:00
created = models . DateTimeField ( auto_now_add = True )
2011-11-07 20:41:51 +00:00
email = models . CharField ( max_length = 100 , blank = True )
2012-03-28 18:12:10 +00:00
rights_holder_name = models . CharField ( max_length = 100 , blank = False )
2011-11-07 20:41:51 +00:00
owner = models . ForeignKey ( User , related_name = " rights_holder " , null = False )
2013-06-17 22:53:21 +00:00
can_sell = models . BooleanField ( default = False )
2011-11-15 23:20:29 +00:00
def __unicode__ ( self ) :
return self . rights_holder_name
2011-11-07 20:41:51 +00:00
2011-11-06 19:41:14 +00:00
class Premium ( models . Model ) :
2012-03-26 19:31:41 +00:00
PREMIUM_TYPES = ( ( u ' 00 ' , u ' Default ' ) , ( u ' CU ' , u ' Custom ' ) , ( u ' XX ' , u ' Inactive ' ) )
2012-07-07 22:13:05 +00:00
TIERS = { " supporter " : 25 , " patron " : 50 , " bibliophile " : 100 } #should load this from fixture
2011-11-19 16:55:35 +00:00
created = models . DateTimeField ( auto_now_add = True )
2011-11-06 19:43:10 +00:00
type = models . CharField ( max_length = 2 , choices = PREMIUM_TYPES )
2011-11-06 19:58:51 +00:00
campaign = models . ForeignKey ( " Campaign " , related_name = " premiums " , null = True )
2011-11-06 19:04:31 +00:00
amount = models . DecimalField ( max_digits = 10 , decimal_places = 0 , blank = False )
description = models . TextField ( null = True , blank = False )
2012-03-26 19:31:41 +00:00
limit = models . IntegerField ( default = 0 )
2011-11-06 19:04:31 +00:00
2012-03-26 22:46:34 +00:00
@property
def premium_count ( self ) :
t_model = get_model ( ' payment ' , ' Transaction ' )
return t_model . objects . filter ( premium = self ) . count ( )
@property
def premium_remaining ( self ) :
t_model = get_model ( ' payment ' , ' Transaction ' )
return self . limit - t_model . objects . filter ( premium = self ) . count ( )
2013-05-03 23:21:10 +00:00
def __unicode__ ( self ) :
return ( self . campaign . work . title if self . campaign else ' ' ) + ' $ ' + str ( self . amount )
2012-07-07 22:13:05 +00:00
2012-09-07 13:46:38 +00:00
class PledgeExtra :
2013-08-16 19:49:44 +00:00
def __init__ ( self , premium = None , anonymous = False , ack_name = ' ' , ack_dedication = ' ' , offer = None ) :
2013-08-18 19:21:55 +00:00
self . anonymous = anonymous
self . premium = premium
self . offer = offer
2013-11-18 23:22:09 +00:00
self . extra = { }
2013-08-18 19:21:55 +00:00
if ack_name :
self . extra [ ' ack_name ' ] = ack_name
if ack_dedication :
self . extra [ ' ack_dedication ' ] = ack_dedication
2012-03-26 22:46:34 +00:00
2011-12-13 21:24:39 +00:00
class CampaignAction ( models . Model ) :
timestamp = models . DateTimeField ( auto_now_add = True )
# anticipated types: activated, withdrawn, suspended, restarted, succeeded, failed, unglued
type = models . CharField ( max_length = 15 )
comment = models . TextField ( null = True , blank = True )
campaign = models . ForeignKey ( " Campaign " , related_name = " actions " , null = False )
2012-05-20 04:10:56 +00:00
2013-06-12 20:43:11 +00:00
class Offer ( models . Model ) :
2013-06-17 22:53:21 +00:00
CHOICES = ( ( INDIVIDUAL , ' Individual license ' ) , ( LIBRARY , ' Library License ' ) )
2013-06-12 20:43:11 +00:00
work = models . ForeignKey ( " Work " , related_name = " offers " , null = False )
price = models . DecimalField ( max_digits = 6 , decimal_places = 2 , null = True , blank = False )
license = models . PositiveSmallIntegerField ( null = False , default = INDIVIDUAL ,
2013-06-17 22:53:21 +00:00
choices = CHOICES )
2013-06-12 20:43:11 +00:00
active = models . BooleanField ( default = False )
2013-06-17 22:53:21 +00:00
2013-08-22 18:23:47 +00:00
@property
def days_per_copy ( self ) :
return Decimal ( float ( self . price ) / self . work . last_campaign ( ) . dollar_per_day )
2014-02-11 16:37:39 +00:00
@property
def get_thanks_display ( self ) :
if self . license == LIBRARY :
return ' Suggested contribution for libraries '
else :
return ' Suggested contribution for individuals '
2013-08-20 02:54:43 +00:00
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
"""
2014-02-20 03:18:23 +00:00
CHOICES = ( ( INDIVIDUAL , ' Individual license ' ) , ( LIBRARY , ' Library License ' ) , ( BORROWED , ' Borrowed from Library ' ) , ( TESTING , ' Just for Testing ' ) , ( RESERVE , ' On Reserve ' ) , ( THANKED , ' Already Thanked ' ) , )
2014-07-31 16:17:42 +00:00
created = models . DateTimeField ( auto_now_add = True , db_index = True , )
2013-08-20 02:54:43 +00:00
expires = models . DateTimeField ( null = True )
2013-10-18 16:36:55 +00:00
refreshes = models . DateTimeField ( auto_now_add = True , default = now ( ) )
2013-11-07 04:25:52 +00:00
refreshes . editable = True
2013-11-08 17:13:34 +00:00
refreshed = models . BooleanField ( default = True )
2013-08-20 02:54:43 +00:00
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 )
2013-08-27 03:56:01 +00:00
watermarked = models . ForeignKey ( " booxtream.Boox " , null = True )
2013-09-06 02:54:11 +00:00
nonce = models . CharField ( max_length = 32 , null = True )
2013-09-20 21:46:06 +00:00
2013-10-17 02:48:29 +00:00
# when the acq is a loan, this points at the library's acq it's derived from
lib_acq = models . ForeignKey ( " self " , related_name = " loans " , null = True )
2013-11-07 04:25:52 +00:00
def __unicode__ ( self ) :
if self . lib_acq :
return " %s , %s : %s for %s " % ( self . work , self . get_license_display ( ) , self . lib_acq . user , self . user )
else :
return " %s , %s for %s " % ( self . work , self . get_license_display ( ) , self . user , )
2013-09-06 02:54:11 +00:00
@property
def expired ( self ) :
if self . expires is None :
return False
else :
return self . expires < datetime . now ( )
def get_mobi_url ( self ) :
2013-10-17 02:48:29 +00:00
if self . expired :
return ' '
2013-09-06 02:54:11 +00:00
return self . get_watermarked ( ) . download_link_mobi
2013-08-27 03:56:01 +00:00
def get_epub_url ( self ) :
2013-10-17 02:48:29 +00:00
if self . expired :
return ' '
2013-09-06 02:54:11 +00:00
return self . get_watermarked ( ) . download_link_epub
def get_watermarked ( self ) :
2013-08-27 03:56:01 +00:00
if self . watermarked == None or self . watermarked . expired :
2013-10-17 02:48:29 +00:00
if self . on_reserve :
self . borrow ( self . user )
2013-12-18 17:34:53 +00:00
do_watermark = self . work . last_campaign ( ) . do_watermark
2013-08-27 03:56:01 +00:00
params = {
2013-12-18 17:34:53 +00:00
' customeremailaddress ' : self . user . email if do_watermark else ' ' ,
' customername ' : self . user . username if do_watermark else ' an ungluer ' ,
2013-09-20 21:46:06 +00:00
' languagecode ' : ' 1033 ' ,
2013-08-27 03:56:01 +00:00
' expirydays ' : 1 ,
' downloadlimit ' : 7 ,
2013-11-03 22:19:23 +00:00
' exlibris ' : 0 ,
2014-04-30 19:17:10 +00:00
' chapterfooter ' : 0 ,
2013-09-20 21:46:06 +00:00
' disclaimer ' : 0 ,
2013-12-18 17:34:53 +00:00
' referenceid ' : ' %s : %s : %s ' % ( self . work . id , self . user . id , self . id ) if do_watermark else ' N/A ' ,
2013-08-27 22:03:35 +00:00
' kf8mobi ' : True ,
' epub ' : True ,
2013-08-27 03:56:01 +00:00
}
2014-04-28 14:59:03 +00:00
personalized = personalize ( self . work . epubfiles ( ) [ 0 ] . file , self )
2014-04-30 19:17:10 +00:00
personalized . seek ( 0 )
self . watermarked = watermarker . platform ( epubfile = personalized , * * params )
2013-08-27 03:56:01 +00:00
self . save ( )
2013-09-06 02:54:11 +00:00
return self . watermarked
def _hash ( self ) :
2015-04-09 17:12:22 +00:00
return hashlib . md5 ( ' %s : %s : %s : %s ' % ( settings . SOCIAL_AUTH_TWITTER_SECRET , self . user . id , self . work . id , self . created ) ) . hexdigest ( )
2013-10-17 02:48:29 +00:00
def expire_in ( self , delta ) :
2014-12-18 18:37:28 +00:00
self . expires = ( now ( ) + delta ) if delta else now ( )
2013-10-17 02:48:29 +00:00
self . save ( )
2013-10-18 16:36:55 +00:00
if self . lib_acq :
2013-11-01 20:15:01 +00:00
self . lib_acq . refreshes = now ( ) + delta
2013-11-08 17:13:34 +00:00
self . lib_acq . refreshed = False
2013-10-18 16:36:55 +00:00
self . lib_acq . save ( )
2013-10-17 02:48:29 +00:00
@property
def on_reserve ( self ) :
return self . license == RESERVE
def borrow ( self , user = None ) :
if self . on_reserve :
self . license = BORROWED
self . expire_in ( timedelta ( days = 14 ) )
2013-10-18 19:35:48 +00:00
self . user . wishlist . add_work ( self . work , " borrow " )
2013-10-31 16:26:43 +00:00
notification . send ( [ self . user ] , " library_borrow " , { ' acq ' : self } )
2013-10-17 02:48:29 +00:00
return self
2013-10-18 19:35:48 +00:00
elif self . borrowable and user :
user . wishlist . add_work ( self . work , " borrow " )
borrowed = Acq . objects . create ( user = user , work = self . work , license = BORROWED , lib_acq = self )
from regluit . core . tasks import watermark_acq
2013-10-31 16:26:43 +00:00
notification . send ( [ user ] , " library_borrow " , { ' acq ' : borrowed } )
2013-10-18 19:35:48 +00:00
watermark_acq . delay ( borrowed )
return borrowed
2013-10-17 02:48:29 +00:00
@property
def borrowable ( self ) :
if self . license == RESERVE and not self . expired :
return True
2013-10-18 16:36:55 +00:00
if self . license == LIBRARY :
return self . refreshes < datetime . now ( )
else :
2013-10-17 02:48:29 +00:00
return False
2013-11-08 17:13:34 +00:00
@property
def holds ( self ) :
return Hold . objects . filter ( library__user = self . user , work = self . work ) . order_by ( ' created ' )
2013-10-17 02:48:29 +00:00
2013-09-06 02:54:11 +00:00
2013-11-01 20:15:01 +00:00
def config_acq ( sender , instance , created , * * kwargs ) :
2013-09-06 02:54:11 +00:00
if created :
instance . nonce = instance . _hash ( )
instance . save ( )
2013-10-17 02:48:29 +00:00
if instance . license == RESERVE :
2013-11-08 17:13:34 +00:00
instance . expire_in ( timedelta ( hours = 24 ) )
2013-10-17 02:48:29 +00:00
if instance . license == BORROWED :
instance . expire_in ( timedelta ( days = 14 ) )
2013-09-06 02:54:11 +00:00
2013-11-01 20:15:01 +00:00
post_save . connect ( config_acq , sender = Acq )
2013-06-12 20:43:11 +00:00
2013-11-08 17:13:34 +00:00
class Hold ( models . Model ) :
created = models . DateTimeField ( auto_now_add = True )
work = models . ForeignKey ( " Work " , related_name = ' holds ' , null = False )
user = models . ForeignKey ( User , related_name = ' holds ' , null = False )
library = models . ForeignKey ( Library , related_name = ' holds ' , null = False )
def __unicode__ ( self ) :
return ' %s for %s at %s ' % ( self . work , self . user . username , self . library )
def ahead ( self ) :
return Hold . objects . filter ( work = self . work , library = self . library , created__lt = self . created ) . count ( )
2012-05-20 04:10:56 +00:00
class Campaign ( models . Model ) :
2014-11-21 02:34:19 +00:00
LICENSE_CHOICES = cc . FREECHOICES
2014-07-31 16:17:42 +00:00
created = models . DateTimeField ( auto_now_add = True , )
2011-10-14 19:37:22 +00:00
name = models . CharField ( max_length = 500 , null = True , blank = False )
2012-07-27 18:23:19 +00:00
description = RichTextField ( null = True , blank = False )
details = RichTextField ( null = True , blank = True )
2014-01-03 19:15:26 +00:00
target = models . DecimalField ( max_digits = 14 , decimal_places = 2 , null = True , default = 0.00 )
2012-03-23 16:30:49 +00:00
license = models . CharField ( max_length = 255 , choices = LICENSE_CHOICES , default = ' CC BY-NC-ND ' )
2014-07-31 16:17:42 +00:00
left = models . DecimalField ( max_digits = 14 , decimal_places = 2 , null = True , db_index = True , )
2014-01-03 19:15:26 +00:00
deadline = models . DateTimeField ( db_index = True , null = True )
2013-08-10 20:29:58 +00:00
dollar_per_day = models . FloatField ( null = True )
2013-08-08 23:56:26 +00:00
cc_date_initial = models . DateTimeField ( null = True )
2014-07-31 16:17:42 +00:00
activated = models . DateTimeField ( null = True , db_index = True , )
2011-10-14 19:37:22 +00:00
paypal_receiver = models . CharField ( max_length = 100 , blank = True )
amazon_receiver = models . CharField ( max_length = 100 , blank = True )
2011-10-09 18:27:27 +00:00
work = models . ForeignKey ( " Work " , related_name = " campaigns " , null = False )
2011-11-21 03:23:51 +00:00
managers = models . ManyToManyField ( User , related_name = " campaigns " , null = False )
2012-01-03 15:20:17 +00:00
# status: INITIALIZED, ACTIVE, SUSPENDED, WITHDRAWN, SUCCESSFUL, UNSUCCESSFUL
2014-07-31 16:17:42 +00:00
status = models . CharField ( max_length = 15 , null = True , blank = False , default = " INITIALIZED " , db_index = True , )
2013-06-17 22:53:21 +00:00
type = models . PositiveSmallIntegerField ( null = False , default = REWARDS ,
2014-07-04 00:26:36 +00:00
choices = ( ( REWARDS , ' Pledge-to-unglue campaign ' ) , ( BUY2UNGLUE , ' Buy-to-unglue campaign ' ) , ( THANKS , ' Thanks-for-ungluing campaign ' ) ) )
2012-05-01 14:56:19 +00:00
edition = models . ForeignKey ( " Edition " , related_name = " campaigns " , null = True )
2013-03-26 03:41:19 +00:00
email = models . CharField ( max_length = 100 , blank = True )
publisher = models . ForeignKey ( " Publisher " , related_name = " campaigns " , null = True )
2013-12-18 17:34:53 +00:00
do_watermark = models . BooleanField ( default = True )
2014-08-30 16:04:50 +00:00
use_add_ask = models . BooleanField ( default = True )
2013-11-18 23:22:09 +00:00
def __init__ ( self , * args , * * kwargs ) :
self . problems = [ ]
return super ( Campaign , self ) . __init__ ( * args , * * kwargs )
2011-11-24 02:41:06 +00:00
2011-09-10 11:36:38 +00:00
def __unicode__ ( self ) :
2011-10-08 03:11:57 +00:00
try :
return u " Campaign for %s " % self . work . title
except :
return u " Campaign %s (no associated work) " % self . name
2011-11-24 02:41:06 +00:00
2012-09-21 16:10:13 +00:00
def clone ( self ) :
2013-08-18 19:21:55 +00:00
""" use a previous UNSUCCESSFUL campaign ' s data as the basis for a new campaign
assume that B2U campaigns don ' t need cloning
"""
2012-09-21 16:10:13 +00:00
if self . clonable ( ) :
old_managers = self . managers . all ( )
2012-09-27 20:45:13 +00:00
# copy custom premiums
2012-09-21 17:54:59 +00:00
new_premiums = self . premiums . filter ( type = ' CU ' )
2012-09-27 20:45:13 +00:00
# setting pk to None will insert new copy http://stackoverflow.com/a/4736172/7782
2012-09-21 16:10:13 +00:00
self . pk = None
self . status = ' INITIALIZED '
2012-09-27 20:45:13 +00:00
# set deadline far in future -- presumably RH will set deadline to proper value before campaign launched
2012-09-21 16:10:13 +00:00
self . deadline = date_today ( ) + timedelta ( days = int ( settings . UNGLUEIT_LONGEST_DEADLINE ) )
2012-09-27 20:45:13 +00:00
# allow created, activated dates to be autoset by db
2012-09-21 16:10:13 +00:00
self . created = None
self . name = ' copy of %s ' % self . name
self . activated = None
2012-09-21 16:32:32 +00:00
self . update_left ( )
2012-09-21 16:10:13 +00:00
self . save ( )
self . managers = old_managers
2012-09-27 20:45:13 +00:00
# clone associated premiums
2012-09-21 16:10:13 +00:00
for premium in new_premiums :
premium . pk = None
premium . created = None
premium . campaign = self
premium . save ( )
2012-09-21 17:54:59 +00:00
return self
2012-09-21 16:10:13 +00:00
else :
return None
def clonable ( self ) :
2012-09-27 20:45:13 +00:00
""" campaign clonable if it ' s UNSUCCESSFUL and is the last campaign associated with a Work """
2012-09-21 16:10:13 +00:00
if self . status == ' UNSUCCESSFUL ' and self . work . last_campaign ( ) . id == self . id :
return True
else :
return False
2013-11-18 23:22:09 +00:00
2011-11-24 02:41:06 +00:00
@property
def launchable ( self ) :
may_launch = True
2013-08-19 20:01:32 +00:00
try :
if self . status != ' INITIALIZED ' :
if self . status == ' ACTIVE ' :
self . problems . append ( _ ( ' The campaign is already launched ' ) )
else :
self . problems . append ( _ ( ' A campaign must initialized properly before it can be launched ' ) )
may_launch = False
2014-01-15 15:19:57 +00:00
if not self . description :
self . problems . append ( _ ( ' A campaign must have a description ' ) )
2013-08-19 20:01:32 +00:00
may_launch = False
2014-01-15 15:19:57 +00:00
if self . type == REWARDS :
if self . deadline :
if self . deadline . date ( ) - date_today ( ) > timedelta ( days = int ( settings . UNGLUEIT_LONGEST_DEADLINE ) ) :
self . problems . append ( _ ( ' The chosen closing date is more than %s days from now ' % settings . UNGLUEIT_LONGEST_DEADLINE ) )
may_launch = False
else :
self . problems . append ( _ ( ' A pledge campaign must have a closing date ' ) )
may_launch = False
if self . target :
if self . target < Decimal ( settings . UNGLUEIT_MINIMUM_TARGET ) :
self . problems . append ( _ ( ' A pledge campaign may not be launched with a target less than $ %s ' % settings . UNGLUEIT_MINIMUM_TARGET ) )
may_launch = False
else :
self . problems . append ( _ ( ' A campaign must have a target ' ) )
may_launch = False
if self . type == BUY2UNGLUE :
if self . work . offers . filter ( price__gt = 0 , active = True ) . count ( ) == 0 :
self . problems . append ( _ ( ' You can \' t launch a buy-to-unglue campaign before setting a price for your ebooks ' ) )
may_launch = False
if EbookFile . objects . filter ( edition__work = self . work ) . count ( ) == 0 :
self . problems . append ( _ ( ' You can \' t launch a buy-to-unglue campaign if you don \' t have any ebook files uploaded ' ) )
may_launch = False
if ( ( self . cc_date_initial is None ) or ( self . cc_date_initial > datetime . combine ( settings . MAX_CC_DATE , datetime . min . time ( ) ) ) or ( self . cc_date_initial < now ( ) ) ) :
self . problems . append ( _ ( ' You must set an initial Ungluing Date that is in the future and not after %s ' % settings . MAX_CC_DATE ) )
may_launch = False
if self . target :
if self . target < Decimal ( settings . UNGLUEIT_MINIMUM_TARGET ) :
self . problems . append ( _ ( ' A buy-to-unglue campaign may not be launched with a target less than $ %s ' % settings . UNGLUEIT_MINIMUM_TARGET ) )
may_launch = False
else :
self . problems . append ( _ ( ' A buy-to-unglue campaign must have a target ' ) )
may_launch = False
if self . type == THANKS :
if EbookFile . objects . filter ( edition__work = self . work ) . count ( ) == 0 :
self . problems . append ( _ ( ' You can \' t launch a thanks-for-ungluing campaign if you don \' t have any ebook files uploaded ' ) )
may_launch = False
2013-08-19 20:01:32 +00:00
except Exception as e :
self . problems . append ( ' Exception checking launchability ' + str ( e ) )
2011-11-24 02:41:06 +00:00
may_launch = False
return may_launch
2011-12-06 14:40:04 +00:00
2012-11-13 18:15:38 +00:00
def update_status ( self , ignore_deadline_for_success = False , send_notice = False , process_transactions = False ) :
2012-06-20 22:48:55 +00:00
""" Updates the campaign ' s status. returns true if updated.
2013-11-12 19:58:29 +00:00
for REWARDS :
2012-06-21 19:01:48 +00:00
Computes UNSUCCESSFUL only after the deadline has passed
Computes SUCCESSFUL only after the deadline has passed if ignore_deadline_for_success is TRUE - - otherwise looks just at amount of pledges accumulated
2012-07-05 14:04:27 +00:00
by default , send_notice is False so that we have to explicitly send specify delivery of successful_campaign notice
2013-11-12 19:58:29 +00:00
for BUY2UNGLUE :
Sets SUCCESSFUL when cc_date is in the past .
2012-11-13 18:15:38 +00:00
if process_transactions is True , also execute or cancel associated transactions
2012-04-02 23:10:56 +00:00
2011-10-07 21:17:54 +00:00
"""
2011-12-06 15:35:05 +00:00
if not self . status == ' ACTIVE ' :
return False
2013-11-12 19:48:04 +00:00
elif self . type == REWARDS :
if ( ignore_deadline_for_success or self . deadline < now ( ) ) and self . current_total > = self . target :
self . status = ' SUCCESSFUL '
self . save ( )
action = CampaignAction ( campaign = self , type = ' succeeded ' , comment = self . current_total )
action . save ( )
2012-11-21 17:21:01 +00:00
2013-11-12 19:48:04 +00:00
if process_transactions :
p = PaymentManager ( )
results = p . execute_campaign ( self )
2012-11-21 17:21:01 +00:00
2013-11-12 19:48:04 +00:00
if send_notice :
successful_campaign . send ( sender = None , campaign = self )
2012-11-21 17:21:01 +00:00
2013-11-12 19:48:04 +00:00
# should be more sophisticated in whether to return True -- look at all the transactions?
return True
elif self . deadline < now ( ) and self . current_total < self . target :
self . status = ' UNSUCCESSFUL '
self . save ( )
action = CampaignAction ( campaign = self , type = ' failed ' , comment = self . current_total )
action . save ( )
2012-11-21 17:21:01 +00:00
2013-11-12 19:48:04 +00:00
if process_transactions :
p = PaymentManager ( )
results = p . cancel_campaign ( self )
2012-11-21 17:21:01 +00:00
2013-11-12 19:48:04 +00:00
if send_notice :
regluit . core . signals . unsuccessful_campaign . send ( sender = None , campaign = self )
# should be more sophisticated in whether to return True -- look at all the transactions?
return True
elif self . type == BUY2UNGLUE :
2013-11-14 17:38:58 +00:00
if self . cc_date < date_today ( ) :
2013-11-12 19:48:04 +00:00
self . status = ' SUCCESSFUL '
self . save ( )
action = CampaignAction ( campaign = self , type = ' succeeded ' , comment = self . current_total )
2013-11-15 07:10:14 +00:00
action . save ( )
self . watermark_success ( )
2013-11-12 19:48:04 +00:00
if send_notice :
successful_campaign . send ( sender = None , campaign = self )
# should be more sophisticated in whether to return True -- look at all the transactions?
return True
return False
2013-08-08 23:56:26 +00:00
_current_total = None
2011-10-10 20:56:53 +00:00
@property
def current_total ( self ) :
2013-08-08 23:56:26 +00:00
if self . _current_total is None :
p = PaymentManager ( )
self . _current_total = p . query_campaign ( self , summary = True , campaign_total = True )
return self . _current_total
def set_dollar_per_day ( self ) :
if self . status != ' INITIALIZED ' and self . dollar_per_day :
return self . dollar_per_day
2013-08-10 20:29:58 +00:00
if self . cc_date_initial is None :
return None
2013-09-26 13:26:50 +00:00
start_datetime = self . activated if self . activated else datetime . today ( )
time_to_cc = self . cc_date_initial - start_datetime
2013-08-08 23:56:26 +00:00
self . dollar_per_day = float ( self . target ) / float ( time_to_cc . days )
2013-10-04 02:54:25 +00:00
if self . status != ' DEMO ' :
self . save ( )
2013-08-08 23:56:26 +00:00
return self . dollar_per_day
2013-08-19 20:01:32 +00:00
def set_cc_date_initial ( self , a_date = settings . MAX_CC_DATE ) :
self . cc_date_initial = datetime . combine ( a_date , datetime . min . time ( ) )
2013-08-08 23:56:26 +00:00
@property
def cc_date ( self ) :
2014-01-13 19:32:03 +00:00
if self . type in { REWARDS , THANKS } :
return None
if self . dollar_per_day == None :
2013-08-19 20:01:32 +00:00
return self . cc_date_initial . date ( )
2013-08-10 20:29:58 +00:00
cc_advance_days = float ( self . current_total ) / self . dollar_per_day
2013-08-08 23:56:26 +00:00
return ( self . cc_date_initial - timedelta ( days = cc_advance_days ) ) . date ( )
2012-06-22 03:20:32 +00:00
2013-08-08 23:56:26 +00:00
2012-06-22 03:20:32 +00:00
def update_left ( self ) :
2014-03-01 22:13:57 +00:00
self . _current_total = None
2014-01-03 19:15:26 +00:00
if self . type == THANKS :
self . left == Decimal ( 0.00 )
elif self . type == BUY2UNGLUE :
2013-08-10 20:29:58 +00:00
self . left = Decimal ( self . dollar_per_day * float ( ( self . cc_date_initial - datetime . today ( ) ) . days ) ) - self . current_total
2013-08-08 23:56:26 +00:00
else :
2013-08-10 20:29:58 +00:00
self . left = self . target - self . current_total
2013-10-04 02:54:25 +00:00
if self . status != ' DEMO ' :
self . save ( )
2011-10-10 20:56:53 +00:00
2012-06-22 03:20:32 +00:00
def transactions ( self , * * kwargs ) :
2011-10-13 17:28:23 +00:00
p = PaymentManager ( )
2012-09-13 21:12:49 +00:00
# handle default parameter values
kw = { ' summary ' : False , ' campaign_total ' : True }
kw . update ( kwargs )
return p . query_campaign ( self , * * kw )
2012-04-02 18:17:18 +00:00
2011-10-08 03:11:57 +00:00
def activate ( self ) :
2011-10-10 20:35:22 +00:00
status = self . status
2011-10-08 03:11:57 +00:00
if status != ' INITIALIZED ' :
2011-11-24 02:41:06 +00:00
raise UnglueitError ( _ ( ' Campaign needs to be initialized in order to be activated ' ) )
2012-05-03 00:19:40 +00:00
try :
active_claim = self . work . claim . filter ( status = " active " ) [ 0 ]
except IndexError , e :
raise UnglueitError ( _ ( ' Campaign needs to have an active claim in order to be activated ' ) )
2014-02-21 18:16:55 +00:00
if not self . launchable :
raise UnglueitError ( ' Configuration issues need to be addressed before campaign is activated: %s ' % unicode ( self . problems [ 0 ] ) )
2011-12-06 15:35:05 +00:00
self . status = ' ACTIVE '
2011-12-14 05:52:42 +00:00
self . left = self . target
2013-09-26 13:26:50 +00:00
self . activated = datetime . today ( )
2014-02-11 01:16:56 +00:00
if self . type == THANKS :
# make ebooks from ebookfiles
self . work . make_ebooks_from_ebfs ( )
2011-10-09 18:27:27 +00:00
self . save ( )
2013-09-26 13:26:50 +00:00
action = CampaignAction ( campaign = self , type = ' activated ' , comment = self . get_type_display ( ) )
2012-04-03 15:04:12 +00:00
ungluers = self . work . wished_by ( )
2013-02-28 21:04:41 +00:00
notification . queue ( ungluers , " wishlist_active " , { ' campaign ' : self } , True )
2012-04-02 18:17:18 +00:00
return self
2012-04-03 15:04:12 +00:00
2011-10-08 03:11:57 +00:00
def suspend ( self , reason ) :
2011-10-10 20:35:22 +00:00
status = self . status
2011-10-08 03:11:57 +00:00
if status != ' ACTIVE ' :
2011-11-24 02:41:06 +00:00
raise UnglueitError ( _ ( ' Campaign needs to be active in order to be suspended ' ) )
2011-12-13 21:24:39 +00:00
action = CampaignAction ( campaign = self , type = ' suspended ' , comment = reason )
action . save ( )
2011-12-06 15:35:05 +00:00
self . status = ' SUSPENDED '
2011-10-09 18:27:27 +00:00
self . save ( )
return self
2011-10-08 03:11:57 +00:00
def withdraw ( self , reason ) :
2011-10-10 20:35:22 +00:00
status = self . status
2011-10-08 03:11:57 +00:00
if status != ' ACTIVE ' :
2011-11-24 02:41:06 +00:00
raise UnglueitError ( _ ( ' Campaign needs to be active in order to be withdrawn ' ) )
2011-12-13 21:24:39 +00:00
action = CampaignAction ( campaign = self , type = ' withdrawn ' , comment = reason )
action . save ( )
2011-12-06 15:35:05 +00:00
self . status = ' WITHDRAWN '
2011-10-09 18:27:27 +00:00
self . save ( )
return self
2011-10-08 03:11:57 +00:00
2011-12-13 21:24:39 +00:00
def resume ( self , reason ) :
2011-10-08 03:11:57 +00:00
""" Change campaign status from SUSPENDED to ACTIVE. We may want to track reason for resuming and track history """
2011-10-10 20:35:22 +00:00
status = self . status
2011-10-08 03:11:57 +00:00
if status != ' SUSPENDED ' :
2011-11-24 02:41:06 +00:00
raise UnglueitError ( _ ( ' Campaign needs to be suspended in order to be resumed ' ) )
2011-12-13 21:24:39 +00:00
if not reason :
reason = ' '
action = CampaignAction ( campaign = self , type = ' restarted ' , comment = reason )
action . save ( )
2011-12-06 15:35:05 +00:00
self . status = ' ACTIVE '
2011-10-09 18:27:27 +00:00
self . save ( )
return self
2011-11-06 19:02:29 +00:00
def supporters ( self ) :
2013-02-21 20:10:01 +00:00
# expensive query used in loop; stash it
if hasattr ( self , ' _translist_ ' ) :
return self . _translist_
2012-01-09 18:55:22 +00:00
""" nb: returns (distinct) supporter IDs, not supporter objects """
2013-02-21 20:10:01 +00:00
self . _translist_ = self . transactions ( ) . filter ( status = TRANSACTION_STATUS_ACTIVE ) . values_list ( ' user ' , flat = True ) . distinct ( )
2013-02-25 23:31:37 +00:00
return self . _translist_
2012-07-07 20:38:23 +00:00
@property
def supporters_count ( self ) :
# avoid transmitting the whole list if you don't need to; let the db do the count.
2012-09-21 16:32:32 +00:00
active = self . transactions ( ) . filter ( status = TRANSACTION_STATUS_ACTIVE ) . values_list ( ' user ' , flat = True ) . distinct ( ) . count ( )
complete = self . transactions ( ) . filter ( status = TRANSACTION_STATUS_COMPLETE ) . values_list ( ' user ' , flat = True ) . distinct ( ) . count ( )
return active + complete
2014-02-20 21:31:13 +00:00
@property
def anon_count ( self ) :
# avoid transmitting the whole list if you don't need to; let the db do the count.
complete = self . transactions ( ) . filter ( status = TRANSACTION_STATUS_COMPLETE , user = None ) . count ( )
return complete
2012-07-12 02:51:36 +00:00
def transaction_to_recharge ( self , user ) :
""" given a user, return the transaction to be recharged if there is one -- None otherwise """
2012-07-07 22:13:05 +00:00
2012-07-12 02:51:36 +00:00
# only if a campaign is SUCCESSFUL, we allow for recharged
if self . status == ' SUCCESSFUL ' :
if self . transaction_set . filter ( Q ( user = user ) & ( Q ( status = TRANSACTION_STATUS_COMPLETE ) | Q ( status = TRANSACTION_STATUS_ACTIVE ) ) ) . count ( ) :
# presence of an active or complete transaction means no transaction to recharge
return None
else :
transactions = self . transaction_set . filter ( Q ( user = user ) & ( Q ( status = TRANSACTION_STATUS_ERROR ) | Q ( status = TRANSACTION_STATUS_FAILED ) ) )
# assumption --that the first failed/errored transaction has the amount we need to recharge
if transactions . count ( ) :
return transactions [ 0 ]
else :
return None
else :
return None
2013-02-21 20:10:01 +00:00
2012-07-07 22:13:05 +00:00
def ungluers ( self ) :
2013-02-21 20:10:01 +00:00
# expensive query used in loop; stash it
if hasattr ( self , ' _ungluers_ ' ) :
return self . _ungluers_
2012-07-07 22:13:05 +00:00
p = PaymentManager ( )
ungluers = { " all " : [ ] , " supporters " : [ ] , " patrons " : [ ] , " bibliophiles " : [ ] }
if self . status == " ACTIVE " :
translist = p . query_campaign ( self , summary = False , pledged = True , authorized = True )
elif self . status == " SUCCESSFUL " :
translist = p . query_campaign ( self , summary = False , pledged = True , completed = True )
else :
translist = [ ]
for transaction in translist :
ungluers [ ' all ' ] . append ( transaction . user )
if not transaction . anonymous :
if transaction . amount > = Premium . TIERS [ " bibliophile " ] :
ungluers [ ' bibliophiles ' ] . append ( transaction . user )
elif transaction . amount > = Premium . TIERS [ " patron " ] :
ungluers [ ' patrons ' ] . append ( transaction . user )
elif transaction . amount > = Premium . TIERS [ " supporter " ] :
ungluers [ ' supporters ' ] . append ( transaction . user )
2013-02-21 20:10:01 +00:00
self . _ungluers_ = ungluers
2012-07-07 22:13:05 +00:00
return ungluers
2011-12-13 21:24:39 +00:00
2012-08-06 17:35:29 +00:00
def ungluer_transactions ( self ) :
"""
returns a list of authorized transactions for campaigns in progress ,
or completed transactions for successful campaigns
used to build the acks page - - because ack_name , _link , _dedication adhere to transactions ,
it ' s easier to return transactions than ungluers
"""
p = PaymentManager ( )
2012-12-11 18:17:28 +00:00
ungluers = { " all " : [ ] , " supporters " : [ ] , " anon_supporters " : 0 , " patrons " : [ ] , " anon_patrons " : 0 , " bibliophiles " : [ ] }
2012-08-06 17:35:29 +00:00
if self . status == " ACTIVE " :
translist = p . query_campaign ( self , summary = False , pledged = True , authorized = True )
elif self . status == " SUCCESSFUL " :
translist = p . query_campaign ( self , summary = False , pledged = True , completed = True )
else :
translist = [ ]
for transaction in translist :
2013-12-31 02:51:32 +00:00
ungluers [ ' all ' ] . append ( transaction )
2012-08-06 19:44:43 +00:00
if transaction . amount > = Premium . TIERS [ " bibliophile " ] :
ungluers [ ' bibliophiles ' ] . append ( transaction )
elif transaction . amount > = Premium . TIERS [ " patron " ] :
2012-12-11 18:17:28 +00:00
if transaction . anonymous :
ungluers [ ' anon_patrons ' ] + = 1
else :
ungluers [ ' patrons ' ] . append ( transaction )
2012-08-06 19:44:43 +00:00
elif transaction . amount > = Premium . TIERS [ " supporter " ] :
2012-12-11 18:17:28 +00:00
if transaction . anonymous :
ungluers [ ' anon_supporters ' ] + = 1
else :
ungluers [ ' supporters ' ] . append ( transaction )
2012-08-06 19:44:43 +00:00
2012-08-06 17:35:29 +00:00
return ungluers
2011-11-30 16:58:26 +00:00
def effective_premiums ( self ) :
2012-05-13 01:49:18 +00:00
""" returns the available premiums for the Campaign including any default premiums """
2013-06-27 17:10:33 +00:00
if self . type is BUY2UNGLUE :
return Premium . objects . none ( )
2012-03-26 22:46:34 +00:00
q = Q ( campaign = self ) | Q ( campaign__isnull = True )
return Premium . objects . filter ( q ) . exclude ( type = ' XX ' ) . order_by ( ' amount ' )
2012-04-06 02:44:45 +00:00
def custom_premiums ( self ) :
2012-05-13 01:49:18 +00:00
""" returns only the active custom premiums for the Campaign """
2013-06-27 17:10:33 +00:00
if self . type is BUY2UNGLUE :
return Premium . objects . none ( )
2012-09-07 21:56:58 +00:00
return Premium . objects . filter ( campaign = self ) . filter ( type = ' CU ' ) . order_by ( ' amount ' )
2013-08-20 02:54:43 +00:00
2013-10-11 21:50:54 +00:00
@property
def library_offer ( self ) :
return self . _offer ( LIBRARY )
@property
def individual_offer ( self ) :
return self . _offer ( INDIVIDUAL )
def _offer ( self , license ) :
2013-08-20 02:54:43 +00:00
if self . type is REWARDS :
2013-10-19 21:27:53 +00:00
return None
2013-10-11 21:50:54 +00:00
try :
return Offer . objects . get ( work = self . work , active = True , license = license )
except Offer . DoesNotExist :
2013-10-19 21:27:53 +00:00
return None
2013-08-22 18:23:47 +00:00
2014-07-25 18:43:54 +00:00
@property
def ask_money ( self ) :
# true if there's an offer asking for money
if self . type is REWARDS :
return True
try :
Offer . objects . get ( work = self . work , active = True , price__gt = 0.00 )
return True
except Offer . DoesNotExist :
return False
except Offer . MultipleObjectsReturned :
return True
2013-08-22 18:23:47 +00:00
@property
def days_per_copy ( self ) :
2013-10-19 21:27:53 +00:00
if self . individual_offer :
return Decimal ( float ( self . individual_offer . price ) / self . dollar_per_day )
2013-08-22 18:23:47 +00:00
else :
return Decimal ( 0 )
2013-08-20 02:54:43 +00:00
2012-05-13 01:49:18 +00:00
@property
2013-06-17 22:53:21 +00:00
def rh ( self ) :
""" returns the rights holder for an active or initialized campaign """
2012-05-13 01:49:18 +00:00
try :
2012-11-16 16:52:54 +00:00
q = Q ( status = ' ACTIVE ' ) | Q ( status = ' INITIALIZED ' )
2013-06-17 22:53:21 +00:00
rh = self . work . claim . filter ( q ) [ 0 ] . rights_holder
2012-11-16 16:52:54 +00:00
return rh
2013-06-17 22:53:21 +00:00
except :
return None
@property
def rightsholder ( self ) :
""" returns the name of the rights holder for an active or initialized campaign """
try :
return self . rh . rights_holder_name
2012-05-13 01:49:18 +00:00
except :
2012-11-16 16:52:54 +00:00
return ' '
2012-05-20 04:10:56 +00:00
@property
def license_url ( self ) :
2014-05-08 14:21:50 +00:00
return cc . CCLicense . url ( self . license )
2012-05-20 04:10:56 +00:00
@property
def license_badge ( self ) :
2014-05-08 14:21:50 +00:00
return cc . CCLicense . badge ( self . license )
2012-12-31 16:00:45 +00:00
@property
def success_date ( self ) :
if self . status == ' SUCCESSFUL ' :
2013-02-21 20:57:49 +00:00
try :
return self . actions . filter ( type = ' succeeded ' ) [ 0 ] . timestamp
except :
return ' '
2012-12-31 20:02:39 +00:00
return ' '
2013-03-15 19:51:17 +00:00
2013-11-14 17:40:01 +00:00
def percent_of_goal ( self ) :
2014-01-30 16:59:17 +00:00
if self . type == THANKS :
return 100
2013-11-16 04:06:08 +00:00
percent = 0
2013-11-14 17:40:01 +00:00
if ( self . status == ' SUCCESSFUL ' or self . status == ' ACTIVE ' ) :
if self . type == BUY2UNGLUE :
percent = int ( 100 - 100 * self . left / self . target )
else :
percent = int ( self . current_total / self . target * 100 )
return percent
2013-03-15 19:51:17 +00:00
@property
def countdown ( self ) :
from math import ceil
2014-01-15 22:59:45 +00:00
if not self . deadline :
return ' '
2013-03-15 19:51:17 +00:00
time_remaining = self . deadline - now ( )
countdown = " "
2012-05-20 04:10:56 +00:00
2013-03-15 19:51:17 +00:00
if time_remaining . days :
countdown = " %s days " % str ( time_remaining . days + 1 )
elif time_remaining . seconds > 3600 :
countdown = " %s hours " % str ( time_remaining . seconds / 3600 + 1 )
elif time_remaining . seconds > 60 :
countdown = " %s minutes " % str ( time_remaining . seconds / 60 + 1 )
else :
countdown = " Seconds "
2014-01-15 22:59:45 +00:00
return countdown
@property
def deadline_or_now ( self ) :
return self . deadline if self . deadline else now ( )
2012-05-13 01:49:18 +00:00
2013-06-17 21:12:58 +00:00
@classmethod
def latest_ending ( cls ) :
return ( timedelta ( days = int ( settings . UNGLUEIT_LONGEST_DEADLINE ) ) + now ( ) )
2013-11-15 04:16:55 +00:00
2014-08-28 19:29:41 +00:00
def add_ask_to_ebfs ( self , position = 0 ) :
2014-09-04 22:33:20 +00:00
if not self . use_add_ask or self . type != THANKS :
return
2014-09-05 20:35:57 +00:00
done_formats = [ ]
for ebf in self . work . ebookfiles ( ) . filter ( asking = False ) . order_by ( ' -created ' ) :
if ebf . format == ' pdf ' and ' pdf ' not in done_formats :
2014-08-28 19:29:41 +00:00
try :
added = ask_pdf ( { ' campaign ' : self , ' work ' : self . work , ' site ' : Site . objects . get_current ( ) } )
new_file = SpooledTemporaryFile ( )
old_file = SpooledTemporaryFile ( )
ebf . file . open ( )
old_file . write ( ebf . file . read ( ) )
if position == 0 :
pdf_append ( added , old_file , new_file )
else :
pdf_append ( old_file , added , new_file )
new_file . seek ( 0 )
2014-09-05 20:35:57 +00:00
new_ebf = EbookFile . objects . create ( edition = ebf . edition , format = ' pdf ' , asking = True )
2014-08-28 19:29:41 +00:00
new_ebf . file . save ( path_for_file ( ebf , None ) , ContentFile ( new_file . read ( ) ) )
new_ebf . save ( )
2014-09-05 20:35:57 +00:00
for old_ebf in self . work . ebookfiles ( ) . filter ( asking = True , format = ' pdf ' ) . exclude ( pk = new_ebf . pk ) :
2014-09-04 22:33:20 +00:00
obsolete = Ebook . objects . filter ( url = old_ebf . file . url )
for eb in obsolete :
eb . deactivate ( )
2014-08-28 19:29:41 +00:00
old_ebf . delete ( )
2014-09-05 20:35:57 +00:00
done_formats . append ( ' pdf ' )
2014-08-28 19:29:41 +00:00
except Exception as e :
logger . error ( " error appending pdf ask %s " % ( e ) )
2014-09-05 20:35:57 +00:00
elif ebf . format == ' epub ' and ' epub ' not in done_formats :
try :
new_file = SpooledTemporaryFile ( )
old_file = SpooledTemporaryFile ( )
ebf . file . open ( )
old_file . write ( ebf . file . read ( ) )
new_file = ask_epub ( old_file , { ' campaign ' : self , ' work ' : self . work , ' site ' : Site . objects . get_current ( ) } )
new_file . seek ( 0 )
new_ebf = EbookFile . objects . create ( edition = ebf . edition , format = ' epub ' , asking = True )
new_ebf . file . save ( path_for_file ( ebf , None ) , ContentFile ( new_file . read ( ) ) )
new_ebf . save ( )
for old_ebf in self . work . ebookfiles ( ) . filter ( asking = True , format = ' epub ' ) . exclude ( pk = new_ebf . pk ) :
obsolete = Ebook . objects . filter ( url = old_ebf . file . url )
for eb in obsolete :
eb . deactivate ( )
old_ebf . delete ( )
done_formats . append ( ' epub ' )
except Exception as e :
logger . error ( " error making epub ask %s " % ( e ) )
2014-10-04 20:51:16 +00:00
self . work . make_ebooks_from_ebfs ( add_ask = True )
2014-09-04 22:33:20 +00:00
2014-08-28 19:29:41 +00:00
2013-11-15 04:16:55 +00:00
def make_unglued_ebf ( self , format , watermarked ) :
ebf = EbookFile . objects . create ( edition = self . work . preferred_edition , format = format )
r = urllib2 . urlopen ( watermarked . download_link ( format ) )
ebf . file . save ( path_for_file ( ebf , None ) , ContentFile ( r . read ( ) ) )
ebf . file . close ( )
ebf . save ( )
2013-11-15 07:10:14 +00:00
ebook = Ebook . objects . create (
edition = self . work . preferred_edition ,
format = format ,
rights = self . license ,
provider = " Unglue.it " ,
url = settings . BASE_URL_SECURE + reverse ( ' download_campaign ' , args = [ self . work . id , format ] ) ,
)
2014-02-05 22:02:21 +00:00
old_ebooks = Ebook . objects . exclude ( pk = ebook . pk ) . filter (
format = format ,
rights = self . license ,
provider = " Unglue.it " ,
)
for old_ebook in old_ebooks :
2014-09-04 22:33:20 +00:00
old_ebook . deactivate ( )
2013-11-15 07:10:14 +00:00
return ebook . pk
2013-11-15 04:16:55 +00:00
def watermark_success ( self ) :
if self . status == ' SUCCESSFUL ' and self . type == BUY2UNGLUE :
params = {
' customeremailaddress ' : self . license ,
' customername ' : ' The Public ' ,
' languagecode ' : ' 1033 ' ,
' expirydays ' : 1 ,
' downloadlimit ' : 7 ,
' exlibris ' : 0 ,
' chapterfooter ' : 0 ,
' disclaimer ' : 0 ,
' referenceid ' : ' %s : %s : %s ' % ( self . work . id , self . id , self . license ) ,
' kf8mobi ' : True ,
' epub ' : True ,
}
2014-04-28 14:59:03 +00:00
ungluified = ungluify ( self . work . epubfiles ( ) [ 0 ] . file , self )
2013-11-15 04:16:55 +00:00
ungluified . filename . seek ( 0 )
watermarked = watermarker . platform ( epubfile = ungluified . filename , * * params )
self . make_unglued_ebf ( ' epub ' , watermarked )
self . make_unglued_ebf ( ' mobi ' , watermarked )
return True
return False
2013-12-15 05:31:06 +00:00
def is_pledge ( self ) :
return self . type == REWARDS
@property
def user_to_pay ( self ) :
return self . rh . owner
2014-10-27 15:55:46 +00:00
### for compatibility with MARC output
def marc_records ( self ) :
return self . work . marc_records ( )
2013-06-17 21:12:58 +00:00
2012-01-02 22:22:25 +00:00
class Identifier ( models . Model ) :
2012-05-07 02:31:38 +00:00
# olib, ltwk, goog, gdrd, thng, isbn, oclc, olwk, olib, gute, glue
2012-01-02 22:22:25 +00:00
type = models . CharField ( max_length = 4 , null = False )
2014-10-14 19:42:57 +00:00
value = models . CharField ( max_length = 250 , null = False )
2012-01-02 22:22:25 +00:00
work = models . ForeignKey ( " Work " , related_name = " identifiers " , null = False )
edition = models . ForeignKey ( " Edition " , related_name = " identifiers " , null = True )
class Meta :
unique_together = ( " type " , " value " )
2012-09-25 01:51:57 +00:00
@staticmethod
def set ( type = None , value = None , edition = None , work = None ) :
# if there's already an id of this type for this work and edition, change it
# if not, create it. if the id exists and points to something else, change it.
2012-09-25 20:26:45 +00:00
identifier = Identifier . get_or_add ( type = type , value = value , edition = edition , work = work )
if identifier . work . id != work . id :
identifier . work = work
identifier . save ( )
if identifier . edition and edition :
if identifier . edition . id != edition . id :
identifier . edition = edition
identifier . save ( )
2012-09-25 01:51:57 +00:00
others = Identifier . objects . filter ( type = type , work = work , edition = edition ) . exclude ( value = value )
if others . count ( ) > 0 :
for other in others :
other . delete ( )
2012-09-25 20:26:45 +00:00
return identifier
2012-01-09 18:55:22 +00:00
2012-09-07 05:04:18 +00:00
@staticmethod
def get_or_add ( type = ' goog ' , value = None , edition = None , work = None ) :
2012-01-09 18:55:22 +00:00
try :
return Identifier . objects . get ( type = type , value = value )
except Identifier . DoesNotExist :
i = Identifier ( type = type , value = value , edition = edition , work = work )
i . save ( )
return i
2012-02-06 19:26:48 +00:00
def __unicode__ ( self ) :
return u ' {0} : {1} ' . format ( self . type , self . value )
2011-09-10 11:36:38 +00:00
2011-08-31 03:46:55 +00:00
class Work ( models . Model ) :
2014-07-31 16:17:42 +00:00
created = models . DateTimeField ( auto_now_add = True , db_index = True , )
2011-09-02 04:10:54 +00:00
title = models . CharField ( max_length = 1000 )
2014-07-31 16:17:42 +00:00
language = models . CharField ( max_length = 5 , default = " en " , null = False , db_index = True , )
2011-12-19 06:33:13 +00:00
openlibrary_lookup = models . DateTimeField ( null = True )
2013-02-04 18:32:21 +00:00
num_wishes = models . IntegerField ( default = 0 , db_index = True )
2013-05-28 14:19:01 +00:00
description = models . TextField ( default = ' ' , null = True , blank = True )
2014-03-11 02:22:14 +00:00
selected_edition = models . ForeignKey ( " Edition " , related_name = ' selected_works ' , null = True )
earliest_publication = models . CharField ( max_length = 50 , null = True )
2014-11-06 19:23:34 +00:00
featured = models . DateTimeField ( null = True , blank = True , db_index = True , )
2014-12-12 04:01:13 +00:00
is_free = models . BooleanField ( default = False )
2014-03-11 02:22:14 +00:00
2011-09-10 11:36:38 +00:00
2011-12-03 23:04:53 +00:00
class Meta :
ordering = [ ' title ' ]
2011-11-18 03:11:40 +00:00
def __init__ ( self , * args , * * kwargs ) :
self . _last_campaign = None
super ( Work , self ) . __init__ ( * args , * * kwargs )
2011-12-03 04:07:55 +00:00
@property
def googlebooks_id ( self ) :
2012-09-13 18:55:46 +00:00
preferred_id = self . preferred_edition . googlebooks_id
2013-04-04 00:21:04 +00:00
# note that there's always a preferred edition
2012-09-13 18:55:46 +00:00
if preferred_id :
return preferred_id
2011-12-31 18:48:54 +00:00
try :
2013-04-04 00:21:04 +00:00
return self . identifiers . filter ( type = ' goog ' ) [ 0 ] . value
2011-12-31 18:48:54 +00:00
except IndexError :
2012-01-09 18:55:22 +00:00
return ' '
2011-12-03 04:07:55 +00:00
@property
def googlebooks_url ( self ) :
2012-01-17 21:27:58 +00:00
if self . googlebooks_id :
return " http://books.google.com/books?id= %s " % self . googlebooks_id
else :
return ' '
2011-12-03 04:07:55 +00:00
2011-12-05 05:56:24 +00:00
@property
def goodreads_id ( self ) :
2012-09-13 18:55:46 +00:00
preferred_id = self . preferred_edition . goodreads_id
if preferred_id :
return preferred_id
2012-01-09 18:55:22 +00:00
try :
2013-04-04 00:21:04 +00:00
return self . identifiers . filter ( type = ' gdrd ' ) [ 0 ] . value
2012-01-09 18:55:22 +00:00
except IndexError :
return ' '
2011-12-05 05:56:24 +00:00
@property
def goodreads_url ( self ) :
return " http://www.goodreads.com/book/show/ %s " % self . goodreads_id
2012-01-09 18:55:22 +00:00
@property
def librarything_id ( self ) :
try :
2013-04-04 00:21:04 +00:00
return self . identifiers . filter ( type = ' ltwk ' ) [ 0 ] . value
2012-01-09 18:55:22 +00:00
except IndexError :
return ' '
2011-12-05 05:56:24 +00:00
@property
def librarything_url ( self ) :
return " http://www.librarything.com/work/ %s " % self . librarything_id
2012-01-09 18:55:22 +00:00
@property
def openlibrary_id ( self ) :
try :
2013-04-04 00:21:04 +00:00
return self . identifiers . filter ( type = ' olwk ' ) [ 0 ] . value
2012-01-09 18:55:22 +00:00
except IndexError :
return ' '
2014-10-08 20:23:24 +00:00
2011-12-19 07:20:24 +00:00
@property
def openlibrary_url ( self ) :
return " http://openlibrary.org " + self . openlibrary_id
2014-10-29 19:18:32 +00:00
2014-11-04 00:57:58 +00:00
def cover_filetype ( self ) :
if self . uses_google_cover ( ) :
return ' jpeg '
else :
2015-01-15 02:29:17 +00:00
# consider the path only and not the params, query, or fragment
url = urlparse ( self . cover_image_small ( ) . lower ( ) ) . path
2014-11-04 00:57:58 +00:00
if url . endswith ( ' .png ' ) :
return ' png '
elif url . endswith ( ' .gif ' ) :
return ' gif '
elif url . endswith ( ' .jpg ' ) or url . endswith ( ' .jpeg ' ) :
return ' jpeg '
else :
return ' image '
2014-10-29 19:18:32 +00:00
def uses_google_cover ( self ) :
if self . preferred_edition and self . preferred_edition . cover_image :
return False
else :
return self . googlebooks_id
2011-09-29 06:23:50 +00:00
def cover_image_small ( self ) :
2014-02-06 01:56:30 +00:00
if self . preferred_edition and self . preferred_edition . cover_image_small ( ) :
return self . preferred_edition . cover_image_small ( )
2012-05-12 02:46:03 +00:00
return " /static/images/generic_cover_larger.png "
2011-09-29 06:23:50 +00:00
2011-11-03 20:28:53 +00:00
def cover_image_thumbnail ( self ) :
2011-12-31 18:48:54 +00:00
try :
2014-01-18 02:37:19 +00:00
if self . preferred_edition and self . preferred_edition . cover_image_thumbnail ( ) :
2012-05-12 02:46:03 +00:00
return self . preferred_edition . cover_image_thumbnail ( )
2011-12-31 18:48:54 +00:00
except IndexError :
2012-05-12 02:46:03 +00:00
pass
return " /static/images/generic_cover_larger.png "
2011-11-03 20:28:53 +00:00
2014-01-15 20:16:24 +00:00
def authors ( self ) :
# assumes that they come out in the same order they go in!
2014-01-18 02:37:19 +00:00
if self . preferred_edition and self . preferred_edition . authors . all ( ) . count ( ) > 0 :
return self . preferred_edition . authors . all ( )
for edition in self . editions . all ( ) :
if edition . authors . all ( ) . count ( ) > 0 :
return edition . authors . all ( )
2014-01-24 04:04:52 +00:00
return Author . objects . none ( )
2014-01-15 20:16:24 +00:00
2011-11-03 20:28:53 +00:00
def author ( self ) :
2014-01-15 20:16:24 +00:00
# assumes that they come out in the same order they go in!
if self . authors ( ) . count ( ) > 0 :
return self . authors ( ) [ 0 ] . name
2014-01-18 02:37:19 +00:00
return ' '
2011-11-07 20:39:02 +00:00
2014-01-18 02:37:19 +00:00
def authors_short ( self ) :
# assumes that they come out in the same order they go in!
if self . authors ( ) . count ( ) == 1 :
return self . authors ( ) [ 0 ] . name
elif self . authors ( ) . count ( ) == 2 :
return " %s and %s " % ( self . authors ( ) [ 0 ] . name , self . authors ( ) [ 1 ] . name )
elif self . authors ( ) . count ( ) > 2 :
return " %s et al. " % self . authors ( ) [ 0 ] . name
return ' '
2015-03-03 22:39:23 +00:00
def kindle_safe_title ( self ) :
2015-03-06 23:16:04 +00:00
"""
Removes accents , keeps letters and numbers , replaces non - Latin characters with " # " , and replaces punctuation with " _ "
"""
2015-03-03 22:39:23 +00:00
safe = u ' '
nkfd_form = unicodedata . normalize ( ' NFKD ' , self . title ) #unaccent accented letters
for c in nkfd_form :
ccat = unicodedata . category ( c )
#print ccat
if ccat . startswith ( ' L ' ) or ccat . startswith ( ' N ' ) : # only letters and numbers
if ord ( c ) > 127 :
safe = safe + ' # ' #a non latin script letter or number
else :
safe = safe + c
elif not unicodedata . combining ( c ) : #not accents (combining forms)
safe = safe + ' _ ' #punctuation
return safe
2014-01-18 02:37:19 +00:00
2011-11-06 19:02:29 +00:00
def last_campaign ( self ) :
2011-11-18 03:11:40 +00:00
# stash away the last campaign to prevent repeated lookups
2011-11-22 20:55:05 +00:00
if hasattr ( self , ' _last_campaign_ ' ) :
return self . _last_campaign_
2011-10-25 23:07:44 +00:00
try :
2011-11-22 20:55:05 +00:00
self . _last_campaign_ = self . campaigns . order_by ( ' -created ' ) [ 0 ]
2011-11-18 03:11:40 +00:00
except IndexError :
2011-11-22 20:55:05 +00:00
self . _last_campaign_ = None
return self . _last_campaign_
2011-11-06 19:31:08 +00:00
2012-05-01 14:56:19 +00:00
@property
def preferred_edition ( self ) :
2014-03-11 02:22:14 +00:00
if self . selected_edition :
return self . selected_edition
2012-05-01 14:56:19 +00:00
if self . last_campaign ( ) :
if self . last_campaign ( ) . edition :
2014-03-11 02:22:14 +00:00
self . selected_edition = self . last_campaign ( ) . edition
self . save ( )
2012-05-01 14:56:19 +00:00
return self . last_campaign ( ) . edition
2014-03-11 21:34:27 +00:00
try :
2014-03-11 02:22:14 +00:00
self . selected_edition = self . editions . all ( ) [ 0 ]
self . save ( )
2014-03-11 21:34:27 +00:00
return self . selected_edition
except IndexError :
2014-04-28 18:23:43 +00:00
#should only happen if there are no editions for the work
2014-03-11 21:34:27 +00:00
return None
2012-05-01 14:56:19 +00:00
2011-11-06 19:31:08 +00:00
def last_campaign_status ( self ) :
2011-11-06 20:24:16 +00:00
campaign = self . last_campaign ( )
2011-11-06 19:31:08 +00:00
if campaign :
status = campaign . status
else :
2012-01-20 00:57:51 +00:00
if self . first_ebook ( ) :
2012-01-16 17:10:38 +00:00
status = " Available "
else :
2012-01-17 21:27:58 +00:00
status = " No campaign yet "
2011-11-06 19:31:08 +00:00
return status
2011-10-14 13:43:30 +00:00
def percent_unglued ( self ) :
2011-11-06 19:24:54 +00:00
status = 0
2013-08-09 02:32:58 +00:00
campaign = self . last_campaign ( )
if campaign is not None :
if ( campaign . status == ' SUCCESSFUL ' ) :
2012-05-17 13:24:37 +00:00
status = 6
2013-08-09 02:32:58 +00:00
elif ( campaign . status == ' ACTIVE ' ) :
target = float ( campaign . target )
2011-11-06 19:24:54 +00:00
if target < = 0 :
status = 6
2011-10-25 23:07:44 +00:00
else :
2013-08-09 02:32:58 +00:00
if campaign . type == BUY2UNGLUE :
status = int ( 6 - 6 * campaign . left / campaign . target )
2011-11-06 19:24:54 +00:00
else :
2013-08-09 02:32:58 +00:00
status = int ( float ( campaign . current_total ) * 6 / target )
if status > = 6 :
status = 6
2012-05-17 13:24:37 +00:00
return status
def percent_of_goal ( self ) :
campaign = self . last_campaign ( )
2013-11-14 17:40:01 +00:00
return 0 if campaign is None else campaign . percent_of_goal ( )
2015-02-23 21:52:55 +00:00
def ebooks_all ( self ) :
return self . ebooks ( all = True )
2014-09-04 22:33:20 +00:00
def ebooks ( self , all = False ) :
if all :
return Ebook . objects . filter ( edition__work = self ) . order_by ( ' -created ' )
else :
return Ebook . objects . filter ( edition__work = self , active = True ) . order_by ( ' -created ' )
2013-08-20 02:54:43 +00:00
def ebookfiles ( self ) :
2014-09-04 22:33:20 +00:00
return EbookFile . objects . filter ( edition__work = self ) . exclude ( file = ' ' ) . order_by ( ' -created ' )
2014-04-28 14:59:03 +00:00
def epubfiles ( self ) :
# filter out non-epub because that's what booxtream accepts
2014-09-04 22:33:20 +00:00
return EbookFile . objects . filter ( edition__work = self , format = ' epub ' ) . exclude ( file = ' ' ) . order_by ( ' -created ' )
2014-02-11 01:16:56 +00:00
2014-04-28 14:59:03 +00:00
def mobifiles ( self ) :
2014-09-04 22:33:20 +00:00
return EbookFile . objects . filter ( edition__work = self , format = ' mobi ' ) . exclude ( file = ' ' ) . order_by ( ' -created ' )
2014-04-28 14:59:03 +00:00
def pdffiles ( self ) :
2014-09-04 22:33:20 +00:00
return EbookFile . objects . filter ( edition__work = self , format = ' pdf ' ) . exclude ( file = ' ' ) . order_by ( ' -created ' )
2014-04-28 14:59:03 +00:00
2014-10-04 20:51:16 +00:00
def make_ebooks_from_ebfs ( self , add_ask = True ) :
2014-02-11 01:16:56 +00:00
if self . last_campaign ( ) . type != THANKS : # just to make sure that ebf's can be unglued by mistake
return
2014-09-04 22:33:20 +00:00
ebfs = EbookFile . objects . filter ( edition__work = self ) . exclude ( file = ' ' ) . order_by ( ' -created ' )
2014-02-11 01:16:56 +00:00
done_formats = [ ]
for ebf in ebfs :
2014-10-04 20:51:16 +00:00
previous_ebooks = Ebook . objects . filter ( url = ebf . file . url , )
try :
previous_ebook = previous_ebooks [ 0 ]
for eb in previous_ebooks [ 1 : ] : #housekeeping
eb . deactivate ( )
except IndexError :
previous_ebook = None
2014-02-11 01:16:56 +00:00
if ebf . format not in done_formats :
2014-10-04 20:51:16 +00:00
if ebf . asking == add_ask or ebf . format == ' mobi ' :
if previous_ebook :
previous_ebook . activate ( )
else :
ebook = Ebook . objects . get_or_create (
edition = ebf . edition ,
format = ebf . format ,
rights = self . last_campaign ( ) . license ,
provider = " Unglue.it " ,
url = ebf . file . url ,
)
done_formats . append ( ebf . format )
elif previous_ebook :
previous_ebook . deactivate ( )
elif previous_ebook :
previous_ebook . deactivate ( )
2014-02-11 01:16:56 +00:00
return
2014-09-04 22:33:20 +00:00
def remove_old_ebooks ( self ) :
2014-09-05 17:30:09 +00:00
old = Ebook . objects . filter ( edition__work = self , active = True ) . order_by ( ' -created ' )
2014-09-04 22:33:20 +00:00
done_formats = [ ]
for eb in old :
if eb . format in done_formats :
eb . deactivate ( )
else :
done_formats . append ( eb . format )
2014-09-05 20:35:57 +00:00
null_files = EbookFile . objects . filter ( edition__work = self , file = ' ' )
for ebf in null_files :
ebf . delete ( )
2014-09-04 22:33:20 +00:00
2013-04-20 14:08:10 +00:00
@property
def download_count ( self ) :
dlc = 0
2014-09-04 22:33:20 +00:00
for ebook in self . ebooks ( all = True ) :
2013-04-20 14:08:10 +00:00
dlc + = ebook . download_count
return dlc
2011-11-07 20:39:02 +00:00
def first_pdf ( self ) :
return self . first_ebook ( ' pdf ' )
def first_epub ( self ) :
return self . first_ebook ( ' epub ' )
2011-11-23 17:28:59 +00:00
def first_pdf_url ( self ) :
2011-11-24 02:41:06 +00:00
try :
url = self . first_ebook ( ' pdf ' ) . url
return url
2011-11-23 17:28:59 +00:00
except :
2011-11-24 02:41:06 +00:00
return None
2011-11-23 17:28:59 +00:00
def first_epub_url ( self ) :
2011-11-24 02:41:06 +00:00
try :
url = self . first_ebook ( ' epub ' ) . url
return url
2011-11-23 17:28:59 +00:00
except :
2011-11-24 02:41:06 +00:00
return None
2011-11-23 17:28:59 +00:00
2011-11-07 20:39:02 +00:00
def first_ebook ( self , ebook_format = None ) :
2012-01-20 00:57:51 +00:00
if ebook_format :
2012-02-28 22:27:48 +00:00
for ebook in self . ebooks ( ) . filter ( format = ebook_format ) :
2012-01-20 00:57:51 +00:00
return ebook
else :
2012-02-28 22:27:48 +00:00
for ebook in self . ebooks ( ) :
2012-01-20 00:57:51 +00:00
return ebook
2011-11-11 22:33:58 +00:00
def wished_by ( self ) :
return User . objects . filter ( wishlist__works__in = [ self ] )
2012-02-11 19:15:06 +00:00
def update_num_wishes ( self ) :
2012-02-28 22:27:48 +00:00
self . num_wishes = Wishes . objects . filter ( work = self ) . count ( )
self . save ( )
2011-11-19 16:55:35 +00:00
2012-09-24 19:36:39 +00:00
def first_oclc ( self ) :
2014-04-28 18:23:43 +00:00
if self . preferred_edition == None :
return ' '
2012-09-24 19:36:39 +00:00
preferred_id = self . preferred_edition . oclc
if preferred_id :
return preferred_id
try :
2013-04-03 06:14:39 +00:00
return self . identifiers . filter ( type = ' oclc ' ) [ 0 ] . value
2012-09-24 19:36:39 +00:00
except IndexError :
return ' '
2011-12-20 04:26:55 +00:00
def first_isbn_13 ( self ) :
2014-04-28 18:23:43 +00:00
if self . preferred_edition == None :
return ' '
2012-09-13 18:55:46 +00:00
preferred_id = self . preferred_edition . isbn_13
if preferred_id :
return preferred_id
2012-01-09 18:55:22 +00:00
try :
2013-04-03 06:14:39 +00:00
return self . identifiers . filter ( type = ' isbn ' ) [ 0 ] . value
2012-01-09 18:55:22 +00:00
except IndexError :
return ' '
2011-12-05 05:56:24 +00:00
2012-01-17 04:28:34 +00:00
@property
def publication_date ( self ) :
2014-03-11 02:22:14 +00:00
if self . earliest_publication :
return self . earliest_publication
for edition in Edition . objects . filter ( work = self , publication_date__isnull = False ) . order_by ( ' publication_date ' ) :
2012-01-17 21:27:58 +00:00
if edition . publication_date :
2014-03-11 02:22:14 +00:00
self . earliest_publication = edition . publication_date
self . save ( )
2012-01-17 21:27:58 +00:00
return edition . publication_date
return ' '
2012-01-17 04:28:34 +00:00
2012-09-07 20:07:47 +00:00
@property
def publication_date_year ( self ) :
try :
return self . publication_date [ : 4 ]
except IndexError :
return ' unknown '
2011-09-10 11:36:38 +00:00
def __unicode__ ( self ) :
return self . title
2012-12-14 15:45:16 +00:00
@property
def has_unglued_edition ( self ) :
"""
allows us to distinguish successful campaigns with ebooks still in progress from successful campaigns with ebooks available
"""
if self . ebooks ( ) . filter ( edition__unglued = True ) :
return True
return False
2013-01-03 18:18:00 +00:00
@property
2013-01-04 20:12:35 +00:00
def user_with_rights ( self ) :
2013-01-03 18:18:00 +00:00
"""
2013-01-04 20:12:35 +00:00
return queryset of users ( should be at most one ) who act for rights holders with active claims to the work
2013-01-03 18:18:00 +00:00
"""
2013-01-04 20:12:35 +00:00
claims = self . claim . filter ( status = ' active ' )
assert claims . count ( ) < 2 , " There is more than one active claim on %r " % self . title
try :
return claims [ 0 ] . user
except :
return False
2011-09-09 05:38:28 +00:00
2013-03-09 22:37:33 +00:00
def get_absolute_url ( self ) :
return reverse ( ' work ' , args = [ str ( self . id ) ] )
2013-03-27 16:22:30 +00:00
def publishers ( self ) :
# returns a set of publishers associated with this Work
return Publisher . objects . filter ( name__editions__work = self ) . distinct ( )
2013-06-17 22:53:21 +00:00
def create_offers ( self ) :
for choice in Offer . CHOICES :
if not self . offers . filter ( license = choice [ 0 ] ) :
2013-10-11 16:50:59 +00:00
self . offers . create ( license = choice [ 0 ] , active = True , price = Decimal ( 10 ) )
2013-06-17 22:53:21 +00:00
return self . offers . all ( )
2013-10-18 17:33:47 +00:00
2013-11-08 17:13:34 +00:00
def get_lib_license ( self , user ) :
lib_user = ( lib . user for lib in user . profile . libraries )
return self . get_user_license ( lib_user )
2013-10-18 17:33:47 +00:00
def borrowable ( self , user ) :
if user . is_anonymous ( ) :
return False
2013-11-08 17:13:34 +00:00
lib_license = self . get_lib_license ( user )
2013-10-19 20:54:09 +00:00
if lib_license and lib_license . borrowable :
return True
return False
2014-03-19 02:30:21 +00:00
def lib_thanked ( self , user ) :
if user . is_anonymous ( ) :
return False
lib_license = self . get_lib_license ( user )
if lib_license and lib_license . thanked :
return True
return False
2013-10-19 20:54:09 +00:00
def in_library ( self , user ) :
if user . is_anonymous ( ) :
return False
2013-11-08 17:13:34 +00:00
lib_license = self . get_lib_license ( user )
2013-10-19 20:54:09 +00:00
if lib_license and lib_license . acqs . count ( ) :
return True
2013-10-18 17:33:47 +00:00
return False
@property
def lib_acqs ( self ) :
return self . acqs . filter ( license = LIBRARY )
2014-02-25 18:58:33 +00:00
@property
def test_acqs ( self ) :
return self . acqs . filter ( license = TESTING ) . order_by ( ' -created ' )
2013-10-11 21:50:54 +00:00
class user_license :
acqs = Acq . objects . none ( )
def __init__ ( self , acqs ) :
self . acqs = acqs
2013-10-18 16:36:55 +00:00
@property
2013-10-11 21:50:54 +00:00
def is_active ( self ) :
2013-10-18 16:36:55 +00:00
return self . acqs . filter ( expires__isnull = True ) . count ( ) > 0 or self . acqs . filter ( expires__gt = now ( ) ) . count ( ) > 0
2013-10-11 21:50:54 +00:00
2013-10-18 16:36:55 +00:00
@property
def borrowed ( self ) :
loans = self . acqs . filter ( license = BORROWED , expires__gt = now ( ) )
if loans . count ( ) == 0 :
return None
else :
return loans [ 0 ]
@property
2013-10-11 21:50:54 +00:00
def purchased ( self ) :
2014-12-20 17:47:15 +00:00
purchases = self . acqs . filter ( license = INDIVIDUAL , expires__isnull = True )
2013-10-18 16:36:55 +00:00
if purchases . count ( ) == 0 :
return None
else :
return purchases [ 0 ]
2014-02-20 03:18:23 +00:00
@property
def thanked ( self ) :
purchases = self . acqs . filter ( license = THANKED )
if purchases . count ( ) == 0 :
return None
else :
return purchases [ 0 ]
2013-10-18 16:36:55 +00:00
@property
def lib_acqs ( self ) :
return self . acqs . filter ( license = LIBRARY )
2013-10-19 20:54:09 +00:00
@property
2013-11-08 17:13:34 +00:00
def next_acq ( self ) :
""" This is the next available copy in the user ' s libraries """
2013-10-19 20:54:09 +00:00
loans = self . acqs . filter ( license = LIBRARY , refreshes__gt = now ( ) ) . order_by ( ' refreshes ' )
if loans . count ( ) == 0 :
return None
else :
return loans [ 0 ]
2013-10-18 16:36:55 +00:00
@property
def borrowable ( self ) :
return self . acqs . filter ( license = LIBRARY , refreshes__lt = now ( ) ) . count ( ) > 0
2013-11-08 17:13:34 +00:00
2014-03-19 02:30:21 +00:00
@property
def thanked ( self ) :
return self . acqs . filter ( license = THANKED ) . count ( ) > 0
2013-11-08 17:13:34 +00:00
@property
def borrowable_acq ( self ) :
for acq in self . acqs . filter ( license = LIBRARY , refreshes__lt = now ( ) ) :
return acq
else :
return None
2014-12-20 17:47:15 +00:00
@property
2014-12-20 18:14:05 +00:00
def is_duplicate ( self ) :
2014-12-20 17:47:15 +00:00
# does user have two individual licenses?
2014-12-22 16:47:34 +00:00
pending = self . acqs . filter ( license = INDIVIDUAL , expires__isnull = True , gifts__used__isnull = True ) . count ( )
2014-12-21 21:02:02 +00:00
return self . acqs . filter ( license = INDIVIDUAL , expires__isnull = True ) . count ( ) > pending
2014-12-20 17:47:15 +00:00
2013-10-11 21:50:54 +00:00
2013-10-19 20:54:09 +00:00
def get_user_license ( self , user ) :
2013-11-08 17:13:34 +00:00
""" This is all the acqs, wrapped in user_license object for the work, user(s) """
2013-10-19 20:54:09 +00:00
if user == None :
2013-10-11 21:50:54 +00:00
return None
2014-02-20 03:18:23 +00:00
if hasattr ( user , ' is_anonymous ' ) :
2013-10-19 20:54:09 +00:00
if user . is_anonymous ( ) :
return None
return self . user_license ( self . acqs . filter ( user = user ) )
else :
# assume it's several users
return self . user_license ( self . acqs . filter ( user__in = user ) )
2014-11-03 21:29:36 +00:00
@property
def has_marc ( self ) :
for record in NewMARC . objects . filter ( edition__work = self ) :
return True
return False
2014-10-27 15:55:46 +00:00
### for compatibility with MARC output
def marc_records ( self ) :
record_list = [ ]
record_list . extend ( NewMARC . objects . filter ( edition__work = self ) )
for obj in record_list :
break
else :
for ebook in self . ebooks ( ) :
record_list . append ( ebook . edition )
break
return record_list
2013-06-17 22:53:21 +00:00
2014-10-27 15:55:46 +00:00
2011-09-09 05:38:28 +00:00
class Author ( models . Model ) :
created = models . DateTimeField ( auto_now_add = True )
name = models . CharField ( max_length = 500 )
2011-10-10 16:57:10 +00:00
editions = models . ManyToManyField ( " Edition " , related_name = " authors " )
2011-09-09 05:38:28 +00:00
def __unicode__ ( self ) :
return self . name
2014-10-16 21:14:54 +00:00
@property
def last_name_first ( self ) :
names = self . name . rsplit ( )
if len ( names ) == 0 :
return ' '
elif len ( names ) == 1 :
return names [ 0 ]
elif len ( names ) == 2 :
return names [ 1 ] + " , " + names [ 0 ]
else :
reversed_name = names [ - 1 ] + " , "
for name in names [ 0 : - 1 ] :
reversed_name + = " "
reversed_name + = name
return reversed_name
2011-09-09 05:38:28 +00:00
class Subject ( models . Model ) :
created = models . DateTimeField ( auto_now_add = True )
2011-12-19 06:33:13 +00:00
name = models . CharField ( max_length = 200 , unique = True )
works = models . ManyToManyField ( " Work " , related_name = " subjects " )
2015-01-09 22:05:34 +00:00
is_visible = models . BooleanField ( default = True )
2011-09-09 05:38:28 +00:00
2011-12-19 07:20:24 +00:00
class Meta :
ordering = [ ' name ' ]
2011-09-10 11:36:38 +00:00
def __unicode__ ( self ) :
return self . name
2015-01-09 22:05:34 +00:00
@property
def kw ( self ) :
return ' kw. %s ' % self . name
def free_works ( self ) :
return self . works . filter ( is_free = True )
2011-09-10 11:36:38 +00:00
2011-08-31 03:46:55 +00:00
class Edition ( models . Model ) :
created = models . DateTimeField ( auto_now_add = True )
title = models . CharField ( max_length = 1000 )
2013-03-26 03:41:19 +00:00
publisher_name = models . ForeignKey ( " PublisherName " , related_name = " editions " , null = True )
2014-07-31 16:17:42 +00:00
publication_date = models . CharField ( max_length = 50 , null = True , blank = True , db_index = True , )
2011-09-30 01:57:12 +00:00
work = models . ForeignKey ( " Work " , related_name = " editions " , null = True )
2012-05-11 18:13:09 +00:00
cover_image = models . URLField ( null = True , blank = True )
2012-09-10 19:18:40 +00:00
unglued = models . BooleanField ( blank = True )
2011-08-31 03:46:55 +00:00
2011-09-09 18:27:29 +00:00
def __unicode__ ( self ) :
2012-05-01 14:56:19 +00:00
if self . isbn_13 :
return " %s (ISBN %s ) %s " % ( self . title , self . isbn_13 , self . publisher )
if self . oclc :
return " %s (OCLC %s ) %s " % ( self . title , self . oclc , self . publisher )
if self . googlebooks_id :
return " %s (GOOG %s ) %s " % ( self . title , self . googlebooks_id , self . publisher )
else :
return " %s (GLUE %s ) %s " % ( self . title , self . id , self . publisher )
2011-09-09 18:27:29 +00:00
2011-10-20 03:31:16 +00:00
def cover_image_small ( self ) :
2012-05-11 18:13:09 +00:00
if self . cover_image :
return self . cover_image
elif self . googlebooks_id :
2012-04-03 17:55:45 +00:00
return " https://encrypted.google.com/books?id= %s &printsec=frontcover&img=1&zoom=5 " % self . googlebooks_id
2012-01-17 21:27:58 +00:00
else :
return ' '
2011-10-20 03:31:16 +00:00
def cover_image_thumbnail ( self ) :
2012-05-11 18:13:09 +00:00
if self . cover_image :
return self . cover_image
elif self . googlebooks_id :
2012-04-03 17:55:45 +00:00
return " https://encrypted.google.com/books?id= %s &printsec=frontcover&img=1&zoom=1 " % self . googlebooks_id
2012-01-17 21:27:58 +00:00
else :
return ' '
2013-03-26 03:41:19 +00:00
@property
def publisher ( self ) :
if self . publisher_name :
return self . publisher_name . name
return ' '
2011-12-20 04:26:55 +00:00
@property
def isbn_10 ( self ) :
return regluit . core . isbn . convert_13_to_10 ( self . isbn_13 )
2014-08-06 20:18:00 +00:00
def id_for ( self , type ) :
if not self . pk :
return ' '
2012-01-09 18:55:22 +00:00
try :
2014-08-06 20:18:00 +00:00
return self . identifiers . filter ( type = type ) [ 0 ] . value
2012-01-09 18:55:22 +00:00
except IndexError :
return ' '
2014-08-06 20:18:00 +00:00
@property
def isbn_13 ( self ) :
return self . id_for ( ' isbn ' )
2012-01-09 18:55:22 +00:00
@property
def googlebooks_id ( self ) :
2014-08-06 20:18:00 +00:00
return self . id_for ( ' goog ' )
2012-01-09 18:55:22 +00:00
@property
def librarything_id ( self ) :
2014-08-06 20:18:00 +00:00
return self . id_for ( ' thng ' )
2012-01-09 18:55:22 +00:00
@property
def oclc ( self ) :
2014-08-06 20:18:00 +00:00
return self . id_for ( ' oclc ' )
2012-01-09 18:55:22 +00:00
@property
def goodreads_id ( self ) :
2014-08-06 20:18:00 +00:00
return self . id_for ( ' gdrd ' )
2012-01-09 18:55:22 +00:00
2014-10-08 20:23:24 +00:00
@property
def http_id ( self ) :
return self . id_for ( ' http ' )
2012-09-07 05:04:18 +00:00
@staticmethod
def get_by_isbn ( isbn ) :
2012-02-10 01:49:52 +00:00
if len ( isbn ) == 10 :
2011-12-20 04:26:55 +00:00
isbn = regluit . core . isbn . convert_10_to_13 ( isbn )
2012-01-09 18:55:22 +00:00
try :
return Identifier . objects . get ( type = ' isbn ' , value = isbn ) . edition
except Identifier . DoesNotExist :
return None
2013-03-26 03:41:19 +00:00
2014-10-20 20:54:19 +00:00
def add_author ( self , author_name ) :
if author_name :
try :
author = Author . objects . get ( name = author_name )
except Author . DoesNotExist :
author = Author . objects . create ( name = author_name )
author . editions . add ( self )
2013-03-26 03:41:19 +00:00
def set_publisher ( self , publisher_name ) :
if publisher_name and publisher_name != ' ' :
try :
pub_name = PublisherName . objects . get ( name = publisher_name )
2013-03-27 16:51:10 +00:00
if pub_name . publisher :
pub_name = pub_name . publisher . name
2013-03-26 03:41:19 +00:00
except PublisherName . DoesNotExist :
pub_name = PublisherName . objects . create ( name = publisher_name )
pub_name . save ( )
self . publisher_name = pub_name
self . save ( )
2014-10-20 20:57:20 +00:00
#### following methods for compatibility with marc outputter
2014-10-17 21:14:02 +00:00
def downloads ( self ) :
return self . ebooks . all ( )
def download_via_url ( self ) :
return settings . BASE_URL_SECURE + reverse ( ' download ' , args = [ self . work . id ] )
2014-10-20 20:57:20 +00:00
def authnames ( self ) :
return [ auth . last_name_first for auth in self . authors . all ( ) ]
@property
def license ( self ) :
try :
return self . ebooks . all ( ) [ 0 ] . rights
except :
return None
@property
def funding_info ( self ) :
if self . ebooks . all ( ) . count ( ) == 0 :
return ' '
if self . unglued :
return ' The book is available as a free download thanks to the generous support of interested readers and organizations, who made donations using the crowd-funding website Unglue.it. '
else :
if self . ebooks . all ( ) [ 0 ] . rights in cc . LICENSE_LIST :
return ' The book is available as a free download thanks to a Creative Commons license. '
else :
return ' The book is available as a free download because it is in the Public Domain. '
@property
def description ( self ) :
return self . work . description
2014-10-17 21:14:02 +00:00
2013-03-26 03:41:19 +00:00
class Publisher ( models . Model ) :
created = models . DateTimeField ( auto_now_add = True )
name = models . ForeignKey ( ' PublisherName ' , related_name = ' key_publisher ' )
2013-03-27 16:22:30 +00:00
url = models . URLField ( max_length = 1024 , null = True , blank = True )
logo_url = models . URLField ( max_length = 1024 , null = True , blank = True )
description = models . TextField ( default = ' ' , null = True , blank = True )
2013-03-26 03:41:19 +00:00
2013-03-26 18:12:58 +00:00
def __unicode__ ( self ) :
return self . name . name
2013-03-26 03:41:19 +00:00
class PublisherName ( models . Model ) :
name = models . CharField ( max_length = 255 , blank = False )
2013-03-26 18:12:58 +00:00
2013-03-29 18:58:54 +00:00
publisher = models . ForeignKey ( ' Publisher ' , related_name = ' alternate_names ' , null = True )
2013-03-26 03:41:19 +00:00
def __unicode__ ( self ) :
return self . name
2013-03-26 18:12:58 +00:00
def save ( self , * args , * * kwargs ) :
super ( PublisherName , self ) . save ( * args , * * kwargs ) # Call the "real" save() method.
if self . publisher and self != self . publisher . name :
#this name is an alias, repoint all editions with this name to the other.
for edition in Edition . objects . filter ( publisher_name = self ) :
edition . publisher_name = self . publisher . name
edition . save ( )
2011-08-31 03:46:55 +00:00
2012-02-10 03:30:33 +00:00
class WasWork ( models . Model ) :
2012-02-28 22:27:48 +00:00
work = models . ForeignKey ( ' Work ' )
was = models . IntegerField ( unique = True )
moved = models . DateTimeField ( auto_now_add = True )
user = models . ForeignKey ( User , null = True )
2013-06-17 22:53:21 +00:00
2015-02-18 17:47:19 +00:00
def safe_get_work ( work_id ) :
"""
use this rather than querying the db directly for a work by id
"""
try :
work = Work . objects . get ( id = work_id )
except Work . DoesNotExist :
try :
work = WasWork . objects . get ( was = work_id ) . work
except WasWork . DoesNotExist :
raise Work . DoesNotExist ( )
return work
2013-06-17 22:53:21 +00:00
FORMAT_CHOICES = ( ( ' pdf ' , ' PDF ' ) , ( ' epub ' , ' EPUB ' ) , ( ' html ' , ' HTML ' ) , ( ' text ' , ' TEXT ' ) , ( ' mobi ' , ' MOBI ' ) )
def path_for_file ( instance , filename ) :
version = EbookFile . objects . filter ( edition = instance . edition , format = instance . format ) . count ( )
2015-04-09 17:12:22 +00:00
hash = hashlib . md5 ( ' %s . %s . %d ' % ( settings . SOCIAL_AUTH_TWITTER_SECRET , instance . edition . pk , version ) ) . hexdigest ( )
2013-11-14 19:48:16 +00:00
fn = " ebf/ %s . %s " % ( hash , instance . format )
2013-06-17 22:53:21 +00:00
return fn
class EbookFile ( models . Model ) :
file = models . FileField ( upload_to = path_for_file )
format = models . CharField ( max_length = 25 , choices = FORMAT_CHOICES )
edition = models . ForeignKey ( ' Edition ' , related_name = ' ebook_files ' )
created = models . DateTimeField ( auto_now_add = True )
2014-08-28 19:29:41 +00:00
asking = models . BooleanField ( default = False )
2012-02-28 22:27:48 +00:00
2014-01-15 13:32:55 +00:00
def check_file ( self ) :
if self . format == ' epub ' :
return test_epub ( self . file )
return None
2012-02-28 22:27:48 +00:00
2014-10-04 20:51:16 +00:00
@property
def active ( self ) :
try :
return Ebook . objects . filter ( url = self . file . url ) [ 0 ] . active
except :
return False
2015-03-06 03:30:03 +00:00
send_to_kindle_limit = 7492232
2011-11-06 21:33:04 +00:00
class Ebook ( models . Model ) :
2013-08-08 22:21:33 +00:00
FORMAT_CHOICES = settings . FORMATS
2014-08-06 21:14:09 +00:00
RIGHTS_CHOICES = cc . CHOICES
2012-02-28 22:27:48 +00:00
url = models . URLField ( max_length = 1024 )
2014-07-31 16:17:42 +00:00
created = models . DateTimeField ( auto_now_add = True , db_index = True , )
2012-02-28 22:27:48 +00:00
format = models . CharField ( max_length = 25 , choices = FORMAT_CHOICES )
2011-11-06 21:33:04 +00:00
provider = models . CharField ( max_length = 255 )
2013-04-20 04:02:36 +00:00
download_count = models . IntegerField ( default = 0 )
2014-09-04 22:33:20 +00:00
active = models . BooleanField ( default = True )
2015-03-06 03:30:03 +00:00
filesize = models . PositiveIntegerField ( null = True )
2012-02-28 22:27:48 +00:00
# use 'PD-US', 'CC BY', 'CC BY-NC-SA', 'CC BY-NC-ND', 'CC BY-NC', 'CC BY-ND', 'CC BY-SA', 'CC0'
2013-02-04 18:32:21 +00:00
rights = models . CharField ( max_length = 255 , null = True , choices = RIGHTS_CHOICES , db_index = True )
2011-11-06 21:33:04 +00:00
edition = models . ForeignKey ( ' Edition ' , related_name = ' ebooks ' )
2012-02-28 22:27:48 +00:00
user = models . ForeignKey ( User , null = True )
2011-10-14 14:18:38 +00:00
2015-03-06 03:30:03 +00:00
def kindle_sendable ( self ) :
if not self . filesize or self . filesize < send_to_kindle_limit :
return True
else :
return False
2012-02-28 22:27:48 +00:00
def set_provider ( self ) :
self . provider = Ebook . infer_provider ( self . url )
return self . provider
2012-04-26 21:26:27 +00:00
@property
def rights_badge ( self ) :
2012-05-20 04:10:56 +00:00
if self . rights is None :
2014-05-08 14:21:50 +00:00
return cc . CCLicense . badge ( ' PD-US ' )
return cc . CCLicense . badge ( self . rights )
2012-02-28 22:27:48 +00:00
2012-09-07 05:04:18 +00:00
@staticmethod
def infer_provider ( url ) :
2012-02-28 22:27:48 +00:00
if not url :
return None
# provider derived from url. returns provider value. remember to call save() afterward
2014-11-17 18:30:02 +00:00
if re . match ( ' https?://books.google.com/ ' , url ) :
2012-02-28 22:27:48 +00:00
provider = ' Google Books '
2014-11-17 18:30:02 +00:00
elif re . match ( ' https?://www.gutenberg.org/ ' , url ) :
2012-02-28 22:27:48 +00:00
provider = ' Project Gutenberg '
2013-06-03 20:29:52 +00:00
elif re . match ( ' https?://(www \ .|)archive.org/ ' , url ) :
2012-02-28 22:27:48 +00:00
provider = ' Internet Archive '
elif url . startswith ( ' http://hdl.handle.net/2027/ ' ) or url . startswith ( ' http://babel.hathitrust.org/ ' ) :
provider = ' Hathitrust '
2014-11-17 16:51:36 +00:00
elif re . match ( ' https?:// \ w \ w \ .wikisource \ .org/ ' , url ) :
2012-02-28 22:27:48 +00:00
provider = ' Wikisource '
2014-11-17 16:51:36 +00:00
elif re . match ( ' https?:// \ w \ w \ .wikibooks \ .org/ ' , url ) :
provider = ' Wikibooks '
2015-01-19 17:10:07 +00:00
elif re . match ( ' https://github \ .com/[^/ ]+/[^/ ]+/raw/[^ ]+ ' , url ) :
2014-11-17 16:51:36 +00:00
provider = ' Github '
2012-02-28 22:27:48 +00:00
else :
provider = None
return provider
2013-04-20 04:02:36 +00:00
def increment ( self ) :
Ebook . objects . filter ( id = self . id ) . update ( download_count = F ( ' download_count ' ) + 1 )
@property
def download_url ( self ) :
2013-04-26 21:49:57 +00:00
return settings . BASE_URL_SECURE + reverse ( ' download_ebook ' , args = [ self . id ] )
2014-05-20 16:19:34 +00:00
def is_direct ( self ) :
return self . provider != ' Google Books '
2013-04-20 04:02:36 +00:00
2011-11-19 16:55:35 +00:00
def __unicode__ ( self ) :
return " %s ( %s from %s ) " % ( self . edition . title , self . format , self . provider )
2014-09-04 22:33:20 +00:00
def deactivate ( self ) :
self . active = False
self . save ( )
2014-10-04 20:51:16 +00:00
def activate ( self ) :
self . active = True
self . save ( )
2011-11-19 16:55:35 +00:00
2014-12-13 17:37:35 +00:00
def set_free_flag ( sender , instance , created , * * kwargs ) :
if created :
if not instance . edition . work . is_free :
instance . edition . work . is_free = True
instance . edition . work . save ( )
post_save . connect ( set_free_flag , sender = Ebook )
def reset_free_flag ( sender , instance , * * kwargs ) :
2014-12-15 19:46:59 +00:00
# if the Work associated with the instance Ebook currenly has only 1 Ebook, then it's no longer a free Work
# once the instance Ebook is deleted.
2014-12-13 17:37:35 +00:00
if instance . edition . work . ebooks ( ) . count ( ) == 1 :
instance . edition . work . is_free = False
instance . edition . work . save ( )
pre_delete . connect ( reset_free_flag , sender = Ebook )
2011-09-02 04:10:54 +00:00
class Wishlist ( models . Model ) :
created = models . DateTimeField ( auto_now_add = True )
user = models . OneToOneField ( User , related_name = ' wishlist ' )
2011-12-09 13:07:44 +00:00
works = models . ManyToManyField ( ' Work ' , related_name = ' wishlists ' , through = ' Wishes ' )
2011-09-12 05:53:54 +00:00
2011-11-19 17:07:44 +00:00
def __unicode__ ( self ) :
2013-08-18 22:10:25 +00:00
return " %s ' s Books " % self . user . username
2011-12-08 23:22:05 +00:00
2012-10-16 14:41:30 +00:00
def add_work ( self , work , source , notify = False ) :
2011-12-08 23:22:05 +00:00
try :
2012-01-09 18:55:22 +00:00
w = Wishes . objects . get ( wishlist = self , work = work )
except :
2012-02-11 19:15:06 +00:00
Wishes . objects . create ( source = source , wishlist = self , work = work )
2012-08-31 17:41:16 +00:00
work . update_num_wishes ( )
2012-10-16 14:20:24 +00:00
# only send notification in case of new wishes
# and only when they result from user action, not (e.g.) our tests
2012-10-16 14:41:30 +00:00
if notify :
2012-10-16 14:20:24 +00:00
wishlist_added . send ( sender = self , work = work , supporter = self . user )
2011-12-08 23:22:05 +00:00
def remove_work ( self , work ) :
w = Wishes . objects . filter ( wishlist = self , work = work )
if w :
w . delete ( )
2012-02-11 19:15:06 +00:00
work . update_num_wishes ( )
2011-12-08 23:22:05 +00:00
def work_source ( self , work ) :
w = Wishes . objects . filter ( wishlist = self , work = work )
if w :
return w [ 0 ] . source
else :
return ' '
class Wishes ( models . Model ) :
2014-07-31 16:17:42 +00:00
created = models . DateTimeField ( auto_now_add = True , db_index = True , )
source = models . CharField ( max_length = 15 , blank = True , db_index = True , )
2011-12-08 23:22:05 +00:00
wishlist = models . ForeignKey ( ' Wishlist ' )
2012-01-15 23:03:54 +00:00
work = models . ForeignKey ( ' Work ' , related_name = ' wishes ' )
2011-12-08 23:22:05 +00:00
class Meta :
db_table = ' core_wishlist_works '
2011-11-19 17:07:44 +00:00
2012-10-03 12:57:24 +00:00
class Badge ( models . Model ) :
name = models . CharField ( max_length = 72 , blank = True )
description = models . TextField ( default = ' ' , null = True )
@property
def path ( self ) :
return ' /static/images/ %s .png ' % self . name
2013-05-03 23:21:10 +00:00
def __unicode__ ( self ) :
return self . name
2012-10-03 12:57:24 +00:00
2012-10-04 00:35:05 +00:00
def pledger ( ) :
if not pledger . instance :
pledger . instance = Badge . objects . get ( name = ' pledger ' )
return pledger . instance
pledger . instance = None
2012-10-18 17:50:02 +00:00
2012-10-04 00:35:05 +00:00
def pledger2 ( ) :
if not pledger2 . instance :
pledger2 . instance = Badge . objects . get ( name = ' pledger2 ' )
return pledger2 . instance
pledger2 . instance = None
2012-10-03 12:57:24 +00:00
2013-03-14 19:43:24 +00:00
ANONYMOUS_AVATAR = ' /static/images/header/avatar.png '
2014-11-07 19:47:12 +00:00
( NO_AVATAR , GRAVATAR , TWITTER , FACEBOOK , UNGLUEITAR ) = ( 0 , 1 , 2 , 3 , 4 )
2013-03-14 19:43:24 +00:00
2013-08-08 22:21:33 +00:00
class Libpref ( models . Model ) :
user = models . OneToOneField ( User , related_name = ' libpref ' )
marc_link_target = models . CharField (
max_length = 6 ,
default = ' UNGLUE ' ,
2013-09-23 16:39:47 +00:00
choices = settings . MARC_PREF_OPTIONS ,
2013-08-08 22:21:33 +00:00
verbose_name = " MARC record link targets "
)
2013-03-14 19:43:24 +00:00
2011-10-03 16:36:04 +00:00
class UserProfile ( models . Model ) :
2011-11-19 16:55:35 +00:00
created = models . DateTimeField ( auto_now_add = True )
2011-10-13 16:22:38 +00:00
user = models . OneToOneField ( User , related_name = ' profile ' )
2011-10-03 16:36:04 +00:00
tagline = models . CharField ( max_length = 140 , blank = True )
2013-05-31 15:35:17 +00:00
pic_url = models . URLField ( blank = True )
home_url = models . URLField ( blank = True )
twitter_id = models . CharField ( max_length = 15 , blank = True )
2015-04-17 03:20:29 +00:00
facebook_id = models . BigIntegerField ( null = True )
2013-05-31 15:35:17 +00:00
librarything_id = models . CharField ( max_length = 31 , blank = True )
2012-08-07 02:34:20 +00:00
badges = models . ManyToManyField ( ' Badge ' , related_name = ' holders ' )
2013-05-31 15:35:17 +00:00
kindle_email = models . EmailField ( max_length = 254 , blank = True )
2013-03-14 13:58:21 +00:00
2011-10-29 22:40:00 +00:00
goodreads_user_id = models . CharField ( max_length = 32 , null = True , blank = True )
goodreads_user_name = models . CharField ( max_length = 200 , null = True , blank = True )
goodreads_auth_token = models . TextField ( null = True , blank = True )
goodreads_auth_secret = models . TextField ( null = True , blank = True )
2012-10-03 12:57:24 +00:00
goodreads_user_link = models . CharField ( max_length = 200 , null = True , blank = True )
2012-08-07 02:34:20 +00:00
2014-11-07 19:47:12 +00:00
avatar_source = models . PositiveSmallIntegerField ( null = True , default = UNGLUEITAR ,
choices = ( ( NO_AVATAR , ' No Avatar, Please ' ) , ( GRAVATAR , ' Gravatar ' ) , ( TWITTER , ' Twitter ' ) , ( FACEBOOK , ' Facebook ' ) , ( UNGLUEITAR , ' Unglueitar ' ) ) )
2013-03-14 13:58:21 +00:00
2013-05-03 23:21:10 +00:00
def __unicode__ ( self ) :
return self . user . username
2012-10-03 12:57:24 +00:00
def reset_pledge_badge ( self ) :
#count user pledges
n_pledges = self . pledge_count
if self . badges . exists ( ) :
2012-10-04 00:35:05 +00:00
self . badges . remove ( pledger ( ) )
self . badges . remove ( pledger2 ( ) )
2012-10-03 12:57:24 +00:00
if n_pledges == 1 :
2012-10-04 00:35:05 +00:00
self . badges . add ( pledger ( ) )
2012-10-03 12:57:24 +00:00
elif n_pledges > 1 :
2012-10-04 00:35:05 +00:00
self . badges . add ( pledger2 ( ) )
2012-10-03 12:56:29 +00:00
2012-10-03 12:57:24 +00:00
@property
def pledge_count ( self ) :
return self . user . transaction_set . exclude ( status = ' NONE ' ) . exclude ( status = ' Canceled ' , reason = None ) . exclude ( anonymous = True ) . count ( )
2012-10-13 17:45:46 +00:00
@property
def account ( self ) :
# there should be only one active account per user
accounts = self . user . account_set . filter ( date_deactivated__isnull = True )
if accounts . count ( ) == 0 :
return None
else :
return accounts [ 0 ]
2012-10-18 17:50:02 +00:00
2012-10-15 03:41:17 +00:00
@property
def old_account ( self ) :
accounts = self . user . account_set . filter ( date_deactivated__isnull = False ) . order_by ( ' -date_deactivated ' )
if accounts . count ( ) == 0 :
return None
else :
return accounts [ 0 ]
2012-10-13 17:45:46 +00:00
2012-10-14 21:44:17 +00:00
@property
def pledges ( self ) :
2014-12-18 06:07:59 +00:00
return self . user . transaction_set . filter ( status = TRANSACTION_STATUS_ACTIVE , campaign__type = 1 )
2012-10-18 19:51:52 +00:00
@property
def last_transaction ( self ) :
from regluit . payment . models import Transaction
try :
return Transaction . objects . filter ( user = self . user ) . order_by ( ' -date_modified ' ) [ 0 ]
except IndexError :
return None
2012-10-18 17:50:02 +00:00
@property
def ack_name ( self ) :
# use preferences from last transaction, if any
2012-10-18 19:51:52 +00:00
last = self . last_transaction
2013-08-18 19:21:55 +00:00
if last and last . extra :
return last . extra . get ( ' ack_name ' , self . user . username )
2012-10-18 17:50:02 +00:00
else :
return self . user . username
@property
def anon_pref ( self ) :
# use preferences from last transaction, if any
2012-10-18 19:51:52 +00:00
last = self . last_transaction
2012-10-18 17:50:02 +00:00
if last :
return last . anonymous
else :
2012-10-19 18:01:22 +00:00
return None
2013-03-04 22:01:33 +00:00
@property
def on_ml ( self ) :
2013-12-16 20:11:25 +00:00
if " @example.org " in self . user . email :
# use @example.org email addresses for testing!
return False
2013-03-04 22:01:33 +00:00
try :
return settings . MAILCHIMP_NEWS_ID in pm . listsForEmail ( email_address = self . user . email )
except MailChimpException , e :
if e . code != 215 : # don't log case where user is not on a list
logger . error ( " error getting mailchimp status %s " % ( e ) )
except Exception , e :
logger . error ( " error getting mailchimp status %s " % ( e ) )
return False
def ml_subscribe ( self , * * kwargs ) :
2013-03-06 14:39:06 +00:00
if " @example.org " in self . user . email :
# use @example.org email addresses for testing!
return True
2014-05-07 22:37:19 +00:00
from regluit . core . tasks import ml_subscribe_task
ml_subscribe_task . delay ( self , * * kwargs )
2013-03-04 22:01:33 +00:00
def ml_unsubscribe ( self ) :
2013-12-16 20:11:25 +00:00
if " @example.org " in self . user . email :
# use @example.org email addresses for testing!
return True
2013-03-04 22:01:33 +00:00
try :
return pm . listUnsubscribe ( id = settings . MAILCHIMP_NEWS_ID , email_address = self . user . email )
except Exception , e :
logger . error ( " error unsubscribing from mailchimp list %s " % ( e ) )
return False
2013-03-14 13:58:21 +00:00
def gravatar ( self ) :
# construct the url
2013-03-28 16:27:37 +00:00
gravatar_url = " https://www.gravatar.com/avatar/ " + hashlib . md5 ( self . user . email . lower ( ) ) . hexdigest ( ) + " ? "
2013-03-15 01:42:00 +00:00
gravatar_url + = urllib . urlencode ( { ' d ' : ' wavatar ' , ' s ' : ' 50 ' } )
return gravatar_url
2013-03-14 19:43:24 +00:00
2014-11-07 19:47:12 +00:00
def unglueitar ( self ) :
# construct the url
gravatar_url = " https://www.gravatar.com/avatar/ " + hashlib . md5 ( self . user . username + ' @unglue.it ' ) . hexdigest ( ) + " ? "
gravatar_url + = urllib . urlencode ( { ' d ' : ' wavatar ' , ' s ' : ' 50 ' } )
return gravatar_url
2013-03-14 19:43:24 +00:00
@property
def avatar_url ( self ) :
2013-03-15 01:42:00 +00:00
if self . avatar_source is None or self . avatar_source is TWITTER :
2013-03-14 19:43:24 +00:00
if self . pic_url :
return self . pic_url
else :
return ANONYMOUS_AVATAR
2014-11-07 19:47:12 +00:00
elif self . avatar_source == UNGLUEITAR :
return self . unglueitar ( )
2013-03-15 01:42:00 +00:00
elif self . avatar_source == GRAVATAR :
return self . gravatar ( )
2013-03-18 18:56:27 +00:00
elif self . avatar_source == FACEBOOK and self . facebook_id != None :
2015-04-17 15:46:29 +00:00
return ' https://graph.facebook.com/v2.3/ ' + str ( self . facebook_id ) + ' /picture?redirect=true '
2013-03-28 01:46:52 +00:00
else :
return ANONYMOUS_AVATAR
2013-03-08 02:48:56 +00:00
@property
def social_auths ( self ) :
socials = self . user . social_auth . all ( )
auths = { }
for social in socials :
auths [ social . provider ] = True
return auths
2013-10-10 06:41:50 +00:00
@property
def libraries ( self ) :
libs = [ ]
for group in self . user . groups . all ( ) :
try :
libs . append ( group . library )
except Library . DoesNotExist :
pass
return libs
2013-03-08 02:48:56 +00:00
2013-04-04 14:15:15 +00:00
class Press ( models . Model ) :
2013-04-04 13:44:41 +00:00
url = models . URLField ( )
title = models . CharField ( max_length = 140 )
source = models . CharField ( max_length = 140 )
2014-07-31 16:17:42 +00:00
date = models . DateField ( db_index = True , )
2013-04-04 13:44:41 +00:00
language = models . CharField ( max_length = 20 , blank = True )
highlight = models . BooleanField ( default = False )
note = models . CharField ( max_length = 140 , blank = True )
2014-12-15 05:56:08 +00:00
class Gift ( models . Model ) :
# the acq will contain the recipient, and the work
2014-12-16 19:18:51 +00:00
acq = models . ForeignKey ( ' Acq ' , related_name = ' gifts ' )
2014-12-22 18:49:39 +00:00
to = models . CharField ( max_length = 75 , blank = True ) # store the email address originally sent to, not necessarily the email of the recipient
2014-12-16 19:18:51 +00:00
giver = models . ForeignKey ( User , related_name = ' gifts ' )
2014-12-22 18:49:39 +00:00
message = models . TextField ( max_length = 512 , default = ' ' )
2014-12-16 19:18:51 +00:00
used = models . DateTimeField ( null = True )
@staticmethod
def giftee ( email , t_id ) :
# return a user (create a user if necessary)
2014-12-22 18:24:22 +00:00
( giftee , new_user ) = User . objects . get_or_create ( email = email , defaults = { ' username ' : ' giftee %s ' % t_id } )
2014-12-16 19:18:51 +00:00
giftee . new_user = new_user
return giftee
2014-12-18 06:07:59 +00:00
def use ( self ) :
self . used = now ( )
self . save ( )
2014-12-19 20:13:54 +00:00
notification . send ( [ self . giver ] , " purchase_got_gift " , { ' gift ' : self } , True )
2014-12-18 06:07:59 +00:00
2013-07-25 14:36:03 +00:00
2012-05-30 00:50:53 +00:00
# this was causing a circular import problem and we do not seem to be using
# anything from regluit.core.signals after this line
# from regluit.core import signals
2011-10-08 03:11:57 +00:00
from regluit . payment . manager import PaymentManager
2011-11-12 18:58:31 +00:00