style: apply black and isort formatting across entire codebase
- Standardize quote style (single to double quotes) - Reorder and group imports alphabetically - Fix line breaks and indentation for consistency - Apply PEP 8 formatting standards Also updated Makefile to exclude both venv and .venv from code quality checks. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,17 +1,18 @@
|
||||
# models/database/__init__.py
|
||||
"""Database models package."""
|
||||
from .admin import AdminAuditLog, AdminNotification, AdminSetting, PlatformAlert, AdminSession
|
||||
from .admin import (AdminAuditLog, AdminNotification, AdminSession,
|
||||
AdminSetting, PlatformAlert)
|
||||
from .base import Base
|
||||
from .customer import Customer, CustomerAddress
|
||||
from .order import Order, OrderItem
|
||||
from .user import User
|
||||
from .marketplace_product import MarketplaceProduct
|
||||
from .inventory import Inventory
|
||||
from .vendor import Vendor, Role, VendorUser
|
||||
from .marketplace_import_job import MarketplaceImportJob
|
||||
from .marketplace_product import MarketplaceProduct
|
||||
from .order import Order, OrderItem
|
||||
from .product import Product
|
||||
from .user import User
|
||||
from .vendor import Role, Vendor, VendorUser
|
||||
from .vendor_domain import VendorDomain
|
||||
from .vendor_theme import VendorTheme
|
||||
from .product import Product
|
||||
from .marketplace_import_job import MarketplaceImportJob
|
||||
|
||||
__all__ = [
|
||||
# Admin-specific models
|
||||
@@ -34,5 +35,5 @@ __all__ = [
|
||||
"MarketplaceImportJob",
|
||||
"MarketplaceProduct",
|
||||
"VendorDomain",
|
||||
"VendorTheme"
|
||||
"VendorTheme",
|
||||
]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Admin-specific models
|
||||
# Admin-specific models
|
||||
# models/database/admin.py
|
||||
"""
|
||||
Admin-specific database models.
|
||||
@@ -10,9 +10,12 @@ This module provides models for:
|
||||
- Platform alerts (system-wide issues)
|
||||
"""
|
||||
|
||||
from sqlalchemy import Column, Integer, String, DateTime, Boolean, Text, JSON, ForeignKey
|
||||
from sqlalchemy import (JSON, Boolean, Column, DateTime, ForeignKey, Integer,
|
||||
String, Text)
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from app.core.database import Base
|
||||
|
||||
from .base import TimestampMixin
|
||||
|
||||
|
||||
@@ -23,12 +26,17 @@ class AdminAuditLog(Base, TimestampMixin):
|
||||
Separate from regular audit logs - focuses on admin-specific operations
|
||||
like vendor creation, user management, and system configuration changes.
|
||||
"""
|
||||
|
||||
__tablename__ = "admin_audit_logs"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
admin_user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True)
|
||||
action = Column(String(100), nullable=False, index=True) # create_vendor, delete_vendor, etc.
|
||||
target_type = Column(String(50), nullable=False, index=True) # vendor, user, import_job, setting
|
||||
action = Column(
|
||||
String(100), nullable=False, index=True
|
||||
) # create_vendor, delete_vendor, etc.
|
||||
target_type = Column(
|
||||
String(50), nullable=False, index=True
|
||||
) # vendor, user, import_job, setting
|
||||
target_id = Column(String(100), nullable=False, index=True)
|
||||
details = Column(JSON) # Additional context about the action
|
||||
ip_address = Column(String(45)) # IPv4 or IPv6
|
||||
@@ -49,11 +57,16 @@ class AdminNotification(Base, TimestampMixin):
|
||||
Different from vendor/customer notifications - these are for platform
|
||||
administrators to track system health and issues requiring attention.
|
||||
"""
|
||||
|
||||
__tablename__ = "admin_notifications"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
type = Column(String(50), nullable=False, index=True) # system_alert, vendor_issue, import_failure
|
||||
priority = Column(String(20), default="normal", index=True) # low, normal, high, critical
|
||||
type = Column(
|
||||
String(50), nullable=False, index=True
|
||||
) # system_alert, vendor_issue, import_failure
|
||||
priority = Column(
|
||||
String(20), default="normal", index=True
|
||||
) # low, normal, high, critical
|
||||
title = Column(String(200), nullable=False)
|
||||
message = Column(Text, nullable=False)
|
||||
is_read = Column(Boolean, default=False, index=True)
|
||||
@@ -84,13 +97,16 @@ class AdminSetting(Base, TimestampMixin):
|
||||
- smtp_settings
|
||||
- stripe_api_keys (encrypted)
|
||||
"""
|
||||
|
||||
__tablename__ = "admin_settings"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
key = Column(String(100), unique=True, nullable=False, index=True)
|
||||
value = Column(Text, nullable=False)
|
||||
value_type = Column(String(20), default="string") # string, integer, boolean, json
|
||||
category = Column(String(50), index=True) # system, security, marketplace, notifications
|
||||
category = Column(
|
||||
String(50), index=True
|
||||
) # system, security, marketplace, notifications
|
||||
description = Column(Text)
|
||||
is_encrypted = Column(Boolean, default=False)
|
||||
is_public = Column(Boolean, default=False) # Can be exposed to frontend?
|
||||
@@ -110,11 +126,16 @@ class PlatformAlert(Base, TimestampMixin):
|
||||
Tracks platform issues, performance problems, security incidents,
|
||||
and other system-level concerns that require admin attention.
|
||||
"""
|
||||
|
||||
__tablename__ = "platform_alerts"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
alert_type = Column(String(50), nullable=False, index=True) # security, performance, capacity, integration
|
||||
severity = Column(String(20), nullable=False, index=True) # info, warning, error, critical
|
||||
alert_type = Column(
|
||||
String(50), nullable=False, index=True
|
||||
) # security, performance, capacity, integration
|
||||
severity = Column(
|
||||
String(20), nullable=False, index=True
|
||||
) # info, warning, error, critical
|
||||
title = Column(String(200), nullable=False)
|
||||
description = Column(Text)
|
||||
affected_vendors = Column(JSON) # List of affected vendor IDs
|
||||
@@ -142,6 +163,7 @@ class AdminSession(Base, TimestampMixin):
|
||||
Helps identify suspicious login patterns, track concurrent sessions,
|
||||
and enforce session policies for admin users.
|
||||
"""
|
||||
|
||||
__tablename__ = "admin_sessions"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
|
||||
@@ -1 +1 @@
|
||||
# AuditLog, DataExportLog models
|
||||
# AuditLog, DataExportLog models
|
||||
|
||||
@@ -1 +1 @@
|
||||
# BackupLog, RestoreLog models
|
||||
# BackupLog, RestoreLog models
|
||||
|
||||
@@ -10,5 +10,8 @@ class TimestampMixin:
|
||||
|
||||
created_at = Column(DateTime, default=datetime.now(timezone.utc), nullable=False)
|
||||
updated_at = Column(
|
||||
DateTime, default=datetime.now(timezone.utc), onupdate=datetime.now(timezone.utc), nullable=False
|
||||
DateTime,
|
||||
default=datetime.now(timezone.utc),
|
||||
onupdate=datetime.now(timezone.utc),
|
||||
nullable=False,
|
||||
)
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
# models/database/cart.py
|
||||
"""Cart item database model."""
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Column, Float, ForeignKey, Index, Integer, String, UniqueConstraint
|
||||
|
||||
from sqlalchemy import (Column, Float, ForeignKey, Index, Integer, String,
|
||||
UniqueConstraint)
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from app.core.database import Base
|
||||
@@ -15,6 +17,7 @@ class CartItem(Base, TimestampMixin):
|
||||
Stores cart items per session, vendor, and product.
|
||||
Sessions are identified by a session_id string (from browser cookies).
|
||||
"""
|
||||
|
||||
__tablename__ = "cart_items"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
|
||||
@@ -1 +1 @@
|
||||
# PlatformConfig, VendorConfig, FeatureFlag models
|
||||
# PlatformConfig, VendorConfig, FeatureFlag models
|
||||
|
||||
@@ -15,7 +15,9 @@ Features:
|
||||
"""
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String, Text, UniqueConstraint, Index
|
||||
|
||||
from sqlalchemy import (Boolean, Column, DateTime, ForeignKey, Index, Integer,
|
||||
String, Text, UniqueConstraint)
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from app.core.database import Base
|
||||
@@ -34,15 +36,20 @@ class ContentPage(Base):
|
||||
2. If not found, use platform default (slug only)
|
||||
3. If neither exists, show 404 or default template
|
||||
"""
|
||||
|
||||
__tablename__ = "content_pages"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
|
||||
# Vendor association (NULL = platform default)
|
||||
vendor_id = Column(Integer, ForeignKey("vendors.id", ondelete="CASCADE"), nullable=True, index=True)
|
||||
vendor_id = Column(
|
||||
Integer, ForeignKey("vendors.id", ondelete="CASCADE"), nullable=True, index=True
|
||||
)
|
||||
|
||||
# Page identification
|
||||
slug = Column(String(100), nullable=False, index=True) # about, faq, contact, shipping, returns, etc.
|
||||
slug = Column(
|
||||
String(100), nullable=False, index=True
|
||||
) # about, faq, contact, shipping, returns, etc.
|
||||
title = Column(String(200), nullable=False)
|
||||
|
||||
# Content
|
||||
@@ -68,12 +75,25 @@ class ContentPage(Base):
|
||||
show_in_header = Column(Boolean, default=False)
|
||||
|
||||
# Timestamps
|
||||
created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False)
|
||||
updated_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc), nullable=False)
|
||||
created_at = Column(
|
||||
DateTime(timezone=True),
|
||||
default=lambda: datetime.now(timezone.utc),
|
||||
nullable=False,
|
||||
)
|
||||
updated_at = Column(
|
||||
DateTime(timezone=True),
|
||||
default=lambda: datetime.now(timezone.utc),
|
||||
onupdate=lambda: datetime.now(timezone.utc),
|
||||
nullable=False,
|
||||
)
|
||||
|
||||
# Author tracking (admin or vendor user who created/updated)
|
||||
created_by = Column(Integer, ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
|
||||
updated_by = Column(Integer, ForeignKey("users.id", ondelete="SET NULL"), nullable=True)
|
||||
created_by = Column(
|
||||
Integer, ForeignKey("users.id", ondelete="SET NULL"), nullable=True
|
||||
)
|
||||
updated_by = Column(
|
||||
Integer, ForeignKey("users.id", ondelete="SET NULL"), nullable=True
|
||||
)
|
||||
|
||||
# Relationships
|
||||
vendor = relationship("Vendor", back_populates="content_pages")
|
||||
@@ -84,11 +104,10 @@ class ContentPage(Base):
|
||||
__table_args__ = (
|
||||
# Unique combination: vendor can only have one page per slug
|
||||
# Platform defaults (vendor_id=NULL) can only have one page per slug
|
||||
UniqueConstraint('vendor_id', 'slug', name='uq_vendor_slug'),
|
||||
|
||||
UniqueConstraint("vendor_id", "slug", name="uq_vendor_slug"),
|
||||
# Indexes for performance
|
||||
Index('idx_vendor_published', 'vendor_id', 'is_published'),
|
||||
Index('idx_slug_published', 'slug', 'is_published'),
|
||||
Index("idx_vendor_published", "vendor_id", "is_published"),
|
||||
Index("idx_slug_published", "slug", "is_published"),
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
@@ -119,7 +138,9 @@ class ContentPage(Base):
|
||||
"meta_description": self.meta_description,
|
||||
"meta_keywords": self.meta_keywords,
|
||||
"is_published": self.is_published,
|
||||
"published_at": self.published_at.isoformat() if self.published_at else None,
|
||||
"published_at": (
|
||||
self.published_at.isoformat() if self.published_at else None
|
||||
),
|
||||
"display_order": self.display_order,
|
||||
"show_in_footer": self.show_in_footer,
|
||||
"show_in_header": self.show_in_header,
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
from datetime import datetime
|
||||
from decimal import Decimal
|
||||
from sqlalchemy import Column, Integer, String, Boolean, DateTime, ForeignKey, Text, JSON, Numeric
|
||||
|
||||
from sqlalchemy import (JSON, Boolean, Column, DateTime, ForeignKey, Integer,
|
||||
Numeric, String, Text)
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from app.core.database import Base
|
||||
|
||||
from .base import TimestampMixin
|
||||
|
||||
|
||||
@@ -11,12 +15,16 @@ class Customer(Base, TimestampMixin):
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
vendor_id = Column(Integer, ForeignKey("vendors.id"), nullable=False)
|
||||
email = Column(String(255), nullable=False, index=True) # Unique within vendor scope
|
||||
email = Column(
|
||||
String(255), nullable=False, index=True
|
||||
) # Unique within vendor scope
|
||||
hashed_password = Column(String(255), nullable=False)
|
||||
first_name = Column(String(100))
|
||||
last_name = Column(String(100))
|
||||
phone = Column(String(50))
|
||||
customer_number = Column(String(100), nullable=False, index=True) # Vendor-specific ID
|
||||
customer_number = Column(
|
||||
String(100), nullable=False, index=True
|
||||
) # Vendor-specific ID
|
||||
preferences = Column(JSON, default=dict)
|
||||
marketing_consent = Column(Boolean, default=False)
|
||||
last_order_date = Column(DateTime)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# models/database/inventory.py
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Column, ForeignKey, Index, Integer, String, UniqueConstraint
|
||||
|
||||
from sqlalchemy import (Column, ForeignKey, Index, Integer, String,
|
||||
UniqueConstraint)
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from app.core.database import Base
|
||||
@@ -27,7 +29,9 @@ class Inventory(Base, TimestampMixin):
|
||||
|
||||
# Constraints
|
||||
__table_args__ = (
|
||||
UniqueConstraint("product_id", "location", name="uq_inventory_product_location"),
|
||||
UniqueConstraint(
|
||||
"product_id", "location", name="uq_inventory_product_location"
|
||||
),
|
||||
Index("idx_inventory_vendor_product", "vendor_id", "product_id"),
|
||||
Index("idx_inventory_product_location", "product_id", "location"),
|
||||
)
|
||||
|
||||
@@ -1 +1 @@
|
||||
# MarketplaceImportJob model
|
||||
# MarketplaceImportJob model
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from sqlalchemy import Column, DateTime, ForeignKey, Index, Integer, String, Text
|
||||
from sqlalchemy import (Column, DateTime, ForeignKey, Index, Integer, String,
|
||||
Text)
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from app.core.database import Base
|
||||
|
||||
@@ -55,7 +55,9 @@ class MarketplaceProduct(Base, TimestampMixin):
|
||||
marketplace = Column(
|
||||
String, index=True, nullable=True, default="Letzshop"
|
||||
) # Index for marketplace filtering
|
||||
vendor_name = Column(String, index=True, nullable=True) # Index for vendor filtering
|
||||
vendor_name = Column(
|
||||
String, index=True, nullable=True
|
||||
) # Index for vendor filtering
|
||||
|
||||
product = relationship("Product", back_populates="marketplace_product")
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
# MediaFile, ProductMedia models
|
||||
# MediaFile, ProductMedia models
|
||||
|
||||
@@ -1 +1 @@
|
||||
# PerformanceMetric, ErrorLog, SystemAlert models
|
||||
# PerformanceMetric, ErrorLog, SystemAlert models
|
||||
|
||||
@@ -1 +1 @@
|
||||
# NotificationTemplate, NotificationQueue, NotificationLog models
|
||||
# NotificationTemplate, NotificationQueue, NotificationLog models
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# models/database/order.py
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Column, DateTime, Float, ForeignKey, Integer, String, Text, Boolean
|
||||
|
||||
from sqlalchemy import (Boolean, Column, DateTime, Float, ForeignKey, Integer,
|
||||
String, Text)
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from app.core.database import Base
|
||||
@@ -9,11 +11,14 @@ from models.database.base import TimestampMixin
|
||||
|
||||
class Order(Base, TimestampMixin):
|
||||
"""Customer orders."""
|
||||
|
||||
__tablename__ = "orders"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
vendor_id = Column(Integer, ForeignKey("vendors.id"), nullable=False, index=True)
|
||||
customer_id = Column(Integer, ForeignKey("customers.id"), nullable=False, index=True)
|
||||
customer_id = Column(
|
||||
Integer, ForeignKey("customers.id"), nullable=False, index=True
|
||||
)
|
||||
|
||||
order_number = Column(String, nullable=False, unique=True, index=True)
|
||||
|
||||
@@ -30,8 +35,12 @@ class Order(Base, TimestampMixin):
|
||||
currency = Column(String, default="EUR")
|
||||
|
||||
# Addresses (stored as IDs)
|
||||
shipping_address_id = Column(Integer, ForeignKey("customer_addresses.id"), nullable=False)
|
||||
billing_address_id = Column(Integer, ForeignKey("customer_addresses.id"), nullable=False)
|
||||
shipping_address_id = Column(
|
||||
Integer, ForeignKey("customer_addresses.id"), nullable=False
|
||||
)
|
||||
billing_address_id = Column(
|
||||
Integer, ForeignKey("customer_addresses.id"), nullable=False
|
||||
)
|
||||
|
||||
# Shipping
|
||||
shipping_method = Column(String, nullable=True)
|
||||
@@ -50,8 +59,12 @@ class Order(Base, TimestampMixin):
|
||||
# Relationships
|
||||
vendor = relationship("Vendor")
|
||||
customer = relationship("Customer", back_populates="orders")
|
||||
items = relationship("OrderItem", back_populates="order", cascade="all, delete-orphan")
|
||||
shipping_address = relationship("CustomerAddress", foreign_keys=[shipping_address_id])
|
||||
items = relationship(
|
||||
"OrderItem", back_populates="order", cascade="all, delete-orphan"
|
||||
)
|
||||
shipping_address = relationship(
|
||||
"CustomerAddress", foreign_keys=[shipping_address_id]
|
||||
)
|
||||
billing_address = relationship("CustomerAddress", foreign_keys=[billing_address_id])
|
||||
|
||||
def __repr__(self):
|
||||
@@ -60,6 +73,7 @@ class Order(Base, TimestampMixin):
|
||||
|
||||
class OrderItem(Base, TimestampMixin):
|
||||
"""Individual items in an order."""
|
||||
|
||||
__tablename__ = "order_items"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
|
||||
@@ -1 +1 @@
|
||||
# Payment, PaymentMethod, VendorPaymentConfig models
|
||||
# Payment, PaymentMethod, VendorPaymentConfig models
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# models/database/product.py
|
||||
from datetime import datetime
|
||||
from sqlalchemy import Boolean, Column, Float, ForeignKey, Index, Integer, String, UniqueConstraint
|
||||
|
||||
from sqlalchemy import (Boolean, Column, Float, ForeignKey, Index, Integer,
|
||||
String, UniqueConstraint)
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from app.core.database import Base
|
||||
@@ -12,7 +14,9 @@ class Product(Base, TimestampMixin):
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
vendor_id = Column(Integer, ForeignKey("vendors.id"), nullable=False)
|
||||
marketplace_product_id = Column(Integer, ForeignKey("marketplace_products.id"), nullable=False)
|
||||
marketplace_product_id = Column(
|
||||
Integer, ForeignKey("marketplace_products.id"), nullable=False
|
||||
)
|
||||
|
||||
# Vendor-specific overrides
|
||||
product_id = Column(String) # Vendor's internal SKU
|
||||
@@ -34,7 +38,9 @@ class Product(Base, TimestampMixin):
|
||||
# Relationships
|
||||
vendor = relationship("Vendor", back_populates="products")
|
||||
marketplace_product = relationship("MarketplaceProduct", back_populates="product")
|
||||
inventory_entries = relationship("Inventory", back_populates="product", cascade="all, delete-orphan")
|
||||
inventory_entries = relationship(
|
||||
"Inventory", back_populates="product", cascade="all, delete-orphan"
|
||||
)
|
||||
|
||||
# Constraints
|
||||
__table_args__ = (
|
||||
|
||||
@@ -1 +1 @@
|
||||
# SearchIndex, SearchQuery models
|
||||
# SearchIndex, SearchQuery models
|
||||
|
||||
@@ -1 +1 @@
|
||||
# TaskLog model
|
||||
# TaskLog model
|
||||
|
||||
@@ -10,16 +10,18 @@ ROLE CLARIFICATION:
|
||||
- Vendor-specific roles (manager, staff, etc.) are stored in VendorUser.role
|
||||
- Customers are NOT in the User table - they use the Customer model
|
||||
"""
|
||||
from sqlalchemy import Boolean, Column, DateTime, Integer, String, Enum
|
||||
from sqlalchemy.orm import relationship
|
||||
import enum
|
||||
|
||||
from sqlalchemy import Boolean, Column, DateTime, Enum, Integer, String
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from app.core.database import Base
|
||||
from models.database.base import TimestampMixin
|
||||
|
||||
|
||||
class UserRole(str, enum.Enum):
|
||||
"""Platform-level user roles."""
|
||||
|
||||
ADMIN = "admin" # Platform administrator
|
||||
VENDOR = "vendor" # Vendor owner or team member
|
||||
|
||||
@@ -44,12 +46,12 @@ class User(Base, TimestampMixin):
|
||||
last_login = Column(DateTime, nullable=True)
|
||||
|
||||
# Relationships
|
||||
marketplace_import_jobs = relationship("MarketplaceImportJob", back_populates="user")
|
||||
marketplace_import_jobs = relationship(
|
||||
"MarketplaceImportJob", back_populates="user"
|
||||
)
|
||||
owned_vendors = relationship("Vendor", back_populates="owner")
|
||||
vendor_memberships = relationship(
|
||||
"VendorUser",
|
||||
foreign_keys="[VendorUser.user_id]",
|
||||
back_populates="user"
|
||||
"VendorUser", foreign_keys="[VendorUser.user_id]", back_populates="user"
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
@@ -84,8 +86,7 @@ class User(Base, TimestampMixin):
|
||||
return True
|
||||
# Check if team member
|
||||
return any(
|
||||
vm.vendor_id == vendor_id and vm.is_active
|
||||
for vm in self.vendor_memberships
|
||||
vm.vendor_id == vendor_id and vm.is_active for vm in self.vendor_memberships
|
||||
)
|
||||
|
||||
def get_vendor_role(self, vendor_id: int) -> str:
|
||||
|
||||
@@ -5,29 +5,37 @@ Vendor model representing entities that sell products or services.
|
||||
This module defines the Vendor model along with its relationships to
|
||||
other models such as User (owner), Product, Customer, Order, and MarketplaceImportJob.
|
||||
"""
|
||||
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, Text, JSON, DateTime
|
||||
import enum
|
||||
|
||||
from sqlalchemy import (JSON, Boolean, Column, DateTime, ForeignKey, Integer,
|
||||
String, Text)
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from app.core.config import settings
|
||||
# Import Base from the central database module instead of creating a new one
|
||||
from app.core.database import Base
|
||||
from models.database.base import TimestampMixin
|
||||
from app.core.config import settings
|
||||
import enum
|
||||
|
||||
|
||||
class Vendor(Base, TimestampMixin):
|
||||
"""Represents a vendor in the system."""
|
||||
|
||||
__tablename__ = "vendors" # Name of the table in the database
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True) # Primary key and indexed column for vendor ID
|
||||
vendor_code = Column(String, unique=True, index=True,
|
||||
nullable=False) # Unique, indexed, non-nullable vendor code column
|
||||
subdomain = Column(String(100), unique=True, nullable=False,
|
||||
index=True) # Unique, non-nullable subdomain column with indexing
|
||||
id = Column(
|
||||
Integer, primary_key=True, index=True
|
||||
) # Primary key and indexed column for vendor ID
|
||||
vendor_code = Column(
|
||||
String, unique=True, index=True, nullable=False
|
||||
) # Unique, indexed, non-nullable vendor code column
|
||||
subdomain = Column(
|
||||
String(100), unique=True, nullable=False, index=True
|
||||
) # Unique, non-nullable subdomain column with indexing
|
||||
name = Column(String, nullable=False) # Non-nullable name column for the vendor
|
||||
description = Column(Text) # Optional text description column for the vendor
|
||||
owner_user_id = Column(Integer, ForeignKey("users.id"),
|
||||
nullable=False) # Foreign key to user ID of the vendor's owner
|
||||
owner_user_id = Column(
|
||||
Integer, ForeignKey("users.id"), nullable=False
|
||||
) # Foreign key to user ID of the vendor's owner
|
||||
|
||||
# Contact information
|
||||
contact_email = Column(String) # Optional email column for contact information
|
||||
@@ -40,34 +48,46 @@ class Vendor(Base, TimestampMixin):
|
||||
letzshop_csv_url_de = Column(String) # URL for German CSV in Letzshop
|
||||
|
||||
# Business information
|
||||
business_address = Column(Text) # Optional text address column for business information
|
||||
business_address = Column(
|
||||
Text
|
||||
) # Optional text address column for business information
|
||||
tax_number = Column(String) # Optional tax number column for business information
|
||||
|
||||
# Status
|
||||
is_active = Column(Boolean, default=True) # Boolean to indicate if the vendor is active
|
||||
is_verified = Column(Boolean, default=False) # Boolean to indicate if the vendor is verified
|
||||
|
||||
is_active = Column(
|
||||
Boolean, default=True
|
||||
) # Boolean to indicate if the vendor is active
|
||||
is_verified = Column(
|
||||
Boolean, default=False
|
||||
) # Boolean to indicate if the vendor is verified
|
||||
|
||||
# ========================================================================
|
||||
# Relationships
|
||||
# ========================================================================
|
||||
owner = relationship("User", back_populates="owned_vendors") # Relationship with User model for the vendor's owner
|
||||
vendor_users = relationship("VendorUser",
|
||||
back_populates="vendor") # Relationship with VendorUser model for users in this vendor
|
||||
products = relationship("Product",
|
||||
back_populates="vendor") # Relationship with Product model for products of this vendor
|
||||
customers = relationship("Customer",
|
||||
back_populates="vendor") # Relationship with Customer model for customers of this vendor
|
||||
orders = relationship("Order",
|
||||
back_populates="vendor") # Relationship with Order model for orders placed by this vendor
|
||||
marketplace_import_jobs = relationship("MarketplaceImportJob",
|
||||
back_populates="vendor") # Relationship with MarketplaceImportJob model for import jobs related to this vendor
|
||||
owner = relationship(
|
||||
"User", back_populates="owned_vendors"
|
||||
) # Relationship with User model for the vendor's owner
|
||||
vendor_users = relationship(
|
||||
"VendorUser", back_populates="vendor"
|
||||
) # Relationship with VendorUser model for users in this vendor
|
||||
products = relationship(
|
||||
"Product", back_populates="vendor"
|
||||
) # Relationship with Product model for products of this vendor
|
||||
customers = relationship(
|
||||
"Customer", back_populates="vendor"
|
||||
) # Relationship with Customer model for customers of this vendor
|
||||
orders = relationship(
|
||||
"Order", back_populates="vendor"
|
||||
) # Relationship with Order model for orders placed by this vendor
|
||||
marketplace_import_jobs = relationship(
|
||||
"MarketplaceImportJob", back_populates="vendor"
|
||||
) # Relationship with MarketplaceImportJob model for import jobs related to this vendor
|
||||
|
||||
domains = relationship(
|
||||
"VendorDomain",
|
||||
back_populates="vendor",
|
||||
cascade="all, delete-orphan",
|
||||
order_by="VendorDomain.is_primary.desc()"
|
||||
order_by="VendorDomain.is_primary.desc()",
|
||||
) # Relationship with VendorDomain model for custom domains of the vendor
|
||||
|
||||
# Single theme relationship (ONE vendor = ONE theme)
|
||||
@@ -77,14 +97,12 @@ class Vendor(Base, TimestampMixin):
|
||||
"VendorTheme",
|
||||
back_populates="vendor",
|
||||
uselist=False,
|
||||
cascade="all, delete-orphan"
|
||||
cascade="all, delete-orphan",
|
||||
) # Relationship with VendorTheme model for the active theme of the vendor
|
||||
|
||||
# Content pages relationship (vendor can override platform default pages)
|
||||
content_pages = relationship(
|
||||
"ContentPage",
|
||||
back_populates="vendor",
|
||||
cascade="all, delete-orphan"
|
||||
"ContentPage", back_populates="vendor", cascade="all, delete-orphan"
|
||||
) # Relationship with ContentPage model for vendor-specific content pages
|
||||
|
||||
def __repr__(self):
|
||||
@@ -121,23 +139,16 @@ class Vendor(Base, TimestampMixin):
|
||||
"accent": "#ec4899",
|
||||
"background": "#ffffff",
|
||||
"text": "#1f2937",
|
||||
"border": "#e5e7eb"
|
||||
},
|
||||
"fonts": {
|
||||
"heading": "Inter, sans-serif",
|
||||
"body": "Inter, sans-serif"
|
||||
"border": "#e5e7eb",
|
||||
},
|
||||
"fonts": {"heading": "Inter, sans-serif", "body": "Inter, sans-serif"},
|
||||
"branding": {
|
||||
"logo": None,
|
||||
"logo_dark": None,
|
||||
"favicon": None,
|
||||
"banner": None
|
||||
},
|
||||
"layout": {
|
||||
"style": "grid",
|
||||
"header": "fixed",
|
||||
"product_card": "modern"
|
||||
"banner": None,
|
||||
},
|
||||
"layout": {"style": "grid", "header": "fixed", "product_card": "modern"},
|
||||
"social_links": {},
|
||||
"custom_css": None,
|
||||
"css_variables": {
|
||||
@@ -149,18 +160,22 @@ class Vendor(Base, TimestampMixin):
|
||||
"--color-border": "#e5e7eb",
|
||||
"--font-heading": "Inter, sans-serif",
|
||||
"--font-body": "Inter, sans-serif",
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
def get_primary_color(self) -> str:
|
||||
"""Get primary color from active theme."""
|
||||
theme = self.get_effective_theme()
|
||||
return theme.get("colors", {}).get("primary", "#6366f1") # Default to default theme if not found
|
||||
return theme.get("colors", {}).get(
|
||||
"primary", "#6366f1"
|
||||
) # Default to default theme if not found
|
||||
|
||||
def get_logo_url(self) -> str:
|
||||
"""Get logo URL from active theme."""
|
||||
theme = self.get_effective_theme()
|
||||
return theme.get("branding", {}).get("logo") # Return None or the logo URL if found
|
||||
return theme.get("branding", {}).get(
|
||||
"logo"
|
||||
) # Return None or the logo URL if found
|
||||
|
||||
# ========================================================================
|
||||
# Domain Helper Methods
|
||||
@@ -177,7 +192,9 @@ class Vendor(Base, TimestampMixin):
|
||||
@property
|
||||
def all_domains(self):
|
||||
"""Get all active domains (subdomain + custom domains)."""
|
||||
domains = [f"{self.subdomain}.{settings.platform_domain}"] # Start with the main subdomain
|
||||
domains = [
|
||||
f"{self.subdomain}.{settings.platform_domain}"
|
||||
] # Start with the main subdomain
|
||||
for domain in self.domains:
|
||||
if domain.is_active:
|
||||
domains.append(domain.domain) # Add other active custom domains
|
||||
@@ -186,6 +203,7 @@ class Vendor(Base, TimestampMixin):
|
||||
|
||||
class VendorUserType(str, enum.Enum):
|
||||
"""Types of vendor users."""
|
||||
|
||||
OWNER = "owner" # Vendor owner (full access to vendor area)
|
||||
TEAM_MEMBER = "member" # Team member (role-based access to vendor area)
|
||||
|
||||
@@ -222,14 +240,18 @@ class VendorUser(Base, TimestampMixin):
|
||||
invitation_sent_at = Column(DateTime, nullable=True)
|
||||
invitation_accepted_at = Column(DateTime, nullable=True)
|
||||
|
||||
is_active = Column(Boolean, default=False, nullable=False) # False until invitation accepted
|
||||
is_active = Column(
|
||||
Boolean, default=False, nullable=False
|
||||
) # False until invitation accepted
|
||||
"""Indicates whether the VendorUser role is active."""
|
||||
|
||||
# Relationships
|
||||
vendor = relationship("Vendor", back_populates="vendor_users")
|
||||
"""Relationship to the Vendor model, representing the associated vendor."""
|
||||
|
||||
user = relationship("User", foreign_keys=[user_id], back_populates="vendor_memberships")
|
||||
user = relationship(
|
||||
"User", foreign_keys=[user_id], back_populates="vendor_memberships"
|
||||
)
|
||||
"""Relationship to the User model, representing the user who holds this role within the vendor."""
|
||||
|
||||
inviter = relationship("User", foreign_keys=[invited_by])
|
||||
@@ -287,6 +309,7 @@ class VendorUser(Base, TimestampMixin):
|
||||
if self.is_owner:
|
||||
# Return all possible permissions
|
||||
from app.core.permissions import VendorPermissions
|
||||
|
||||
return list(VendorPermissions.__members__.values())
|
||||
|
||||
if self.role and self.role.permissions:
|
||||
@@ -294,6 +317,7 @@ class VendorUser(Base, TimestampMixin):
|
||||
|
||||
return []
|
||||
|
||||
|
||||
class Role(Base, TimestampMixin):
|
||||
"""Represents a role within a vendor's system."""
|
||||
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
Vendor Domain Model - Maps custom domains to vendors
|
||||
"""
|
||||
from datetime import datetime, timezone
|
||||
from sqlalchemy import (
|
||||
Column, Integer, String, Boolean, DateTime,
|
||||
ForeignKey, UniqueConstraint, Index
|
||||
)
|
||||
|
||||
from sqlalchemy import (Boolean, Column, DateTime, ForeignKey, Index, Integer,
|
||||
String, UniqueConstraint)
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from app.core.database import Base
|
||||
from models.database.base import TimestampMixin
|
||||
|
||||
@@ -21,10 +21,13 @@ class VendorDomain(Base, TimestampMixin):
|
||||
- shop.mybusiness.com → Vendor 2
|
||||
- www.customdomain1.com → Vendor 1 (www is stripped)
|
||||
"""
|
||||
|
||||
__tablename__ = "vendor_domains"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
vendor_id = Column(Integer, ForeignKey("vendors.id", ondelete="CASCADE"), nullable=False)
|
||||
vendor_id = Column(
|
||||
Integer, ForeignKey("vendors.id", ondelete="CASCADE"), nullable=False
|
||||
)
|
||||
|
||||
# Domain configuration
|
||||
domain = Column(String(255), nullable=False, unique=True, index=True)
|
||||
@@ -32,7 +35,9 @@ class VendorDomain(Base, TimestampMixin):
|
||||
is_active = Column(Boolean, default=True, nullable=False)
|
||||
|
||||
# SSL/TLS status (for monitoring)
|
||||
ssl_status = Column(String(50), default="pending") # pending, active, expired, error
|
||||
ssl_status = Column(
|
||||
String(50), default="pending"
|
||||
) # pending, active, expired, error
|
||||
ssl_verified_at = Column(DateTime(timezone=True), nullable=True)
|
||||
|
||||
# DNS verification (to confirm domain ownership)
|
||||
@@ -45,9 +50,9 @@ class VendorDomain(Base, TimestampMixin):
|
||||
|
||||
# Constraints
|
||||
__table_args__ = (
|
||||
UniqueConstraint('vendor_id', 'domain', name='uq_vendor_domain'),
|
||||
Index('idx_domain_active', 'domain', 'is_active'),
|
||||
Index('idx_vendor_primary', 'vendor_id', 'is_primary'),
|
||||
UniqueConstraint("vendor_id", "domain", name="uq_vendor_domain"),
|
||||
Index("idx_domain_active", "domain", "is_active"),
|
||||
Index("idx_vendor_primary", "vendor_id", "is_primary"),
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
Vendor Theme Configuration Model
|
||||
Allows each vendor to customize their shop's appearance
|
||||
"""
|
||||
from sqlalchemy import Column, Integer, String, Boolean, Text, JSON, ForeignKey
|
||||
from sqlalchemy import JSON, Boolean, Column, ForeignKey, Integer, String, Text
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
from app.core.database import Base
|
||||
from models.database.base import TimestampMixin
|
||||
|
||||
@@ -22,6 +23,7 @@ class VendorTheme(Base, TimestampMixin):
|
||||
|
||||
Theme presets available: default, modern, classic, minimal, vibrant
|
||||
"""
|
||||
|
||||
__tablename__ = "vendor_themes"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
@@ -29,22 +31,27 @@ class VendorTheme(Base, TimestampMixin):
|
||||
Integer,
|
||||
ForeignKey("vendors.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
unique=True # ONE vendor = ONE theme
|
||||
unique=True, # ONE vendor = ONE theme
|
||||
)
|
||||
|
||||
# Basic Theme Settings
|
||||
theme_name = Column(String(100), default="default") # default, modern, classic, minimal, vibrant
|
||||
theme_name = Column(
|
||||
String(100), default="default"
|
||||
) # default, modern, classic, minimal, vibrant
|
||||
is_active = Column(Boolean, default=True)
|
||||
|
||||
# Color Scheme (JSON for flexibility)
|
||||
colors = Column(JSON, default={
|
||||
"primary": "#6366f1", # Indigo
|
||||
"secondary": "#8b5cf6", # Purple
|
||||
"accent": "#ec4899", # Pink
|
||||
"background": "#ffffff", # White
|
||||
"text": "#1f2937", # Gray-800
|
||||
"border": "#e5e7eb" # Gray-200
|
||||
})
|
||||
colors = Column(
|
||||
JSON,
|
||||
default={
|
||||
"primary": "#6366f1", # Indigo
|
||||
"secondary": "#8b5cf6", # Purple
|
||||
"accent": "#ec4899", # Pink
|
||||
"background": "#ffffff", # White
|
||||
"text": "#1f2937", # Gray-800
|
||||
"border": "#e5e7eb", # Gray-200
|
||||
},
|
||||
)
|
||||
|
||||
# Typography
|
||||
font_family_heading = Column(String(100), default="Inter, sans-serif")
|
||||
@@ -59,7 +66,9 @@ class VendorTheme(Base, TimestampMixin):
|
||||
# Layout Preferences
|
||||
layout_style = Column(String(50), default="grid") # grid, list, masonry
|
||||
header_style = Column(String(50), default="fixed") # fixed, static, transparent
|
||||
product_card_style = Column(String(50), default="modern") # modern, classic, minimal
|
||||
product_card_style = Column(
|
||||
String(50), default="modern"
|
||||
) # modern, classic, minimal
|
||||
|
||||
# Custom CSS (for advanced customization)
|
||||
custom_css = Column(Text, nullable=True)
|
||||
@@ -68,14 +77,18 @@ class VendorTheme(Base, TimestampMixin):
|
||||
social_links = Column(JSON, default={}) # {facebook: "url", instagram: "url", etc.}
|
||||
|
||||
# SEO & Meta
|
||||
meta_title_template = Column(String(200), nullable=True) # e.g., "{product_name} - {shop_name}"
|
||||
meta_title_template = Column(
|
||||
String(200), nullable=True
|
||||
) # e.g., "{product_name} - {shop_name}"
|
||||
meta_description = Column(Text, nullable=True)
|
||||
|
||||
# Relationships - FIXED: back_populates must match the relationship name in Vendor model
|
||||
vendor = relationship("Vendor", back_populates="vendor_theme")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<VendorTheme(vendor_id={self.vendor_id}, theme_name='{self.theme_name}')>"
|
||||
return (
|
||||
f"<VendorTheme(vendor_id={self.vendor_id}, theme_name='{self.theme_name}')>"
|
||||
)
|
||||
|
||||
@property
|
||||
def primary_color(self):
|
||||
|
||||
Reference in New Issue
Block a user