Define core domain models and add first Alembic migration
Nine SQLModel table models covering the Organization/Route hierarchy (WineCategory, Organization, User, Route, Participant, PurchaseRound, WineOffering, Order, OrderLine), with explicit tablenames, FK cascade behavior, and a CHECK constraint preventing a PurchaseRound from reaching open/closed status without its opens_at/order_deadline_at/ pickup_at dates set. Also fixes the Alembic script template to import sqlmodel (needed for autogenerate's AutoString column type). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
589dcbcdf5
commit
d2abe70333
11 changed files with 415 additions and 0 deletions
48
app/models/order.py
Normal file
48
app/models/order.py
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from enum import Enum
|
||||||
|
from typing import TYPE_CHECKING, List, Optional
|
||||||
|
|
||||||
|
from sqlalchemy import Column
|
||||||
|
from sqlalchemy import Enum as SAEnum
|
||||||
|
from sqlmodel import Field, Relationship, SQLModel
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from app.models.order_line import OrderLine
|
||||||
|
from app.models.participant import Participant
|
||||||
|
from app.models.purchase_round import PurchaseRound
|
||||||
|
|
||||||
|
|
||||||
|
class PaymentStatus(str, Enum):
|
||||||
|
UNPAID = "unpaid"
|
||||||
|
PAID = "paid"
|
||||||
|
|
||||||
|
|
||||||
|
class OrderBase(SQLModel):
|
||||||
|
payment_status: PaymentStatus = Field(
|
||||||
|
default=PaymentStatus.UNPAID,
|
||||||
|
sa_column=Column(
|
||||||
|
SAEnum(
|
||||||
|
PaymentStatus,
|
||||||
|
name="payment_status",
|
||||||
|
native_enum=False,
|
||||||
|
values_callable=lambda enum_cls: [member.value for member in enum_cls],
|
||||||
|
),
|
||||||
|
nullable=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
paid_at: Optional[datetime] = Field(default=None)
|
||||||
|
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
||||||
|
|
||||||
|
|
||||||
|
class Order(OrderBase, table=True):
|
||||||
|
__tablename__ = "orders"
|
||||||
|
|
||||||
|
id: Optional[int] = Field(default=None, primary_key=True)
|
||||||
|
participant_id: int = Field(foreign_key="participant.id", ondelete="CASCADE")
|
||||||
|
purchase_round_id: int = Field(foreign_key="purchase_round.id", ondelete="CASCADE")
|
||||||
|
|
||||||
|
participant: "Participant" = Relationship(back_populates="orders")
|
||||||
|
purchase_round: "PurchaseRound" = Relationship(back_populates="orders")
|
||||||
|
order_lines: List["OrderLine"] = Relationship(
|
||||||
|
back_populates="order", sa_relationship_kwargs={"cascade": "all, delete-orphan"}
|
||||||
|
)
|
||||||
24
app/models/order_line.py
Normal file
24
app/models/order_line.py
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
from typing import TYPE_CHECKING, Optional
|
||||||
|
|
||||||
|
from sqlalchemy import CheckConstraint
|
||||||
|
from sqlmodel import Field, Relationship, SQLModel
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from app.models.order import Order
|
||||||
|
from app.models.wine_offering import WineOffering
|
||||||
|
|
||||||
|
|
||||||
|
class OrderLineBase(SQLModel):
|
||||||
|
quantity: int = Field(gt=0)
|
||||||
|
|
||||||
|
|
||||||
|
class OrderLine(OrderLineBase, table=True):
|
||||||
|
__tablename__ = "order_line"
|
||||||
|
__table_args__ = (CheckConstraint("quantity > 0", name="ck_order_line_quantity_positive"),)
|
||||||
|
|
||||||
|
id: Optional[int] = Field(default=None, primary_key=True)
|
||||||
|
order_id: int = Field(foreign_key="orders.id", ondelete="CASCADE")
|
||||||
|
wine_offering_id: int = Field(foreign_key="wine_offering.id", ondelete="RESTRICT")
|
||||||
|
|
||||||
|
order: "Order" = Relationship(back_populates="order_lines")
|
||||||
|
wine_offering: "WineOffering" = Relationship(back_populates="order_lines")
|
||||||
20
app/models/organization.py
Normal file
20
app/models/organization.py
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
from typing import TYPE_CHECKING, List, Optional
|
||||||
|
|
||||||
|
from sqlmodel import Field, Relationship, SQLModel
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from app.models.route import Route
|
||||||
|
from app.models.user import User
|
||||||
|
|
||||||
|
|
||||||
|
class OrganizationBase(SQLModel):
|
||||||
|
name: str = Field(unique=True)
|
||||||
|
|
||||||
|
|
||||||
|
class Organization(OrganizationBase, table=True):
|
||||||
|
__tablename__ = "organization"
|
||||||
|
|
||||||
|
id: Optional[int] = Field(default=None, primary_key=True)
|
||||||
|
|
||||||
|
users: List["User"] = Relationship(back_populates="organization")
|
||||||
|
routes: List["Route"] = Relationship(back_populates="organization")
|
||||||
26
app/models/participant.py
Normal file
26
app/models/participant.py
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
from typing import TYPE_CHECKING, List, Optional
|
||||||
|
|
||||||
|
from sqlmodel import Field, Relationship, SQLModel
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from app.models.order import Order
|
||||||
|
from app.models.route import Route
|
||||||
|
|
||||||
|
|
||||||
|
class ParticipantBase(SQLModel):
|
||||||
|
name: str
|
||||||
|
email: str
|
||||||
|
phone: Optional[str] = Field(default=None)
|
||||||
|
is_active: bool = Field(default=True)
|
||||||
|
|
||||||
|
|
||||||
|
class Participant(ParticipantBase, table=True):
|
||||||
|
__tablename__ = "participant"
|
||||||
|
|
||||||
|
id: Optional[int] = Field(default=None, primary_key=True)
|
||||||
|
route_id: int = Field(foreign_key="route.id", ondelete="CASCADE")
|
||||||
|
|
||||||
|
route: "Route" = Relationship(back_populates="participants")
|
||||||
|
orders: List["Order"] = Relationship(
|
||||||
|
back_populates="participant", sa_relationship_kwargs={"cascade": "all, delete-orphan"}
|
||||||
|
)
|
||||||
63
app/models/purchase_round.py
Normal file
63
app/models/purchase_round.py
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
from datetime import datetime
|
||||||
|
from decimal import Decimal
|
||||||
|
from enum import Enum
|
||||||
|
from typing import TYPE_CHECKING, List, Optional
|
||||||
|
|
||||||
|
from sqlalchemy import CheckConstraint, Column
|
||||||
|
from sqlalchemy import Enum as SAEnum
|
||||||
|
from sqlmodel import Field, Relationship, SQLModel
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from app.models.order import Order
|
||||||
|
from app.models.route import Route
|
||||||
|
from app.models.wine_offering import WineOffering
|
||||||
|
|
||||||
|
|
||||||
|
class PurchaseRoundStatus(str, Enum):
|
||||||
|
DRAFT = "draft"
|
||||||
|
OPEN = "open"
|
||||||
|
CLOSED = "closed"
|
||||||
|
|
||||||
|
|
||||||
|
class PurchaseRoundBase(SQLModel):
|
||||||
|
name: str
|
||||||
|
status: PurchaseRoundStatus = Field(
|
||||||
|
default=PurchaseRoundStatus.DRAFT,
|
||||||
|
sa_column=Column(
|
||||||
|
SAEnum(
|
||||||
|
PurchaseRoundStatus,
|
||||||
|
name="purchase_round_status",
|
||||||
|
native_enum=False,
|
||||||
|
values_callable=lambda enum_cls: [member.value for member in enum_cls],
|
||||||
|
),
|
||||||
|
nullable=False,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
opens_at: Optional[datetime] = Field(default=None)
|
||||||
|
order_deadline_at: Optional[datetime] = Field(default=None)
|
||||||
|
pickup_at: Optional[datetime] = Field(default=None)
|
||||||
|
eur_dkk_rate: Optional[Decimal] = Field(default=None, max_digits=10, decimal_places=4)
|
||||||
|
intro_text: Optional[str] = Field(default=None)
|
||||||
|
pickup_info_text: Optional[str] = Field(default=None)
|
||||||
|
|
||||||
|
|
||||||
|
class PurchaseRound(PurchaseRoundBase, table=True):
|
||||||
|
__tablename__ = "purchase_round"
|
||||||
|
__table_args__ = (
|
||||||
|
CheckConstraint(
|
||||||
|
"status = 'draft' OR "
|
||||||
|
"(opens_at IS NOT NULL AND order_deadline_at IS NOT NULL AND pickup_at IS NOT NULL)",
|
||||||
|
name="ck_purchase_round_dates_required_unless_draft",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
id: Optional[int] = Field(default=None, primary_key=True)
|
||||||
|
route_id: int = Field(foreign_key="route.id", ondelete="CASCADE")
|
||||||
|
|
||||||
|
route: "Route" = Relationship(back_populates="purchase_rounds")
|
||||||
|
wine_offerings: List["WineOffering"] = Relationship(
|
||||||
|
back_populates="purchase_round", sa_relationship_kwargs={"cascade": "all, delete-orphan"}
|
||||||
|
)
|
||||||
|
orders: List["Order"] = Relationship(
|
||||||
|
back_populates="purchase_round", sa_relationship_kwargs={"cascade": "all, delete-orphan"}
|
||||||
|
)
|
||||||
30
app/models/route.py
Normal file
30
app/models/route.py
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
from typing import TYPE_CHECKING, List, Optional
|
||||||
|
|
||||||
|
from sqlmodel import Field, Relationship, SQLModel
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from app.models.organization import Organization
|
||||||
|
from app.models.participant import Participant
|
||||||
|
from app.models.purchase_round import PurchaseRound
|
||||||
|
|
||||||
|
|
||||||
|
class RouteBase(SQLModel):
|
||||||
|
name: str
|
||||||
|
meeting_info: Optional[str] = Field(default=None)
|
||||||
|
contact_person: Optional[str] = Field(default=None)
|
||||||
|
contact_phone: Optional[str] = Field(default=None)
|
||||||
|
|
||||||
|
|
||||||
|
class Route(RouteBase, table=True):
|
||||||
|
__tablename__ = "route"
|
||||||
|
|
||||||
|
id: Optional[int] = Field(default=None, primary_key=True)
|
||||||
|
organization_id: int = Field(foreign_key="organization.id", ondelete="RESTRICT")
|
||||||
|
|
||||||
|
organization: "Organization" = Relationship(back_populates="routes")
|
||||||
|
participants: List["Participant"] = Relationship(
|
||||||
|
back_populates="route", sa_relationship_kwargs={"cascade": "all, delete-orphan"}
|
||||||
|
)
|
||||||
|
purchase_rounds: List["PurchaseRound"] = Relationship(
|
||||||
|
back_populates="route", sa_relationship_kwargs={"cascade": "all, delete-orphan"}
|
||||||
|
)
|
||||||
23
app/models/user.py
Normal file
23
app/models/user.py
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
from typing import TYPE_CHECKING, Optional
|
||||||
|
|
||||||
|
from sqlmodel import Field, Relationship, SQLModel
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from app.models.organization import Organization
|
||||||
|
|
||||||
|
|
||||||
|
class UserBase(SQLModel):
|
||||||
|
email: str = Field(unique=True, index=True)
|
||||||
|
name: str
|
||||||
|
is_active: bool = Field(default=True)
|
||||||
|
is_superadmin: bool = Field(default=False)
|
||||||
|
|
||||||
|
|
||||||
|
class User(UserBase, table=True):
|
||||||
|
__tablename__ = "users"
|
||||||
|
|
||||||
|
id: Optional[int] = Field(default=None, primary_key=True)
|
||||||
|
hashed_password: str
|
||||||
|
organization_id: int = Field(foreign_key="organization.id", ondelete="RESTRICT")
|
||||||
|
|
||||||
|
organization: "Organization" = Relationship(back_populates="users")
|
||||||
19
app/models/wine_category.py
Normal file
19
app/models/wine_category.py
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
from typing import TYPE_CHECKING, List, Optional
|
||||||
|
|
||||||
|
from sqlmodel import Field, Relationship, SQLModel
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from app.models.wine_offering import WineOffering
|
||||||
|
|
||||||
|
|
||||||
|
class WineCategoryBase(SQLModel):
|
||||||
|
name: str = Field(unique=True, index=True)
|
||||||
|
sort_order: int = Field(default=0)
|
||||||
|
|
||||||
|
|
||||||
|
class WineCategory(WineCategoryBase, table=True):
|
||||||
|
__tablename__ = "wine_category"
|
||||||
|
|
||||||
|
id: Optional[int] = Field(default=None, primary_key=True)
|
||||||
|
|
||||||
|
wine_offerings: List["WineOffering"] = Relationship(back_populates="category")
|
||||||
27
app/models/wine_offering.py
Normal file
27
app/models/wine_offering.py
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
from decimal import Decimal
|
||||||
|
from typing import TYPE_CHECKING, List, Optional
|
||||||
|
|
||||||
|
from sqlmodel import Field, Relationship, SQLModel
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from app.models.order_line import OrderLine
|
||||||
|
from app.models.purchase_round import PurchaseRound
|
||||||
|
from app.models.wine_category import WineCategory
|
||||||
|
|
||||||
|
|
||||||
|
class WineOfferingBase(SQLModel):
|
||||||
|
name: str
|
||||||
|
price: Decimal = Field(max_digits=10, decimal_places=2)
|
||||||
|
is_organic: bool = Field(default=False)
|
||||||
|
|
||||||
|
|
||||||
|
class WineOffering(WineOfferingBase, table=True):
|
||||||
|
__tablename__ = "wine_offering"
|
||||||
|
|
||||||
|
id: Optional[int] = Field(default=None, primary_key=True)
|
||||||
|
purchase_round_id: int = Field(foreign_key="purchase_round.id", ondelete="CASCADE")
|
||||||
|
category_id: int = Field(foreign_key="wine_category.id", ondelete="RESTRICT")
|
||||||
|
|
||||||
|
purchase_round: "PurchaseRound" = Relationship(back_populates="wine_offerings")
|
||||||
|
category: "WineCategory" = Relationship(back_populates="wine_offerings")
|
||||||
|
order_lines: List["OrderLine"] = Relationship(back_populates="wine_offering")
|
||||||
|
|
@ -9,6 +9,7 @@ from typing import Sequence, Union
|
||||||
|
|
||||||
from alembic import op
|
from alembic import op
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
import sqlmodel
|
||||||
${imports if imports else ""}
|
${imports if imports else ""}
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
|
|
|
||||||
134
migrations/versions/31918c010919_add_core_domain_models.py
Normal file
134
migrations/versions/31918c010919_add_core_domain_models.py
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
"""add core domain models
|
||||||
|
|
||||||
|
Revision ID: 31918c010919
|
||||||
|
Revises:
|
||||||
|
Create Date: 2026-09-27 18:45:45.613766
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
import sqlmodel
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = '31918c010919'
|
||||||
|
down_revision: Union[str, Sequence[str], None] = None
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
"""Upgrade schema."""
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table('organization',
|
||||||
|
sa.Column('name', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint('id'),
|
||||||
|
sa.UniqueConstraint('name')
|
||||||
|
)
|
||||||
|
op.create_table('wine_category',
|
||||||
|
sa.Column('name', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||||
|
sa.Column('sort_order', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
op.create_index(op.f('ix_wine_category_name'), 'wine_category', ['name'], unique=True)
|
||||||
|
op.create_table('route',
|
||||||
|
sa.Column('name', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||||
|
sa.Column('meeting_info', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||||
|
sa.Column('contact_person', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||||
|
sa.Column('contact_phone', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('organization_id', sa.Integer(), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(['organization_id'], ['organization.id'], ondelete='RESTRICT'),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
op.create_table('users',
|
||||||
|
sa.Column('email', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||||
|
sa.Column('name', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||||
|
sa.Column('is_active', sa.Boolean(), nullable=False),
|
||||||
|
sa.Column('is_superadmin', sa.Boolean(), nullable=False),
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('hashed_password', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||||
|
sa.Column('organization_id', sa.Integer(), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(['organization_id'], ['organization.id'], ondelete='RESTRICT'),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
op.create_index(op.f('ix_users_email'), 'users', ['email'], unique=True)
|
||||||
|
op.create_table('participant',
|
||||||
|
sa.Column('name', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||||
|
sa.Column('email', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||||
|
sa.Column('phone', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||||
|
sa.Column('is_active', sa.Boolean(), nullable=False),
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('route_id', sa.Integer(), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(['route_id'], ['route.id'], ondelete='CASCADE'),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
op.create_table('purchase_round',
|
||||||
|
sa.Column('name', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||||
|
sa.Column('status', sa.Enum('draft', 'open', 'closed', name='purchase_round_status', native_enum=False), nullable=False),
|
||||||
|
sa.Column('opens_at', sqlmodel.sql.sqltypes.UTCDateTime(), nullable=True),
|
||||||
|
sa.Column('order_deadline_at', sqlmodel.sql.sqltypes.UTCDateTime(), nullable=True),
|
||||||
|
sa.Column('pickup_at', sqlmodel.sql.sqltypes.UTCDateTime(), nullable=True),
|
||||||
|
sa.Column('eur_dkk_rate', sa.Numeric(precision=10, scale=4), nullable=True),
|
||||||
|
sa.Column('intro_text', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||||
|
sa.Column('pickup_info_text', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('route_id', sa.Integer(), nullable=False),
|
||||||
|
sa.CheckConstraint("status = 'draft' OR (opens_at IS NOT NULL AND order_deadline_at IS NOT NULL AND pickup_at IS NOT NULL)", name='ck_purchase_round_dates_required_unless_draft'),
|
||||||
|
sa.ForeignKeyConstraint(['route_id'], ['route.id'], ondelete='CASCADE'),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
op.create_table('orders',
|
||||||
|
sa.Column('payment_status', sa.Enum('unpaid', 'paid', name='payment_status', native_enum=False), nullable=False),
|
||||||
|
sa.Column('paid_at', sqlmodel.sql.sqltypes.UTCDateTime(), nullable=True),
|
||||||
|
sa.Column('created_at', sqlmodel.sql.sqltypes.UTCDateTime(), nullable=False),
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('participant_id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('purchase_round_id', sa.Integer(), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(['participant_id'], ['participant.id'], ondelete='CASCADE'),
|
||||||
|
sa.ForeignKeyConstraint(['purchase_round_id'], ['purchase_round.id'], ondelete='CASCADE'),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
op.create_table('wine_offering',
|
||||||
|
sa.Column('name', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||||
|
sa.Column('price', sa.Numeric(precision=10, scale=2), nullable=False),
|
||||||
|
sa.Column('is_organic', sa.Boolean(), nullable=False),
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('purchase_round_id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('category_id', sa.Integer(), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(['category_id'], ['wine_category.id'], ondelete='RESTRICT'),
|
||||||
|
sa.ForeignKeyConstraint(['purchase_round_id'], ['purchase_round.id'], ondelete='CASCADE'),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
op.create_table('order_line',
|
||||||
|
sa.Column('quantity', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('order_id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('wine_offering_id', sa.Integer(), nullable=False),
|
||||||
|
sa.CheckConstraint('quantity > 0', name='ck_order_line_quantity_positive'),
|
||||||
|
sa.ForeignKeyConstraint(['order_id'], ['orders.id'], ondelete='CASCADE'),
|
||||||
|
sa.ForeignKeyConstraint(['wine_offering_id'], ['wine_offering.id'], ondelete='RESTRICT'),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
"""Downgrade schema."""
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_table('order_line')
|
||||||
|
op.drop_table('wine_offering')
|
||||||
|
op.drop_table('orders')
|
||||||
|
op.drop_table('purchase_round')
|
||||||
|
op.drop_table('participant')
|
||||||
|
op.drop_index(op.f('ix_users_email'), table_name='users')
|
||||||
|
op.drop_table('users')
|
||||||
|
op.drop_table('route')
|
||||||
|
op.drop_index(op.f('ix_wine_category_name'), table_name='wine_category')
|
||||||
|
op.drop_table('wine_category')
|
||||||
|
op.drop_table('organization')
|
||||||
|
# ### end Alembic commands ###
|
||||||
Loading…
Add table
Reference in a new issue