Skip to content

Commit

Permalink
Merge pull request #22 from Maestro-Zacht/feature/estimated-total-on-…
Browse files Browse the repository at this point in the history
…projects

Feature/estimated total on projects
  • Loading branch information
Maestro-Zacht committed Oct 21, 2023
2 parents bb2dfd4 + e0d4d04 commit 3458eae
Show file tree
Hide file tree
Showing 9 changed files with 275 additions and 44 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/python-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4
with:
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/python-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:
run: sudo /etc/init.d/mysql start

- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Setup Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
Expand Down Expand Up @@ -70,7 +70,7 @@ jobs:

steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Download artifacts
uses: actions/download-artifact@v3
Expand All @@ -91,7 +91,7 @@ jobs:

steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Setup Python 3
uses: actions/setup-python@v4
Expand Down
68 changes: 60 additions & 8 deletions allianceauth_pve/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,14 @@ def with_totals(self):
models.F('entry__rotation__actual_total') / models.Subquery(estimated_total)
)

def with_contributions_to(self, funding_project):
return self.filter(
entry__rotation__is_closed=True,
def with_contributions_to(self, funding_project, rotation_closed: bool = None):
res = self.filter(
entry__funding_percentage__gt=0,
entry__funding_project=funding_project
)
if rotation_closed is not None:
res = res.filter(entry__rotation__is_closed=rotation_closed)
return res


class EntryCharacterManager(models.Manager):
Expand All @@ -111,8 +113,8 @@ def get_queryset(self):
def with_totals(self):
return self.get_queryset().with_totals()

def with_contributions_to(self, funding_project):
return self.get_queryset().with_contributions_to(funding_project)
def with_contributions_to(self, funding_project, rotation_closed: bool = None):
return self.get_queryset().with_contributions_to(funding_project, rotation_closed)


class PveButton(models.Model):
Expand Down Expand Up @@ -352,7 +354,7 @@ def __str__(self):
def current_total(self):
return (
EntryCharacter.objects
.with_contributions_to(self)
.with_contributions_to(self, True)
.with_totals()
.aggregate(
current_total=Coalesce(
Expand All @@ -363,9 +365,43 @@ def current_total(self):
)

@cached_property
def current_percentage(self):
def estimated_total(self):
return self.current_total + (
EntryCharacter.objects
.with_contributions_to(self, False)
.with_totals()
.aggregate(
estimated_total=Coalesce(
models.Sum('estimated_funding_amount'),
0
)
)['estimated_total']
)

@cached_property
def actual_percentage(self):
return self.current_total / self.goal * 100

@cached_property
def estimated_missing_percentage(self):
return (self.estimated_total - self.current_total) / self.goal * 100

@cached_property
def total_percentage(self):
return self.actual_percentage + self.estimated_missing_percentage

@property
def html_actual_percentage_width(self):
return int(self.actual_percentage) if self.actual_percentage <= 100 else 100

@property
def html_estimated_percentage_width(self):
return (
int(self.estimated_missing_percentage)
if self.actual_percentage + self.estimated_missing_percentage <= 100
else 100 - int(self.actual_percentage)
)

@property
def days_since(self):
return (timezone.now() - self.created_at).days
Expand All @@ -380,16 +416,32 @@ def num_participants(self) -> int:
num=models.Count('user', distinct=True)
)['num']

@property
@cached_property
def summary(self):
estimated_part = (
EntryCharacter.objects
.filter(user=models.OuterRef('user'))
.with_contributions_to(self, False)
.with_totals()
.order_by()
.values('user')
.annotate(estimated_total=models.Sum('estimated_funding_amount'))
.values('estimated_total')
)

return (
EntryCharacter.objects
.with_contributions_to(self)
.with_totals()
.order_by()
.values('user')
.annotate(actual_total=models.Sum('actual_funding_amount'))
.annotate(estimated_total=models.F('actual_total') + Coalesce(models.Subquery(estimated_part), 0))
)

@property
def has_open_contributions(self) -> bool:
return self.entries.filter(rotation__is_closed=False).exists()

class Meta:
default_permissions = ()
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ <h3 class="panel-title text-center">Funding Project</h3>
<p>Users</p>
<p>{{ funding_project.num_participants }}</p>
</li>
{% if funding_project.is_active %}
<li class="list-group-item text-center">
<p>Estimated Total</p>
<p>{{ funding_project.estimated_total|intcomma }}</p>
</li>
{% endif %}
<li class="list-group-item text-center">
<p>Current Total</p>
<p>{{ funding_project.current_total|intcomma }}</p>
Expand All @@ -47,8 +53,10 @@ <h3 class="panel-title text-center">Funding Project</h3>
<br>
<div class="panel-body">
<div class="progress" style="height: 20px;">
{% min_value 100 funding_project.current_percentage as width %}
<div class="progress-bar {% if funding_project.is_active %}progress-bar-striped active{% endif %} {% if funding_project.current_percentage >= 100 %}progress-bar-success{% elif funding_project.current_percentage <= 1 %}progress-bar-danger{% elif funding_project.current_percentage < 25 %}progress-bar-warning{% else %}progress-bar-info{% endif %}" role="progressbar" style="min-width: 2em; width: {{ width|floatformat:0 }}%;" aria-valuenow="{{ width|floatformat:0 }}" aria-valuemin="0" aria-valuemax="100">{{ funding_project.current_percentage|floatformat }} &percnt;</div>
<div class="progress-bar {% if funding_project.is_active %}progress-bar-striped active{% endif %} {% if funding_project.actual_percentage >= 100 %}progress-bar-success{% elif funding_project.actual_percentage <= 1 %}progress-bar-danger{% elif funding_project.actual_percentage < 25 %}progress-bar-warning{% else %}progress-bar-info{% endif %}" role="progressbar" style="min-width: 1%; width: {{ funding_project.html_actual_percentage_width }}%;" aria-valuenow="{{ funding_project.html_actual_percentage_width }}" aria-valuemin="0" aria-valuemax="100">{{ funding_project.actual_percentage|floatformat }} &percnt;</div>
{% if funding_project.is_active %}
<div class="progress-bar progress-bar-striped active{% if funding_project.actual_percentage >= 25 %} progress-bar-success{% endif %}" role="progressbar" style="min-width: 1%; width: {{ funding_project.html_estimated_percentage_width }}%;" aria-valuenow="{{ funding_project.html_estimated_percentage_width }}" aria-valuemin="0" aria-valuemax="100">{{ funding_project.estimated_missing_percentage|floatformat }} &percnt;</div>
{% endif %}
</div>
</div>
</div>
Expand All @@ -65,7 +73,10 @@ <h3 class="panel-title text-center">Contributors</h3>
<thead>
<tr>
<th>User's Main Character</th>
<th>Total</th>
{% if funding_project.is_active %}
<th>Estimated Total</th>
{% endif %}
<th>Actual Total</th>
</tr>
</thead>

Expand All @@ -76,6 +87,9 @@ <h3 class="panel-title text-center">Contributors</h3>
<img src="{{ row.character_id|character_portrait_url:32 }}" class="img-circle" style="margin-right: 1rem;">
{{ row.character_name }}
</td>
{% if funding_project.is_active %}
<td>{{ row.estimated_total|intcomma }}</td>
{% endif %}
<td>{{ row.actual_total|intcomma }}</td>
</tr>
{% endfor %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,9 +162,9 @@ <h3 class="panel-title text-center">Running Averages</h3>
<tr>
<th style="width: 20%;">Name</th>
<th style="width: 20%;">Age (days)</th>
<th style="width: 20%;">Current Total</th>
<th style="width: 20%;">Estimated Total (Actual)</th>
<th style="width: 20%;">Goal</th>
<th style="width: 20%;">Completion</th>
<th style="width: 20%;">Completed (Actual)</th>
</tr>
</thead>

Expand All @@ -173,9 +173,9 @@ <h3 class="panel-title text-center">Running Averages</h3>
<tr>
<td style="width: 20%;"><a href="{% url 'allianceauth_pve:project_detail' project.pk %}">{{ project.name }}</a></td>
<td style="width: 20%;">{{ project.days_since }}</td>
<td style="width: 20%;">{{ project.current_total|intcomma }}</td>
<td style="width: 20%;">{{ project.estimated_total|intcomma }} ({{ project.current_total|intcomma }})</td>
<td style="width: 20%;">{{ project.goal|intcomma }}</td>
<td style="width: 20%;">{{ project.current_percentage|floatformat }} %</td>
<td style="width: 20%;">{{ project.total_percentage|floatformat }} &percnt; ({{ project.actual_percentage|floatformat }} &percnt;)</td>
</tr>
{% endfor %}
</tbody>
Expand Down
101 changes: 96 additions & 5 deletions allianceauth_pve/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ def test_actual_total_after_tax(self):
self.assertAlmostEqual(self.entry.actual_total_after_tax, 810_000_000.0)

def test_with_totals_valid(self):
for count1, count2, count3, value1, value2, value3 in itertools.combinations_with_replacement(range(8), 6):
for count1, count2, count3, value1, value2, value3 in itertools.combinations_with_replacement(range(6), 6):
total_count = count1 + count2 + count3
total_roles = value1 + value2 + value3
total_value = value1 * count1 + value2 * count2 + value3 * count3
Expand Down Expand Up @@ -438,20 +438,111 @@ def test_with_contributions_to(self):
helped_setup=False
)

self.assertQuerysetEqual(EntryCharacter.objects.with_contributions_to(self.funding_project), [self.share.pk], transform=lambda x: x.pk)
self.assertQuerysetEqual(
EntryCharacter.objects.with_contributions_to(self.funding_project, True),
[self.share.pk],
transform=lambda x: x.pk
)

open_rotation = Rotation.objects.create(
name='test1rot',
tax_rate=0.0
)

entry4 = Entry.objects.create(
rotation=open_rotation,
created_by=self.testuser,
estimated_total=1_000_000_000,
funding_project=self.funding_project,
funding_percentage=10,
)

share_open = EntryCharacter.objects.create(
entry=entry4,
user=self.testuser,
user_character=self.testcharacter,
role=self.role,
site_count=2,
helped_setup=False
)

self.assertQuerysetEqual(
EntryCharacter.objects.with_contributions_to(self.funding_project, False),
[share_open.pk],
transform=lambda x: x.pk
)

self.assertQuerysetEqual(
EntryCharacter.objects.with_contributions_to(self.funding_project),
[share_open.pk, self.share.pk],
transform=lambda x: x.pk,
ordered=False
)

def test_properties(self):
self.assertEqual(self.funding_project.current_total, 500_000_000)

self.assertEqual(self.funding_project.current_percentage, 50)
self.assertEqual(self.funding_project.actual_percentage, 50)

self.assertEqual(self.funding_project.days_since, 0)

self.assertEqual(self.funding_project.num_participants, 1)

a = self.funding_project.summary[0]
self.assertDictEqual(
self.funding_project.summary[0],
{
'user': self.testuser.pk,
'actual_total': 500_000_000,
'estimated_total': 500_000_000
}
)

del self.funding_project.summary

self.assertFalse(self.funding_project.has_open_contributions)

rotation_open = Rotation.objects.create(
name='test1rot',
tax_rate=0.0
)

entry_open = Entry.objects.create(
rotation=rotation_open,
created_by=self.testuser,
estimated_total=1_000_000_000,
funding_project=self.funding_project,
funding_percentage=10,
)

EntryCharacter.objects.create(
entry=entry_open,
user=self.testuser,
user_character=self.testcharacter,
role=self.role,
site_count=1,
helped_setup=False
)

self.assertDictEqual(
self.funding_project.summary[0],
{
'user': self.testuser.pk,
'actual_total': 500_000_000,
'estimated_total': 600_000_000
}
)

self.assertEqual(self.funding_project.estimated_total, 600_000_000)

self.assertAlmostEqual(self.funding_project.estimated_missing_percentage, 10.0)

self.assertAlmostEqual(self.funding_project.total_percentage, 60.0)

self.assertEqual(self.funding_project.html_actual_percentage_width, 50)

self.assertEqual(self.funding_project.html_estimated_percentage_width, 10)

self.assertDictEqual(a, {'user': self.testuser.pk, 'actual_total': 500_000_000})
self.assertTrue(self.funding_project.has_open_contributions)

self.assertIsNone(self.funding_project.completed_at)

Expand Down
Loading

0 comments on commit 3458eae

Please sign in to comment.