Files
Reklamator/app/models/product.py
T

213 lines
6.1 KiB
Python
Raw Normal View History

"""Product model"""
import os
import yaml
from flask import current_app
class Product:
"""Product/Service model
Attributes:
product_id: Unique product identifier
name: Product/service name
submission_url_slug: URL slug for submission form
owner_language: Preferred language for product owner
assigned_owner_ids: List of product owner user IDs
status: Product status ('active' or 'archived')
2025-10-17 15:32:20 +02:00
description: Optional product description for landing page
"""
def __init__(self, product_id, name, submission_url_slug, owner_language,
2025-10-17 15:32:20 +02:00
assigned_owner_ids, status='active', description=None):
self.product_id = product_id
self.name = name
self.submission_url_slug = submission_url_slug
self.owner_language = owner_language
self.assigned_owner_ids = assigned_owner_ids or []
self.status = status
2025-10-17 15:32:20 +02:00
self.description = description
def to_dict(self):
"""Convert product to dictionary
Returns:
dict: Product data
"""
2025-10-17 15:32:20 +02:00
data = {
'product_id': self.product_id,
'name': self.name,
'submission_url_slug': self.submission_url_slug,
'owner_language': self.owner_language,
'assigned_owner_ids': self.assigned_owner_ids,
'status': self.status
}
2025-10-17 15:32:20 +02:00
if self.description:
data['description'] = self.description
return data
@classmethod
def from_dict(cls, data):
"""Create product from dictionary
Args:
data: Dictionary with product data
Returns:
Product: Product instance
"""
return cls(
product_id=data['product_id'],
name=data['name'],
submission_url_slug=data['submission_url_slug'],
owner_language=data['owner_language'],
assigned_owner_ids=data.get('assigned_owner_ids', []),
2025-10-17 15:32:20 +02:00
status=data.get('status', 'active'),
description=data.get('description')
)
@staticmethod
def _get_product_dir(product_id):
"""Get product directory path
Args:
product_id: Product ID
Returns:
str: Path to product directory
"""
return os.path.join(current_app.config['DATA_DIR'], 'products', product_id)
@staticmethod
def _get_config_file(product_id):
"""Get product config file path
Args:
product_id: Product ID
Returns:
str: Path to config.yaml
"""
product_dir = Product._get_product_dir(product_id)
return os.path.join(product_dir, 'config.yaml')
@classmethod
def get_by_id(cls, product_id):
"""Load product by ID
Args:
product_id: Product ID to load
Returns:
Product or None: Product instance if found, None otherwise
"""
config_file = cls._get_config_file(product_id)
if not os.path.exists(config_file):
return None
with open(config_file, 'r') as f:
data = yaml.safe_load(f)
return cls.from_dict(data)
@classmethod
def get_by_slug(cls, slug):
"""Load product by submission URL slug
Args:
slug: Submission URL slug
Returns:
Product or None: Product instance if found, None otherwise
"""
# Scan all product directories
products_dir = os.path.join(current_app.config['DATA_DIR'], 'products')
if not os.path.exists(products_dir):
return None
for product_id in os.listdir(products_dir):
product_dir = os.path.join(products_dir, product_id)
if not os.path.isdir(product_dir):
continue
config_file = os.path.join(product_dir, 'config.yaml')
if not os.path.exists(config_file):
continue
with open(config_file, 'r') as f:
data = yaml.safe_load(f)
if data.get('submission_url_slug') == slug:
return cls.from_dict(data)
return None
@classmethod
def get_all(cls):
"""Get all products
Returns:
list: List of Product instances
"""
products = []
products_dir = os.path.join(current_app.config['DATA_DIR'], 'products')
if not os.path.exists(products_dir):
return products
for product_id in os.listdir(products_dir):
product = cls.get_by_id(product_id)
if product:
products.append(product)
return products
@classmethod
def load_active(cls):
"""Load all active products, sorted alphabetically by name then product_id
Returns:
list[Product]: Active products with valid submission_url_slug, sorted by:
1. name (case-insensitive alphabetical)
2. product_id (alphabetical) as tiebreaker
Products with missing/invalid submission_url_slug are excluded.
"""
all_products = cls.get_all()
active = [p for p in all_products
if p.status == 'active' and p.submission_url_slug]
return sorted(active, key=lambda p: (p.name.lower(), p.product_id))
def save(self):
"""Save product to filesystem"""
product_dir = self._get_product_dir(self.product_id)
os.makedirs(product_dir, exist_ok=True)
config_file = self._get_config_file(self.product_id)
with open(config_file, 'w') as f:
yaml.dump(self.to_dict(), f, default_flow_style=False)
def delete(self):
"""Delete product (not implemented - use archive instead)"""
raise NotImplementedError("Products should be archived, not deleted")
def is_active(self):
"""Check if product is active
Returns:
bool: True if status is 'active', False otherwise
"""
return self.status == 'active'
def is_archived(self):
"""Check if product is archived
Returns:
bool: True if status is 'archived', False otherwise
"""
return self.status == 'archived'