pull/1/head
eric 2012-06-19 07:34:52 -04:00
commit f7282d4e11
15 changed files with 257 additions and 54 deletions

View File

@ -62,7 +62,7 @@ class BookLoaderTests(TestCase):
models.Identifier(type='isbn', value='9780226032030', work=w, edition=e).save()
bookloader.update_edition(e)
self.assertEqual(e.work.language, 'en')
self.assertEqual(e.title, 'Forbidden journeys')
self.assertEqual(e.title, 'Forbidden Journeys')
def test_double_add(self):
bookloader.add_by_isbn('0441012035')

View File

@ -1 +1,81 @@
# Create your views here.
from regluit.core.models import Campaign
from django.http import HttpResponse
import traceback
from django.db import transaction
import time
def test_read(request):
try:
row_id = 1
print "Attempting to read row"
# A read the waits for the exclusive lock for the row
campaign = Campaign.objects.raw("SELECT * FROM core_campaign WHERE id=%d FOR UPDATE" % row_id)[0]
print "Successfully read row data %d" % campaign.target
except:
traceback.print_exc()
return HttpResponse("Success")
def test_write(request):
try:
row_id = 1
campaign = Campaign.objects.get(id=row_id)
print "Attempting to write row via ordinary ORM"
#
# Modify the data. This will block if any shared lock (Either FOR UPDATE or LOCK IN SHARED MODE is held
#
campaign.target = campaign.target + 10
campaign.save()
print "Successfully write new row data %d" % campaign.target
except:
traceback.print_exc()
return HttpResponse("Success")
@transaction.commit_on_success
def test_lock(request):
try:
row_id = 1
print "Attempting to acquire row lock"
campaign = Campaign.objects.raw("SELECT * FROM core_campaign WHERE id=%d FOR UPDATE" % row_id)[0]
print "Row lock acquired, modifying data"
# Modify the data
campaign.target = campaign.target + 10
campaign.save()
#
# test by sleeping here for 10 seconds.
#
# The FOR UPDATE request will lock the row exclusively. All write/delete operations require a compatible lock
# and will block until this transaction is complete. Some reads will block, but some will not. If we want to
# block the read until this transaction is complete, the read should also acquire an exlusive OR a shared lock.
#
# As soon as the function completes, the transaction will be committed and the lock released.
# You can modify the commit_on_success decorator to get different transaction behaviors
#
print "Thread sleeping for 10 seconds"
time.sleep(10)
print "Thread sleep complete"
except:
traceback.print_exc()
return HttpResponse("Success")

View File

@ -31,7 +31,7 @@
<b>UNGLUE IT!</b>
<p><b>${{ work.last_campaign.current_total|floatformat:0|intcomma }}</b> raised</p>
<p><b>${{ work.last_campaign.target|floatformat:0|intcomma }}</b> needed</p>
<p>by {{ deadline|date:"M d, Y" }}</p>
<p>by {{ deadline|naturalday:"M d, Y" }}</p>
<a href="/pledge/{{workid}}"><div class="read_itbutton pledge"><span>Support</span></div></a>
{% else %}{% if status == 'INITIALIZED' %}

View File

@ -1,6 +1,4 @@
We thought you might like to know there are new comments for a book you have commented on.
{{ comment.user.username }} on {{ comment.content_object.title }}:
{{ comment.user.username }} has commented on a book you also commented on, {{ comment.content_object.title }}, as follows.
{{ comment.comment }}

View File

@ -1,12 +1,17 @@
{% load humanize %}You have modified a pledge that you had previously made to the campaign to unglue {{ transaction.campaign.work.title }}.
{% load humanize %}{% if up_or_down == 'canceled' %}You have canceled a pledge that you had previously made to the campaign to unglue {{ transaction.campaign.work.title }}.
Your canceled pledge
Amount: ${{ transaction.amount|intcomma }}
Premium: {% if transaction.premium %}{{ transaction.premium.description }}{% else %}None requested{% endif %}
{% else %}You have modified a pledge that you had previously made to the campaign to unglue {{ transaction.campaign.work.title }}.
Your new pledge summary
Amount pledged: ${{ transaction.amount|intcomma }}
Premium: {% if transaction.premium %}{{ transaction.premium.description }}{% else %}None requested{% endif %}
Premium: {% if transaction.premium %}{{ transaction.premium.description }}{% else %}None requested{% endif %}{% endif %}
{% if transaction.host|lower == 'amazon' %}{% if status == 'increased' %}
{% if transaction.host|lower == 'amazon' %}{% if up_or_down == 'increased' %}
You will also receive an email from Amazon confirming this.
{% else %}{% if status == 'decreased' %}
{% else %}{% if up_or_down == 'decreased' %}
Your Amazon Payments account may still show an authorization to Unglue.it for the entire amount of your earlier pledge, but never fear -- if the campaign succeeds, we'll only charge you ${{ transaction.amount|intcomma }}.
{% endif %}{% endif %}{% else %}{% endif %}

