Files
thrillwiki_django_no_react/apps/core/analytics.py
pacnpal 75cc618c2b update
2025-09-21 20:04:42 -04:00

126 lines
4.6 KiB
Python

from django.db import models
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.utils import timezone
from django.db.models import Count
from datetime import timedelta
import pghistory
@pghistory.track()
class PageView(models.Model):
content_type = models.ForeignKey(
ContentType, on_delete=models.CASCADE, related_name="page_views"
)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey("content_type", "object_id")
timestamp = models.DateTimeField(auto_now_add=True, db_index=True)
ip_address = models.GenericIPAddressField()
user_agent = models.CharField(max_length=512, blank=True)
class Meta:
indexes = [
models.Index(fields=["timestamp"]),
models.Index(fields=["content_type", "object_id"]),
]
@classmethod
def get_trending_items(cls, model_class, hours=168, limit=10):
"""Get trending items of a specific model class based on views in last X hours.
Args:
model_class: The model class to get trending items for (e.g., Park, Ride)
hours (int): Number of hours to look back for views (default: 168 = 7 days)
limit (int): Maximum number of items to return (default: 10)
Returns:
QuerySet: The trending items ordered by view count
"""
content_type = ContentType.objects.get_for_model(model_class)
cutoff = timezone.now() - timedelta(hours=hours)
# Query through the ContentType relationship
item_ids = (
cls.objects.filter(content_type=content_type, timestamp__gte=cutoff)
.values("object_id")
.annotate(view_count=Count("id"))
.filter(view_count__gt=0)
.order_by("-view_count")
.values_list("object_id", flat=True)[:limit]
)
# Get the actual items in the correct order
if item_ids:
# Convert the list to a string of comma-separated values
id_list = list(item_ids)
# Use Case/When to preserve the ordering
from django.db.models import Case, When
preserved = Case(*[When(pk=pk, then=pos) for pos, pk in enumerate(id_list)])
return model_class.objects.filter(pk__in=id_list).order_by(preserved)
return model_class.objects.none()
@classmethod
def get_views_growth(
cls, content_type, object_id, current_period_hours, previous_period_hours
):
"""Get view growth statistics between two time periods.
Args:
content_type: ContentType instance for the model
object_id: ID of the specific object
current_period_hours: Hours for current period (e.g., 24)
previous_period_hours: Hours for previous period (e.g., 48)
Returns:
tuple: (current_views, previous_views, growth_percentage)
"""
from datetime import timedelta
now = timezone.now()
# Current period: last X hours
current_start = now - timedelta(hours=current_period_hours)
current_views = cls.objects.filter(
content_type=content_type, object_id=object_id, timestamp__gte=current_start
).count()
# Previous period: X hours before current period
previous_start = now - timedelta(hours=previous_period_hours)
previous_end = current_start
previous_views = cls.objects.filter(
content_type=content_type,
object_id=object_id,
timestamp__gte=previous_start,
timestamp__lt=previous_end,
).count()
# Calculate growth percentage
if previous_views == 0:
growth_percentage = current_views * 100 if current_views > 0 else 0
else:
growth_percentage = (
(current_views - previous_views) / previous_views
) * 100
return current_views, previous_views, growth_percentage
@classmethod
def get_total_views_count(cls, content_type, object_id, hours=168):
"""Get total view count for an object within specified hours.
Args:
content_type: ContentType instance for the model
object_id: ID of the specific object
hours: Number of hours to look back (default: 168 = 7 days)
Returns:
int: Total view count
"""
cutoff = timezone.now() - timedelta(hours=hours)
return cls.objects.filter(
content_type=content_type, object_id=object_id, timestamp__gte=cutoff
).count()