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
|
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
|
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
|
|
|
|
)
|
2012-07-12 02:51:36 +00:00
|
|
|
|
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:
|
|
|
|
premium=None
|
|
|
|
anonymous=False
|
|
|
|
ack_name=''
|
|
|
|
ack_dedication=''
|
2013-08-16 19:49:44 +00:00
|
|
|
offer=None
|
|
|
|
def __init__(self,premium=None,anonymous=False,ack_name='',ack_dedication='',offer=None):
|
2012-09-07 13:46:38 +00:00
|
|
|
self.premium=premium
|
|
|
|
self.anonymous=anonymous
|
|
|
|
self.ack_name=ack_name
|
|
|
|
self.ack_dedication=ack_dedication
|
2013-08-16 19:49:44 +00:00
|
|
|
self.offer=offer
|
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
|
|
|
(INDIVIDUAL, LIBRARY) = (1, 2)
|
|
|
|
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-06-12 20:43:11 +00:00
|
|
|
|
2013-06-17 22:53:21 +00:00
|
|
|
(REWARDS, BUY2UNGLUE) = (1, 2)
|
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):
|
2012-09-22 16:00:17 +00:00
|
|
|
"""use a previous UNSUCCESSFUL campaign's data as the basis for a new campaign"""
|
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
|
|
|
|
if self.status != 'INITIALIZED':
|
2011-11-29 15:48:37 +00:00
|
|
|
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'))
|
2011-11-24 02:41:06 +00:00
|
|
|
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
|
2013-06-17 22:53:21 +00:00
|
|
|
if self.type==REWARDS and self.deadline.date()- date_today() > timedelta(days=int(settings.UNGLUEIT_LONGEST_DEADLINE)):
|
2011-11-24 02:41:06 +00:00
|
|
|
self.problems.append(_('The chosen closing date is more than %s days from now' % settings.UNGLUEIT_LONGEST_DEADLINE))
|
2011-11-29 15:48:37 +00:00
|
|
|
may_launch = False
|
2012-05-17 15:36:15 +00:00
|
|
|
elif self.deadline.date()- date_today() < timedelta(days=0):
|
|
|
|
self.problems.append(_('The chosen closing date is in the past'))
|
2011-11-29 15:48:37 +00:00
|
|
|
may_launch = False
|
2013-06-17 22:53:21 +00:00
|
|
|
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
|
2013-08-10 20:29:58 +00:00
|
|
|
if self.type==BUY2UNGLUE and ((self.cc_date_initial is None) or (self.cc_date_initial.date() > settings.MAX_CC_DATE) or (self.cc_date_initial < now())):
|
|
|
|
self.problems.append(_('You must set an initial CC Date that is in the future and not after %s' % settings.MAX_CC_DATE ))
|
|
|
|
may_launch = False
|
2011-11-24 02:41:06 +00:00
|
|
|
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-08-08 23:56:26 +00:00
|
|
|
time_to_cc = self.cc_date_initial - datetime.today()
|
|
|
|
self.dollar_per_day = float(self.target)/float(time_to_cc.days)
|
|
|
|
self.save()
|
|
|
|
return self.dollar_per_day
|
|
|
|
|
|
|
|
@property
|
|
|
|
def cc_date(self):
|
|
|
|
if self.dollar_per_day is None:
|
|
|
|
return self.cc_date_initial
|
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
|
2012-06-22 03:20:32 +00:00
|
|
|
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
|
2011-10-09 18:27:27 +00:00
|
|
|
self.save()
|
2012-05-03 00:19:40 +00:00
|
|
|
|
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')
|
2012-01-02 22:22:25 +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-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]):
|
|
|
|
self.offers.create(license=choice[0])
|
|
|
|
return self.offers.all()
|
|
|
|
|
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):
|
|
|
|
return "%s's Wishlist" % 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',
|
|
|
|
choices = settings.MARC_CHOICES,
|
|
|
|
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
|
2012-10-23 13:37:00 +00:00
|
|
|
if last and last.ack_name:
|
2012-10-18 17:50:02 +00:00
|
|
|
return last.ack_name
|
|
|
|
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-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):
|
2013-07-25 14:36:03 +00:00
|
|
|
xml_record = models.URLField(blank=True)
|
|
|
|
mrc_record = models.URLField(blank=True)
|
2013-07-08 14:03:22 +00:00
|
|
|
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-07-25 14:36:03 +00:00
|
|
|
|
|
|
|
def clean(self):
|
|
|
|
from django.core.exceptions import ValidationError
|
|
|
|
super(MARCRecord, self).clean()
|
|
|
|
if not self.xml_record and not self.mrc_record:
|
|
|
|
raise ValidationError('You must have at least one of xml_record and mrc_record')
|
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
|
|
|
|