Files
orion/models/database/product_translation.py
Samir Boulahtit 9920430b9e fix: correct tojson|safe usage in templates and update validator
- Remove |safe from |tojson in HTML attributes (x-data) - quotes must
  become " for browsers to parse correctly
- Update LANG-002 and LANG-003 architecture rules to document correct
  |tojson usage patterns:
  - HTML attributes: |tojson (no |safe)
  - Script blocks: |tojson|safe
- Fix validator to warn when |tojson|safe is used in x-data (breaks
  HTML attribute parsing)
- Improve code quality across services, APIs, and tests

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-13 22:59:51 +01:00

217 lines
7.7 KiB
Python

"""Product Translation model for vendor-specific localized content.
This model stores vendor-specific translations that can override the
marketplace product translations. The override pattern works as follows:
- NULL value = inherit from marketplace_product_translations
- Non-NULL value = vendor-specific override
This allows vendors to customize titles, descriptions, and SEO content
per language while still being able to "reset to source" by setting
values back to NULL.
"""
from sqlalchemy import (
Column,
ForeignKey,
Index,
Integer,
String,
Text,
UniqueConstraint,
)
from sqlalchemy.orm import relationship
from app.core.database import Base
from models.database.base import TimestampMixin
class ProductTranslation(Base, TimestampMixin):
"""Vendor-specific localized content with override capability.
Each vendor can have their own translations that override the
marketplace product translations. Fields set to NULL inherit
their value from the linked marketplace_product_translation.
"""
__tablename__ = "product_translations"
id = Column(Integer, primary_key=True, index=True)
product_id = Column(
Integer,
ForeignKey("products.id", ondelete="CASCADE"),
nullable=False,
)
language = Column(String(5), nullable=False) # 'en', 'fr', 'de', 'lb'
# === OVERRIDABLE LOCALIZED FIELDS (NULL = inherit) ===
title = Column(String)
description = Column(Text)
short_description = Column(String(500))
# SEO Overrides
meta_title = Column(String(70))
meta_description = Column(String(160))
url_slug = Column(String(255))
# === RELATIONSHIPS ===
product = relationship("Product", back_populates="translations")
__table_args__ = (
UniqueConstraint("product_id", "language", name="uq_product_translation"),
Index("idx_pt_product_id", "product_id"),
Index("idx_pt_product_language", "product_id", "language"),
)
# === OVERRIDABLE FIELDS LIST ===
OVERRIDABLE_FIELDS = [
"title",
"description",
"short_description",
"meta_title",
"meta_description",
"url_slug",
]
def __repr__(self):
return (
f"<ProductTranslation(id={self.id}, "
f"product_id={self.product_id}, "
f"language='{self.language}', "
f"title='{self.title[:30] if self.title else None}...')>"
)
# === HELPER METHODS ===
def _get_marketplace_translation(self):
"""Get the corresponding marketplace translation for fallback."""
product = self.product
if product and product.marketplace_product:
mp = product.marketplace_product
for t in mp.translations:
if t.language == self.language:
return t
return None
def _get_marketplace_translation_field(self, field: str):
"""Helper to get a field from marketplace translation."""
mp_translation = self._get_marketplace_translation()
if mp_translation:
return getattr(mp_translation, field, None)
return None
# === EFFECTIVE PROPERTIES (Override Pattern) ===
def get_effective_title(self) -> str | None:
"""Get title with fallback to marketplace translation."""
if self.title is not None:
return self.title
return self._get_marketplace_translation_field("title")
def get_effective_description(self) -> str | None:
"""Get description with fallback to marketplace translation."""
if self.description is not None:
return self.description
return self._get_marketplace_translation_field("description")
def get_effective_short_description(self) -> str | None:
"""Get short description with fallback to marketplace translation."""
if self.short_description is not None:
return self.short_description
return self._get_marketplace_translation_field("short_description")
def get_effective_meta_title(self) -> str | None:
"""Get meta title with fallback to marketplace translation."""
if self.meta_title is not None:
return self.meta_title
return self._get_marketplace_translation_field("meta_title")
def get_effective_meta_description(self) -> str | None:
"""Get meta description with fallback to marketplace translation."""
if self.meta_description is not None:
return self.meta_description
return self._get_marketplace_translation_field("meta_description")
def get_effective_url_slug(self) -> str | None:
"""Get URL slug with fallback to marketplace translation."""
if self.url_slug is not None:
return self.url_slug
return self._get_marketplace_translation_field("url_slug")
# === OVERRIDE INFO METHOD ===
def get_override_info(self) -> dict:
"""Get all fields with inheritance flags.
Returns a dict with effective values and override flags.
"""
mp_translation = self._get_marketplace_translation()
return {
# Title
"title": self.get_effective_title(),
"title_overridden": self.title is not None,
"title_source": mp_translation.title if mp_translation else None,
# Description
"description": self.get_effective_description(),
"description_overridden": self.description is not None,
"description_source": mp_translation.description
if mp_translation
else None,
# Short Description
"short_description": self.get_effective_short_description(),
"short_description_overridden": self.short_description is not None,
"short_description_source": (
mp_translation.short_description if mp_translation else None
),
# Meta Title
"meta_title": self.get_effective_meta_title(),
"meta_title_overridden": self.meta_title is not None,
"meta_title_source": mp_translation.meta_title if mp_translation else None,
# Meta Description
"meta_description": self.get_effective_meta_description(),
"meta_description_overridden": self.meta_description is not None,
"meta_description_source": (
mp_translation.meta_description if mp_translation else None
),
# URL Slug
"url_slug": self.get_effective_url_slug(),
"url_slug_overridden": self.url_slug is not None,
"url_slug_source": mp_translation.url_slug if mp_translation else None,
}
# === RESET METHODS ===
def reset_field_to_source(self, field_name: str) -> bool:
"""Reset a single field to inherit from marketplace translation.
Args:
field_name: Name of the field to reset
Returns:
True if field was reset, False if field is not overridable
"""
if field_name in self.OVERRIDABLE_FIELDS:
setattr(self, field_name, None)
return True
return False
def reset_to_source(self) -> None:
"""Reset all fields to inherit from marketplace translation."""
for field in self.OVERRIDABLE_FIELDS:
setattr(self, field, None)
def reset_fields_to_source(self, field_names: list[str]) -> list[str]:
"""Reset multiple fields to inherit from marketplace translation.
Args:
field_names: List of field names to reset
Returns:
List of fields that were successfully reset
"""
reset_fields = []
for field in field_names:
if self.reset_field_to_source(field):
reset_fields.append(field)
return reset_fields