pull/1/head
eric 2011-09-29 19:32:23 -04:00
commit 1fe7bf3dc9
21 changed files with 696 additions and 128 deletions

View File

@ -9,7 +9,7 @@ from tastypie import fields
from tastypie.constants import ALL, ALL_WITH_RELATIONS
from tastypie.resources import ModelResource, Resource, Bundle
from tastypie.utils import trailing_slash
from tastypie.authentication import ApiKeyAuthentication
from tastypie.authentication import ApiKeyAuthentication, Authentication
from regluit.core import models
@ -17,6 +17,7 @@ from regluit.core import models
logger = logging.getLogger(__name__)
class UserResource(ModelResource):
class Meta:
authentication = ApiKeyAuthentication()
@ -97,6 +98,7 @@ class EditionCoverResource(ModelResource):
class WishlistResource(ModelResource):
user = fields.ToOneField(UserResource, 'user')
works = fields.ToManyField(WorkResource, 'works')
class Meta:
authentication = ApiKeyAuthentication()
queryset = models.Wishlist.objects.all()

View File

@ -1,4 +1,5 @@
from django.db import models
from django.db.models import Q
from django.contrib.auth.models import User
@ -15,16 +16,22 @@ class Campaign(models.Model):
def __unicode__(self):
return u"Campaign for %s" % self.work.title
def cover_image_small(self):
first_isbn = self.work.editions.all()[0].isbn_10
return "http://covers.openlibrary.org/b/isbn/%s-S.jpg" % first_isbn
class Work(models.Model):
created = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=1000)
openlibrary_id = models.CharField(max_length=50, null=True)
@classmethod
def get_by_isbn(klass, isbn):
for w in Work.objects.filter(Q(editions__isbn_10=isbn) | Q(editions__isbn_13=isbn)):
return w
return None
def cover_image_small(self):
first_isbn = self.editions.all()[0].isbn_10
return "http://covers.openlibrary.org/b/isbn/%s-S.jpg" % first_isbn
def __unicode__(self):
return self.title
@ -62,6 +69,11 @@ class Edition(models.Model):
def __unicode__(self):
return self.title
@classmethod
def get_by_isbn(klass, isbn):
for e in Edition.objects.filter(Q(isbn_10=isbn) | Q(isbn_13=isbn)):
return e
return None
class EditionCover(models.Model):
openlibrary_id = models.IntegerField()

49
core/search.py Normal file
View File

@ -0,0 +1,49 @@
import json
import requests
def gluejar_search(q):
"""normalizes results from the google books search suitable for gluejar
"""
results = []
for item in googlebooks_search(q)['items']:
# TODO: better to think in terms of editions with titles
# instead of titles with names?
v = item['volumeInfo']
r = {'title': v.get('title', ""),
'description': v.get('description', ""),
'publisher': v.get('publisher', ""),
'google_id': item.get('selfLink')}
# TODO: allow multiple authors
if v.has_key('authors') and len(v['authors']) > 0:
r['author'] = v['authors'][0]
else:
r['author'] = ""
r['isbn_10'] = None
r['isbn_13'] = None
# pull out isbns
for i in v.get('industryIdentifiers', []):
if i['type'] == 'ISBN_13':
r['isbn_13'] = i['identifier']
if i['type'] == 'ISBN_10':
r['isbn_10'] = i['identifier']
# cover image
if v.has_key('imageLinks'):
r['image'] = v['imageLinks'].get('smallThumbnail', "")
else:
r['image'] = ""
results.append(r)
return results
def googlebooks_search(q):
# XXX: need to pass IP address of user in from the frontend
headers = {'X-Forwarded-For': '69.243.24.29'}
r = requests.get('https://www.googleapis.com/books/v1/volumes',
params={'q': q}, headers=headers)
return json.loads(r.content)

View File

@ -1,6 +1,6 @@
from django.test import TestCase
from regluit.core import bookloader, models
from regluit.core import bookloader, models, search
class TestBooks(TestCase):
@ -41,3 +41,21 @@ class TestBooks(TestCase):
self.assertEqual(models.Work.objects.all().count(), 1)
self.assertEqual(models.Subject.objects.all().count(), 18)
class SearchTests(TestCase):
def test_basic_search(self):
results = search.gluejar_search('melville')
self.assertEqual(len(results), 10)
r = results[0]
self.assertTrue(r.has_key('title'))
self.assertTrue(r.has_key('author'))
self.assertTrue(r.has_key('description'))
self.assertTrue(r.has_key('image'))
self.assertTrue(r.has_key('publisher'))
self.assertTrue(r.has_key('isbn_10'))
def test_googlebooks_search(self):
response = search.googlebooks_search('melville')
self.assertEqual(len(response['items']), 10)

