Kompletter Rewrite
This commit is contained in:
232
serien_checker/database/models.py
Normal file
232
serien_checker/database/models.py
Normal file
@@ -0,0 +1,232 @@
|
||||
"""
|
||||
Data models and enums for Serien-Checker database
|
||||
"""
|
||||
|
||||
from enum import Enum
|
||||
from typing import Optional, Union
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def _safe_parse_datetime(value: Union[str, datetime, None]) -> Optional[datetime]:
|
||||
"""
|
||||
Safely parse a datetime value that might be a string, datetime object, or None
|
||||
|
||||
Args:
|
||||
value: String ISO format, datetime object, or None
|
||||
|
||||
Returns:
|
||||
datetime object or None
|
||||
"""
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, datetime):
|
||||
return value
|
||||
if isinstance(value, str):
|
||||
return datetime.fromisoformat(value)
|
||||
return None
|
||||
|
||||
|
||||
class SeasonType(Enum):
|
||||
"""Types of seasons"""
|
||||
NORMAL = "normal"
|
||||
SPECIALS = "specials"
|
||||
EXTRAS = "extras"
|
||||
BEST_OF = "best_of"
|
||||
YEAR_BASED = "year_based"
|
||||
|
||||
|
||||
class DatePreference(Enum):
|
||||
"""Preferred date types for episode releases"""
|
||||
DE_FIRST = "de_first" # Deutsche Erstausstrahlung (frühestes deutsches Datum)
|
||||
DE_TV = "de_tv" # Deutsche TV-Premiere
|
||||
DE_STREAMING = "de_streaming" # Deutsche Streaming-Premiere
|
||||
DE_HOME_MEDIA = "de_home_media" # Deutsche Home-Media-Premiere
|
||||
DE_SYNC = "de_sync" # Deutsche Synchronfassung
|
||||
ORIGINAL = "original" # Erstausstrahlung (Original)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Series:
|
||||
"""Represents a TV series"""
|
||||
id: Optional[int]
|
||||
title: str
|
||||
url: str
|
||||
date_preference: DatePreference
|
||||
last_updated: Optional[datetime] = None
|
||||
|
||||
@classmethod
|
||||
def from_row(cls, row: tuple) -> 'Series':
|
||||
"""Create Series from database row"""
|
||||
return cls(
|
||||
id=row[0],
|
||||
title=row[1],
|
||||
url=row[2],
|
||||
date_preference=DatePreference(row[3]),
|
||||
last_updated=datetime.fromisoformat(row[4]) if row[4] else None
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Season:
|
||||
"""Represents a season of a series"""
|
||||
id: Optional[int]
|
||||
series_id: int
|
||||
name: str
|
||||
season_type: SeasonType
|
||||
sort_order: int
|
||||
|
||||
@classmethod
|
||||
def from_row(cls, row: tuple) -> 'Season':
|
||||
"""Create Season from database row"""
|
||||
return cls(
|
||||
id=row[0],
|
||||
series_id=row[1],
|
||||
name=row[2],
|
||||
season_type=SeasonType(row[3]),
|
||||
sort_order=row[4]
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Episode:
|
||||
"""Represents an episode"""
|
||||
id: Optional[int]
|
||||
season_id: int
|
||||
episode_code: str # e.g., "01", "01a", "01b"
|
||||
title: str
|
||||
|
||||
# Episode number (overall series number from fernsehserien.de)
|
||||
episode_number: Optional[int] = None
|
||||
|
||||
# Episode ID from fernsehserien.de (e.g., "1828679")
|
||||
episode_id: Optional[str] = None
|
||||
|
||||
# All available air dates
|
||||
date_de_tv: Optional[datetime] = None
|
||||
date_de_streaming: Optional[datetime] = None
|
||||
date_de_home_media: Optional[datetime] = None
|
||||
date_de_sync: Optional[datetime] = None
|
||||
date_original: Optional[datetime] = None
|
||||
|
||||
# Comparison date (based on series preference)
|
||||
comparison_date: Optional[datetime] = None
|
||||
|
||||
@classmethod
|
||||
def from_row(cls, row: tuple) -> 'Episode':
|
||||
"""Create Episode from database row"""
|
||||
# Handle old (9/10/11 fields) and new (12 fields) schema
|
||||
if len(row) >= 12:
|
||||
# New schema with episode_number, episode_id, and date_de_home_media
|
||||
return cls(
|
||||
id=row[0],
|
||||
season_id=row[1],
|
||||
episode_number=row[2],
|
||||
episode_code=row[3],
|
||||
title=row[4],
|
||||
episode_id=row[5],
|
||||
date_de_tv=_safe_parse_datetime(row[6]),
|
||||
date_de_streaming=_safe_parse_datetime(row[7]),
|
||||
date_de_home_media=_safe_parse_datetime(row[8]),
|
||||
date_de_sync=_safe_parse_datetime(row[9]),
|
||||
date_original=_safe_parse_datetime(row[10]),
|
||||
comparison_date=_safe_parse_datetime(row[11])
|
||||
)
|
||||
elif len(row) >= 11:
|
||||
# Schema with episode_number and date_de_home_media but no episode_id
|
||||
return cls(
|
||||
id=row[0],
|
||||
season_id=row[1],
|
||||
episode_number=row[2],
|
||||
episode_code=row[3],
|
||||
title=row[4],
|
||||
episode_id=None,
|
||||
date_de_tv=_safe_parse_datetime(row[5]),
|
||||
date_de_streaming=_safe_parse_datetime(row[6]),
|
||||
date_de_home_media=_safe_parse_datetime(row[7]),
|
||||
date_de_sync=_safe_parse_datetime(row[8]),
|
||||
date_original=_safe_parse_datetime(row[9]),
|
||||
comparison_date=_safe_parse_datetime(row[10])
|
||||
)
|
||||
elif len(row) >= 10:
|
||||
# Schema with episode_number but without date_de_home_media and episode_id
|
||||
return cls(
|
||||
id=row[0],
|
||||
season_id=row[1],
|
||||
episode_number=row[2],
|
||||
episode_code=row[3],
|
||||
title=row[4],
|
||||
episode_id=None,
|
||||
date_de_tv=_safe_parse_datetime(row[5]),
|
||||
date_de_streaming=_safe_parse_datetime(row[6]),
|
||||
date_de_home_media=None,
|
||||
date_de_sync=_safe_parse_datetime(row[7]),
|
||||
date_original=_safe_parse_datetime(row[8]),
|
||||
comparison_date=_safe_parse_datetime(row[9])
|
||||
)
|
||||
else:
|
||||
# Old schema without episode_number, episode_id, and date_de_home_media
|
||||
return cls(
|
||||
id=row[0],
|
||||
season_id=row[1],
|
||||
episode_number=None,
|
||||
episode_code=row[2],
|
||||
title=row[3],
|
||||
episode_id=None,
|
||||
date_de_tv=_safe_parse_datetime(row[4]),
|
||||
date_de_streaming=_safe_parse_datetime(row[5]),
|
||||
date_de_home_media=None,
|
||||
date_de_sync=_safe_parse_datetime(row[6]),
|
||||
date_original=_safe_parse_datetime(row[7]),
|
||||
comparison_date=_safe_parse_datetime(row[8])
|
||||
)
|
||||
|
||||
def calculate_comparison_date(self, preference: DatePreference) -> Optional[datetime]:
|
||||
"""
|
||||
Calculate the comparison date based on preference with fallback logic
|
||||
"""
|
||||
# Special case: DE_FIRST = earliest German date
|
||||
if preference == DatePreference.DE_FIRST:
|
||||
german_dates = [
|
||||
d for d in [self.date_de_tv, self.date_de_streaming, self.date_de_home_media, self.date_de_sync]
|
||||
if d is not None
|
||||
]
|
||||
if german_dates:
|
||||
return min(german_dates)
|
||||
# No fallback - return None if no German date available
|
||||
return None
|
||||
|
||||
# Priority order based on preference
|
||||
# For German preferences: only use German dates, no fallback to original
|
||||
# For ORIGINAL preference: only use original date
|
||||
# Priority for German dates: TV → Streaming → Home-Media → Sync
|
||||
priority_map = {
|
||||
DatePreference.DE_TV: [self.date_de_tv, self.date_de_streaming, self.date_de_home_media, self.date_de_sync],
|
||||
DatePreference.DE_STREAMING: [self.date_de_streaming, self.date_de_tv, self.date_de_home_media, self.date_de_sync],
|
||||
DatePreference.DE_HOME_MEDIA: [self.date_de_home_media, self.date_de_streaming, self.date_de_tv, self.date_de_sync],
|
||||
DatePreference.DE_SYNC: [self.date_de_sync, self.date_de_home_media, self.date_de_streaming, self.date_de_tv],
|
||||
DatePreference.ORIGINAL: [self.date_original]
|
||||
}
|
||||
|
||||
dates = priority_map.get(preference, [])
|
||||
for date in dates:
|
||||
if date is not None:
|
||||
return date
|
||||
return None
|
||||
|
||||
|
||||
@dataclass
|
||||
class Settings:
|
||||
"""Application settings"""
|
||||
id: Optional[int]
|
||||
key: str
|
||||
value: str
|
||||
|
||||
@classmethod
|
||||
def from_row(cls, row: tuple) -> 'Settings':
|
||||
"""Create Settings from database row"""
|
||||
return cls(
|
||||
id=row[0],
|
||||
key=row[1],
|
||||
value=row[2]
|
||||
)
|
||||
Reference in New Issue
Block a user