Files
booking_backend/marketing/services.py
2026-04-10 20:51:43 -05:00

183 lines
5.4 KiB
Python

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