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-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
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-06-03 16:31:39 +00:00
from django . db import models
from django . db . models import F , Q , get_model
2013-09-06 02:54:11 +00:00
from django . db . models . signals import post_save
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
2013-09-16 01:43:58 +00:00
from regluit . core . epub import personalize
2011-11-10 17:33:22 +00:00
2013-06-03 16:31:39 +00:00
from regluit . core . signals import (
successful_campaign ,
unsuccessful_campaign ,
wishlist_added
)
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 ,
INDIVIDUAL ,
LIBRARY ,
BORROWED ,
2013-10-17 02:48:29 +00:00
TESTING ,
RESERVE ,
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 )
2013-08-08 22:21:33 +00:00
status = models . CharField ( max_length = 7 , choices = STATUSES , default = ' pending ' )
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
2013-05-03 22:05:43 +00:00
def __unicode__ ( self ) :
return self . work . title
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-18 19:21:55 +00:00
extra = { }
anonymous = False
premium = None
offer = None
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
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
class CCLicense ( ) :
2013-08-08 22:21:33 +00:00
CHOICES = settings . CHOICES
2012-05-20 04:10:56 +00:00
2012-09-07 05:04:18 +00:00
@staticmethod
def url ( license ) :
2012-05-20 04:10:56 +00:00
if license == ' PD-US ' :
return ' http://creativecommons.org/publicdomain/mark/1.0/ '
elif license == ' CC0 ' :
return ' http://creativecommons.org/publicdomain/zero/1.0/ '
elif license == ' CC BY ' :
return ' http://creativecommons.org/licenses/by/3.0/ '
elif license == ' CC BY-NC-ND ' :
return ' http://creativecommons.org/licenses/by-nc-nd/3.0/ '
elif license == ' CC BY-NC-SA ' :
return ' http://creativecommons.org/licenses/by-nc-sa/3.0/ '
elif license == ' CC BY-NC ' :
return ' http://creativecommons.org/licenses/by-nc/3.0/ '
elif license == ' CC BY-SA ' :
return ' http://creativecommons.org/licenses/by-sa/3.0/ '
elif license == ' CC BY-ND ' :
return ' http://creativecommons.org/licenses/by-nd/3.0/ '
else :
return ' '
2012-09-07 05:04:18 +00:00
@staticmethod
def badge ( license ) :
2012-05-20 04:10:56 +00:00
if license == ' PD-US ' :
return ' https://i.creativecommons.org/p/mark/1.0/88x31.png '
elif license == ' CC0 ' :
return ' https://i.creativecommons.org/p/zero/1.0/88x31.png '
elif license == ' CC BY ' :
return ' https://i.creativecommons.org/l/by/3.0/88x31.png '
elif license == ' CC BY-NC-ND ' :
return ' https://i.creativecommons.org/l/by-nc-nd/3.0/88x31.png '
elif license == ' CC BY-NC-SA ' :
return ' https://i.creativecommons.org/l/by-nc-sa/3.0/88x31.png '
elif license == ' CC BY-NC ' :
return ' https://i.creativecommons.org/l/by-nc/3.0/88x31.png '
elif license == ' CC BY-SA ' :
return ' https://i.creativecommons.org/l/by-sa/3.0/88x31.png '
elif license == ' CC BY-ND ' :
return ' https://i.creativecommons.org/l/by-nd/3.0/88x31.png '
else :
return ' '
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 )
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
"""
2013-10-17 02:48:29 +00:00
CHOICES = ( ( INDIVIDUAL , ' Individual license ' ) , ( LIBRARY , ' Library License ' ) , ( BORROWED , ' Borrowed from Library ' ) , ( TESTING , ' Just for Testing ' ) , ( RESERVE , ' On Reserve ' ) , )
2013-08-20 02:54:43 +00:00
created = models . DateTimeField ( auto_now_add = True )
expires = models . DateTimeField ( null = True )
work = models . ForeignKey ( " Work " , related_name = ' acqs ' , null = False )
user = models . ForeignKey ( User , related_name = ' acqs ' )
license = models . PositiveSmallIntegerField ( null = False , default = INDIVIDUAL ,
choices = CHOICES )
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-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-08-27 03:56:01 +00:00
params = {
' customeremailaddress ' : self . user . email ,
' customername ' : self . user . username ,
2013-09-20 21:46:06 +00:00
' languagecode ' : ' 1033 ' ,
2013-08-27 03:56:01 +00:00
' expirydays ' : 1 ,
' downloadlimit ' : 7 ,
' exlibris ' : 1 ,
' chapterfooter ' : 1 ,
2013-09-20 21:46:06 +00:00
' disclaimer ' : 0 ,
2013-08-27 03:56:01 +00:00
' referenceid ' : ' %s : %s : %s ' % ( self . work . id , self . user . id , self . id ) ,
2013-08-27 22:03:35 +00:00
' kf8mobi ' : True ,
' epub ' : True ,
2013-08-27 03:56:01 +00:00
}
2013-09-16 01:43:58 +00:00
personalized = personalize ( self . work . ebookfiles ( ) [ 0 ] . file , self )
personalized . filename . seek ( 0 )
self . watermarked = watermarker . platform ( epubfile = personalized . filename , * * 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 ) :
2013-09-20 21:46:06 +00:00
return hashlib . md5 ( ' 1c1a56974ef08edc %s : %s : %s ' % ( self . user . id , self . work . id , self . created ) ) . hexdigest ( )
2013-10-17 02:48:29 +00:00
def expire_in ( self , delta ) :
self . expires = now ( ) + delta
self . save ( )
@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 ) )
return self
if self . borrowable and user :
return Acq . objects . create ( user = user , work = self . work , license = BORROWED )
@property
def borrowable ( self ) :
if self . license == RESERVE and not self . expired :
return True
if self . license != LIBRARY :
return False
return Acq . objects . filter ( lib_acq = self , expires__gt = datetime . now ( ) ) . count ( ) == 0
2013-09-06 02:54:11 +00:00
def add_acq_nonce ( sender , instance , created , * * kwargs ) :
if created :
instance . nonce = instance . _hash ( )
instance . save ( )
2013-10-17 02:48:29 +00:00
def set_expiration ( sender , instance , created , * * kwargs ) :
if created :
if instance . license == RESERVE :
instance . expire_in ( timedelta ( hours = 2 ) )
if instance . license == BORROWED :
instance . expire_in ( timedelta ( days = 14 ) )
2013-09-06 02:54:11 +00:00
post_save . connect ( add_acq_nonce , sender = Acq )
2013-10-17 02:48:29 +00:00
post_save . connect ( set_expiration , sender = Acq )
2013-06-12 20:43:11 +00:00
2012-05-20 04:10:56 +00:00
class Campaign ( models . Model ) :
2013-08-08 22:21:33 +00:00
LICENSE_CHOICES = settings . CCCHOICES
2011-09-02 04:10:54 +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 )
2011-10-14 19:37:22 +00:00
target = models . DecimalField ( max_digits = 14 , decimal_places = 2 , null = True , blank = False )
2012-03-23 16:30:49 +00:00
license = models . CharField ( max_length = 255 , choices = LICENSE_CHOICES , default = ' CC BY-NC-ND ' )
2011-12-14 05:52:42 +00:00
left = models . DecimalField ( max_digits = 14 , decimal_places = 2 , null = True , blank = False )
2013-02-04 18:32:21 +00:00
deadline = models . DateTimeField ( db_index = 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 )
2011-10-07 21:17:54 +00:00
activated = models . DateTimeField ( null = 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
2011-12-06 15:35:05 +00:00
status = models . CharField ( max_length = 15 , null = True , blank = False , default = " INITIALIZED " )
2013-06-17 22:53:21 +00:00
type = models . PositiveSmallIntegerField ( null = False , default = REWARDS ,
choices = ( ( REWARDS , ' Rewards-based fixed duration campaign ' ) , ( BUY2UNGLUE , ' Buy-to-unglue 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 )
2011-11-24 02:41:06 +00:00
problems = [ ]
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
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
if self . target < Decimal ( settings . UNGLUEIT_MINIMUM_TARGET ) :
self . problems . append ( _ ( ' A campaign may not be launched with a target less than $ %s ' % settings . UNGLUEIT_MINIMUM_TARGET ) )
may_launch = False
if self . type == REWARDS and 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
elif self . deadline . date ( ) - date_today ( ) < timedelta ( days = 0 ) :
self . problems . append ( _ ( ' The chosen closing date is in the past ' ) )
may_launch = False
if self . type == BUY2UNGLUE and 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 self . type == BUY2UNGLUE and 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 . type == BUY2UNGLUE and ( ( 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 ( ) ) ) :
2013-10-04 02:54:25 +00:00
self . problems . append ( _ ( ' You must set an initial Ungluing Date that is in the future and not after %s ' % settings . MAX_CC_DATE ) )
2013-08-19 20:01:32 +00:00
may_launch = False
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.
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
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
2012-06-20 22:48:55 +00:00
elif ( ignore_deadline_for_success or self . deadline < now ( ) ) and self . current_total > = self . target :
2012-06-21 19:01:48 +00:00
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
2012-11-13 18:15:38 +00:00
if process_transactions :
p = PaymentManager ( )
results = p . execute_campaign ( self )
2012-11-21 17:21:01 +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?
2012-06-21 19:01:48 +00:00
return True
elif self . deadline < now ( ) and self . current_total < self . target :
self . status = ' UNSUCCESSFUL '
2011-12-06 15:35:05 +00:00
self . save ( )
2012-06-21 19:01:48 +00:00
action = CampaignAction ( campaign = self , type = ' failed ' , comment = self . current_total )
action . save ( )
2012-11-21 17:21:01 +00:00
2012-11-13 18:15:38 +00:00
if process_transactions :
p = PaymentManager ( )
2012-11-21 17:21:01 +00:00
results = p . cancel_campaign ( self )
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?
2012-11-15 21:06:12 +00:00
return True
2011-10-09 18:27:27 +00:00
else :
2011-12-06 15:35:05 +00:00
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 ) :
if self . dollar_per_day is 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 ) :
2013-08-08 23:56:26 +00:00
if 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 ' ) )
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 ( )
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
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 :
ungluers [ ' all ' ] . append ( transaction . user )
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 :
return Offer . objects . none ( )
2013-10-11 21:50:54 +00:00
try :
return Offer . objects . get ( work = self . work , active = True , license = license )
except Offer . DoesNotExist :
return Offer . objects . none ( )
2013-08-22 18:23:47 +00:00
@property
def days_per_copy ( self ) :
if self . active_offers ( ) . count ( ) > 0 :
return Decimal ( float ( self . active_offers ( ) [ 0 ] . price ) / self . dollar_per_day )
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 ) :
return CCLicense . url ( self . license )
@property
def license_badge ( self ) :
return 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
@property
def countdown ( self ) :
from math import ceil
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 "
return countdown
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 ( ) )
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 )
value = models . CharField ( max_length = 31 , null = False )
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 ) :
created = models . DateTimeField ( auto_now_add = True )
2011-09-02 04:10:54 +00:00
title = models . CharField ( max_length = 1000 )
2011-12-13 14:55:26 +00:00
language = models . CharField ( max_length = 2 , default = " en " , null = False )
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 )
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 ' '
2011-12-19 07:20:24 +00:00
@property
def openlibrary_url ( self ) :
return " http://openlibrary.org " + self . openlibrary_id
2011-09-29 06:23:50 +00:00
def cover_image_small ( self ) :
2011-12-31 18:48:54 +00:00
try :
2012-05-12 02:46:03 +00:00
if self . preferred_edition . cover_image_small ( ) :
return self . preferred_edition . cover_image_small ( )
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-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 :
2012-05-12 02:46:03 +00:00
if self . preferred_edition . cover_image_thumbnail ( ) :
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
def author ( self ) :
2012-03-05 16:12:41 +00:00
# note: if you want this to be a real list, use distinct()
# perhaps should change this to vote on authors.
2011-11-18 03:11:40 +00:00
authors = list ( Author . objects . filter ( editions__work = self ) . all ( ) )
2012-03-05 16:12:41 +00:00
try :
2012-03-05 22:15:23 +00:00
return authors [ 0 ] . name
2012-03-05 16:12:41 +00:00
except :
2012-03-05 22:15:23 +00:00
return ' '
2011-11-07 20:39:02 +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 ) :
if self . last_campaign ( ) :
if self . last_campaign ( ) . edition :
return self . last_campaign ( ) . edition
return self . editions . all ( ) [ 0 ]
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 ) :
percent = 0
campaign = self . last_campaign ( )
if campaign is not None :
2012-05-17 18:51:53 +00:00
if ( campaign . status == ' SUCCESSFUL ' or campaign . status == ' ACTIVE ' ) :
2013-08-09 02:32:58 +00:00
if campaign . type == BUY2UNGLUE :
percent = int ( 100 - 100 * campaign . left / campaign . target )
else :
percent = int ( campaign . current_total / campaign . target * 100 )
2012-05-17 13:24:37 +00:00
return percent
2011-10-14 13:43:30 +00:00
2012-02-28 22:27:48 +00:00
def ebooks ( self ) :
return Ebook . objects . filter ( edition__work = self ) . order_by ( ' -created ' )
2013-08-20 02:54:43 +00:00
def ebookfiles ( self ) :
2013-08-27 03:56:01 +00:00
return EbookFile . objects . filter ( edition__work = self ) . order_by ( ' -created ' )
2012-02-28 22:27:48 +00:00
2013-04-20 14:08:10 +00:00
@property
def download_count ( self ) :
dlc = 0
for ebook in self . ebooks ( ) :
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 ) :
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 ) :
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 ) :
2012-01-17 21:27:58 +00:00
for edition in Edition . objects . filter ( work = self ) :
if edition . publication_date :
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-08-20 02:54:43 +00:00
2013-10-11 21:50:54 +00:00
class user_license :
acqs = Acq . objects . none ( )
def __init__ ( self , acqs ) :
self . acqs = acqs
def is_active ( self ) :
if self . acqs . count ( ) == 0 :
return False
for acq in self . acqs :
if acq . expires is None :
return True
if acq . expires > now ( ) :
return True
2013-08-20 02:54:43 +00:00
return False
2013-10-11 21:50:54 +00:00
def purchased ( self ) :
for acq in self . acqs :
if acq . license == INDIVIDUAL :
return True
2013-08-20 02:54:43 +00:00
return False
2013-10-11 21:50:54 +00:00
def get_user_license ( self , user ) :
if user == None or not user . is_authenticated ( ) :
return None
return self . user_license ( self . acqs . filter ( user = user ) )
2013-06-17 22:53:21 +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
2011-09-10 11:36:38 +00:00
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 " )
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
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 )
2012-05-11 18:13:09 +00:00
publication_date = models . CharField ( max_length = 50 , null = True , blank = True )
public_domain = models . NullBooleanField ( null = True , blank = 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 )
2012-01-09 18:55:22 +00:00
@property
def isbn_13 ( self ) :
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 ' '
@property
def googlebooks_id ( self ) :
try :
2013-04-03 06:14:39 +00:00
return self . identifiers . filter ( type = ' goog ' ) [ 0 ] . value
2012-01-09 18:55:22 +00:00
except IndexError :
return ' '
@property
def librarything_id ( self ) :
try :
2013-04-03 06:14:39 +00:00
return self . identifiers . filter ( type = ' thng ' ) [ 0 ] . value
2012-01-09 18:55:22 +00:00
except IndexError :
return ' '
@property
def oclc ( self ) :
try :
2013-04-03 06:14:39 +00:00
return self . identifiers . filter ( type = ' oclc ' ) [ 0 ] . value
2012-01-09 18:55:22 +00:00
except IndexError :
return ' '
@property
def goodreads_id ( self ) :
try :
2013-04-03 06:14:39 +00:00
return self . identifiers . filter ( type = ' gdrd ' ) [ 0 ] . value
2012-01-09 18:55:22 +00:00
except IndexError :
return ' '
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
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 ( )
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
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 ( )
fn = " ebf/ %s . %d . %s " % ( instance . edition . pk , version , instance . format )
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 )
2012-02-28 22:27:48 +00:00
2011-11-06 21:33:04 +00:00
class Ebook ( models . Model ) :
2013-08-08 22:21:33 +00:00
FORMAT_CHOICES = settings . FORMATS
RIGHTS_CHOICES = settings . CCCHOICES
2012-02-28 22:27:48 +00:00
url = models . URLField ( max_length = 1024 )
2011-11-19 16:55:35 +00:00
created = models . DateTimeField ( auto_now_add = 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 )
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
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 :
return CCLicense . badge ( ' PD-US ' )
return 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
if url . startswith ( ' http://books.google.com/ ' ) :
provider = ' Google Books '
elif url . startswith ( ' http://www.gutenberg.org/ ' ) :
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 '
elif re . match ( ' http:// \ w \ w \ .wikisource \ .org/ ' , url ) :
provider = ' Wikisource '
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 ] )
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 )
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 ) :
created = models . DateTimeField ( auto_now_add = True )
source = models . CharField ( max_length = 15 , blank = True )
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 '
2013-03-15 01:42:00 +00:00
( NO_AVATAR , GRAVATAR , TWITTER , FACEBOOK ) = ( 0 , 1 , 2 , 3 )
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 )
facebook_id = models . PositiveIntegerField ( null = True )
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
2013-03-28 14:39:48 +00:00
avatar_source = models . PositiveSmallIntegerField ( null = True , default = GRAVATAR ,
2013-03-15 01:42:00 +00:00
choices = ( ( NO_AVATAR , ' No Avatar, Please ' ) , ( GRAVATAR , ' Gravatar ' ) , ( TWITTER , ' Twitter ' ) , ( FACEBOOK , ' Facebook ' ) ) )
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 ) :
return self . user . transaction_set . filter ( status = TRANSACTION_STATUS_ACTIVE )
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 ) :
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
2013-03-04 22:01:33 +00:00
try :
2013-03-06 15:27:51 +00:00
if not self . on_ml :
2013-03-06 14:39:06 +00:00
return pm . listSubscribe ( id = settings . MAILCHIMP_NEWS_ID , email_address = self . user . email , * * kwargs )
2013-03-04 22:01:33 +00:00
except Exception , e :
logger . error ( " error subscribing to mailchimp list %s " % ( e ) )
return False
def ml_unsubscribe ( self ) :
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
@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
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 :
return ' https://graph.facebook.com/ ' + str ( self . facebook_id ) + ' /picture '
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 )
date = models . DateField ( )
language = models . CharField ( max_length = 20 , blank = True )
highlight = models . BooleanField ( default = False )
note = models . CharField ( max_length = 140 , blank = True )
2013-07-08 14:03:22 +00:00
class MARCRecord ( models . Model ) :
edition = models . ForeignKey ( " Edition " , related_name = " MARCrecords " , null = True )
2013-07-25 14:18:36 +00:00
# this is where the download link points to, direct link or via Unglue.it.
link_target = models . CharField ( max_length = 6 , choices = settings . MARC_CHOICES , default = ' DIRECT ' )
2013-09-23 04:34:51 +00:00
@property
def accession ( self ) :
zeroes = 9 - len ( str ( self . id ) )
return ' ung ' + zeroes * ' 0 ' + str ( self . id )
@property
def xml_record ( self ) :
return self . _record ( ' xml ' )
@property
def mrc_record ( self ) :
return self . _record ( ' mrc ' )
def _record ( self , filetype ) :
test = ' ' if ' /unglue.it ' in settings . BASE_URL else ' _test '
2013-09-23 16:39:47 +00:00
if self . link_target == ' DIRECT ' :
fn = ' _unglued. '
elif self . link_target == ' UNGLUE ' :
fn = ' _via_unglueit. '
else :
fn = ' _ungluing. '
return ' marc ' + test + ' / ' + self . accession + fn + filetype
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