diff --git a/make_post_sell/models/__init__.py b/make_post_sell/models/__init__.py index 4abf079..4e84bdb 100644 --- a/make_post_sell/models/__init__.py +++ b/make_post_sell/models/__init__.py @@ -7,6 +7,7 @@ import zope.sqlalchemy # Base.metadata prior to any initialization routines. from .product import * from .shop import * +from .shop_location import * from .user import * from .user_shop import * from .user_product import * @@ -18,6 +19,7 @@ from .market import * from .coupon import * from .coupon_redemption import * from .invoice import * +from .inventory import * from .stripe_user_shop import * diff --git a/make_post_sell/models/inventory.py b/make_post_sell/models/inventory.py index 737d994..430f274 100644 --- a/make_post_sell/models/inventory.py +++ b/make_post_sell/models/inventory.py @@ -13,8 +13,8 @@ class Inventory(RBase, Base): created_timestamp = Column(BigInteger, nullable=False) updated_timestamp = Column(BigInteger, nullable=False) - shop_location = relationship("ShopLocation", backref=backref("inventories", cascade="all, delete-orphan")) - product = relationship("Product", backref=backref("inventories", cascade="all, delete-orphan")) + shop_location = relationship("ShopLocation", uselist=False) + product = relationship("Product", uselist=False) def __init__(self, shop_location, product, quantity=0): if not product.is_physical: diff --git a/make_post_sell/models/meta.py b/make_post_sell/models/meta.py index c6cc878..7368a9e 100644 --- a/make_post_sell/models/meta.py +++ b/make_post_sell/models/meta.py @@ -16,7 +16,6 @@ UUIDType = TempUUIDType(binary=False) CLASS_TO_TABLE = { - "Address": "mps_address", "Shop": "mps_shop", "ShopLocation": "mps_shop_location", "Inventory": "mps_inventory", diff --git a/make_post_sell/models/product.py b/make_post_sell/models/product.py index 773de89..3339c2e 100644 --- a/make_post_sell/models/product.py +++ b/make_post_sell/models/product.py @@ -104,6 +104,12 @@ class Product(RBase, Base): # this value should be treated as immutable after object creation. is_sellable = Column(Boolean, default=False, nullable=False) + # this value should be treated as immutable after object creation. + is_physical = Column(Boolean, default=False, nullable=False) + + # Add relationship to inventories + inventories = relationship("Inventory", lazy="dynamic", back_populates="product") + # lazy="joined" performs a left join to reduce queries, it's magic. # set uselist=False creates one-to-one relationship. shop = relationship(argument="Shop", uselist=False, lazy="joined") @@ -121,13 +127,6 @@ class Product(RBase, Base): "product_users", "user", creator=lambda u: UserProduct(user=u) ) - """ - _users = relationship(UserProduct, order_by=[UserProduct.created_timestamp.desc()]) - users = association_proxy( - "_users", "user", creator=lambda u: UserProduct(user=u) - ) - """ - file_keys = [ "product", "preview", @@ -241,6 +240,12 @@ class Product(RBase, Base): """return slug from title""" return slugify(self.title) + @property + def total_inventory_quantity(self): + if not self.is_physical: + return None + return sum(inventory.quantity for inventory in self.inventories) + @property def s3_path(self): """return the S3 path shop.id/product.id""" diff --git a/make_post_sell/models/shop.py b/make_post_sell/models/shop.py index 7c8a4fd..856f28b 100644 --- a/make_post_sell/models/shop.py +++ b/make_post_sell/models/shop.py @@ -99,6 +99,10 @@ class Shop(RBase, Base): order_by="desc(Coupon.created_timestamp)", ) + # lazy='dynamic' returns a query object instead of collection. + # Reference: http://docs.sqlalchemy.org/en/latest/orm/collections.html + shop_locations = relationship(argument="ShopLocation", lazy="dynamic", back_populates="shop") + # lazy='dynamic' returns a query object instead of collection. # Reference: http://docs.sqlalchemy.org/en/latest/orm/collections.html search_requests = relationship( diff --git a/make_post_sell/models/shop_location.py b/make_post_sell/models/shop_location.py index 4885af8..03d03bf 100644 --- a/make_post_sell/models/shop_location.py +++ b/make_post_sell/models/shop_location.py @@ -16,7 +16,7 @@ class ShopLocation(RBase, Base): postal_code = Column(Unicode(16), nullable=False) created_timestamp = Column(BigInteger, nullable=False) - shop = relationship("Shop", backref=backref("shop_locations", cascade="all, delete-orphan")) + shop = relationship("Shop", uselist=False) def __init__(self, shop, name, address, city, state, country, postal_code): self.id = uuid.uuid1() diff --git a/make_post_sell/scripts/alembic/versions/cc6211ec360e_physical_products_shop_locations_and_.py b/make_post_sell/scripts/alembic/versions/cc6211ec360e_physical_products_shop_locations_and_.py new file mode 100644 index 0000000..2ebe126 --- /dev/null +++ b/make_post_sell/scripts/alembic/versions/cc6211ec360e_physical_products_shop_locations_and_.py @@ -0,0 +1,61 @@ +"""physical products shop_locations and inventory + +Revision ID: cc6211ec360e +Revises: 7217f39a20a8 +Create Date: 2024-06-08 09:40:24.200194 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'cc6211ec360e' +down_revision = '7217f39a20a8' +branch_labels = None +depends_on = None + +from make_post_sell.models.meta import UUIDType + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('mps_shop_location', + sa.Column('id', UUIDType, nullable=False), + sa.Column('shop_id', UUIDType, nullable=False), + sa.Column('name', sa.Unicode(length=64), nullable=False), + sa.Column('address', sa.Unicode(length=256), nullable=False), + sa.Column('city', sa.Unicode(length=64), nullable=False), + sa.Column('state', sa.Unicode(length=64), nullable=False), + sa.Column('country', sa.Unicode(length=64), nullable=False), + sa.Column('postal_code', sa.Unicode(length=16), nullable=False), + sa.Column('created_timestamp', sa.BigInteger(), nullable=False), + sa.ForeignKeyConstraint(['shop_id'], ['mps_shop.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_mps_shop_location_id'), 'mps_shop_location', ['id'], unique=False) + op.create_table('mps_inventory', + sa.Column('id', UUIDType, nullable=False), + sa.Column('shop_location_id', UUIDType, nullable=False), + sa.Column('product_id', UUIDType, nullable=False), + sa.Column('quantity', sa.Integer(), nullable=False), + sa.Column('created_timestamp', sa.BigInteger(), nullable=False), + sa.Column('updated_timestamp', sa.BigInteger(), nullable=False), + sa.ForeignKeyConstraint(['product_id'], ['mps_product.id'], ), + sa.ForeignKeyConstraint(['shop_location_id'], ['mps_shop_location.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_mps_inventory_id'), 'mps_inventory', ['id'], unique=False) + + op.add_column('mps_product', sa.Column('is_physical', sa.Boolean(), nullable=False, server_default=sa.false())) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('mps_product', 'is_physical') + op.drop_index(op.f('ix_mps_inventory_id'), table_name='mps_inventory') + op.drop_table('mps_inventory') + op.drop_index(op.f('ix_mps_shop_location_id'), table_name='mps_shop_location') + op.drop_table('mps_shop_location') + # ### end Alembic commands ###