Skip to content

Commit 3109df4

Browse files
committed
Merge branch 'croolicjah-papal-mrr-page'
2 parents 79dcb4b + 422c82f commit 3109df4

File tree

4 files changed

+220
-1
lines changed

4 files changed

+220
-1
lines changed

money/models.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,3 @@ def get_absolute_url(self):
4949
"month_pk": self.id,
5050
"month_slug": self.slug(),
5151
})
52-
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
{% extends 'sitewide/base.html' %}
2+
3+
{% block content %}
4+
<div id="content" class="bg-white container">
5+
<div class="col text-center w-100">
6+
<div class="row pt-3 mr-auto ml-auto justify-content-between">
7+
<h2>Paypal Monthly Stats</h2>
8+
<form action="" method="post">
9+
{% csrf_token %}
10+
<button id="grab-from-api" type="submit" class="btn btn-outline-info btn-lg">
11+
<span id="spinner" class="spinner-border spinner-border" role="status" aria-hidden="true" hidden></span>
12+
<span id="text">Grab data from the Paypal API</span>
13+
</button>
14+
</form>
15+
</div>
16+
<div class="col pt-md-5 pb-md-5 badge badge-danger mt-4 mb-4">
17+
<div class="h3"><strong>MRR on {{ current_date|date }}</strong></div>
18+
<div class="badge text-danger badge-pill badge-light"><span class="display-2"><strong>${{ chart.monthly_revenue|floatformat:2 }}</strong></span></div>
19+
</div>
20+
</div>
21+
22+
<div class="row">
23+
<div class="table-responsive col-12 col-md-9 mt-3">
24+
<h2>Prices table</h2>
25+
<table class="bg-white table-primary table table-hover">
26+
<thead>
27+
<tr>
28+
<th class="text-center" scope="col">Price of subscription</th>
29+
<th class="text-center" scope="col">Number of subscriptions</th>
30+
<th class="text-center" scope="col">Type of subscription</th>
31+
</tr>
32+
</thead>
33+
<tbody>
34+
{% for key, value in chart.monthly.items %}
35+
<tr>
36+
<td class="text-center">${{ key|floatformat }}</td>
37+
<td class="text-center">{{ value|floatformat }}</td>
38+
<td class="text-center"> monthly</td>
39+
</tr>
40+
{% endfor %}
41+
{% for key, value in chart.yearly.items %}
42+
<tr>
43+
<td class="text-center">${{ key|floatformat }}</td>
44+
<td class="text-center">{{ value|floatformat }}</td>
45+
<td class="text-center"> yearly </td>
46+
</tr>
47+
{% endfor %}
48+
</tbody>
49+
</table>
50+
</div>
51+
<div class="col-11 col-md-3 text-center align-self-center">
52+
<h3>Average prices:</h3>
53+
<div class="badge badge-primary p-2 mb-2" >
54+
<span class="h2">Monthly <span class="badge badge-light">${{ chart.avg.monthly|floatformat:2 }}</span></span>
55+
</div>
56+
<div class="badge badge-warning p-2 mb-2" >
57+
<span class="h2">Yearly <span class="badge badge-light">${{ chart.avg.yearly|floatformat:2 }}</span></span>
58+
</div>
59+
<div class="badge badge-success p-2 mb-4">
60+
<span class="h2">Combined <span class="badge badge-light">${{ chart.avg.combined|floatformat:2 }}</span></span>
61+
</div>
62+
</div>
63+
</div>
64+
<div class="table-responsive">
65+
<h2>Active Paypal users</h2>
66+
<table class="bg-white table-primary table table-hover">
67+
<thead>
68+
<tr>
69+
<th scope="col">#</th>
70+
<th scope="col">User</th>
71+
<th scope="col">Email</th>
72+
<th class="text-center" scope="col">Subscription price</th>
73+
<th class="text-center" scope="col">Type od subscription</th>
74+
</tr>
75+
</thead>
76+
<tbody>
77+
{% for user in active_users %}
78+
<tr>
79+
<th scope="row">{{ forloop.counter }}</th>
80+
<td>{{ user.username }}</td>
81+
<td>{{ user.email }}</td>
82+
<td class="text-center">${{ user.sub_price|floatformat }}</td>
83+
<td class="text-center">{{ user.sub_type }}</td>
84+
</tr>
85+
{% endfor %}
86+
</tbody>
87+
</table>
88+
</div>
89+
</div>
90+
91+
{% endblock %}
92+
93+
{% block javascript %}
94+
<script>
95+
$("#grab-from-api").click(
96+
function() {
97+
$("#spinner").removeAttr("hidden");
98+
$("#text").text(" Wait a sec. Fetching data from API");
99+
},
100+
);
101+
</script>
102+
{% endblock %}

money/urls.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
from django.urls import path
22
from . import views
3+
from .views import Paypal
34

45
app_name = 'money'
56

67
urlpatterns = [
78
path('/<int:month_pk>/<slug:month_slug>/', views.view_month, name='view_month'),
89
path('', views.home, name='home'),
10+
path('/paypal', Paypal.as_view(), name='paypal_mrr'),
911
]

