annotate gpp/donations/views.py @ 265:1ba2c6bf6eb7

Closing #98. Animated GIFs were losing their transparency and animated properties when saved as avatars. Reworked the avatar save process to only run the avatar through PIL if it is too big. This preserves the original uploaded file if it is within the desired size settings. This may still mangle big animated gifs. If this becomes a problem, then maybe look into calling the PIL Image.resize() method directly. Moved the PIL image specific functions from bio.forms to a new module: core.image for better reusability in the future.
author Brian Neal <bgneal@gmail.com>
date Fri, 24 Sep 2010 02:12:09 +0000
parents a3b47d0f4df1
children 767cedc7d12a
rev   line source
bgneal@35 1 """
bgneal@35 2 Views for the donations application.
bgneal@35 3 """
bgneal@36 4 import urllib2
bgneal@36 5 import decimal
bgneal@36 6 import datetime
bgneal@36 7
bgneal@35 8 from django.shortcuts import render_to_response
bgneal@35 9 from django.template import RequestContext
bgneal@35 10 from django.conf import settings
bgneal@35 11 from django.contrib.sites.models import Site
bgneal@36 12 from django.http import HttpResponse
bgneal@36 13 from django.http import HttpResponseServerError
bgneal@36 14 from django.contrib.auth.models import User
bgneal@238 15 from django.views.decorators.csrf import csrf_exempt
bgneal@238 16
bgneal@35 17
bgneal@35 18 from donations.models import Donation
bgneal@35 19
bgneal@63 20 PP_DATE_FMT = '%H:%M:%S %b %d, %Y'
bgneal@35 21
bgneal@36 22 def paypal_params():
bgneal@36 23 """
bgneal@36 24 This function returns a tuple where the 1st element is the Paypal
bgneal@36 25 URL and the 2nd element is the Paypal business email. This information
bgneal@36 26 depends on the setting DONATIONS_DEBUG.
bgneal@36 27 """
bgneal@36 28 if settings.DONATIONS_DEBUG:
bgneal@35 29 form_action = 'https://www.sandbox.paypal.com/cgi-bin/webscr'
bgneal@35 30 business = settings.DONATIONS_BUSINESS_DEBUG
bgneal@35 31 else:
bgneal@35 32 form_action = 'https://www.paypal.com/cgi-bin/webscr'
bgneal@35 33 business = settings.DONATIONS_BUSINESS
bgneal@35 34
bgneal@36 35 return form_action, business
bgneal@36 36
bgneal@36 37
bgneal@36 38 def index(request):
bgneal@36 39 gross, net, donations = Donation.objects.monthly_stats()
bgneal@36 40 current_site = Site.objects.get_current()
bgneal@36 41 form_action, business = paypal_params()
bgneal@36 42
bgneal@35 43 return render_to_response('donations/index.html', {
bgneal@35 44 'goal': settings.DONATIONS_GOAL,
bgneal@35 45 'gross': gross,
bgneal@35 46 'net': net,
bgneal@35 47 'left': settings.DONATIONS_GOAL - net,
bgneal@35 48 'donations': donations,
bgneal@35 49 'form_action': form_action,
bgneal@35 50 'business': business,
bgneal@35 51 'anonymous': settings.DONATIONS_ANON_NAME,
bgneal@35 52 'item_name': settings.DONATIONS_ITEM_NAME,
bgneal@35 53 'item_number': settings.DONATIONS_ITEM_NUM,
bgneal@35 54 'item_anon_number': settings.DONATIONS_ITEM_ANON_NUM,
bgneal@35 55 'domain': current_site.domain,
bgneal@35 56 },
bgneal@35 57 context_instance = RequestContext(request))
bgneal@35 58
bgneal@35 59
bgneal@238 60 @csrf_exempt
bgneal@35 61 def ipn(request):
bgneal@36 62 """
bgneal@36 63 This function is the IPN listener and handles the IPN POST from Paypal.
bgneal@39 64 The algorithm here roughly follows the outline described in chapter 2
bgneal@39 65 of Paypal's IPNGuide.pdf "Implementing an IPN Listener".
bgneal@39 66 """
bgneal@39 67 import logging
bgneal@35 68
bgneal@39 69 # Log some info about this IPN event
bgneal@39 70 ip = request.META.get('REMOTE_ADDR', '?')
bgneal@39 71 parameters = request.POST.copy()
bgneal@39 72 logging.info('IPN from %s; post data: %s' % (ip, parameters.urlencode()))
bgneal@35 73
bgneal@39 74 # Now we follow the instructions in chapter 2 of the Paypal IPNGuide.pdf.
bgneal@39 75 # Create a request that contains exactly the same IPN variables and values in
bgneal@39 76 # the same order, preceded with cmd=_notify-validate
bgneal@39 77 parameters['cmd']='_notify-validate'
bgneal@36 78
bgneal@39 79 # Post the request back to Paypal (either to the sandbox or the real deal).
bgneal@39 80 req = urllib2.Request(paypal_params()[0], parameters.urlencode())
bgneal@39 81 req.add_header("Content-type", "application/x-www-form-urlencoded")
bgneal@39 82 response = urllib2.urlopen(req)
bgneal@36 83
bgneal@39 84 # Wait for the response from Paypal, which should be either VERIFIED or INVALID.
bgneal@39 85 status = response.read()
bgneal@39 86 if status != 'VERIFIED':
bgneal@39 87 logging.warning('IPN: Payapl did not verify; status was %s' % status)
bgneal@39 88 return HttpResponse()
bgneal@36 89
bgneal@39 90 # Response was VERIFIED; act on this if it is a Completed donation,
bgneal@39 91 # otherwise don't handle it (we are just a donations application. Here
bgneal@39 92 # is where we could be expanded to be a more general payment processor).
bgneal@36 93
bgneal@39 94 payment_status = parameters.get('payment_status')
bgneal@39 95 if payment_status != 'Completed':
bgneal@39 96 logging.info('IPN: payment_status is %s; we are done.' % payment_status)
bgneal@39 97 return HttpResponse()
bgneal@39 98
bgneal@39 99 # Is this a donation to the site?
bgneal@39 100 item_number = parameters.get('item_number')
bgneal@39 101 if item_number == settings.DONATIONS_ITEM_NUM or \
bgneal@39 102 item_number == settings.DONATIONS_ITEM_ANON_NUM:
bgneal@39 103 process_donation(item_number, parameters)
bgneal@39 104 else:
bgneal@39 105 logging.info('IPN: not a donation; done.')
bgneal@39 106
bgneal@39 107 return HttpResponse()
bgneal@36 108
bgneal@36 109
bgneal@36 110 def process_donation(item_number, params):
bgneal@36 111 """
bgneal@39 112 A few validity and duplicate checks are made on the donation params.
bgneal@39 113 If everything is ok, construct a donation object from the parameters and
bgneal@39 114 store it in the database.
bgneal@36 115 """
bgneal@39 116 import logging
bgneal@39 117
bgneal@36 118 # Has this transaction been processed before?
bgneal@39 119 txn_id = params.get('txn_id')
bgneal@39 120 if txn_id is None:
bgneal@39 121 logging.error('IPN: missing txn_id')
bgneal@36 122 return
bgneal@39 123
bgneal@36 124 try:
bgneal@36 125 donation = Donation.objects.get(txn_id__exact=txn_id)
bgneal@36 126 except Donation.DoesNotExist:
bgneal@36 127 pass
bgneal@36 128 else:
bgneal@39 129 logging.warning('IPN: duplicate txn_id')
bgneal@36 130 return # no exception, this is a duplicate
bgneal@36 131
bgneal@36 132 # Is the email address ours?
bgneal@36 133 business = params.get('business')
bgneal@36 134 if business != paypal_params()[1]:
bgneal@39 135 logging.warning('IPN: invalid business: %s' % business)
bgneal@36 136 return
bgneal@36 137
bgneal@36 138 # is this a payment received?
bgneal@36 139 txn_type = params.get('txn_type')
bgneal@36 140 if txn_type != 'web_accept':
bgneal@39 141 logging.warning('IPN: invalid txn_type: %s' % txn_type)
bgneal@36 142 return
bgneal@61 143
bgneal@36 144 # Looks like a donation, save it to the database.
bgneal@36 145 # Determine which user this came from, if any.
bgneal@36 146 # The username is stored in the custom field if the user was logged in when
bgneal@36 147 # the donation was made.
bgneal@36 148 user = None
bgneal@65 149 if 'custom' in params and params['custom']:
bgneal@36 150 try:
bgneal@36 151 user = User.objects.get(username__exact=params['custom'])
bgneal@36 152 except User.DoesNotExist:
bgneal@36 153 pass
bgneal@36 154
bgneal@36 155 is_anonymous = item_number == settings.DONATIONS_ITEM_ANON_NUM
bgneal@36 156 test_ipn = params.get('test_ipn') == '1'
bgneal@36 157
bgneal@36 158 first_name = params.get('first_name', '')
bgneal@36 159 last_name = params.get('last_name', '')
bgneal@36 160 payer_email = params.get('payer_email', '')
bgneal@36 161 payer_id = params.get('payer_id', '')
bgneal@36 162 memo = params.get('memo', '')
bgneal@36 163 payer_status = params.get('payer_status', '')
bgneal@36 164
bgneal@36 165 try:
bgneal@36 166 mc_gross = decimal.Decimal(params['mc_gross'])
bgneal@36 167 mc_fee = decimal.Decimal(params['mc_fee'])
bgneal@36 168 except KeyError, decimal.InvalidOperation:
bgneal@39 169 logging.error('IPN: invalid/missing mc_gross or mc_fee')
bgneal@36 170 return
bgneal@36 171
bgneal@61 172 payment_date = params.get('payment_date')
bgneal@61 173 if payment_date is None:
bgneal@61 174 logging.error('IPN: missing payment_date')
bgneal@36 175 return
bgneal@36 176
bgneal@61 177 # strip off the timezone
bgneal@61 178 payment_date = payment_date[:-4]
bgneal@61 179 try:
bgneal@61 180 payment_date = datetime.datetime.strptime(payment_date, PP_DATE_FMT)
bgneal@61 181 except ValueError:
bgneal@61 182 logging.error('IPN: invalid payment_date "%s"' % params['payment_date'])
bgneal@61 183 return
bgneal@36 184
bgneal@61 185 try:
bgneal@61 186 donation = Donation(
bgneal@61 187 user=user,
bgneal@61 188 is_anonymous=is_anonymous,
bgneal@61 189 test_ipn=test_ipn,
bgneal@61 190 txn_id=txn_id,
bgneal@61 191 txn_type=txn_type,
bgneal@61 192 first_name=first_name,
bgneal@61 193 last_name=last_name,
bgneal@61 194 payer_email=payer_email,
bgneal@61 195 payer_id=payer_id,
bgneal@61 196 memo=memo,
bgneal@61 197 payer_status=payer_status,
bgneal@61 198 mc_gross=mc_gross,
bgneal@61 199 mc_fee=mc_fee,
bgneal@61 200 payment_date=payment_date)
bgneal@61 201 except:
bgneal@61 202 logging.exception('IPN: exception during donation creation')
bgneal@61 203 else:
bgneal@61 204 donation.save()
bgneal@61 205 logging.info('IPN: donation saved')
bgneal@36 206