View File

@ -6,15 +6,19 @@
<a href="{% url work transaction.campaign.work.id %}"><img src="{{ transaction.campaign.work.cover_image_small }}" alt="cover image for {{ title }}" /></a>
</div>
<div class="comments_graphical">
Your pledge for the campaign to unglue {{ title }} has been modified.
Your pledge for the campaign to unglue {{ title }} has been {% if up_or_down == 'canceled'%}canceled{% else %}modified{% endif %}.
</div>
</div>
<div class="comments_textual">
{% if up_or_down == 'canceled' %}<div class="comments_textual">
Your canceled pledge was as follows:<br />
Amount: ${{ transaction.amount|intcomma }}<br />
Premium: {% if transaction.premium %}{{ transaction.premium.description }}{% else %}None requested{% endif %}<br />
</div>{% else %}<div class="comments_textual">
Your new pledge is as follows:<br />
Amount: ${{ transaction.amount|intcomma }}<br />
Premium: {% if transaction.premium %}{{ transaction.premium.description }}{% else %}None requested{% endif %}<br />
Thank you for your continued support of {{ title }}.
</div>
</div>{% endif %}
</div>
{% endwith %}

View File

@ -1 +1 @@
Your pledge has been modified for {{ transaction.campaign.work.title}}
Your pledge has been {% if up_or_down == 'canceled'%}canceled{% else %}modified{% endif %} for {{ transaction.campaign.work.title}}

View File

@ -1,6 +1,4 @@
We thought you might like to know there's a new comment for a book on your wishlist.
{{ comment.user.username }} has commented on {{ comment.content_object.title }}:
{{ comment.user.username }} has commented on a book on your wishlist, {{ comment.content_object.title }}, as follows.
{{ comment.comment }}

View File

@ -1,6 +1,4 @@
We thought you might like to know that Unglue.it staff or the book's rights holder have news about a book on your wishlist.
{{ comment.user.username }} has commented on {{ comment.content_object.title }}:
{{ comment.user.username }} has official news about a book on your wishlist, {{ comment.content_object.title }}.
{{ comment.comment }}

View File

