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()