From 58e9aa14295dec73c05584c2cacccbe58748be80 Mon Sep 17 00:00:00 2001 From: gilles Date: Tue, 20 Jan 2026 03:30:09 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20stocker=20et=20afficher=20les=20donn?= =?UTF-8?q?=C3=A9es=20=C3=A9tendues=20dans=20le=20modal=20d=C3=A9tail?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Ajout des champs a_propos, description, carateristique, details au modèle ProductSnapshot - Sérialisation JSON pour les listes et dictionnaires - Modification du CRUD pour stocker/lire les données étendues - Modification du runner pour passer les données lors du scrape - AddProductModal envoie les données étendues lors de la création - La base SQLite doit être recréée (suppression de suivi.db) Co-Authored-By: Claude Opus 4.5 --- backend/app/db/crud.py | 40 +++++++++++++++++++ backend/app/db/models.py | 5 +++ backend/app/db/schemas.py | 17 +++++++- backend/app/scraper/runner.py | 17 ++++++++ .../components/products/AddProductModal.jsx | 5 +++ 5 files changed, 83 insertions(+), 1 deletion(-) diff --git a/backend/app/db/crud.py b/backend/app/db/crud.py index 52e33bb..1405488 100644 --- a/backend/app/db/crud.py +++ b/backend/app/db/crud.py @@ -1,5 +1,7 @@ from __future__ import annotations +import json + from sqlalchemy.exc import IntegrityError from sqlalchemy.orm import Session @@ -57,9 +59,21 @@ def create_product_with_snapshot( "choix_amazon", "offre_limitee", "exclusivite_amazon", + "a_propos", + "description", + "carateristique", + "details", ] snapshot_data = {k: data_dict.pop(k) for k in snapshot_fields if k in data_dict} + # Sérialiser les champs JSON pour le stockage + if snapshot_data.get("a_propos") is not None: + snapshot_data["a_propos"] = json.dumps(snapshot_data["a_propos"], ensure_ascii=False) + if snapshot_data.get("carateristique") is not None: + snapshot_data["carateristique"] = json.dumps(snapshot_data["carateristique"], ensure_ascii=False) + if snapshot_data.get("details") is not None: + snapshot_data["details"] = json.dumps(snapshot_data["details"], ensure_ascii=False) + # Convertir les HttpUrl en strings pour SQLite if data_dict.get("url"): data_dict["url"] = str(data_dict["url"]) @@ -161,6 +175,28 @@ def _enrich_product_with_snapshot(db: Session, product: models.Product) -> dict: if snapshot.prix_actuel and snapshot.prix_conseille: reduction = round((1 - snapshot.prix_actuel / snapshot.prix_conseille) * 100) + # Désérialiser les champs JSON + a_propos = None + if snapshot.a_propos: + try: + a_propos = json.loads(snapshot.a_propos) + except json.JSONDecodeError: + a_propos = None + + carateristique = None + if snapshot.carateristique: + try: + carateristique = json.loads(snapshot.carateristique) + except json.JSONDecodeError: + carateristique = None + + details = None + if snapshot.details: + try: + details = json.loads(snapshot.details) + except json.JSONDecodeError: + details = None + result.update( { "prix_actuel": snapshot.prix_actuel, @@ -175,6 +211,10 @@ def _enrich_product_with_snapshot(db: Session, product: models.Product) -> dict: "choix_amazon": snapshot.choix_amazon, "offre_limitee": snapshot.offre_limitee, "exclusivite_amazon": snapshot.exclusivite_amazon, + "a_propos": a_propos, + "description": snapshot.description, + "carateristique": carateristique, + "details": details, "dernier_scrape": snapshot.scrape_le, "statut_scrap": snapshot.statut_scrap, } diff --git a/backend/app/db/models.py b/backend/app/db/models.py index e64ff87..2866011 100644 --- a/backend/app/db/models.py +++ b/backend/app/db/models.py @@ -62,6 +62,11 @@ class ProductSnapshot(Base): choix_amazon = Column(Boolean, nullable=True) offre_limitee = Column(Boolean, nullable=True) exclusivite_amazon = Column(Boolean, nullable=True) + # Données étendues (stockées en JSON) + a_propos = Column(Text, nullable=True) # JSON array + description = Column(Text, nullable=True) + carateristique = Column(Text, nullable=True) # JSON object + details = Column(Text, nullable=True) # JSON object chemin_json_brut = Column(Text, nullable=True) statut_scrap = Column(String(32), default="ok") message_erreur = Column(Text, nullable=True) diff --git a/backend/app/db/schemas.py b/backend/app/db/schemas.py index 5f8fe1c..ec69910 100644 --- a/backend/app/db/schemas.py +++ b/backend/app/db/schemas.py @@ -1,7 +1,7 @@ from __future__ import annotations from datetime import datetime -from typing import Optional +from typing import Any, Optional from pydantic import BaseModel, HttpUrl @@ -50,6 +50,11 @@ class ProductSnapshotBase(BaseModel): choix_amazon: Optional[bool] offre_limitee: Optional[bool] exclusivite_amazon: Optional[bool] + # Données étendues + a_propos: Optional[list[str]] = None + description: Optional[str] = None + carateristique: Optional[dict[str, Any]] = None + details: Optional[dict[str, Any]] = None statut_scrap: Optional[str] message_erreur: Optional[str] @@ -82,6 +87,11 @@ class ProductWithSnapshot(ProductBase): choix_amazon: Optional[bool] = None offre_limitee: Optional[bool] = None exclusivite_amazon: Optional[bool] = None + # Données étendues + a_propos: Optional[list[str]] = None + description: Optional[str] = None + carateristique: Optional[dict[str, Any]] = None + details: Optional[dict[str, Any]] = None dernier_scrape: Optional[datetime] = None statut_scrap: Optional[str] = None @@ -104,3 +114,8 @@ class ProductCreateWithSnapshot(ProductBase): choix_amazon: Optional[bool] = None offre_limitee: Optional[bool] = None exclusivite_amazon: Optional[bool] = None + # Données étendues + a_propos: Optional[list[str]] = None + description: Optional[str] = None + carateristique: Optional[dict[str, Any]] = None + details: Optional[dict[str, Any]] = None diff --git a/backend/app/scraper/runner.py b/backend/app/scraper/runner.py index 14a9905..04565bc 100644 --- a/backend/app/scraper/runner.py +++ b/backend/app/scraper/runner.py @@ -93,6 +93,19 @@ def _create_snapshot( # Mettre à jour le produit avec titre/image si manquants _update_product_from_scrape(session, product, data) + # Sérialiser les données étendues en JSON + a_propos = data.get("a_propos") + if a_propos is not None: + a_propos = json.dumps(a_propos, ensure_ascii=False) + + carateristique = data.get("carateristique") + if carateristique is not None: + carateristique = json.dumps(carateristique, ensure_ascii=False) + + details = data.get("details") + if details is not None: + details = json.dumps(details, ensure_ascii=False) + snapshot = models.ProductSnapshot( produit_id=product.id, run_scrap_id=run.id, @@ -107,6 +120,10 @@ def _create_snapshot( choix_amazon=data.get("choix_amazon"), offre_limitee=data.get("offre_limitee"), exclusivite_amazon=data.get("exclusivite_amazon"), + a_propos=a_propos, + description=data.get("description"), + carateristique=carateristique, + details=details, chemin_json_brut=str(raw_json_path) if raw_json_path else None, statut_scrap=status, message_erreur=error_message, diff --git a/frontend/src/components/products/AddProductModal.jsx b/frontend/src/components/products/AddProductModal.jsx index ef28ee7..7f5829f 100644 --- a/frontend/src/components/products/AddProductModal.jsx +++ b/frontend/src/components/products/AddProductModal.jsx @@ -100,6 +100,11 @@ const AddProductModal = ({ onClose }) => { choix_amazon: data.choix_amazon ?? null, offre_limitee: data.offre_limitee ?? null, exclusivite_amazon: data.exclusivite_amazon ?? null, + // Données étendues + a_propos: data.a_propos ?? null, + description: data.description ?? null, + carateristique: data.carateristique ?? null, + details: data.details ?? null, }); onClose();