View File

@ -10,6 +10,7 @@
<link type="text/css" rel="stylesheet" href="/static/css/book_list.css" />
<script type="text/javascript" src="/static/js/jquery-1.6.3.min.js"></script>
<script type="text/javascript" src="/static/js/book-panel.js"></script>
{% block extra_head %}{% endblock %}
</head>
<body>

View File

@ -1,25 +0,0 @@
<div class="book-list row1">
<div class="book-thumb">
<a href="#"><img src="{{ campaign.cover_image_small }}" alt="Book name" title="book name" /></a>
</div>
<div class="book-name">
<span>
{{ campaign.work.title }}
</span>
</div>
<div class="add-wishlist">
<a href="#">Add to Wishlist</a>
</div>
<div class="booklist-status">
<span class="booklist-status-text">Status: In Progress</span>
<span class="booklist-status-img">
<img src="/static/images/booklist/icon1.png" title="book list status" alt="book list status" />
</span>
</div>
<div class="unglue-this none">
<div class="unglue-this-inner1">
<div class="unglue-this-inner2">
</div>
</div>
</div>
</div>

View File

@ -1,28 +1,37 @@
<div id="js-leftcol">
<div class="jsmodule">
<h3 class="jsmod-title"><span>Explore</span></h3>
<div id="js-leftcol">
<div class="jsmodule">
<h3 class="jsmod-title"><span>Explore</span></h3>
<div class="jsmod-content">
<ul class="menu level1">
<li class="first parent">
<a href="#"><span>Show me...</span></a>
<ul class="menu level2">
<li class="first"><a href="#"><span>Recommended</span></a></li>
<li><a href="#"><span>Popular</span></a></li>
<li><a href="#"><span>Almost unglued</span></a></li>
<li><a href="#"><span>Recently unglued</span></a></li>
<li><a href="#"><span>Ending Soon</span></a></li>
<li class="last"><a href="#"><span>Just Listed</span></a></li>
<ul class="menu level1">
<li>
<div class="js-search" style="margin-top: 10px; margin-bottom: 10px;">
<form action="{% url search %}" method="get"><input type="text" placeholder="Search for a book..." size="20" class="inputbox" id="ssearchword" name="q" value="{{ q }}">
</form>
<input type="button" onclick="this.form.searchword.focus();" class="button" value="Search">
</div>
</li>
<li class="first parent">
<a href="#"><span>Show me...</span></a>
<ul class="menu level2">
<li class="first"><a href="#"><span>Recommended</span></a></li>
<li><a href="#"><span>Popular</span></a></li>
<li><a href="#"><span>Almost unglued</span></a></li>
<li><a href="#"><span>Recently unglued</span></a></li>
<li><a href="#"><span>Ending Soon</span></a></li>
<li class="last"><a href="#"><span>Just Listed</span></a></li>
</ul>
</li>
<li class="parent">
<a href="#"><span>Show me...</span></a>
<ul class="menu level2">
<li class="first"><a href="#"><span>Recommended</span></a></li>
<li><a href="#"><span>Popular</span></a></li>
<li><a href="#"><span>Almost unglued</span></a></li>
<li><a href="#"><span>Recently unglued</span></a></li>
<li><a href="#"><span>Ending Soon</span></a></li>
<li class="last"><a href="#"><span>Just Listed</span></a></li>
<a href="#"><span>Show me...</span></a>
<ul class="menu level2">
<li class="first"><a href="#"><span>Recommended</span></a></li>
<li><a href="#"><span>Popular</span></a></li>
<li><a href="#"><span>Almost unglued</span></a></li>
<li><a href="#"><span>Recently unglued</span></a></li>
<li><a href="#"><span>Ending Soon</span></a></li>
<li class="last"><a href="#"><span>Just Listed</span></a></li>
</ul>
</li>
</ul>

View File

@ -11,8 +11,7 @@
<div class="js-search">
<h2>What book would you give to the world?</h2>
<div class="js-search-inner">
<form action="">
<input type="text" onfocus="if (this.value=='Search for a book...') this.value='';" onblur="if (this.value=='') this.value='Search for a book...';" value="Search for a book..." size="30" class="inputbox" maxlength="200" id="ssearchword" name="searchword">
<form action="{% url search %}" method="get"> <input type="text" onfocus="if (this.value=='Search for a book...') this.value='';" onblur="if (this.value=='') this.value='Search for a book...';" value="Search for a book..." size="30" class="inputbox" maxlength="200" id="ssearchword" name="q">
<input type="button" onclick="this.form.searchword.focus();" class="button" value="Search">
</form>
</div>
@ -22,4 +21,4 @@
{% endblock %}

