from datetime import timedelta from django.utils import timezone from .models import ListingClick def _clean_param(params, key: str, max_len: int = 255) -> str: raw = params.get(key) if raw is None: return "" s = str(raw).strip() return s[:max_len] def classify_traffic( *, utm_source: str, utm_medium: str, utm_campaign: str, utm_term: str, utm_content: str, gclid: str, fbclid: str, ) -> str: if any( ( utm_source, utm_medium, utm_campaign, utm_term, utm_content, gclid, fbclid, ) ): return ListingClick.TrafficType.MARKETING return ListingClick.TrafficType.ORGANIC def extract_attribution_from_request(request) -> dict: p = request.query_params if hasattr(request, "query_params") else request.GET utm_source = _clean_param(p, "utm_source") utm_medium = _clean_param(p, "utm_medium") utm_campaign = _clean_param(p, "utm_campaign") utm_term = _clean_param(p, "utm_term") utm_content = _clean_param(p, "utm_content") gclid = _clean_param(p, "gclid") fbclid = _clean_param(p, "fbclid") referrer = "" if hasattr(request, "META"): ref = request.META.get("HTTP_REFERER") or "" referrer = ref[:2048] traffic_type = classify_traffic( utm_source=utm_source, utm_medium=utm_medium, utm_campaign=utm_campaign, utm_term=utm_term, utm_content=utm_content, gclid=gclid, fbclid=fbclid, ) return { "utm_source": utm_source, "utm_medium": utm_medium, "utm_campaign": utm_campaign, "utm_term": utm_term, "utm_content": utm_content, "gclid": gclid, "fbclid": fbclid, "referrer": referrer, "traffic_type": traffic_type, } def record_equipment_listing_click(request, equipment_item) -> ListingClick: attrs = extract_attribution_from_request(request) user = request.user if getattr(request, "user", None) and request.user.is_authenticated else None return ListingClick.objects.create( vendor=equipment_item.vendor, listing_type=ListingClick.ListingType.EQUIPMENT, equipment_item=equipment_item, adventure_offering=None, user=user, **attrs, ) def record_adventure_listing_click(request, adventure_offering) -> ListingClick: attrs = extract_attribution_from_request(request) user = request.user if getattr(request, "user", None) and request.user.is_authenticated else None return ListingClick.objects.create( vendor=adventure_offering.vendor, listing_type=ListingClick.ListingType.ADVENTURE, equipment_item=None, adventure_offering=adventure_offering, user=user, **attrs, ) def record_listing_click_from_payload( *, listing_type: str, equipment_item=None, adventure_offering=None, utm_source: str = "", utm_medium: str = "", utm_campaign: str = "", utm_term: str = "", utm_content: str = "", gclid: str = "", fbclid: str = "", referrer: str = "", user=None, ) -> ListingClick: traffic_type = classify_traffic( utm_source=utm_source or "", utm_medium=utm_medium or "", utm_campaign=utm_campaign or "", utm_term=utm_term or "", utm_content=utm_content or "", gclid=gclid or "", fbclid=fbclid or "", ) if listing_type == ListingClick.ListingType.EQUIPMENT: return ListingClick.objects.create( vendor=equipment_item.vendor, listing_type=ListingClick.ListingType.EQUIPMENT, equipment_item=equipment_item, adventure_offering=None, traffic_type=traffic_type, utm_source=utm_source[:255], utm_medium=utm_medium[:255], utm_campaign=utm_campaign[:255], utm_term=utm_term[:255], utm_content=utm_content[:255], gclid=gclid[:255], fbclid=fbclid[:255], referrer=referrer[:2048], user=user, ) return ListingClick.objects.create( vendor=adventure_offering.vendor, listing_type=ListingClick.ListingType.ADVENTURE, equipment_item=None, adventure_offering=adventure_offering, traffic_type=traffic_type, utm_source=utm_source[:255], utm_medium=utm_medium[:255], utm_campaign=utm_campaign[:255], utm_term=utm_term[:255], utm_content=utm_content[:255], gclid=gclid[:255], fbclid=fbclid[:255], referrer=referrer[:2048], user=user, ) ATTRIBUTION_MAX_AGE_DAYS = 90 def listing_click_valid_for_booking(*, click, equipment_item=None, adventure_offering=None) -> bool: if click is None: return False cutoff = timezone.now() - timedelta(days=ATTRIBUTION_MAX_AGE_DAYS) if click.created_at < cutoff: return False if equipment_item is not None: return ( click.listing_type == ListingClick.ListingType.EQUIPMENT and click.equipment_item_id == equipment_item.id and click.vendor_id == equipment_item.vendor_id ) if adventure_offering is not None: return ( click.listing_type == ListingClick.ListingType.ADVENTURE and click.adventure_offering_id == adventure_offering.id and click.vendor_id == adventure_offering.vendor_id ) return False