elmeg-demo/backend/slugify.py
fullsizemalt 3edbcdeb64 feat: Add slug support for all entities
- Add slug fields to Song, Venue, Show, Tour, Performance models
- Update routers to support lookup by slug or ID
- Create slugify.py utility for generating URL-safe slugs
- Add migration script to generate slugs for existing data
- Performance slugs use songslug-YYYY-MM-DD format
2025-12-21 18:46:40 -08:00

134 lines
3.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Slug generation utilities
"""
import re
from typing import Optional
def generate_slug(text: str, max_length: int = 50) -> str:
"""
Generate a URL-safe slug from text.
Examples:
"Tweezer Reprise" -> "tweezer-reprise"
"You Enjoy Myself" -> "you-enjoy-myself"
"The Gorge Amphitheatre" -> "the-gorge-amphitheatre"
"""
if not text:
return ""
# Convert to lowercase
slug = text.lower()
# Replace common special characters
replacements = {
"'": "",
"'": "",
'"': "",
"&": "and",
"+": "and",
"@": "at",
"#": "",
"$": "",
"%": "",
"!": "",
"?": "",
".": "",
",": "",
":": "",
";": "",
"/": "-",
"\\": "-",
"(": "",
")": "",
"[": "",
"]": "",
"{": "",
"}": "",
"<": "",
">": "",
"": "-",
"": "-",
"...": "",
}
for old, new in replacements.items():
slug = slug.replace(old, new)
# Replace any non-alphanumeric characters with dashes
slug = re.sub(r'[^a-z0-9]+', '-', slug)
# Remove leading/trailing dashes and collapse multiple dashes
slug = re.sub(r'-+', '-', slug).strip('-')
# Truncate to max length (at word boundary if possible)
if len(slug) > max_length:
slug = slug[:max_length]
# Try to cut at last dash to avoid partial words
last_dash = slug.rfind('-')
if last_dash > max_length // 2:
slug = slug[:last_dash]
return slug
def generate_unique_slug(
base_text: str,
existing_slugs: list[str],
max_length: int = 50
) -> str:
"""
Generate a unique slug, appending numbers if necessary.
Examples:
"Tweezer" with existing ["tweezer"] -> "tweezer-2"
"Tweezer" with existing ["tweezer", "tweezer-2"] -> "tweezer-3"
"""
base_slug = generate_slug(base_text, max_length - 4) # Leave room for "-999"
if base_slug not in existing_slugs:
return base_slug
# Find next available number
counter = 2
while f"{base_slug}-{counter}" in existing_slugs:
counter += 1
return f"{base_slug}-{counter}"
def generate_show_slug(date_str: str, venue_name: str) -> str:
"""
Generate a slug for a show based on date and venue.
Examples:
"2024-12-31", "Madison Square Garden" -> "2024-12-31-msg"
"2024-07-04", "The Gorge Amphitheatre" -> "2024-07-04-the-gorge"
"""
# Common venue abbreviations
abbreviations = {
"madison square garden": "msg",
"red rocks amphitheatre": "red-rocks",
"the gorge amphitheatre": "the-gorge",
"alpine valley music theatre": "alpine",
"dicks sporting goods park": "dicks",
"mgm grand garden arena": "mgm",
"saratoga performing arts center": "spac",
}
venue_slug = abbreviations.get(venue_name.lower())
if not venue_slug:
# Take first 2-3 words of venue name
venue_slug = generate_slug(venue_name, 25)
return f"{date_str}-{venue_slug}"
def generate_performance_slug(song_title: str, show_date: str) -> str:
"""
Generate a slug for a specific performance.
Examples:
"Tweezer", "2024-12-31" -> "tweezer-2024-12-31"
"""
song_slug = generate_slug(song_title, 30)
return f"{song_slug}-{show_date}"