View File

@ -0,0 +1,95 @@
{% extends "base.html" %}
{% block extra_head %}
<script type="text/javascript">
$(document).ready(function() {
$(".add-wishlist").each(function (index, element) {
$(element).click(function() {
var span = $(element).find("span");
var isbn = span.attr('id')
if (!isbn) return;
$.post('/wishlist/', {'isbn': isbn}, function(data) {
span.fadeOut();
var newSpan = $("<span>On Your Wishlist!</span>").hide();
span.replaceWith(newSpan);
newSpan.fadeIn();
newSpan.removeAttr("id");
});
});
});
});
</script>
{% endblock %}
{% block title %}Search Results{% endblock %}
{% block content %}
<div id="main-container">
<div class="js-main">
{% include "explore.html" %}
<div id="js-maincol-fr">
<div class="js-maincol-inner">
<div class="content-block">
<div class="content-block-heading">
<h2 class="content-heading">Search Results</span></h2>
<ul class="book-list-view">
<li>View As:</li>
<li class="view-list">
<a href="#view-list">
<img src="/static/images/booklist/view-list.png" align="view list" title="view list" height="21" width="24" />
</a>
</li>
<li class="view-list">
<a href="#view-icon">
<img src="/static/images/booklist/view-icon.png" align="view icon" title="view icon" height="22" width="22" />
</a>
</li>
<li class="view-list">
<a href="#view-icon-small">
<img src="/static/images/booklist/view-small-icon.png" align="view icon small" title="view icon small" height="22" width="22" />
</a>
</li>
</ul>
</div>
<div class="content-block-content">
{% for result in results %}
<div class="book-list row1">
<div class="book-thumb">
<a href="#"><img src="{{ result.image }}" alt="{{ result.title }}" title="{{ result.title }}" /></a>
</div>
<div class="book-name">
<span>
{{ result.title }}
</span>
</div>
<div class="add-wishlist">
{% if result.on_wishlist %}
<span>On Your Wishlist!</span>
{% else %}
<span id="{{ result.isbn_10 }}">Add to Wishlist</span>
{% endif %}
</div>
<div class="booklist-status">
<span class="booklist-status-text">Status: In Progress</span>
<span class="booklist-status-img">
<img src="/static/images/booklist/icon1.png" title="book list status" alt="book list status" />
</span>
</div>
<div class="unglue-this none">
<div class="unglue-this-inner1">
<div class="unglue-this-inner2">
</div>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -2,6 +2,23 @@
{% block title %} &#8212; {{ supporter.username }}{% endblock %}
{% block extra_head %}
<script type="text/javascript">
$(document).ready(function() {
$(".remove-wishlist").each(function (index, element) {
$(element).click(function() {
var span = $(element).find("span");
var work_id = span.attr('id')
$.post('/wishlist/', {'remove_work_id': work_id}, function(data) {
var book = $(element).parent();
book.fadeOut();
});
});
});
});
</script>
{% endblock %}
{% block content %}
<div id="main-container">
<div class="js-main">
@ -10,20 +27,26 @@
<div id="js-maincol-fr">
<div class="js-maincol-inner">
<div class="content-block">
<div class="content-block-heading">
<h2 class="content-heading">Wishlist for {{ supporter.username }}</h2>
<ul class="book-list-view">
<li>View As:</li>
<div class="content-block">
<div class="content-block-heading">
<h2 class="content-heading">
{% ifequal supporter request.user %}
Your Wishlist
{% else %}
{{ supporter.username }} Wishlist
{% endifequal %}
</h2>
<ul class="book-list-view">
<li>View As:</li>
<li class="view-list">
<a href="#view-list">
<img src="/static/images/booklist/view-list.png" align="view list" title="view list" height="21" width="24" />
</a>
<a href="#view-list">
<img src="/static/images/booklist/view-list.png" align="view list" title="view list" height="21" width="24" />
</a>
</li>
<li class="view-list">
<a href="#view-icon">
<img src="/static/images/booklist/view-icon.png" align="view icon" title="view icon" height="22" width="22" />
</a>
<a href="#view-icon">
<img src="/static/images/booklist/view-icon.png" align="view icon" title="view icon" height="22" width="22" />
</a>
</li>
<li class="view-list">
<a href="#view-icon-small">
@ -33,12 +56,35 @@
</ul>
</div>
<div class="content-block-content">
{% for campaign in campaigns %}
{% include "book_list.html" %}
{% endfor %}
{% for work in wishlist.works.all %}
<div class="book-list row1">
<div class="book-thumb">
<a href="#"><img src="{{ work.cover_image_small }}" alt="Book name" title="book name" /></a>
</div>
<div class="book-name">
<span>
{{ work.title }}
</span>
</div>
<div class="remove-wishlist">
<span id="{{ work.id }}">Remove from Wishlist</span>
</div>
<div class="booklist-status">
<span class="booklist-status-text">Status: In Progress</span>
<span class="booklist-status-img">
<img src="/static/images/booklist/icon1.png" title="book list status" alt="book list status" />
</span>
</div>
<div class="unglue-this none">
<div class="unglue-this-inner1">
<div class="unglue-this-inner2">
</div>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -1,8 +1,13 @@
from django.conf.urls.defaults import *
from django.views.generic.simple import direct_to_template
from django.views.generic.base import TemplateView
urlpatterns = patterns(
"regluit.frontend.views",
url(r"^$", "home", name="home"),
url(r"^supporter/(?P<supporter_username>.+)/$", "supporter", name="supporter"),
url(r"^privacy$", "textpage", {'page': 'privacy'}, name="privacy"),
url(r"^search/$", "search", name="search"),
url(r"^privacy/$", TemplateView.as_view(template_name="privacy.html"),
name="privacy"),
url(r"^wishlist/$", "wishlist", name="wishlist"),
)