@ -26,6 +26,10 @@
<a id="latest"></a><h2>Latest Press</h2>
<div class="pressarticles">
<div>
<a href="hhttp://mashable.com/2012/06/14/unglueit/">Unglue.it Wants to Make a Creative Commons for Ebooks</a><br />
Mashable - June 14, 2012
</div>
<div>
<a href="http://www.huffingtonpost.com/2012/05/21/unglueit-free-ebooks-crowdfunding_n_1532644.html">Unglue.it Makes Free EBooks Through A Unique Crowdfunding Website</a><br />
Huffington Post - May 21, 2012
@ -34,10 +38,6 @@
<a href="http://boingboing.net/2012/05/18/raising-money-to-free-classic.html">Raising money to free classic volume on Africa's oral literature</a><br />
Boing Boing - May 18, 2012<br />
</div>
<div>
<a href="http://lj.libraryjournal.com/2012/05/publishing/ebook-crowdfunding-platform-unglue-it-launched/">Ebook Crowdfunding Platform Unglue.it Launched</a><br />
Library Journal - May 17, 2012
</div>
</div>
<a id="overview"></a><h2>Overview</h2>
@ -73,6 +73,14 @@ Creative Commons offers a variety of other licenses, many of them with even less
</dl>
<a id="press"></a><h2>Press Coverage</h2>
<div class="pressarticles">
<div>
<a href="hhttp://mashable.com/2012/06/14/unglueit/">Unglue.it Wants to Make a Creative Commons for Ebooks</a><br />
Mashable - June 14, 2012
</div>
<div>
<a href="http://goodereader.com/blog/electronic-readers/buzzwords-from-bookexpo/">Buzzwords from BookExpo</a><br />
Good EReader - June 07, 2012
</div>
<div>
<a href="http://www.huffingtonpost.com/2012/05/21/unglueit-free-ebooks-crowdfunding_n_1532644.html">Unglue.it Makes Free EBooks Through A Unique Crowdfunding Website</a><br />
Huffington Post - May 21, 2012
@ -254,6 +262,11 @@ Creative Commons offers a variety of other licenses, many of them with even less
</div>
<a id="video"></a><h2>Video</h2>
<div class="pressvideos">
<div>
<iframe width="480" height="270" src="https://www.youtube-nocookie.com/embed/HxjTW4OBouo?rel=0" frameborder="0" allowfullscreen></iframe><br />
<I>June 2012</I><br />
Eric Hellman at Book Expo America.
</div>
<div>
<div class="mediaborder">
<object width="480" height="270"><param name="allowfullscreen" value="true" /><param name="allowscriptaccess" value="always" /><param name="movie" value="https://secure.vimeo.com/moogaloop.swf?clip_id=39352026&amp;server=secure.vimeo.com&amp;show_title=1&amp;show_byline=0&amp;show_portrait=0&amp;color=00adef&amp;fullscreen=1&amp;autoplay=0&amp;loop=0" /><embed src="https://secure.vimeo.com/moogaloop.swf?clip_id=39352026&amp;server=secure.vimeo.com&amp;show_title=1&amp;show_byline=0&amp;show_portrait=0&amp;color=00adef&amp;fullscreen=1&amp;autoplay=0&amp;loop=0" type="application/x-shockwave-flash" allowfullscreen="true" allowscriptaccess="always" width="480" height="270"></embed></object></div><br />

View File

@ -139,13 +139,23 @@ def work(request, work_id, action='display'):
countdown = ""
if work.last_campaign_status() == 'ACTIVE':
from math import ceil
time_remaining = campaign.deadline - now()
'''
we want to round up on all of these; if it's the 3rd and the
campaign ends the 8th, users expect to see 5 days remaining,
not 4 (as an artifact of 4 days 11 hours or whatever)
time_remaining.whatever is an int, so just adding 1 will do
that for us (except in the case where .days exists and both other
fields are 0, which is unlikely enough I'm not defending against it)
'''
if time_remaining.days:
countdown = "in %s days" % time_remaining.days
countdown = "in %s days" % str(time_remaining.days + 1)
elif time_remaining.seconds > 3600:
countdown = "in %s hours" % time_remaining.seconds/3600
countdown = "in %s hours" % str(time_remaining.seconds/3600 + 1)
elif time_remaining.seconds > 60:
countdown = "in %s minutes" % time_remaining.seconds/60
countdown = "in %s minutes" % str(time_remaining.seconds/60 + 1)
else:
countdown = "right now"
if action == 'preview':
@ -772,8 +782,6 @@ class PledgeModifyView(FormView):
elif status and url is None:
# let's use the pledge_complete template for now and maybe look into customizing it.
return HttpResponseRedirect("{0}?tid={1}".format(reverse('pledge_complete'), transaction.id))
from regluit.payment.signals import pledge_modified
pledge_modified.send(sender=self, transaction=transaction, status="increased")
else:
return HttpResponse("No modification made")
@ -948,6 +956,11 @@ class PledgeCancelView(FormView):
# We might want to remove this in a production system
if settings.DEBUG:
update_status = p.update_preapproval(transaction)
# send a notice out that the transaction has been canceled -- leverage the pledge_modify notice for now
# BUGBUG: should have a pledge cancel notice actually since I think it's different
from regluit.payment.signals import pledge_modified
pledge_modified.send(sender=self, transaction=transaction, up_or_down="canceled")
logger.info("pledge_modified notice for cancellation: sender {0}, transaction {1}".format(self, transaction))
return HttpResponseRedirect(reverse('work', kwargs={'work_id': campaign.work.id}))
else:
logger.error("Attempt to cancel transaction id {0} failed".format(transaction.id))

