Multitenant implementation with custom Domain, theme per vendor
This commit is contained in:
@@ -1,11 +1,9 @@
|
||||
# models/database/vendor.py - ENHANCED VERSION
|
||||
# models/database/vendor.py
|
||||
"""
|
||||
Enhanced Vendor model with theme support.
|
||||
Vendor model with theme support.
|
||||
|
||||
Changes from your current version:
|
||||
1. Keep existing theme_config JSON field
|
||||
2. Add optional VendorTheme relationship for advanced themes
|
||||
3. Add helper methods for theme access
|
||||
A vendor has ONE active theme stored in the vendor_themes table.
|
||||
Theme presets available: default, modern, classic, minimal, vibrant
|
||||
"""
|
||||
from sqlalchemy import (Boolean, Column, ForeignKey, Integer, String, Text, JSON)
|
||||
from sqlalchemy.orm import relationship
|
||||
@@ -27,10 +25,6 @@ class Vendor(Base, TimestampMixin):
|
||||
description = Column(Text)
|
||||
owner_user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
|
||||
|
||||
# Simple theme config (JSON)
|
||||
# This stores basic theme settings like colors, fonts
|
||||
theme_config = Column(JSON, default=dict)
|
||||
|
||||
# Contact information
|
||||
contact_email = Column(String)
|
||||
contact_phone = Column(String)
|
||||
@@ -49,13 +43,16 @@ class Vendor(Base, TimestampMixin):
|
||||
is_active = Column(Boolean, default=True)
|
||||
is_verified = Column(Boolean, default=False)
|
||||
|
||||
# ========================================================================
|
||||
# Relationships
|
||||
# ========================================================================
|
||||
owner = relationship("User", back_populates="owned_vendors")
|
||||
vendor_users = relationship("VendorUser", back_populates="vendor")
|
||||
products = relationship("Product", back_populates="vendor")
|
||||
customers = relationship("Customer", back_populates="vendor")
|
||||
orders = relationship("Order", back_populates="vendor")
|
||||
marketplace_import_jobs = relationship("MarketplaceImportJob", back_populates="vendor")
|
||||
|
||||
domains = relationship(
|
||||
"VendorDomain",
|
||||
back_populates="vendor",
|
||||
@@ -63,16 +60,8 @@ class Vendor(Base, TimestampMixin):
|
||||
order_by="VendorDomain.is_primary.desc()"
|
||||
)
|
||||
|
||||
theme = relationship(
|
||||
"VendorTheme",
|
||||
back_populates="vendor",
|
||||
uselist=False,
|
||||
cascade="all, delete-orphan"
|
||||
)
|
||||
|
||||
# Optional advanced theme (for premium vendors)
|
||||
# This is optional - vendors can use theme_config OR VendorTheme
|
||||
advanced_theme = relationship(
|
||||
# Single theme relationship (ONE vendor = ONE theme)
|
||||
vendor_theme = relationship(
|
||||
"VendorTheme",
|
||||
back_populates="vendor",
|
||||
uselist=False,
|
||||
@@ -86,81 +75,22 @@ class Vendor(Base, TimestampMixin):
|
||||
# Theme Helper Methods
|
||||
# ========================================================================
|
||||
|
||||
@property
|
||||
def active_theme(self):
|
||||
"""Get vendor's active theme or return default"""
|
||||
if self.theme and self.theme.is_active:
|
||||
return self.theme
|
||||
return None
|
||||
|
||||
@property
|
||||
def theme(self):
|
||||
def get_effective_theme(self) -> dict:
|
||||
"""
|
||||
Get theme configuration for this vendor.
|
||||
Get active theme for this vendor.
|
||||
|
||||
Priority:
|
||||
1. Advanced theme (VendorTheme) if configured
|
||||
2. theme_config JSON field
|
||||
3. Default theme
|
||||
Returns theme from vendor_themes table, or default theme if not set.
|
||||
|
||||
Returns dict with theme configuration.
|
||||
Returns:
|
||||
dict: Theme configuration with colors, fonts, layout, etc.
|
||||
"""
|
||||
# Priority 1: Advanced theme
|
||||
if self.advanced_theme and self.advanced_theme.is_active:
|
||||
return self.advanced_theme.to_dict()
|
||||
# Check vendor_themes table
|
||||
if self.vendor_theme and self.vendor_theme.is_active:
|
||||
return self.vendor_theme.to_dict()
|
||||
|
||||
# Priority 2: Basic theme_config
|
||||
if self.theme_config:
|
||||
return self._normalize_theme_config(self.theme_config)
|
||||
|
||||
# Priority 3: Default theme
|
||||
# Return default theme
|
||||
return self._get_default_theme()
|
||||
|
||||
def _normalize_theme_config(self, config: dict) -> dict:
|
||||
"""
|
||||
Normalize theme_config JSON to standard format.
|
||||
Ensures backward compatibility with existing theme_config.
|
||||
"""
|
||||
return {
|
||||
"theme_name": config.get("theme_name", "basic"),
|
||||
"colors": config.get("colors", {
|
||||
"primary": "#6366f1",
|
||||
"secondary": "#8b5cf6",
|
||||
"accent": "#ec4899"
|
||||
}),
|
||||
"fonts": config.get("fonts", {
|
||||
"heading": "Inter, sans-serif",
|
||||
"body": "Inter, sans-serif"
|
||||
}),
|
||||
"branding": config.get("branding", {
|
||||
"logo": None,
|
||||
"logo_dark": None,
|
||||
"favicon": None
|
||||
}),
|
||||
"layout": config.get("layout", {
|
||||
"style": "grid",
|
||||
"header": "fixed"
|
||||
}),
|
||||
"custom_css": config.get("custom_css", None),
|
||||
"css_variables": self._generate_css_variables(config)
|
||||
}
|
||||
|
||||
def _generate_css_variables(self, config: dict) -> dict:
|
||||
"""Generate CSS custom properties from theme config"""
|
||||
colors = config.get("colors", {})
|
||||
fonts = config.get("fonts", {})
|
||||
|
||||
return {
|
||||
"--color-primary": colors.get("primary", "#6366f1"),
|
||||
"--color-secondary": colors.get("secondary", "#8b5cf6"),
|
||||
"--color-accent": colors.get("accent", "#ec4899"),
|
||||
"--color-background": colors.get("background", "#ffffff"),
|
||||
"--color-text": colors.get("text", "#1f2937"),
|
||||
"--color-border": colors.get("border", "#e5e7eb"),
|
||||
"--font-heading": fonts.get("heading", "Inter, sans-serif"),
|
||||
"--font-body": fonts.get("body", "Inter, sans-serif"),
|
||||
}
|
||||
|
||||
def _get_default_theme(self) -> dict:
|
||||
"""Default theme configuration"""
|
||||
return {
|
||||
@@ -180,12 +110,15 @@ class Vendor(Base, TimestampMixin):
|
||||
"branding": {
|
||||
"logo": None,
|
||||
"logo_dark": None,
|
||||
"favicon": None
|
||||
"favicon": None,
|
||||
"banner": None
|
||||
},
|
||||
"layout": {
|
||||
"style": "grid",
|
||||
"header": "fixed"
|
||||
"header": "fixed",
|
||||
"product_card": "modern"
|
||||
},
|
||||
"social_links": {},
|
||||
"custom_css": None,
|
||||
"css_variables": {
|
||||
"--color-primary": "#6366f1",
|
||||
@@ -199,42 +132,15 @@ class Vendor(Base, TimestampMixin):
|
||||
}
|
||||
}
|
||||
|
||||
@property
|
||||
def primary_color(self):
|
||||
"""Get primary color from theme"""
|
||||
return self.theme.get("colors", {}).get("primary", "#6366f1")
|
||||
def get_primary_color(self) -> str:
|
||||
"""Get primary color from active theme"""
|
||||
theme = self.get_effective_theme()
|
||||
return theme.get("colors", {}).get("primary", "#6366f1")
|
||||
|
||||
@property
|
||||
def logo_url(self):
|
||||
"""Get logo URL from theme"""
|
||||
return self.theme.get("branding", {}).get("logo")
|
||||
|
||||
def update_theme(self, theme_data: dict):
|
||||
"""
|
||||
Update vendor theme configuration.
|
||||
|
||||
Args:
|
||||
theme_data: Dict with theme settings
|
||||
{colors: {...}, fonts: {...}, etc}
|
||||
"""
|
||||
if not self.theme_config:
|
||||
self.theme_config = {}
|
||||
|
||||
# Update theme_config JSON
|
||||
if "colors" in theme_data:
|
||||
self.theme_config["colors"] = theme_data["colors"]
|
||||
|
||||
if "fonts" in theme_data:
|
||||
self.theme_config["fonts"] = theme_data["fonts"]
|
||||
|
||||
if "branding" in theme_data:
|
||||
self.theme_config["branding"] = theme_data["branding"]
|
||||
|
||||
if "layout" in theme_data:
|
||||
self.theme_config["layout"] = theme_data["layout"]
|
||||
|
||||
if "custom_css" in theme_data:
|
||||
self.theme_config["custom_css"] = theme_data["custom_css"]
|
||||
def get_logo_url(self) -> str:
|
||||
"""Get logo URL from active theme"""
|
||||
theme = self.get_effective_theme()
|
||||
return theme.get("branding", {}).get("logo")
|
||||
|
||||
# ========================================================================
|
||||
# Domain Helper Methods
|
||||
@@ -257,6 +163,7 @@ class Vendor(Base, TimestampMixin):
|
||||
domains.append(domain.domain)
|
||||
return domains
|
||||
|
||||
|
||||
# Keep your existing VendorUser and Role models unchanged
|
||||
class VendorUser(Base, TimestampMixin):
|
||||
__tablename__ = "vendor_users"
|
||||
|
||||
Reference in New Issue
Block a user