|
1 | 1 | import os |
2 | 2 | import uuid |
| 3 | +from datetime import timedelta |
3 | 4 |
|
4 | 5 | from django.conf import settings |
5 | 6 | from django.contrib import messages |
|
12 | 13 | from django.db.models import Case, Count, F, Q, Value, When |
13 | 14 | from django.http import Http404, HttpResponse, JsonResponse |
14 | 15 | from django.shortcuts import get_object_or_404, redirect, render |
| 16 | +from django.utils import timezone |
15 | 17 | from django.utils.translation import gettext_lazy as _ |
16 | 18 | from django.views.decorators.http import require_http_methods |
17 | 19 | from martor.utils import LazyEncoder |
18 | 20 |
|
19 | 21 | from project.newsletter import operations |
20 | 22 | from project.newsletter.forms import PostForm, SubscriptionForm |
21 | | -from project.newsletter.models import Post, Subscription |
| 23 | +from project.newsletter.models import Category, Post, Subscription |
22 | 24 |
|
23 | 25 | LIST_POSTS_PAGE_SIZE = 100 |
24 | 26 |
|
@@ -162,6 +164,80 @@ def update_post(request, slug): |
162 | 164 | return render(request, "staff/post_form.html", {"form": form, "post": post}) |
163 | 165 |
|
164 | 166 |
|
| 167 | +def determine_buckets(instance): |
| 168 | + """ |
| 169 | + Helper function to determine which buckets should be incremented. |
| 170 | +
|
| 171 | + :param instance: A model instance with ``created`` property. |
| 172 | + :return: tuple(bool, bool, bool) |
| 173 | + """ |
| 174 | + now = timezone.now() |
| 175 | + bound_30_days = now - timedelta(days=30) |
| 176 | + bound_90_days = now - timedelta(days=90) |
| 177 | + bound_180_days = now - timedelta(days=180) |
| 178 | + return ( |
| 179 | + bound_30_days <= instance.created, |
| 180 | + bound_90_days <= instance.created, |
| 181 | + bound_180_days <= instance.created, |
| 182 | + ) |
| 183 | + |
| 184 | + |
| 185 | +def analyze_categorized_model(Model, label): |
| 186 | + """ |
| 187 | + Count the number of instances in the last X days and with a category. |
| 188 | +
|
| 189 | + This can be used with Subscription or Post. |
| 190 | + """ |
| 191 | + count = 0 |
| 192 | + count_30_days = 0 |
| 193 | + count_90_days = 0 |
| 194 | + count_180_days = 0 |
| 195 | + category_aggregates = { |
| 196 | + category.title: 0 for category in Category.objects.order_by("title") |
| 197 | + } |
| 198 | + for obj in Model.objects.all().prefetch_related("categories"): |
| 199 | + # Increment category buckets |
| 200 | + for category in obj.categories.all(): |
| 201 | + category_aggregates[category.title] += 1 |
| 202 | + # Increment our post counts |
| 203 | + count += 1 |
| 204 | + incr_30, incr_90, incr_180 = determine_buckets(obj) |
| 205 | + if incr_30: |
| 206 | + count_30_days += 1 |
| 207 | + if incr_90: |
| 208 | + count_90_days += 1 |
| 209 | + if incr_180: |
| 210 | + count_180_days += 1 |
| 211 | + return { |
| 212 | + label: count, |
| 213 | + f"{label} (30 days)": count_30_days, |
| 214 | + f"{label} (90 days)": count_90_days, |
| 215 | + f"{label} (180 days)": count_180_days, |
| 216 | + }, category_aggregates |
| 217 | + |
| 218 | + |
| 219 | +@staff_member_required(login_url=settings.LOGIN_URL) |
| 220 | +@require_http_methods(["GET"]) |
| 221 | +def analytics(request): |
| 222 | + """ |
| 223 | + The post detail view. |
| 224 | + """ |
| 225 | + ( |
| 226 | + subscription_aggregates, |
| 227 | + subscription_category_aggregates, |
| 228 | + ) = analyze_categorized_model(Subscription, "Subscriptions") |
| 229 | + post_aggregates, post_category_aggregates = analyze_categorized_model(Post, "Posts") |
| 230 | + return render( |
| 231 | + request, |
| 232 | + "staff/analytics.html", |
| 233 | + { |
| 234 | + "aggregates": {**subscription_aggregates, **post_aggregates}, |
| 235 | + "subscription_category_aggregates": subscription_category_aggregates, |
| 236 | + "post_category_aggregates": post_category_aggregates, |
| 237 | + }, |
| 238 | + ) |
| 239 | + |
| 240 | + |
165 | 241 | @staff_member_required(login_url=settings.LOGIN_URL) |
166 | 242 | @require_http_methods(["POST"]) |
167 | 243 | def toggle_post_privacy(request, slug): |
|
0 commit comments