- Migration 007: parent_event_id (self-ref FK CASCADE), is_recurring, original_start columns on calendar_events - CalendarEvent model: three new Mapped[] columns for recurrence tracking - RecurrenceRule Pydantic model: typed schema for every_n_days, weekly, monthly_nth_weekday, monthly_date - CalendarEventCreate/Update: accept structured RecurrenceRule (router serializes to JSON string for DB) - CalendarEventUpdate: edit_scope field (this | this_and_future) - CalendarEventResponse: exposes parent_event_id, is_recurring, original_start - recurrence.py service: generates unsaved child ORM objects from parent rule up to 365-day horizon - GET /: excludes parent template rows (children are displayed instead) - POST /: creates parent + bulk children when recurrence_rule provided - PUT /: scope=this detaches occurrence; scope=this_and_future deletes future siblings and regenerates - DELETE /: scope=this deletes one; scope=this_and_future deletes future siblings; no scope deletes all (CASCADE) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
63 lines
1.6 KiB
Python
63 lines
1.6 KiB
Python
"""Add recurrence fields to calendar_events
|
|
|
|
Revision ID: 007
|
|
Revises: 006
|
|
Create Date: 2026-02-22 00:00:00.000000
|
|
|
|
"""
|
|
from typing import Sequence, Union
|
|
|
|
from alembic import op
|
|
import sqlalchemy as sa
|
|
|
|
|
|
# revision identifiers, used by Alembic.
|
|
revision: str = '007'
|
|
down_revision: Union[str, None] = '006'
|
|
branch_labels: Union[str, Sequence[str], None] = None
|
|
depends_on: Union[str, Sequence[str], None] = None
|
|
|
|
|
|
def upgrade() -> None:
|
|
# parent_event_id: self-referential FK, nullable, ON DELETE CASCADE
|
|
op.add_column(
|
|
'calendar_events',
|
|
sa.Column('parent_event_id', sa.Integer(), nullable=True)
|
|
)
|
|
op.create_foreign_key(
|
|
'fk_calendar_events_parent_event_id',
|
|
'calendar_events',
|
|
'calendar_events',
|
|
['parent_event_id'],
|
|
['id'],
|
|
ondelete='CASCADE',
|
|
)
|
|
|
|
# is_recurring: tracks whether this row is part of a recurring series
|
|
op.add_column(
|
|
'calendar_events',
|
|
sa.Column(
|
|
'is_recurring',
|
|
sa.Boolean(),
|
|
nullable=False,
|
|
server_default='false',
|
|
)
|
|
)
|
|
|
|
# original_start: the originally computed occurrence datetime (naive, no TZ)
|
|
op.add_column(
|
|
'calendar_events',
|
|
sa.Column('original_start', sa.DateTime(), nullable=True)
|
|
)
|
|
|
|
|
|
def downgrade() -> None:
|
|
op.drop_column('calendar_events', 'original_start')
|
|
op.drop_column('calendar_events', 'is_recurring')
|
|
op.drop_constraint(
|
|
'fk_calendar_events_parent_event_id',
|
|
'calendar_events',
|
|
type_='foreignkey',
|
|
)
|
|
op.drop_column('calendar_events', 'parent_event_id')
|