164 lines
5.1 KiB
Python
164 lines
5.1 KiB
Python
"""
|
|
Template service for applying HTML/CSS templates to generated content
|
|
"""
|
|
|
|
import json
|
|
import random
|
|
from pathlib import Path
|
|
from typing import Optional, Dict
|
|
from src.core.config import get_config
|
|
from src.database.repositories import GeneratedContentRepository
|
|
|
|
|
|
class TemplateService:
|
|
"""Service for loading, selecting, and applying HTML templates"""
|
|
|
|
def __init__(self, content_repo: Optional[GeneratedContentRepository] = None):
|
|
self.templates_dir = Path(__file__).parent / "templates"
|
|
self._template_cache: Dict[str, str] = {}
|
|
self.content_repo = content_repo
|
|
|
|
def get_available_templates(self) -> list[str]:
|
|
"""
|
|
Get list of available template names
|
|
|
|
Returns:
|
|
List of template names without .html extension
|
|
"""
|
|
templates = []
|
|
for template_file in self.templates_dir.glob("*.html"):
|
|
templates.append(template_file.stem)
|
|
return sorted(templates)
|
|
|
|
def load_template(self, template_name: str) -> str:
|
|
"""
|
|
Load a template file by name
|
|
|
|
Args:
|
|
template_name: Name of template (without .html extension)
|
|
|
|
Returns:
|
|
Template content as string
|
|
|
|
Raises:
|
|
FileNotFoundError: If template doesn't exist
|
|
"""
|
|
if template_name in self._template_cache:
|
|
return self._template_cache[template_name]
|
|
|
|
template_path = self.templates_dir / f"{template_name}.html"
|
|
|
|
if not template_path.exists():
|
|
available = self.get_available_templates()
|
|
raise FileNotFoundError(
|
|
f"Template '{template_name}' not found. Available templates: {', '.join(available)}"
|
|
)
|
|
|
|
with open(template_path, 'r', encoding='utf-8') as f:
|
|
template_content = f.read()
|
|
|
|
self._template_cache[template_name] = template_content
|
|
return template_content
|
|
|
|
def select_template_for_content(
|
|
self,
|
|
site_deployment_id: Optional[int] = None,
|
|
site_deployment_repo=None
|
|
) -> str:
|
|
"""
|
|
Select appropriate template based on site deployment
|
|
|
|
Args:
|
|
site_deployment_id: Optional site deployment ID
|
|
site_deployment_repo: Optional repository for querying site deployments
|
|
|
|
Returns:
|
|
Template name to use
|
|
|
|
Logic:
|
|
1. If site_deployment_id exists:
|
|
- Query SiteDeployment and return site.template_name
|
|
- If template not set, default to 'basic'
|
|
2. If site_deployment_id is null: randomly select (don't persist)
|
|
"""
|
|
if site_deployment_id and site_deployment_repo:
|
|
site_deployment = site_deployment_repo.get_by_id(site_deployment_id)
|
|
|
|
if site_deployment:
|
|
return site_deployment.template_name or "basic"
|
|
|
|
return self._select_random_template()
|
|
|
|
def _select_random_template(self) -> str:
|
|
"""
|
|
Randomly select a template from available templates
|
|
|
|
Returns:
|
|
Template name
|
|
"""
|
|
available = self.get_available_templates()
|
|
if not available:
|
|
return "basic"
|
|
|
|
return random.choice(available)
|
|
|
|
def format_content(
|
|
self,
|
|
content: str,
|
|
title: str,
|
|
meta_description: str,
|
|
template_name: str
|
|
) -> str:
|
|
"""
|
|
Format content using specified template
|
|
|
|
Args:
|
|
content: Raw HTML content (h2, h3, p tags)
|
|
title: Article title
|
|
meta_description: Meta description for SEO
|
|
template_name: Name of template to use
|
|
|
|
Returns:
|
|
Complete formatted HTML document
|
|
|
|
Raises:
|
|
FileNotFoundError: If template doesn't exist
|
|
"""
|
|
config = get_config()
|
|
|
|
try:
|
|
template = self.load_template(template_name)
|
|
except FileNotFoundError:
|
|
fallback_template = config.templates.default
|
|
print(f"Warning: Template '{template_name}' not found, using '{fallback_template}'")
|
|
template = self.load_template(fallback_template)
|
|
|
|
formatted_html = template.replace("{{ title }}", self._escape_html(title))
|
|
formatted_html = formatted_html.replace("{{ meta_description }}", self._escape_html(meta_description))
|
|
formatted_html = formatted_html.replace("{{ content }}", content)
|
|
|
|
return formatted_html
|
|
|
|
def _escape_html(self, text: str) -> str:
|
|
"""
|
|
Escape HTML special characters in text
|
|
|
|
Args:
|
|
text: Text to escape
|
|
|
|
Returns:
|
|
Escaped text
|
|
"""
|
|
replacements = {
|
|
'&': '&',
|
|
'<': '<',
|
|
'>': '>',
|
|
'"': '"',
|
|
"'": '''
|
|
}
|
|
|
|
for char, escaped in replacements.items():
|
|
text = text.replace(char, escaped)
|
|
|
|
return text
|