annotate donations/models.py @ 887:9a15f7c27526

Actually save model object upon change. This commit was tested on the comments model. Additional logging added. Added check for Markdown image references. Added TODOs after observing behavior on comments.
author Brian Neal <bgneal@gmail.com>
date Tue, 03 Feb 2015 21:09:44 -0600
parents 40ae28f33b3d
children e14f54f16dbc
rev   line source
bgneal@33 1 """
bgneal@33 2 Models for the donations application.
bgneal@33 3 """
bgneal@34 4 import datetime
bgneal@34 5 import decimal
bgneal@34 6
bgneal@33 7 from django.db import models
bgneal@259 8 from django.contrib.auth.models import User
bgneal@35 9 from django.conf import settings
bgneal@619 10 from django.db.models import Sum
bgneal@33 11
bgneal@34 12
bgneal@34 13 class DonationManager(models.Manager):
bgneal@619 14 """Manager for the Donations model."""
bgneal@619 15
bgneal@34 16 def monthly_stats(self, year=None, month=None):
bgneal@34 17 """
bgneal@35 18 Returns a tuple of items for the given month in the given
bgneal@34 19 year. If year is None, the current year is used. If month is None,
bgneal@34 20 the current month is used.
bgneal@35 21 The returned tuple has the following items, in order:
bgneal@35 22 (gross, net, donations)
bgneal@35 23 where:
bgneal@34 24 'gross': total gross donations
bgneal@34 25 'net': total net donations
bgneal@35 26 'donations': list of donation objects
bgneal@34 27 """
bgneal@34 28 today = datetime.date.today()
bgneal@34 29 if year is None:
bgneal@34 30 year = today.year
bgneal@34 31 if month is None:
bgneal@34 32 month = today.month
bgneal@34 33
bgneal@34 34 qs = self.filter(payment_date__year=year,
bgneal@34 35 payment_date__month=month,
bgneal@350 36 test_ipn=settings.DONATIONS_DEBUG).order_by(
bgneal@350 37 'payment_date').select_related('user')
bgneal@34 38
bgneal@35 39 gross = decimal.Decimal()
bgneal@35 40 net = decimal.Decimal()
bgneal@35 41 donations = []
bgneal@34 42 for donation in qs:
bgneal@35 43 gross += donation.mc_gross
bgneal@35 44 net += donation.mc_gross - donation.mc_fee
bgneal@35 45 donations.append(donation)
bgneal@34 46
bgneal@35 47 return gross, net, donations
bgneal@34 48
bgneal@619 49 def monthly_goal_pct(self, year=None, month=None, limit=True):
bgneal@619 50 """Returns progress towards the given monthly goal as an integer
bgneal@619 51 percent.
bgneal@619 52
bgneal@619 53 If year is None, the current year is used.
bgneal@619 54 If month is None, the current month is used.
bgneal@619 55 If limit is True, the return value is limited to 100.
bgneal@619 56
bgneal@619 57 """
bgneal@619 58 today = datetime.datetime.today()
bgneal@619 59 if year is None:
bgneal@619 60 year = today.year
bgneal@619 61 if month is None:
bgneal@619 62 month = today.month
bgneal@619 63
bgneal@619 64 r = self.filter(payment_date__year=year, payment_date__month=month).aggregate(
bgneal@619 65 Sum('mc_gross'), Sum('mc_fee'))
bgneal@619 66
bgneal@619 67 gross, fee = r['mc_gross__sum'], r['mc_fee__sum']
bgneal@619 68
bgneal@619 69 if gross is not None and fee is not None:
bgneal@619 70 pct = int((gross - fee) / settings.DONATIONS_GOAL * 100)
bgneal@619 71 else:
bgneal@619 72 pct = 0
bgneal@619 73
bgneal@619 74 if limit:
bgneal@619 75 pct = min(pct, 100)
bgneal@619 76
bgneal@619 77 return pct
bgneal@619 78
bgneal@620 79 def top_donors(self, n=10):
bgneal@620 80 """Returns a list of the top n donors as user objects that have a
bgneal@620 81 total_donations field annotation.
bgneal@620 82
bgneal@620 83 The data is taken from non anonymous donations from logged in users.
bgneal@620 84
bgneal@620 85 """
bgneal@620 86 qs = User.objects.filter(donation__isnull=False,
bgneal@620 87 donation__is_anonymous=False) \
bgneal@620 88 .distinct() \
bgneal@620 89 .annotate(total_donations=Sum('donation__mc_gross')) \
bgneal@620 90 .order_by('-total_donations')[:n]
bgneal@620 91
bgneal@620 92 return qs
bgneal@620 93
bgneal@34 94
bgneal@33 95 class Donation(models.Model):
bgneal@33 96 """Model to represent a donation to the website."""
bgneal@33 97
bgneal@259 98 user = models.ForeignKey(User, null=True, blank=True)
bgneal@33 99 is_anonymous = models.BooleanField()
bgneal@33 100 test_ipn = models.BooleanField(default=False, verbose_name="Test IPN")
bgneal@33 101 txn_id = models.CharField(max_length=20, verbose_name="Txn ID")
bgneal@33 102 txn_type = models.CharField(max_length=64)
bgneal@33 103 first_name = models.CharField(max_length=64, blank=True)
bgneal@33 104 last_name = models.CharField(max_length=64, blank=True)
bgneal@33 105 payer_email = models.EmailField(max_length=127, blank=True)
bgneal@33 106 payer_id = models.CharField(max_length=13, blank=True, verbose_name="Payer ID")
bgneal@33 107 mc_fee = models.DecimalField(max_digits=8, decimal_places=2, verbose_name="Fee")
bgneal@33 108 mc_gross = models.DecimalField(max_digits=8, decimal_places=2, verbose_name="Gross")
bgneal@33 109 memo = models.TextField(blank=True)
bgneal@33 110 payer_status = models.CharField(max_length=10, blank=True)
bgneal@33 111 payment_date = models.DateTimeField()
bgneal@33 112
bgneal@34 113 objects = DonationManager()
bgneal@34 114
bgneal@33 115 class Meta:
bgneal@33 116 ordering = ('-payment_date', )
bgneal@33 117
bgneal@33 118 def __unicode__(self):
bgneal@33 119 if self.user:
bgneal@33 120 return u'%s from %s' % (self.mc_gross, self.user.username)
bgneal@33 121 return u'%s from %s %s' % (self.mc_gross, self.first_name, self.last_name)
bgneal@33 122
bgneal@34 123 def donor(self):
bgneal@34 124 """Returns the donor name for the donation."""
bgneal@34 125 if self.is_anonymous:
bgneal@35 126 return settings.DONATIONS_ANON_NAME
bgneal@34 127 if self.user is not None:
bgneal@34 128 return self.user.username
bgneal@34 129 if self.first_name or self.last_name:
bgneal@34 130 name = u'%s %s' % (self.first_name, self.last_name)
bgneal@34 131 return name.strip()
bgneal@35 132 return settings.DONATIONS_ANON_NAME
bgneal@34 133