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>
This commit is contained in:
@@ -20,7 +20,9 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from models.database.marketplace_import_job import MarketplaceImportError
|
||||
from models.database.marketplace_product import MarketplaceProduct
|
||||
from models.database.marketplace_product_translation import MarketplaceProductTranslation
|
||||
from models.database.marketplace_product_translation import (
|
||||
MarketplaceProductTranslation,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -215,9 +217,7 @@ class CSVProcessor:
|
||||
|
||||
return processed_data
|
||||
|
||||
def _extract_translation_data(
|
||||
self, product_data: dict[str, Any]
|
||||
) -> dict[str, Any]:
|
||||
def _extract_translation_data(self, product_data: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Extract translation fields from product data.
|
||||
|
||||
Returns a dict with title, description, etc. that belong
|
||||
@@ -363,11 +363,22 @@ class CSVProcessor:
|
||||
if row_data:
|
||||
# Keep only key fields for review
|
||||
limited_data = {
|
||||
k: v for k, v in row_data.items()
|
||||
if k in [
|
||||
"marketplace_product_id", "title", "gtin", "mpn", "sku",
|
||||
"brand", "price", "availability", "link"
|
||||
] and v is not None and str(v).strip()
|
||||
k: v
|
||||
for k, v in row_data.items()
|
||||
if k
|
||||
in [
|
||||
"marketplace_product_id",
|
||||
"title",
|
||||
"gtin",
|
||||
"mpn",
|
||||
"sku",
|
||||
"brand",
|
||||
"price",
|
||||
"availability",
|
||||
"link",
|
||||
]
|
||||
and v is not None
|
||||
and str(v).strip()
|
||||
}
|
||||
row_data = limited_data if limited_data else None
|
||||
|
||||
@@ -419,7 +430,11 @@ class CSVProcessor:
|
||||
product_data["vendor_name"] = vendor_name
|
||||
|
||||
# Get identifier for error tracking
|
||||
identifier = product_data.get("marketplace_product_id") or product_data.get("gtin") or product_data.get("mpn")
|
||||
identifier = (
|
||||
product_data.get("marketplace_product_id")
|
||||
or product_data.get("gtin")
|
||||
or product_data.get("mpn")
|
||||
)
|
||||
|
||||
# Validate required fields
|
||||
if not product_data.get("marketplace_product_id"):
|
||||
@@ -524,7 +539,8 @@ class CSVProcessor:
|
||||
row_number=row_number,
|
||||
error_type="processing_error",
|
||||
error_message=str(e),
|
||||
identifier=row_dict.get("marketplace_product_id") or row_dict.get("id"),
|
||||
identifier=row_dict.get("marketplace_product_id")
|
||||
or row_dict.get("id"),
|
||||
row_data=row_dict,
|
||||
)
|
||||
errors += 1
|
||||
|
||||
@@ -54,16 +54,18 @@ class GTINProcessor:
|
||||
# Standard lengths - return as-is (already valid)
|
||||
return gtin_clean
|
||||
|
||||
elif length > 14:
|
||||
if length > 14:
|
||||
# Too long - truncate to EAN-13
|
||||
logger.debug(f"GTIN too long ({length} digits), truncating: {gtin_clean}")
|
||||
return gtin_clean[-13:]
|
||||
|
||||
elif 0 < length < 14:
|
||||
if 0 < length < 14:
|
||||
# Non-standard length - pad to EAN-13 (European standard)
|
||||
# EAN-13 is the international standard used in Europe and most of the world
|
||||
# UPC-A (12 digits) is primarily US/Canada
|
||||
logger.debug(f"GTIN non-standard ({length} digits), padding to EAN-13: {gtin_clean}")
|
||||
logger.debug(
|
||||
f"GTIN non-standard ({length} digits), padding to EAN-13: {gtin_clean}"
|
||||
)
|
||||
return gtin_clean.zfill(13)
|
||||
|
||||
logger.warning(f"Invalid GTIN format: '{gtin_value}'")
|
||||
|
||||
@@ -25,8 +25,6 @@ _ENCRYPTION_SALT = b"wizamart_encryption_salt_v1"
|
||||
class EncryptionError(Exception):
|
||||
"""Raised when encryption or decryption fails."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class EncryptionService:
|
||||
"""
|
||||
|
||||
@@ -14,6 +14,7 @@ Supported languages:
|
||||
- de: German
|
||||
- lb: Luxembourgish
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
from functools import lru_cache
|
||||
@@ -97,7 +98,7 @@ def load_translations(language: str) -> dict:
|
||||
return {}
|
||||
|
||||
try:
|
||||
with open(locale_file, "r", encoding="utf-8") as f:
|
||||
with open(locale_file, encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error(f"Invalid JSON in translation file {locale_file}: {e}")
|
||||
@@ -139,7 +140,11 @@ def get_nested_value(data: dict, key_path: str, default: str = None) -> str:
|
||||
else:
|
||||
return default if default is not None else key_path
|
||||
|
||||
return value if isinstance(value, str) else (default if default is not None else key_path)
|
||||
return (
|
||||
value
|
||||
if isinstance(value, str)
|
||||
else (default if default is not None else key_path)
|
||||
)
|
||||
|
||||
|
||||
def translate(
|
||||
@@ -180,7 +185,9 @@ def translate(
|
||||
try:
|
||||
text = text.format(**kwargs)
|
||||
except KeyError as e:
|
||||
logger.warning(f"Missing interpolation variable in translation '{key}': {e}")
|
||||
logger.warning(
|
||||
f"Missing interpolation variable in translation '{key}': {e}"
|
||||
)
|
||||
|
||||
return text
|
||||
|
||||
|
||||
Reference in New Issue
Block a user