annotate donations/models.py @ 1201:fe10aea76cbd tip

Add 2023 MP3 compilation links
author Brian Neal <bgneal@gmail.com>
date Sun, 24 Mar 2024 14:50:23 -0500
parents e14f54f16dbc
children
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@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