refactor: migrate Feature to billing module and split ProductMedia to catalog

- Move Feature model from models/database/ to app/modules/billing/models/
  (tightly coupled to SubscriptionTier for tier-based access control)
- Move ProductMedia from models/database/media.py to app/modules/catalog/models/
  (product-specific media associations belong with catalog)
- Keep MediaFile as CORE in models/database/media.py (cross-cutting file storage)
- Convert legacy feature.py to re-export for backwards compatibility
- Update all imports to use canonical module locations

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-30 19:32:38 +01:00
parent b9f08b853f
commit 0f9b80c634
15 changed files with 336 additions and 268 deletions

View File

@@ -1,27 +1,25 @@
# models/database/media.py
"""
Media file models for vendor media library.
CORE media file model for vendor media library.
This module provides:
- MediaFile: Vendor-uploaded media files (images, documents, videos)
- ProductMedia: Many-to-many relationship between products and media
This is a CORE framework model used across multiple modules.
MediaFile provides vendor-uploaded media files (images, documents, videos).
For product-media associations, use:
from app.modules.catalog.models import ProductMedia
Files are stored in vendor-specific directories:
uploads/vendors/{vendor_id}/{folder}/{filename}
"""
from datetime import datetime
from sqlalchemy import (
Boolean,
Column,
DateTime,
ForeignKey,
Index,
Integer,
String,
Text,
UniqueConstraint,
)
from sqlalchemy.dialects.sqlite import JSON
from sqlalchemy.orm import relationship
@@ -75,6 +73,7 @@ class MediaFile(Base, TimestampMixin):
# Relationships
vendor = relationship("Vendor", back_populates="media_files")
# ProductMedia relationship uses string reference to avoid circular import
product_associations = relationship(
"ProductMedia",
back_populates="media",
@@ -122,52 +121,7 @@ class MediaFile(Base, TimestampMixin):
return self.media_type == "document"
class ProductMedia(Base, TimestampMixin):
"""Association between products and media files.
# Re-export ProductMedia from its canonical location for backwards compatibility
from app.modules.catalog.models import ProductMedia # noqa: E402, F401
Tracks which media files are used by which products,
including the usage type (main image, gallery, variant, etc.)
"""
__tablename__ = "product_media"
id = Column(Integer, primary_key=True, index=True)
product_id = Column(
Integer,
ForeignKey("products.id", ondelete="CASCADE"),
nullable=False,
)
media_id = Column(
Integer,
ForeignKey("media_files.id", ondelete="CASCADE"),
nullable=False,
)
# Usage type
usage_type = Column(String(50), nullable=False, default="gallery")
# Types: main_image, gallery, variant, thumbnail, swatch
# Display order for galleries
display_order = Column(Integer, default=0)
# Variant-specific (if usage_type is variant)
variant_id = Column(Integer) # Reference to variant if applicable
# Relationships
product = relationship("Product")
media = relationship("MediaFile", back_populates="product_associations")
__table_args__ = (
UniqueConstraint(
"product_id", "media_id", "usage_type",
name="uq_product_media_usage"
),
Index("idx_product_media_product", "product_id"),
Index("idx_product_media_media", "media_id"),
)
def __repr__(self):
return (
f"<ProductMedia(product_id={self.product_id}, "
f"media_id={self.media_id}, usage='{self.usage_type}')>"
)
__all__ = ["MediaFile", "ProductMedia"]