money/views.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
1+
from datetime import datetime
2+
from operator import itemgetter
3+
4+
from django.contrib.admin.views.decorators import staff_member_required
5+
from django.contrib.auth.decorators import login_required, permission_required, user_passes_test
6+
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
17
from django.db.models import Sum
28
from django.shortcuts import render, get_object_or_404
9+
from django.utils.decorators import method_decorator
10+
from django.views import View
11+
12+
from paypalrestsdk.exceptions import MethodNotAllowed
13+
from paypalrestsdk import BillingAgreement, ResourceNotFound
14+
315
from .models import Month
16+
from sitewide.models import ZappyUser
417

518

619
def home(request):
@@ -22,3 +35,106 @@ def home(request):
2235
def view_month(request, month_pk, month_slug):
2336
month = get_object_or_404(Month, pk=month_pk)
2437
return render(request, 'money/view_month.html', {'month': month})
38+
39+
class SuperuserRequiredMixin(LoginRequiredMixin, UserPassesTestMixin):
40+
def test_func(self):
41+
return self.request.user.is_superuser
42+
43+
class Paypal(SuperuserRequiredMixin, View):
44+
template_name = "paypal_mrr.html"
45+
46+
def __init__(self):
47+
super().__init__()
48+
user_list = self.get_active_paypal_users()
49+
self.current_date = datetime.now()
50+
if user_list:
51+
self.paypal_user_list = sorted(user_list, key=itemgetter('username'))
52+
self.price_chart = self.get_subs_chart()
53+
else:
54+
self.price_chart = None
55+
self.paypal_user_list = None
56+
57+
def get(self, request, *args, **kwargs):
58+
return render(request, "money/paypal_mrr.html", {
59+
"active_users": self.paypal_user_list,
60+
"chart": self.price_chart,
61+
"current_date": self.current_date
62+
})
63+
64+
def post(self, request, *args, **kwargs):
65+
self.paypal_user_list = sorted(self.get_active_paypal_users(), key=itemgetter('username'))
66+
self.price_chart = self.get_subs_chart()
67+
return render(request, "money/paypal_mrr.html", {
68+
"active_users": self.paypal_user_list,
69+
"chart": self.price_chart,
70+
"current_date": self.current_date
71+
})
72+
73+
@staticmethod
74+
def get_active_paypal_users():
75+
"""returns all active paypal users"""
76+
paypal_users = ZappyUser.objects.filter(paypal_subscription_id__isnull=False)
77+
paypal_active_users = []
78+
79+
for user in paypal_users:
80+
# in case there is empty string instead PayPal sub id
81+
if user.paypal_subscription_id:
82+
try:
83+
billing_agreement = BillingAgreement.find(user.paypal_subscription_id)
84+
if not billing_agreement.id:
85+
continue
86+
except ResourceNotFound:
87+
print("Billing Agreement Not Found")
88+
continue
89+
except MethodNotAllowed:
90+
print("Billing Agreement Not Found")
91+
continue
92+
93+
if billing_agreement.state.lower() == 'active':
94+
if billing_agreement.plan.payment_definitions[0].frequency == 'MONTH':
95+
sub_type = 'monthly'
96+
else:
97+
sub_type = 'yearly'
98+
99+
paypal_user = {
100+
'username': user.username,
101+
'email': user.email,
102+
'sub_price': float(billing_agreement.plan.payment_definitions[0].amount.value),
103+
'sub_type': sub_type,
104+
}
105+
paypal_active_users.append(paypal_user)
106+
107+
return paypal_active_users
108+
109+
def get_subs_chart(self):
110+
"""prepares data for price table. Counts as well average subscriptions prices and MMR"""
111+
chart = {
112+
'monthly': {},
113+
'yearly': {}
114+
}
115+
yearly, monthly = 0, 0
116+
# collect set of subscription prices and amount for each price
117+
for pal in self.paypal_user_list:
118+
chart[pal['sub_type']][pal['sub_price']] = chart[pal['sub_type']].get(pal['sub_price'], 0) + 1
119+
120+
yearly_sum = sum([k * v for k, v in chart['yearly'].items()])
121+
monthly_sum = sum([k * v for k, v in chart['monthly'].items()])
122+
123+
# count average prices of subscriptions. if statements to avoid division by 0
124+
if chart['yearly']:
125+
yearly = yearly_sum/sum([v for v in chart['yearly'].values()])
126+
if chart['monthly']:
127+
monthly = monthly_sum/sum([v for v in chart['monthly'].values()])
128+
129+
combined = (yearly/12 + monthly)/2
130+
131+
chart['avg'] = {
132+
'yearly': yearly,
133+
'monthly': monthly,
134+
'combined': combined,
135+
}
136+
137+
# count MRR
138+
chart['monthly_revenue'] = yearly_sum/12 + monthly_sum
139+
140+
return chart

0 commit comments

Comments
 (0)