Adds the full Django application layer on top of sportstime_parser: - core: Sport, Team, Stadium, Game models with aliases and league structure - scraper: orchestration engine, adapter, job management, Celery tasks - cloudkit: CloudKit sync client, sync state tracking, sync jobs - dashboard: staff dashboard for monitoring scrapers, sync, review queue - notifications: email reports for scrape/sync results - Docker setup for deployment (Dockerfile, docker-compose, entrypoint) Game exports now use game_datetime_utc (ISO 8601 UTC) instead of venue-local date+time strings, matching the canonical format used by the iOS app. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
163 lines
4.7 KiB
Python
163 lines
4.7 KiB
Python
"""Import/Export resources for core models."""
|
|
from import_export import resources, fields
|
|
from import_export.widgets import ForeignKeyWidget
|
|
|
|
from .models import Sport, Conference, Division, Team, Stadium, Game, TeamAlias, StadiumAlias
|
|
|
|
|
|
class SportResource(resources.ModelResource):
|
|
class Meta:
|
|
model = Sport
|
|
import_id_fields = ['code']
|
|
fields = [
|
|
'code', 'name', 'short_name', 'season_type',
|
|
'season_start_month', 'season_end_month',
|
|
'expected_game_count', 'is_active',
|
|
]
|
|
export_order = fields
|
|
|
|
|
|
class ConferenceResource(resources.ModelResource):
|
|
sport = fields.Field(
|
|
column_name='sport',
|
|
attribute='sport',
|
|
widget=ForeignKeyWidget(Sport, 'code')
|
|
)
|
|
|
|
class Meta:
|
|
model = Conference
|
|
import_id_fields = ['sport', 'name']
|
|
fields = ['sport', 'canonical_id', 'name', 'short_name', 'order']
|
|
export_order = fields
|
|
|
|
|
|
class DivisionResource(resources.ModelResource):
|
|
conference = fields.Field(
|
|
column_name='conference',
|
|
attribute='conference',
|
|
widget=ForeignKeyWidget(Conference, 'name')
|
|
)
|
|
sport = fields.Field(attribute='conference__sport__code', readonly=True)
|
|
|
|
class Meta:
|
|
model = Division
|
|
import_id_fields = ['conference', 'name']
|
|
fields = ['sport', 'conference', 'canonical_id', 'name', 'short_name', 'order']
|
|
export_order = fields
|
|
|
|
|
|
class TeamResource(resources.ModelResource):
|
|
sport = fields.Field(
|
|
column_name='sport',
|
|
attribute='sport',
|
|
widget=ForeignKeyWidget(Sport, 'code')
|
|
)
|
|
division = fields.Field(
|
|
column_name='division',
|
|
attribute='division',
|
|
widget=ForeignKeyWidget(Division, 'name')
|
|
)
|
|
home_stadium = fields.Field(
|
|
column_name='home_stadium',
|
|
attribute='home_stadium',
|
|
widget=ForeignKeyWidget(Stadium, 'name')
|
|
)
|
|
|
|
class Meta:
|
|
model = Team
|
|
import_id_fields = ['id']
|
|
fields = [
|
|
'id', 'sport', 'division', 'city', 'name', 'full_name',
|
|
'abbreviation', 'primary_color', 'secondary_color',
|
|
'logo_url', 'home_stadium', 'is_active',
|
|
]
|
|
export_order = fields
|
|
|
|
|
|
class StadiumResource(resources.ModelResource):
|
|
sport = fields.Field(
|
|
column_name='sport',
|
|
attribute='sport',
|
|
widget=ForeignKeyWidget(Sport, 'code')
|
|
)
|
|
|
|
class Meta:
|
|
model = Stadium
|
|
import_id_fields = ['id']
|
|
fields = [
|
|
'id', 'sport', 'name', 'city', 'state', 'country',
|
|
'latitude', 'longitude', 'timezone', 'capacity',
|
|
'surface', 'roof_type', 'opened_year', 'image_url',
|
|
]
|
|
export_order = fields
|
|
|
|
|
|
class GameResource(resources.ModelResource):
|
|
sport = fields.Field(
|
|
column_name='sport',
|
|
attribute='sport',
|
|
widget=ForeignKeyWidget(Sport, 'code')
|
|
)
|
|
home_team = fields.Field(
|
|
column_name='home_team',
|
|
attribute='home_team',
|
|
widget=ForeignKeyWidget(Team, 'abbreviation')
|
|
)
|
|
away_team = fields.Field(
|
|
column_name='away_team',
|
|
attribute='away_team',
|
|
widget=ForeignKeyWidget(Team, 'abbreviation')
|
|
)
|
|
stadium = fields.Field(
|
|
column_name='stadium',
|
|
attribute='stadium',
|
|
widget=ForeignKeyWidget(Stadium, 'name')
|
|
)
|
|
|
|
class Meta:
|
|
model = Game
|
|
import_id_fields = ['id']
|
|
fields = [
|
|
'id', 'sport', 'season', 'home_team', 'away_team',
|
|
'stadium', 'game_date', 'game_number', 'status',
|
|
'home_score', 'away_score', 'is_playoff', 'playoff_round',
|
|
'is_neutral_site', 'source_url',
|
|
]
|
|
export_order = fields
|
|
|
|
|
|
class TeamAliasResource(resources.ModelResource):
|
|
team = fields.Field(
|
|
column_name='team',
|
|
attribute='team',
|
|
widget=ForeignKeyWidget(Team, 'abbreviation')
|
|
)
|
|
sport = fields.Field(attribute='team__sport__code', readonly=True)
|
|
|
|
class Meta:
|
|
model = TeamAlias
|
|
import_id_fields = ['team', 'alias']
|
|
fields = [
|
|
'sport', 'team', 'alias', 'alias_type',
|
|
'valid_from', 'valid_until', 'is_primary', 'source', 'notes',
|
|
]
|
|
export_order = fields
|
|
|
|
|
|
class StadiumAliasResource(resources.ModelResource):
|
|
stadium = fields.Field(
|
|
column_name='stadium',
|
|
attribute='stadium',
|
|
widget=ForeignKeyWidget(Stadium, 'name')
|
|
)
|
|
sport = fields.Field(attribute='stadium__sport__code', readonly=True)
|
|
|
|
class Meta:
|
|
model = StadiumAlias
|
|
import_id_fields = ['stadium', 'alias']
|
|
fields = [
|
|
'sport', 'stadium', 'alias', 'alias_type',
|
|
'valid_from', 'valid_until', 'is_primary', 'source', 'notes',
|
|
]
|
|
export_order = fields
|