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@929
|
99 is_anonymous = models.BooleanField(default=False)
|
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
|