View File

@ -2,30 +2,70 @@ from django.template import RequestContext
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect
from django.shortcuts import render_to_response, get_object_or_404
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
from django.contrib.auth.decorators import login_required
from django.shortcuts import render, render_to_response, get_object_or_404
from regluit.core import models
from regluit.core import models, bookloader
from regluit.core.search import gluejar_search
def home(request):
# if the user is logged in send them to their supporter page
if request.user.is_authenticated():
return HttpResponseRedirect(reverse('supporter',
args=[request.user.username]))
return render_to_response('home.html',
{},
context_instance=RequestContext(request)
)
return render(request, 'home.html')
def supporter(request, supporter_username):
supporter = get_object_or_404(User, username=supporter_username)
campaigns = models.Campaign.objects.all()
return render_to_response('supporter.html',
{"supporter": supporter, "campaigns": campaigns},
context_instance=RequestContext(request)
)
wishlist = supporter.wishlist
context = {
"supporter": supporter,
"wishlist": wishlist,
}
return render(request, 'supporter.html', context)
def textpage(request, page):
return render_to_response(page + '.html',
{},
context_instance=RequestContext(request)
)
def search(request):
q = request.GET.get('q', None)
results = gluejar_search(q)
# flag search result as on wishlist
# TODO: make this better and faster
if request.user:
for result in results:
if not result.has_key('isbn_10'):
continue
work = models.Work.get_by_isbn(result['isbn_10'])
if work and work in request.user.wishlist.works.all():
result['on_wishlist'] = True
else:
result['on_wishlist'] = False
context = {
"q": q,
"results": results,
}
return render(request, 'search.html', context)
# TODO: perhaps this functionality belongs in the API?
@csrf_exempt
@require_POST
@login_required
def wishlist(request):
isbn = request.POST.get('isbn', None)
remove_work_id = request.POST.get('remove_work_id', None)
if isbn:
edition = models.Edition.get_by_isbn(isbn)
if not edition:
print "loading book"
edition = bookloader.add_book(isbn)
if edition:
print "adding edition"
request.user.wishlist.works.add(edition.work)
# TODO: redirect to work page, when it exists
return HttpResponseRedirect('/')
elif remove_work_id:
work = models.Work.objects.get(id=int(remove_work_id))
request.user.wishlist.works.remove(work)
# TODO: where to redirect?
return HttpResponseRedirect('/')

View File

