mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-22 18:51:13 -05:00
Refactor code structure and remove redundant changes
This commit is contained in:
340
django-backend/apps/core/utils/seo.py
Normal file
340
django-backend/apps/core/utils/seo.py
Normal file
@@ -0,0 +1,340 @@
|
||||
"""
|
||||
SEO Meta Tag Generation Utilities
|
||||
|
||||
Generates comprehensive meta tags for social sharing (OpenGraph, Twitter Cards),
|
||||
search engines (structured data), and general SEO optimization.
|
||||
"""
|
||||
|
||||
from typing import Dict, Optional
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
class SEOTags:
|
||||
"""Generate comprehensive SEO meta tags for any page."""
|
||||
|
||||
BASE_URL = getattr(settings, 'SITE_URL', 'https://thrillwiki.com')
|
||||
DEFAULT_OG_IMAGE = f"{BASE_URL}/static/images/og-default.png"
|
||||
TWITTER_HANDLE = "@thrillwiki"
|
||||
SITE_NAME = "ThrillWiki"
|
||||
|
||||
@classmethod
|
||||
def for_park(cls, park) -> Dict[str, str]:
|
||||
"""
|
||||
Generate meta tags for a park page.
|
||||
|
||||
Args:
|
||||
park: Park model instance
|
||||
|
||||
Returns:
|
||||
Dictionary of meta tags for HTML head
|
||||
"""
|
||||
title = f"{park.name} - Theme Park Database | ThrillWiki"
|
||||
description = f"Explore {park.name} in {park.locality.name}, {park.country.name}. View rides, reviews, photos, and history on ThrillWiki."
|
||||
|
||||
og_image = cls._get_og_image_url('park', str(park.id))
|
||||
url = f"{cls.BASE_URL}/parks/{park.slug}/"
|
||||
|
||||
return {
|
||||
# Basic Meta
|
||||
'title': title,
|
||||
'description': description,
|
||||
'keywords': f"{park.name}, theme park, amusement park, {park.locality.name}, {park.country.name}",
|
||||
|
||||
# OpenGraph (Facebook, LinkedIn, Discord)
|
||||
'og:title': park.name,
|
||||
'og:description': description,
|
||||
'og:type': 'website',
|
||||
'og:url': url,
|
||||
'og:image': og_image,
|
||||
'og:image:width': '1200',
|
||||
'og:image:height': '630',
|
||||
'og:site_name': cls.SITE_NAME,
|
||||
'og:locale': 'en_US',
|
||||
|
||||
# Twitter Card
|
||||
'twitter:card': 'summary_large_image',
|
||||
'twitter:site': cls.TWITTER_HANDLE,
|
||||
'twitter:title': park.name,
|
||||
'twitter:description': description,
|
||||
'twitter:image': og_image,
|
||||
|
||||
# Additional
|
||||
'canonical': url,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def for_ride(cls, ride) -> Dict[str, str]:
|
||||
"""
|
||||
Generate meta tags for a ride page.
|
||||
|
||||
Args:
|
||||
ride: Ride model instance
|
||||
|
||||
Returns:
|
||||
Dictionary of meta tags for HTML head
|
||||
"""
|
||||
title = f"{ride.name} at {ride.park.name} | ThrillWiki"
|
||||
|
||||
# Build description with available details
|
||||
description_parts = [
|
||||
f"{ride.name} is a {ride.ride_type.name}",
|
||||
f"at {ride.park.name}",
|
||||
]
|
||||
|
||||
if ride.opened_year:
|
||||
description_parts.append(f"Built in {ride.opened_year}")
|
||||
|
||||
if ride.manufacturer:
|
||||
description_parts.append(f"by {ride.manufacturer.name}")
|
||||
|
||||
description = ". ".join(description_parts) + ". Read reviews and view photos."
|
||||
|
||||
og_image = cls._get_og_image_url('ride', str(ride.id))
|
||||
url = f"{cls.BASE_URL}/parks/{ride.park.slug}/rides/{ride.slug}/"
|
||||
|
||||
keywords_parts = [
|
||||
ride.name,
|
||||
ride.ride_type.name,
|
||||
ride.park.name,
|
||||
]
|
||||
if ride.manufacturer:
|
||||
keywords_parts.append(ride.manufacturer.name)
|
||||
keywords_parts.extend(['roller coaster', 'theme park ride'])
|
||||
|
||||
return {
|
||||
'title': title,
|
||||
'description': description,
|
||||
'keywords': ', '.join(keywords_parts),
|
||||
|
||||
# OpenGraph
|
||||
'og:title': f"{ride.name} at {ride.park.name}",
|
||||
'og:description': description,
|
||||
'og:type': 'article',
|
||||
'og:url': url,
|
||||
'og:image': og_image,
|
||||
'og:image:width': '1200',
|
||||
'og:image:height': '630',
|
||||
'og:site_name': cls.SITE_NAME,
|
||||
'og:locale': 'en_US',
|
||||
|
||||
# Twitter
|
||||
'twitter:card': 'summary_large_image',
|
||||
'twitter:site': cls.TWITTER_HANDLE,
|
||||
'twitter:title': f"{ride.name} at {ride.park.name}",
|
||||
'twitter:description': description,
|
||||
'twitter:image': og_image,
|
||||
|
||||
'canonical': url,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def for_company(cls, company) -> Dict[str, str]:
|
||||
"""
|
||||
Generate meta tags for a manufacturer/company page.
|
||||
|
||||
Args:
|
||||
company: Company model instance
|
||||
|
||||
Returns:
|
||||
Dictionary of meta tags for HTML head
|
||||
"""
|
||||
# Get company type name safely
|
||||
company_type_name = company.company_types.first().name if company.company_types.exists() else "Company"
|
||||
|
||||
title = f"{company.name} - {company_type_name} | ThrillWiki"
|
||||
description = f"{company.name} is a {company_type_name}. View their rides, history, and contributions to the theme park industry."
|
||||
|
||||
url = f"{cls.BASE_URL}/manufacturers/{company.slug}/"
|
||||
|
||||
return {
|
||||
'title': title,
|
||||
'description': description,
|
||||
'keywords': f"{company.name}, {company_type_name}, theme park manufacturer, ride manufacturer",
|
||||
|
||||
# OpenGraph
|
||||
'og:title': company.name,
|
||||
'og:description': description,
|
||||
'og:type': 'website',
|
||||
'og:url': url,
|
||||
'og:image': cls.DEFAULT_OG_IMAGE,
|
||||
'og:image:width': '1200',
|
||||
'og:image:height': '630',
|
||||
'og:site_name': cls.SITE_NAME,
|
||||
'og:locale': 'en_US',
|
||||
|
||||
# Twitter
|
||||
'twitter:card': 'summary',
|
||||
'twitter:site': cls.TWITTER_HANDLE,
|
||||
'twitter:title': company.name,
|
||||
'twitter:description': description,
|
||||
'twitter:image': cls.DEFAULT_OG_IMAGE,
|
||||
|
||||
'canonical': url,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def for_ride_model(cls, model) -> Dict[str, str]:
|
||||
"""
|
||||
Generate meta tags for a ride model page.
|
||||
|
||||
Args:
|
||||
model: RideModel model instance
|
||||
|
||||
Returns:
|
||||
Dictionary of meta tags for HTML head
|
||||
"""
|
||||
title = f"{model.name} by {model.manufacturer.name} | ThrillWiki"
|
||||
description = f"The {model.name} is a {model.ride_type.name} model manufactured by {model.manufacturer.name}. View installations and specifications."
|
||||
|
||||
url = f"{cls.BASE_URL}/models/{model.slug}/"
|
||||
|
||||
return {
|
||||
'title': title,
|
||||
'description': description,
|
||||
'keywords': f"{model.name}, {model.manufacturer.name}, {model.ride_type.name}, ride model, theme park",
|
||||
|
||||
# OpenGraph
|
||||
'og:title': f"{model.name} by {model.manufacturer.name}",
|
||||
'og:description': description,
|
||||
'og:type': 'website',
|
||||
'og:url': url,
|
||||
'og:image': cls.DEFAULT_OG_IMAGE,
|
||||
'og:image:width': '1200',
|
||||
'og:image:height': '630',
|
||||
'og:site_name': cls.SITE_NAME,
|
||||
'og:locale': 'en_US',
|
||||
|
||||
# Twitter
|
||||
'twitter:card': 'summary',
|
||||
'twitter:site': cls.TWITTER_HANDLE,
|
||||
'twitter:title': f"{model.name} by {model.manufacturer.name}",
|
||||
'twitter:description': description,
|
||||
'twitter:image': cls.DEFAULT_OG_IMAGE,
|
||||
|
||||
'canonical': url,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def for_home(cls) -> Dict[str, str]:
|
||||
"""Generate meta tags for home page."""
|
||||
title = "ThrillWiki - The Ultimate Theme Park & Roller Coaster Database"
|
||||
description = "Explore thousands of theme parks and roller coasters worldwide. Read reviews, view photos, track your ride credits, and discover your next adventure."
|
||||
|
||||
return {
|
||||
'title': title,
|
||||
'description': description,
|
||||
'keywords': 'theme parks, roller coasters, amusement parks, ride database, coaster enthusiasts, thrillwiki',
|
||||
|
||||
'og:title': title,
|
||||
'og:description': description,
|
||||
'og:type': 'website',
|
||||
'og:url': cls.BASE_URL,
|
||||
'og:image': cls.DEFAULT_OG_IMAGE,
|
||||
'og:image:width': '1200',
|
||||
'og:image:height': '630',
|
||||
'og:site_name': cls.SITE_NAME,
|
||||
'og:locale': 'en_US',
|
||||
|
||||
'twitter:card': 'summary_large_image',
|
||||
'twitter:site': cls.TWITTER_HANDLE,
|
||||
'twitter:title': title,
|
||||
'twitter:description': description,
|
||||
'twitter:image': cls.DEFAULT_OG_IMAGE,
|
||||
|
||||
'canonical': cls.BASE_URL,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _get_og_image_url(entity_type: str, entity_id: str) -> str:
|
||||
"""
|
||||
Generate dynamic OG image URL.
|
||||
|
||||
Args:
|
||||
entity_type: Type of entity (park, ride, company, model)
|
||||
entity_id: Entity ID
|
||||
|
||||
Returns:
|
||||
URL to dynamic OG image endpoint
|
||||
"""
|
||||
# Use existing ssrOG endpoint
|
||||
return f"{SEOTags.BASE_URL}/api/og?type={entity_type}&id={entity_id}"
|
||||
|
||||
@classmethod
|
||||
def structured_data_for_park(cls, park) -> dict:
|
||||
"""
|
||||
Generate JSON-LD structured data for a park.
|
||||
|
||||
Args:
|
||||
park: Park model instance
|
||||
|
||||
Returns:
|
||||
Dictionary for JSON-LD script tag
|
||||
"""
|
||||
data = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "TouristAttraction",
|
||||
"name": park.name,
|
||||
"description": f"Theme park in {park.locality.name}, {park.country.name}",
|
||||
"url": f"{cls.BASE_URL}/parks/{park.slug}/",
|
||||
"image": cls._get_og_image_url('park', str(park.id)),
|
||||
"address": {
|
||||
"@type": "PostalAddress",
|
||||
"addressLocality": park.locality.name,
|
||||
"addressCountry": park.country.code,
|
||||
},
|
||||
}
|
||||
|
||||
# Add geo coordinates if available
|
||||
if hasattr(park, 'latitude') and hasattr(park, 'longitude') and park.latitude and park.longitude:
|
||||
data["geo"] = {
|
||||
"@type": "GeoCoordinates",
|
||||
"latitude": str(park.latitude),
|
||||
"longitude": str(park.longitude),
|
||||
}
|
||||
|
||||
# Add aggregate rating if available
|
||||
if hasattr(park, 'review_count') and park.review_count > 0:
|
||||
data["aggregateRating"] = {
|
||||
"@type": "AggregateRating",
|
||||
"ratingValue": str(park.average_rating),
|
||||
"reviewCount": park.review_count,
|
||||
}
|
||||
|
||||
return data
|
||||
|
||||
@classmethod
|
||||
def structured_data_for_ride(cls, ride) -> dict:
|
||||
"""
|
||||
Generate JSON-LD structured data for a ride.
|
||||
|
||||
Args:
|
||||
ride: Ride model instance
|
||||
|
||||
Returns:
|
||||
Dictionary for JSON-LD script tag
|
||||
"""
|
||||
data = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Product",
|
||||
"name": ride.name,
|
||||
"description": f"{ride.name} is a {ride.ride_type.name} at {ride.park.name}",
|
||||
"url": f"{cls.BASE_URL}/parks/{ride.park.slug}/rides/{ride.slug}/",
|
||||
"image": cls._get_og_image_url('ride', str(ride.id)),
|
||||
}
|
||||
|
||||
# Add manufacturer if available
|
||||
if ride.manufacturer:
|
||||
data["manufacturer"] = {
|
||||
"@type": "Organization",
|
||||
"name": ride.manufacturer.name,
|
||||
}
|
||||
|
||||
# Add aggregate rating if available
|
||||
if hasattr(ride, 'review_count') and ride.review_count > 0:
|
||||
data["aggregateRating"] = {
|
||||
"@type": "AggregateRating",
|
||||
"ratingValue": str(ride.average_rating),
|
||||
"reviewCount": ride.review_count,
|
||||
}
|
||||
|
||||
return data
|
||||
Reference in New Issue
Block a user