View File

@ -50,7 +50,6 @@ AMAZON_OPERATION_TYPE_REFUND = 'REFUND'
AMAZON_OPERATION_TYPE_CANCEL = 'CANCEL'
# load FPS_ACCESS_KEY and FPS_SECRET_KEY from the database if possible
try:
from regluit.core.models import Key
FPS_ACCESS_KEY = Key.objects.get(name="FPS_ACCESS_KEY").value
@ -210,6 +209,7 @@ def amazonPaymentReturn(request):
approves a preapproval or a pledge. This URL is set via the PAY api.
'''
try:
transaction = None
# pick up all get and post parameters and display
output = "payment complete"
@ -279,7 +279,7 @@ def amazonPaymentReturn(request):
# We may never see an IPN, set the status here
logging.error("Amazon payment authorization failed: ")
logging.error(request.GET)
transaction.status = AMAZON_STATUS_FAILURE
transaction.status = TRANSACTION_STATUS_ERROR
elif transaction.type == PAYMENT_TYPE_AUTHORIZATION:
@ -295,6 +295,14 @@ def amazonPaymentReturn(request):
transaction.status = TRANSACTION_STATUS_ACTIVE
transaction.approved = True
transaction.pay_key = token
transaction.save()
print "Calling CANCEL RELATED"
# clear out any other active transactions for this user and this campaign
from regluit.payment.manager import PaymentManager
p = PaymentManager()
p.cancel_related_transaction(transaction, status=TRANSACTION_STATUS_ACTIVE, campaign=transaction.campaign)
else:
# We may never see an IPN, set the status here
@ -307,24 +315,48 @@ def amazonPaymentReturn(request):
info = str(request.GET),
status = status,
transaction=transaction)
else:
# Corrupt transaciton, unknown type
transaction.status = TRANSACTION_STATUS_ERROR
transaction.save()
# Redirect to our pledge success URL
return_path = "{0}?{1}".format(reverse('pledge_complete'),
urllib.urlencode({'tid':transaction.id}))
return_url = urlparse.urljoin(settings.BASE_URL, return_path)
return HttpResponseRedirect(return_url)
if transaction.status == TRANSACTION_STATUS_ERROR:
# We failed, redirect to a page to allow the user to try again
return_path = "{0}?{1}".format(reverse('pledge_nevermind'),
urllib.urlencode({'tid':transaction.id}))
return_url = urlparse.urljoin(settings.BASE_URL, return_path)
return HttpResponseRedirect(return_url)
else:
# Not a failure, exact status will be updated by IPN
# Redirect to our pledge success URL
return_path = "{0}?{1}".format(reverse('pledge_complete'),
urllib.urlencode({'tid':transaction.id}))
return_url = urlparse.urljoin(settings.BASE_URL, return_path)
return HttpResponseRedirect(return_url)
except Exception, e:
logging.error("Amazon co-branded return-url FAILED with exception:")
traceback.print_exc()
# BUGBUG: check to see whether status is AMAZON_STATUS_ADBANDONED
# if so, ultimately figure out the campaign whose transaction is being canceled out.
# for the moment, return the user to BASE_URL
if transaction:
if request.REQUEST.get("status") == AMAZON_STATUS_ADBANDONED:
# We failed, redirect to a page to allow the user to try again
return_path = "{0}?{1}".format(reverse('pledge_nevermind'),
urllib.urlencode({'tid':transaction.id}))
return_url = urlparse.urljoin(settings.BASE_URL, return_path)
return HttpResponseRedirect(return_url)
else:
#
# If we are here, amazon did not give us a caller reference, so we don't know what transaction was cancelled.
# Some kind of cleanup is required. This can happen if there is an error in the amazon API or if the user clicks
# the cancel button. If the case where the user closes the co-branded window or hits the back button, we will never arrive here.
#
return HttpResponseRedirect(settings.BASE_URL)
class AmazonRequest:

View File

@ -194,19 +194,39 @@ class PaymentManager( object ):
logger.info(preapproval_transactions)
transactions = payment_transactions | preapproval_transactions
for t in transactions:
# deal with preapprovals
if t.date_payment is None:
preapproval_status = self.update_preapproval(t)
logger.info("transaction: {0}, preapproval_status: {1}".format(t, preapproval_status))
if not set(['status', 'currency', 'amount', 'approved']).isdisjoint(set(preapproval_status.keys())):
status["preapprovals"].append(preapproval_status)
# update payments
else:
payment_status = self.update_payment(t)
if not set(["status", "receivers"]).isdisjoint(payment_status.keys()):
status["payments"].append(payment_status)
# Clear out older, duplicate preapproval transactions
cleared_list = []
for p in transactions:
# pick out only the preapprovals
if p.date_payment is None and p.type == PAYMENT_TYPE_AUTHORIZATION and p.status == TRANSACTION_STATUS_ACTIVE and p not in cleared_list:
# keep only the newest transaction for this user and campaign
user_transactions_for_campaign = Transaction.objects.filter(user=p.user, status=TRANSACTION_STATUS_ACTIVE, campaign=p.campaign).order_by('-date_authorized')
if len(user_transactions_for_campaign) > 1:
logger.info("Found %d active transactions for campaign" % len(user_transactions_for_campaign))
self.cancel_related_transaction(user_transactions_for_campaign[0], status=TRANSACTION_STATUS_ACTIVE, campaign=transactions[0].campaign)
cleared_list.extend(user_transactions_for_campaign)
# Note, we may need to call checkstatus again here
return status
@ -629,10 +649,14 @@ class PaymentManager( object ):
# that for whatever reason fail. will need other housekeeping to handle those.
# sadly this point is not yet late enough in the process -- needs to be moved
# until after we are certain.
if modification:
pledge_modified.send(sender=self, transaction=t, up_or_down="increased")
else:
if not modification:
# BUGBUG:
# send the notice here for now
# this is actually premature since we're only about to send the user off to the payment system to
# authorize a charge
pledge_created.send(sender=self, transaction=t)
return t, url
@ -642,6 +666,48 @@ class PaymentManager( object ):
logger.info("Authorize Error: " + p.error_string())
return t, None
def cancel_related_transaction(self, transaction, status=TRANSACTION_STATUS_ACTIVE, campaign=None):
'''
Cancels any other similar status transactions for the same campaign. Used with modify code
Returns the number of transactions successfully canceled
'''
related_transactions = Transaction.objects.filter(status=status, user=transaction.user)
if len(related_transactions) == 0:
return 0
if campaign:
related_transactions = related_transactions.filter(campaign=campaign)
canceled = 0
for t in related_transactions:
if t.id == transaction.id:
# keep our transaction
continue
if self.cancel_transaction(t):
canceled = canceled + 1
# send notice about modification of transaction
if transaction.amount > t.amount:
# this should be the only one that happens
up_or_down = "increased"
elif transaction.amount < t.amount:
# we shouldn't expect any case in which this happens
up_or_down = "decreased"
else:
# we shouldn't expect any case in which this happens
up_or_down = None
pledge_modified.send(sender=self, transaction=transaction, up_or_down=up_or_down)
else:
logger.error("Failed to cancel transaction {0} for related transaction {1} ".format(t, transaction))
return canceled
def modify_transaction(self, transaction, amount, expiry=None, anonymous=None, premium=None,
return_url=None, nevermind_url=None,
paymentReason=None):
@ -701,10 +767,15 @@ class PaymentManager( object ):
if t and url:
# Need to re-direct to approve the transaction
logger.info("New authorization needed, redirection to url %s" % url)
self.cancel_transaction(transaction)
# Do not cancel the transaction here, wait until we get confirmation that the transaction is complete
# then cancel all other active transactions for this campaign
#self.cancel_transaction(transaction)
# while it would seem to make sense to send a pledge notification change here
# if we do, we will also send notifications when we initiate but do not
# successfully complete a pledge modification
return True, url
else:
# a problem in authorize

View File

@ -316,9 +316,6 @@ ul.navigation li.active a {
.listview.icons .booklist-status-label {
display: none;
}
.listview.icons .boolist-ebook span {
padding-bottom: 6px;
}
div#content-block-content {
padding-bottom: 100px;
}

View File

@ -251,12 +251,6 @@ ul.navigation li a:hover, ul.navigation li.active a {
.booklist-status-label, {
display: none;
}
.boolist-ebook {
span {
padding-bottom: 6px;
}
}
}
div#content-block-content {