@ -2,13 +2,21 @@ from regluit.core.models import Campaign, Wishlist
from regluit.payment.models import Transaction, Receiver
from django.contrib.auth.models import User
from regluit.payment.parameters import *
from regluit.payment.paypal import Pay, IPN, IPN_TYPE_PAYMENT
from regluit.payment.paypal import Pay, IPN, IPN_TYPE_PAYMENT, IPN_TYPE_PREAPPROVAL, Preapproval
import uuid
import traceback
class PaymentManager( object ):
'''
processIPN
Turns a request from Paypal into an IPN, and extracts info. We support 2 types of IPNs:
1) Payment - Used for instant payments and to execute pre-approved payments
2) Preapproval - Used for comfirmation of a preapproval
'''
def processIPN(self, request):
try:
@ -16,11 +24,16 @@ class PaymentManager( object ):
if ipn.success():
print "Valid IPN"
t = Transaction.objects.get(reference=ipn.key)
if ipn.transaction_type == IPN_TYPE_PAYMENT:
if ipn.preapproval_key:
key = ipn.preapproval_key
else:
key = ipn.key
t = Transaction.objects.get(reference=key)
t.status = ipn.status
for item in ipn.transactions:
@ -29,16 +42,36 @@ class PaymentManager( object ):
r.status = item['status_for_sender_txn']
r.save()
t.save()
elif ipn.transaction_type == IPN_TYPE_PREAPPROVAL:
t = Transaction.objects.get(reference=ipn.preapproval_key)
t.status = ipn.status
t.save()
else:
print "IPN: Unknown Transaction Type: " + ipn.transaction_type
t.save()
else:
print ipn.error
except:
traceback.print_exc()
'''
query_campaign
Returns either an amount or list of transactions for a campaign
summary: if true, return a float of the total, if false, return a list of transactions
pledged: include amounts pledged
authorized: include amounts pre-authorized
return value: either a float summary or a list of transactions
'''
def query_campaign(self, campaign, summary=False, pledged=True, authorized=True):
if pledged:
@ -51,7 +84,7 @@ class PaymentManager( object ):
if authorized:
authorized_list = Transaction.objects.filter(campaign=campaign,
type=PAYMENT_TYPE_AUTHORIZATION,
status="PENDING")
status="ACTIVE")
else:
authorized_list = []
@ -71,10 +104,131 @@ class PaymentManager( object ):
else:
return pledged_list | authorized_list
'''
execute_campaign
attempts to execute all pending transactions for a campaign.
return value: returns a list of transactions with the status of each receiver/transaction updated
'''
def execute_campaign(self, campaign):
transactions = Transaction.objects.filter(campaign=campaign, status="ACTIVE")
for t in transactions:
receiver_list = [{'email':'jakace_1309677337_biz@gmail.com', 'amount':t.amount * 0.80},
{'email':'boogus@gmail.com', 'amount':t.amount * 0.20}]
self.execute_transaction(t, receiver_list)
return transactions
'''
execute_transaction
executes a single pending transaction.
transaction: the transaction object to execute
receiver_list: a list of receivers for the transaction, in this format:
[
{'email':'email-1', 'amount':amount1},
{'email':'email-2', 'amount':amount2}
]
return value: a bool indicating the success or failure of the process. Please check the transaction status
after the IPN has completed for full information
'''
def execute_transaction(self, transaction, receiver_list):
for r in receiver_list:
receiver = Receiver.objects.create(email=r['email'], amount=r['amount'], currency=transaction.currency, status="ACTIVE", transaction=transaction)
p = Pay(transaction, receiver_list)
transaction.status = p.status()
if p.status() == 'COMPLETED':
print "Execute Success"
return True
else:
transaction.error = p.error()
print "Execute Error: " + p.error()
return False
'''
authorize
authorizes a set amount of money to be collected at a later date
currency: a 3-letter paypal currency code, i.e. USD
target: a defined target type, i.e. TARGET_TYPE_CAMPAIGN, TARGET_TYPE_LIST, TARGET_TYPE_NONE
amount: the amount to authorize
campaign: optional campaign object(to be set with TARGET_TYPE_CAMPAIGN)
list: optional list object(to be set with TARGET_TYPE_LIST)
user: optional user object
return value: a tuple of the new transaction object and a re-direct url. If the process fails,
the redirect url will be None
'''
def authorize(self, currency, target, amount, campaign=None, list=None, user=None):
t = Transaction.objects.create(amount=amount,
type=PAYMENT_TYPE_AUTHORIZATION,
target=target,
currency=currency,
secret = str(uuid.uuid1()),
status='NONE',
campaign=campaign,
list=list,
user=user
)
p = Preapproval(t, amount)
if p.status() == 'Success':
t.status = 'CREATED'
t.reference = p.paykey()
t.save()
print "Authorize Success: " + p.next_url()
return t, p.next_url()
else:
t.status = 'ERROR'
t.error = p.error()
t.save()
print "Authorize Error: " + p.error()
return t, None
'''
pledge
Performs an instant payment
currency: a 3-letter paypal currency code, i.e. USD
target: a defined target type, i.e. TARGET_TYPE_CAMPAIGN, TARGET_TYPE_LIST, TARGET_TYPE_NONE
receiver_list: a list of receivers for the transaction, in this format:
[
{'email':'email-1', 'amount':amount1},
{'email':'email-2', 'amount':amount2}
]
campaign: optional campaign object(to be set with TARGET_TYPE_CAMPAIGN)
list: optional list object(to be set with TARGET_TYPE_LIST)
user: optional user object
return value: a tuple of the new transaction object and a re-direct url. If the process fails,
the redirect url will be None
'''
def pledge(self, currency, target, receiver_list, campaign=None, list=None, user=None):
self.currency = currency
amount = 0.0
for r in receiver_list:
amount += r['amount']
@ -82,7 +236,7 @@ class PaymentManager( object ):
t = Transaction.objects.create(amount=amount,
type=PAYMENT_TYPE_INSTANT,
target=target,
currency=self.currency,
currency=currency,
secret = str(uuid.uuid1()),
status='NONE',
campaign=campaign,
@ -103,9 +257,9 @@ class PaymentManager( object ):
return t, p.next_url()
else:
t.reference = p.error()
t.error = p.error()
t.save()
print "Pledge Error: " + p.error()
print "Pledge Status: " + p.status() + "Error: " + p.error()
return t, None

View File

@ -13,6 +13,7 @@ class Transaction(models.Model):
secret = models.CharField(max_length=64, null=True)
reference = models.CharField(max_length=128, null=True)
receipt = models.CharField(max_length=256, null=True)
error = models.CharField(max_length=256, null=True)
date_created = models.DateTimeField(auto_now_add=True)
date_modified = models.DateTimeField(auto_now=True)
date_payment = models.DateTimeField(null=True)
@ -21,6 +22,9 @@ class Transaction(models.Model):
campaign = models.ForeignKey(Campaign, null=True)
list = models.ForeignKey(Wishlist, null=True)
def __unicode__(self):
return u"-- Transaction:\n \tstatus: %s\n \t amount: %s\n \treference: %s\n \terror: %s\n" % (self.status, str(self.amount), self.reference, self.error)
class Receiver(models.Model):
email = models.CharField(max_length=64)

View File

@ -28,7 +28,7 @@ import decimal
# transaction_type constants
IPN_TYPE_PAYMENT = 'Adaptive Payment PAY'
IPN_TYPE_ADJUSTMENT = 'Adjustment'
IPN_TYPE_PREAPPROVAL = 'Adaptive Payment Preapproval'
IPN_TYPE_PREAPPROVAL = 'Adaptive Payment PREAPPROVAL'
#status constants
IPN_STATUS_CREATED = 'CREATED'
@ -89,16 +89,17 @@ class Pay( object ):
print "Cancel URL: " + cancel_url
data = {
'actionType': 'PAY',
'receiverList': { 'receiver': receiver_list },
'currencyCode': transaction.currency,
'returnUrl': return_url,
'cancelUrl': cancel_url,
'requestEnvelope': { 'errorLanguage': 'en_US' },
'ipnNotificationUrl': BASE_URL + 'paypalipn'
}
data['actionType'] = 'PAY'
data['receiverList'] = { 'receiver': receiver_list }
data['ipnNotificationUrl'] = BASE_URL + 'paypalipn'
if transaction.reference:
data['preapprovalKey'] = transaction.reference
self.raw_request = json.dumps(data)
@ -131,6 +132,66 @@ class Pay( object ):
return '%s?cmd=_ap-payment&paykey=%s' % ( PAYPAL_PAYMENT_HOST, self.response['payKey'] )
class Preapproval( object ):
def __init__( self, transaction, amount ):
headers = {
'X-PAYPAL-SECURITY-USERID':PAYPAL_USERNAME,
'X-PAYPAL-SECURITY-PASSWORD':PAYPAL_PASSWORD,
'X-PAYPAL-SECURITY-SIGNATURE':PAYPAL_SIGNATURE,
'X-PAYPAL-APPLICATION-ID':PAYPAL_APPID,
'X-PAYPAL-REQUEST-DATA-FORMAT':'JSON',
'X-PAYPAL-RESPONSE-DATA-FORMAT':'JSON',
}
return_url = BASE_URL + COMPLETE_URL
cancel_url = BASE_URL + CANCEL_URL
now = datetime.datetime.utcnow()
expiry = now + datetime.timedelta( days=PREAPPROVAL_PERIOD )
data = {
'endingDate': expiry.isoformat(),
'startingDate': now.isoformat(),
'maxTotalAmountOfAllPayments': '%.2f' % transaction.amount,
'currencyCode': transaction.currency,
'returnUrl': return_url,
'cancelUrl': cancel_url,
'requestEnvelope': { 'errorLanguage': 'en_US' },
'ipnNotificationUrl': BASE_URL + 'paypalipn'
}
self.raw_request = json.dumps(data)
self.raw_response = url_request(PAYPAL_ENDPOINT, "/AdaptivePayments/Preapproval", data=self.raw_request, headers=headers ).content()
print "paypal PREAPPROVAL response was: %s" % self.raw_response
self.response = json.loads( self.raw_response )
print self.response
def paykey( self ):
if self.response.has_key( 'preapprovalKey' ):
return self.response['preapprovalKey']
else:
return None
def next_url( self ):
return '%s?cmd=_ap-preapproval&preapprovalkey=%s' % ( PAYPAL_PAYMENT_HOST, self.response['preapprovalKey'] )
def error( self ):
if self.response.has_key('error'):
error = self.response['error']
print error
return error[0]['message']
else:
return None
def status( self ):
if self.response.has_key( 'responseEnvelope' ) and self.response['responseEnvelope'].has_key( 'ack' ):
return self.response['responseEnvelope']['ack']
else:
return None
class IPN( object ):
def __init__( self, request ):
@ -157,8 +218,8 @@ class IPN( object ):
return
# check payment status
if request.POST['status'] != 'COMPLETED':
self.error = 'PayPal status was "%s"' % request.get('status')
if request.POST['status'] != 'COMPLETED' and request.POST['status'] != 'ACTIVE':
self.error = 'PayPal status was "%s"' % request.POST['status']
return
# Process the details
@ -166,6 +227,7 @@ class IPN( object ):
self.sender_email = request.POST.get('sender_email', None)
self.action_type = request.POST.get('action_type', None)
self.key = request.POST.get('pay_key', None)
self.preapproval_key = request.POST.get('preapproval_key', None)
self.transaction_type = request.POST.get('transaction_type', None)
self.process_transactions(request)

View File

@ -3,6 +3,8 @@ from django.conf.urls.defaults import *
urlpatterns = patterns(
"regluit.payment.views",
url(r"^testpledge", "testPledge"),
url(r"^testauthorize", "testAuthorize"),
url(r"^testexecute", "testExecute"),
url(r"^querycampaign", "queryCampaign"),
url(r"^paypalipn", "paypalIPN")
)

View File

@ -8,6 +8,11 @@ from django.http import HttpResponse, HttpRequest, HttpResponseRedirect
from django.views.decorators.csrf import csrf_exempt
import traceback
'''
http://BASE/querycampaign?id=2
Example that returns a summary total for a campaign
'''
def queryCampaign(request):
id = request.GET['id']
@ -20,7 +25,92 @@ def queryCampaign(request):
total = p.query_campaign(campaign, summary=True)
return HttpResponse(str(total))
'''
http://BASE/testexecute?campaign=2
Example that executes a set of transactions that are pre-approved
'''
def testExecute(request):
p = PaymentManager()
if 'campaign' in request.GET.keys():
campaign_id = request.GET['campaign']
campaign = Campaign.objects.get(id=int(campaign_id))
else:
campaign = None
output = ''
if campaign:
result = p.execute_campaign(campaign)
for t in result:
output += str(t)
print str(t)
else:
transactions = Transaction.objects.filter(status='ACTIVE')
for t in transactions:
# Note, set this to 1-5 different receivers with absolute amounts for each
receiver_list = [{'email':'jakace_1309677337_biz@gmail.com', 'amount':t.amount * 0.80},
{'email':'boogus@gmail.com', 'amount':t.amount * 0.20}]
p.execute_transaction(t, receiver_list)
output += str(t)
print str(t)
return HttpResponse(output)
'''
http://BASE/testauthorize?amount=20
Example that initiates a pre-approval for a set amount
'''
def testAuthorize(request):
p = PaymentManager()
if 'campaign' in request.GET.keys():
campaign_id = request.GET['campaign']
else:
campaign_id = None
if 'amount' in request.GET.keys():
amount = float(request.GET['amount'])
else:
return HttpResponse("Error, no amount in request")
# Note, set this to 1-5 different receivers with absolute amounts for each
receiver_list = [{'email':'jakace_1309677337_biz@gmail.com', 'amount':20.00},
{'email':'boogus@gmail.com', 'amount':10.00}]
if campaign_id:
campaign = Campaign.objects.get(id=int(campaign_id))
t, url = p.authorize('USD', TARGET_TYPE_CAMPAIGN, amount, campaign=campaign, list=None, user=None)
else:
t, url = p.authorize('USD', TARGET_TYPE_NONE, amount, campaign=None, list=None, user=None)
if url:
print "testAuthorize: " + url
return HttpResponseRedirect(url)
else:
response = t.reference
print "testAuthorize: Error " + str(t.reference)
return HttpResponse(response)
'''
http://BASE/testpledge?campaign=2
Example that initiates an instant payment for a campaign
'''
def testPledge(request):
p = PaymentManager()

View File

@ -4,7 +4,7 @@ DEBUG = True
TEMPLATE_DEBUG = DEBUG
ADMINS = (
# ('Your Name', 'your_email@domain.com'),
('Ed Summers', 'ehs@pobox.com'),
)
MANAGERS = ADMINS

View File

@ -1,12 +1,12 @@
div.book-list{
clear:both;
display:block;
vertical-align: middle;
height:43px;
line-height:43px;
margin:0 5px 0px 0;
padding:7px 0;
overflow:hidden;
clear:both;
display:block;
vertical-align: middle;
height:43px;
line-height:43px;
margin:0 5px 0px 0;
padding:7px 0;
overflow:hidden;
}
div.book-list.row1{ background:#f6f9f9;}
@ -14,15 +14,16 @@ div.book-list.row2{ background:#fff;}
div.book-list div.book-thumb,
div.book-list div.book-name,
div.book-list div.add-wishlist,
div.book-list div.booklist-status,
div.book-list div.unglue-this{ float: left;}
div.book-list div.add-wishlist,
div.book-list div.remove-wishlist,
div.book-list div.booklist-status,
div.book-list div.unglue-this{ float: left;}
div.book-list div.book-thumb{ margin-right:5px;}
div.book-list div.book-name{ width:260px; margin-right:10px; background:url(../images/booklist/booklist-vline.png) right center no-repeat;}
div.book-list div.book-name span{ display:block; line-height:normal; height:43px; line-height:43px;}
div.book-list div.add-wishlist{ margin-right:10px; padding-right:10px;background:url(../images/booklist/booklist-vline.png) right center no-repeat;}
div.book-list div.add-wishlist a{ font-weight:normal; color:#3d4e53; text-transform: none; background:url(../images/booklist/add-wishlist.png) left center no-repeat; padding-left:20px;}
div.book-list div.add-wishlist, div.remove-wishlist { margin-right: 10px; padding-right: 10px; background:url(../images/booklist/booklist-vline.png) right center no-repeat; cursor: pointer;}
div.book-list div.add-wishlist, div.remove-wishlist span{ font-weight:normal; color:#3d4e53; text-transform: none; background:url(../images/booklist/add-wishlist.png) left center no-repeat; padding-left:20px;}
div.book-list div.booklist-status{ margin-right:7px;}
span.booklist-status-text{ float:left; display:block; padding-right:5px;}
@ -49,3 +50,7 @@ ul.navigation li.arrow-l a{ background:url(../images/booklist/bg.png) 0 -168px n
ul.navigation li.arrow-r a{ background:url(../images/booklist/bg.png) -1px -185px no-repeat;width:10px; height:15px; display:block; text-indent:-10000px;}
.unglue-button { display: block; border: 0;}
.book-thumb img {
height: 50px;
}

View File

@ -44,8 +44,8 @@ body{
.search .button{ background:url(/static/images/bg.png) 100% -144px no-repeat; padding:0; margin:0; width:40px; height:36px; display:block; border:none; text-indent:-10000px; cursor:pointer;}
.first{
color: #6994a3;
font-weight: bold;
color: #6994a3;
font-weight: bold;
}
#leftcol{ float:left; width:235px;}
@ -146,7 +146,7 @@ a{ font-weight:bold; font-size:13px; text-decoration:none; cursor:pointer;}
}
#footer{
border-top: 7px solid #edf3f4;
clear: both;
height:90px;
}
border-top: 7px solid #edf3f4;
clear: both;
height:90px;
}

View File

@ -91,7 +91,7 @@ div.content-block-content .cols3 .column{ width:33.33%; float:left;}
.column-left .item{ margin:0 10px 10px 0;}
.column-center .item{ margin:0 5px 10px 5px;}
.column-right .item{ margin:0 0 10px 10px;}
.column .item{ border:7px solid #edf3f4; padding:10px;}
.column .item{ border:8px solid #edf3f4; padding:10px;}
.book-image{ padding:0 0 10px 0;}
.book-info{ padding:0 0 10px 0; line-height:125%; position:relative;}
.book-info span.book-new{ background:url(/static/images/icon-new.png) 0 0 no-repeat; width:38px; height:36px; display:block; position:absolute;
@ -150,4 +150,4 @@ a{ font-weight:bold; font-size:13px; text-decoration:none; cursor:pointer;}
#footer a{
color:#3d4e53;
}
}