Move fundraiser calculations to model
This changes simplifies the template and adds correct pluralisation of hours/hour remaining.
This commit is contained in:
parent
28f3b8de08
commit
79361cdf97
3 changed files with 58 additions and 73 deletions
|
@ -1,3 +1,5 @@
|
||||||
|
import datetime
|
||||||
|
import math
|
||||||
import random
|
import random
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
@ -10,6 +12,9 @@ class FundraisingGoal(models.Model):
|
||||||
fundraiser_goal_amount = models.DecimalField(max_digits=10, decimal_places=2)
|
fundraiser_goal_amount = models.DecimalField(max_digits=10, decimal_places=2)
|
||||||
fundraiser_so_far_amount = models.DecimalField(max_digits=10, decimal_places=2)
|
fundraiser_so_far_amount = models.DecimalField(max_digits=10, decimal_places=2)
|
||||||
fundraiser_donation_count = models.IntegerField()
|
fundraiser_donation_count = models.IntegerField()
|
||||||
|
# The number of new Sustainers that can be double-matched this fundraiser.
|
||||||
|
# (No, this name makes no sense. We're repurposing an existing model field
|
||||||
|
# for this new reason.)
|
||||||
fundraiser_donation_count_disclose_threshold = models.IntegerField()
|
fundraiser_donation_count_disclose_threshold = models.IntegerField()
|
||||||
fundraiser_endtime = models.DateTimeField(null=True)
|
fundraiser_endtime = models.DateTimeField(null=True)
|
||||||
|
|
||||||
|
@ -34,6 +39,20 @@ class FundraisingGoal(models.Model):
|
||||||
else:
|
else:
|
||||||
return random.sample(providers, k)
|
return random.sample(providers, k)
|
||||||
|
|
||||||
|
def days_remaining(self):
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
return (self.fundraiser_endtime - now).days
|
||||||
|
|
||||||
|
def hours_remaining(self):
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
return int(math.ceil((self.fundraiser_endtime - now).seconds / 3600))
|
||||||
|
|
||||||
|
def match_remaining(self):
|
||||||
|
return self.fundraiser_goal_amount - self.fundraiser_so_far_amount
|
||||||
|
|
||||||
|
def match_exceeded_by(self):
|
||||||
|
return self.fundraiser_so_far_amount - self.fundraiser_goal_amount
|
||||||
|
|
||||||
|
|
||||||
class GoalProvider(models.Model):
|
class GoalProvider(models.Model):
|
||||||
fundraising_goal = models.ForeignKey(
|
fundraising_goal = models.ForeignKey(
|
||||||
|
|
|
@ -1,81 +1,50 @@
|
||||||
{% load humanize %}
|
{% load humanize %}
|
||||||
{% load subtract %}
|
{% load subtract %}
|
||||||
|
|
||||||
{% comment %}
|
{% if sitefundgoal.days_remaining >= -7 %}{# i.e. 7 days over completion #}
|
||||||
# FUNDRAISER VARIABLES AND CONSTANTS GUIDE
|
|
||||||
|
|
||||||
## From Local Context
|
|
||||||
|
|
||||||
* datetime_now: Current DateTime in UTC
|
|
||||||
* sitefundgoal: The current FundraisingGoal. Attributes:
|
|
||||||
* fundraiser_goal_amount: The amount being matched
|
|
||||||
* fundraiser_so_far_amount: The amount contributed so far
|
|
||||||
* fundraiser_donation_count: The number of people who have contributed so far
|
|
||||||
* fundraiser_donation_count_disclose_threshold: The number of new Sustainers that can be double-matched this fundraiser.
|
|
||||||
(No, this name makes no sense. We're repurposing an existing model field for this new reason.)
|
|
||||||
* sitefundgoal_endtime: DateTime when sitefundgoal ends.
|
|
||||||
|
|
||||||
## Local convenience variables
|
|
||||||
|
|
||||||
* sitefundgoal_timeleft: TimeDelta for how much time remains in the current fundraiser
|
|
||||||
* this_match_goal: The amount being matched
|
|
||||||
* this_match_so_far: The amount contributed so far
|
|
||||||
* this_match_remaining: this_match_goal - this_match_so_far
|
|
||||||
* this_match_exceeded: this_match_so_far - this_match_goal
|
|
||||||
|
|
||||||
{% endcomment %}
|
|
||||||
{% with this_match_goal=sitefundgoal.fundraiser_goal_amount this_match_so_far=sitefundgoal.fundraiser_so_far_amount %}
|
|
||||||
{% with this_match_remaining=this_match_goal|subtract:this_match_so_far sitefundgoal_timeleft=sitefundgoal.fundraiser_endtime|subtract:datetime_now this_match_exceeded=this_match_so_far|subtract:this_match_goal %}
|
|
||||||
{% if sitefundgoal_timeleft.total_seconds >= -604800 %}
|
|
||||||
<div class="fundraiser-top-text ph3 pt2 pb3 mb2 mb3-ns">
|
<div class="fundraiser-top-text ph3 pt2 pb3 mb2 mb3-ns">
|
||||||
<div class="mw8 center ph2 ph4-ns">
|
<div class="mw8 center ph2 ph4-ns">
|
||||||
<div class="mt2 mb3 tc">
|
<div class="mt2 mb3 tc">
|
||||||
{% if datetime_now < sitefundgoal.fundraiser_endtime %}
|
{% if sitefundgoal.days_remaining >= 0 %}
|
||||||
{% if this_match_remaining <= 0 %}
|
{% if fundgoal.match_remaining <= 0 %}
|
||||||
Thanks to so many donors, we earned our full match!
|
Thanks to so many donors, we earned our full match!
|
||||||
Help us go further to stand up for software freedom — <a href="/sustainer">sign up now</a>!
|
Help us go further to stand up for software freedom — <a href="/sustainer">sign up now</a>!
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if sitefundgoal_timeleft.days == 0 %}
|
{% if sitefundgoal.days_remaining == 0 %}
|
||||||
For the next {% widthratio sitefundgoal_timeleft.total_seconds 3600 1 %} hours <strong>only</strong>, the
|
For the next {{ sitefundgoal.hours_remaining }} hour{{ sitefundgoal.hours_remaining|pluralize }} <strong>only</strong>, the
|
||||||
{% elif sitefundgoal_timeleft.days == 1 %}
|
{% elif sitefundgoal.days_remaining == 1 %}
|
||||||
Through tomorrow only, the
|
Through tomorrow only, the
|
||||||
{% elif sitefundgoal_timeleft.days < 14 %}
|
{% elif sitefundgoal.days_remaining < 14 %}
|
||||||
For only {{ sitefundgoal_timeleft.days }} more days, the
|
For only {{ sitefundgoal.days_remaining }} more days, the
|
||||||
{% else %}
|
{% else %}
|
||||||
Until January 15, the
|
Until January 15, the
|
||||||
{% endif %}
|
{% endif %}
|
||||||
next ${{ this_match_remaining|floatformat:0|intcomma }} of <a href="/sustainer/">support we receive</a> will be matched!
|
next ${{ sitefundgoal.match_remaining|floatformat:0|intcomma }} of <a href="/sustainer/">support we receive</a> will be matched!
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
Thank you so much to all our donors who participated in our donation match challenge! Here are the results:
|
Thank you so much to all our donors who participated in our donation match challenge! Here are the results:
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if 1 %}
|
<a href="/sustainer/" style="text-decoration: none !important">
|
||||||
<a href="/sustainer/" style="text-decoration: none !important">
|
<div id="siteprogressbar" class="flex items-stretch w-100">
|
||||||
<div id="siteprogressbar" class="flex items-stretch w-100">
|
{% if sitefundgoal.match_remaining <= 0 %}
|
||||||
{% if this_match_remaining <= 0 %}
|
<div class="progress matched ph1 pv1 b flex items-center" style="flex-basis: {{ sitefundgoal.fundraiser_so_far_amount }}px">
|
||||||
<div class="progress matched pv1 b flex items-center" style="flex-basis: {{ this_match_so_far }}px">
|
<span id="site-fundraiser-match-count" class="soFarText tc w-100 ph1">${{ sitefundgoal.fundraiser_goal_amount|floatformat:0|intcomma }} fully matched!</span>
|
||||||
<span id="site-fundraiser-match-count" class="soFarText tc w-100">${{ this_match_goal|floatformat:0|intcomma }} fully matched!</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="progress exceeded pv1 b flex items-center" style="flex-basis: {{ this_match_exceeded }}px">
|
<div class="progress exceeded pv1 b flex items-center" style="flex-basis: {{ sitefundgoal.match_exceeded_by }}px">
|
||||||
<span id="site-fundraiser-match-count" class="soFarText tc w-100 exceeded">${{this_match_exceeded|floatformat:0|intcomma }} additional<br> raised!<br></span>
|
<span id="site-fundraiser-match-count" class="soFarText tc w-100 exceeded ph1">${{sitefundgoal.match_exceeded_by|floatformat:0|intcomma }} additional<br> raised!<br></span>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="progress pv1 b flex items-center" style="flex-basis: {{ this_match_so_far }}px">
|
<div class="progress ph1 pv1 b flex items-center" style="flex-basis: {{ sitefundgoal.fundraiser_so_far_amount }}px">
|
||||||
<span id="site-fundraiser-match-count" class="soFarText tc w-100">${{ this_match_so_far|floatformat:0|intcomma }} matched!</span>
|
<span id="site-fundraiser-match-count" class="soFarText tc w-100">${{ sitefundgoal.fundraiser_so_far_amount|floatformat:0|intcomma }} matched!</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="final-goal pv1 b flex items-center" style="flex-basis: {{ this_match_remaining }}px">
|
<div class="final-goal pv1 b flex items-center" style="flex-basis: {{ sitefundgoal.match_remaining }}px">
|
||||||
<span id="site-fundraiser-final-goal" class="goalText tc w-100">${{ this_match_remaining|floatformat:0|intcomma }} to go!</span>
|
<span id="site-fundraiser-final-goal" class="goalText tc w-100 ph1">${{ sitefundgoal.match_remaining|floatformat:0|intcomma }} to go!</span>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
|
||||||
{% endwith %}
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
from datetime import datetime as DateTime
|
|
||||||
|
|
||||||
from .fundgoal.models import FundraisingGoal
|
from .fundgoal.models import FundraisingGoal
|
||||||
|
|
||||||
SITE_FUNDGOAL = 'cy2023-end-year-match'
|
SITE_FUNDGOAL = 'cy2023-end-year-match'
|
||||||
|
@ -13,7 +11,6 @@ def fundgoal_lookup(fundraiser_sought):
|
||||||
|
|
||||||
def sitefundraiser(request):
|
def sitefundraiser(request):
|
||||||
return {
|
return {
|
||||||
'datetime_now': DateTime.now(),
|
|
||||||
'sitefundgoal': fundgoal_lookup(SITE_FUNDGOAL),
|
'sitefundgoal': fundgoal_lookup(SITE_FUNDGOAL),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue