From 9ed7e126a54547c78832b99d6470b2b5a40e3712 Mon Sep 17 00:00:00 2001 From: Laurent Date: Wed, 5 Mar 2025 12:31:43 +0100 Subject: [PATCH] add views --- .../templates/subscriptions/base.html | 40 +++++++ .../subscriptions/monthly_summary.html | 39 +++++++ .../templates/subscriptions/offer_detail.html | 106 +++++++++++++++++ subscriptions/views.py | 110 +++++++++++++++++- 4 files changed, 294 insertions(+), 1 deletion(-) create mode 100644 subscriptions/templates/subscriptions/base.html create mode 100644 subscriptions/templates/subscriptions/monthly_summary.html create mode 100644 subscriptions/templates/subscriptions/offer_detail.html diff --git a/subscriptions/templates/subscriptions/base.html b/subscriptions/templates/subscriptions/base.html new file mode 100644 index 0000000..dba8f03 --- /dev/null +++ b/subscriptions/templates/subscriptions/base.html @@ -0,0 +1,40 @@ + + + + + + + {% block title %}Poker Analytics{% endblock %} + {% load static %} + + + + + +
+
+
+ Poker Analytics + +
+
+
+ +
+ {% block content %}{% endblock %} +
+ + + + {% block scripts %}{% endblock %} + + diff --git a/subscriptions/templates/subscriptions/monthly_summary.html b/subscriptions/templates/subscriptions/monthly_summary.html new file mode 100644 index 0000000..559da00 --- /dev/null +++ b/subscriptions/templates/subscriptions/monthly_summary.html @@ -0,0 +1,39 @@ + +{% extends "base.html" %} + +{% block title %}Monthly Offer Summary{% endblock %} + +{% block content %} +
+

Monthly Offer Summary

+ + {% if monthly_offers %} + {% for month_data in monthly_offers %} +
+
+

{{ month_data.month|date:"F Y" }}

+
+
+ +
+
+ {% endfor %} + {% else %} + + {% endif %} +
+{% endblock %} diff --git a/subscriptions/templates/subscriptions/offer_detail.html b/subscriptions/templates/subscriptions/offer_detail.html new file mode 100644 index 0000000..5191ea4 --- /dev/null +++ b/subscriptions/templates/subscriptions/offer_detail.html @@ -0,0 +1,106 @@ + +{% extends "base.html" %} + +{% block title %}Offer Detail: {{ offer_id }} - {{ month_name }}{% endblock %} + +{% block content %} +
+
+

{{ offer_id }} - {{ month_name }}

+ + + + + + Back to Summary + +
+ + {% if notifications %} +
+
+ + + + + + + + + + + {% for notification in notifications %} + + + + + + + {% endfor %} + +
+ Signed Date + + Notification Type + + Original Transaction ID + + Offer Identifier +
+ {{ notification.signedDate }} + + {% if notification.notificationType == 'SUBSCRIBED' %} + + {{ notification.notificationType }} + + {% elif notification.notificationType == 'REFUND' %} + + {{ notification.notificationType }} + + {% else %} + {{ notification.notificationType }} + {% endif %} + + {{ notification.originalTransactionId }} + + {{ notification.offerIdentifier }} +
+
+
+ +
+
+

Summary

+
+
+
+
+

Subscribed

+

{{ subscribed_count }}

+
+ +
+

Refunds

+

{{ refund_count }}

+
+ +
+

Net Subscriptions

+

{{ net_count }}

+
+ +
+

Revenue ($15/sub)

+

${{ revenue }}

+
+
+
+
+ {% else %} + + {% endif %} +
+{% endblock %} diff --git a/subscriptions/views.py b/subscriptions/views.py index 7e4fbaa..61b21e5 100644 --- a/subscriptions/views.py +++ b/subscriptions/views.py @@ -1,8 +1,13 @@ from django.shortcuts import render from django.http import HttpResponse, JsonResponse from django.views.decorators.csrf import csrf_exempt -from .models import ASSNotification +from django.views.generic import ListView, DetailView +from django.db.models import Count, Q, Sum, Case, When, IntegerField from django.conf import settings +from django.db.models.functions import TruncMonth +from django.shortcuts import render + +from .models import ASSNotification from appstoreserverlibrary.signed_data_verifier import VerificationException, SignedDataVerifier from appstoreserverlibrary.api_client import AppStoreServerAPIClient, APIException @@ -190,3 +195,106 @@ def _get_root_cert(root_cert_path): root_cert = load_certificate(FILETYPE_ASN1, data) return root_cert + +class MonthlyOfferSummaryView(ListView): + template_name = 'subscriptions/monthly_summary.html' + context_object_name = 'monthly_offers' + + def get_queryset(self): + # Annotate notifications with month, then group by month and offerIdentifier + # Only include entries where offerIdentifier is not null + monthly_offers = ASSNotification.objects.exclude(offerIdentifier__isnull=True)\ + .annotate(month=TruncMonth('signedDate'))\ + .values('month', 'offerIdentifier')\ + .annotate(count=Count('id'))\ + .order_by('-month', 'offerIdentifier') + + # Group by month + result = {} + for offer in monthly_offers: + month = offer['month'] + if month not in result: + result[month] = [] + result[month].append({ + 'offerIdentifier': offer['offerIdentifier'], + 'count': offer['count'] + }) + + # Convert to list of dicts for template + monthly_data = [ + {'month': month, 'offers': offers} + for month, offers in result.items() + ] + + return sorted(monthly_data, key=lambda x: x['month'], reverse=True) + + +class OfferDetailView(ListView): + template_name = 'subscriptions/offer_detail.html' + context_object_name = 'notifications' + + def get_queryset(self): + month = self.kwargs.get('month') + year = self.kwargs.get('year') + offer_id = self.kwargs.get('offer_id') + + # Get the first day of the month and last day of the month + start_date = datetime.date(int(year), int(month), 1) + if int(month) == 12: + end_date = datetime.date(int(year) + 1, 1, 1) - datetime.timedelta(days=1) + else: + end_date = datetime.date(int(year), int(month) + 1, 1) - datetime.timedelta(days=1) + + # Filter notifications + return ASSNotification.objects.filter( + signedDate__date__gte=start_date, + signedDate__date__lte=end_date, + offerIdentifier=offer_id, + notificationType__in=['SUBSCRIBED', 'REFUND'] + ).order_by('signedDate') + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + month = self.kwargs.get('month') + year = self.kwargs.get('year') + offer_id = self.kwargs.get('offer_id') + + # Get the first day of the month and last day of the month + start_date = datetime.date(int(year), int(month), 1) + if int(month) == 12: + end_date = datetime.date(int(year) + 1, 1, 1) - datetime.timedelta(days=1) + else: + end_date = datetime.date(int(year), int(month) + 1, 1) - datetime.timedelta(days=1) + + # Count statistics + stats = ASSNotification.objects.filter( + signedDate__date__gte=start_date, + signedDate__date__lte=end_date, + offerIdentifier=offer_id, + notificationType__in=['SUBSCRIBED', 'REFUND'] + ).aggregate( + subscribed_count=Count(Case( + When(notificationType='SUBSCRIBED', then=1), + output_field=IntegerField() + )), + refund_count=Count(Case( + When(notificationType='REFUND', then=1), + output_field=IntegerField() + )) + ) + + subscribed_count = stats['subscribed_count'] or 0 + refund_count = stats['refund_count'] or 0 + net_count = subscribed_count - refund_count + revenue = net_count * 15 # $15 per net subscription + + context.update({ + 'month_name': start_date.strftime('%B %Y'), + 'offer_id': offer_id, + 'subscribed_count': subscribed_count, + 'refund_count': refund_count, + 'net_count': net_count, + 'revenue': revenue + }) + + return context