Skip to content

Commit

Permalink
Leaderboard performance low-hanging fruit (#344)
Browse files Browse the repository at this point in the history
* Simplify leaderboard HTML template

* Some low-hanging fruit performance improvements and cleanup
  • Loading branch information
RevolutionTech authored Oct 6, 2019
1 parent 68218a8 commit 8ecf4b0
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 36 deletions.
7 changes: 7 additions & 0 deletions accounts/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,11 @@ def public_profile_url(self):
if not self.invest_anonymously:
return reverse("public_profile", args=(self.user.username,))

def get_total_earned(self):
investments = Investment.objects.filter_user_investments(user=self.user)
total_earned = sum(investment.generated_revenue() for investment in investments)
return total_earned

@cache_using_pk
def profile_context(self):
context = {}
Expand Down Expand Up @@ -163,6 +168,8 @@ def profile_context(self):
generated_revenue_user += investment.generated_revenue()
context["artists"][artist.id].total_earned += generated_revenue_user
total_earned += generated_revenue_user

# TODO(lucas): Could refactor to use get_total_earned() instead
context["total_earned"] = total_earned

# Add percentage of return to context
Expand Down
8 changes: 8 additions & 0 deletions campaign/managers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from django.db import models


class InvestmentManager(models.Manager):
def filter_user_investments(self, user):
return self.filter(
charge__customer__user=user, charge__paid=True, charge__refunded=False
)
4 changes: 4 additions & 0 deletions campaign/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

from pinax.stripe.models import Charge

from campaign.managers import InvestmentManager


class Project(models.Model):

Expand Down Expand Up @@ -254,6 +256,8 @@ class Investment(models.Model):
default=1, help_text="The number of shares an investor made in a transaction"
)

objects = InvestmentManager()

def __str__(self):
return "{num_shares} shares in {campaign} to {investor}".format(
num_shares=self.num_shares,
Expand Down
36 changes: 19 additions & 17 deletions campaign/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,48 +5,50 @@
"""

from django.core.cache import cache
from django.db.models import OuterRef, Exists
from django.views.generic import TemplateView

from accounts.models import UserProfile
from campaign.models import Investment


class LeaderboardView(TemplateView):

template_name = "leaderboard/leaderboard.html"

def investor_context(self, investor, key_to_copy):
context = investor.profile_context()
context["amount"] = context[key_to_copy]
context.update(
{
"name": investor.get_display_name(),
"url": investor.public_profile_url(),
"avatar_url": investor.avatar_url(),
}
)
return context
@staticmethod
def investor_context(investor):
return {
"name": investor.get_display_name(),
"url": investor.public_profile_url(),
"avatar_url": investor.avatar_url(),
"amount": investor.get_total_earned(),
}

# TODO(lucas): Review to improve performance
# Warning: top_earned_investors absolutely will not scale, the view is meant
# to be run occasionally (once a day) and then have the whole page cached
def calculate_leaderboard(self):
# Top earned investors
user_profiles = UserProfile.objects.filter(invest_anonymously=False)
investments = Investment.objects.filter_user_investments(user=OuterRef("pk"))
investors = user_profiles.annotate(has_invested=Exists(investments)).filter(
has_invested=True
)

# Top earned investors
top_earned_investors = [
self.investor_context(user_profile, "total_earned")
for user_profile in user_profiles
self.investor_context(investor) for investor in investors
]
top_earned_investors = list(
filter(lambda context: context["amount"] > 0, top_earned_investors)
)
top_earned_investors = sorted(
top_earned_investors, key=lambda context: context["amount"], reverse=True
)[:20]

return {"top_earned_investors": top_earned_investors}
return top_earned_investors

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
leaderboard = cache.get_or_set("leaderboard", self.calculate_leaderboard)
context.update(leaderboard)
context["top_earned_investors"] = leaderboard
return context
17 changes: 0 additions & 17 deletions templates/leaderboard/includes/list.html

This file was deleted.

14 changes: 12 additions & 2 deletions templates/leaderboard/leaderboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,17 @@
{% block content %}
<h2>Top Investors</h2>
<div class="center">
{% include "leaderboard/includes/list.html" with name='Investors' leaders=top_earned_investors %}
<div class="leaderboard">
{% for leader_context in top_earned_investors %}
<a href="{{ leader_context.url }}">
<div class="leaderboard-padding button">
<img class="profile-crop" src="{{ leader_context.avatar_url }}" alt="{{ leader_context.name }}" />
<br/>
<h4 class="chart-name">#{{ forloop.counter }}: {{ leader_context.name }}</h4>
<h4 class="chart-amount">${{ leader_context.amount|notrail_floatformat:2|intcomma }}</h4>
</div>
</a>
{% endfor %}
</div>
</div>

{% endblock %}

0 comments on commit 8ecf4b0

Please sign in to comment.